pipa: refactor: improve keyboard driver reliability and maintenance

- Reduce resource usage with smaller buffers and optimized code
- Add watchdog thread for automatic recovery from hangs
- Implement configuration file support for runtime settings
- Add dynamic keyboard detection with fallback path
- Improve connection handling with debouncing and backoff
- Enhance power management with proper sleep/wake handling
- Add proper signal handling and resource cleanup

Signed-off-by: Abdulwahab Isam <abdoi94.iq@gmail.com>
This commit is contained in:
Abdulwahab Isam
2025-03-24 05:41:01 +03:00
parent a1919b0c02
commit 865e43385b

View File

@@ -15,34 +15,29 @@
const char kPackageName[] = "xiaomi-keyboard";
#define BUFFER_SIZE 1024
#define BUFFER_SIZE 256 // Instead of 1024
// Device path
#define NANODEV_PATH "/dev/nanodev0"
// We'll find this dynamically
char* EVENT_PATH = NULL;
// logging
// Simplify by keeping only essential logging macros
#define TAG "xiaomi-keyboard"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// Enhanced logging macros
#define LOG_WITH_TIME(level, fmt, ...) do { \
// Keep just one enhanced logging macro for important events
#define LOG_IMPORTANT(fmt, ...) do { \
time_t now = time(NULL); \
struct tm* tm_info = localtime(&now); \
char time_str[20]; \
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); \
__android_log_print(level, TAG, "[%s] " fmt, time_str, ##__VA_ARGS__); \
__android_log_print(ANDROID_LOG_INFO, TAG, "[%s] " fmt, time_str, ##__VA_ARGS__); \
} while(0)
#define LOGE_TIME(fmt, ...) LOG_WITH_TIME(ANDROID_LOG_ERROR, fmt, ##__VA_ARGS__)
#define LOGW_TIME(fmt, ...) LOG_WITH_TIME(ANDROID_LOG_WARN, fmt, ##__VA_ARGS__)
#define LOGI_TIME(fmt, ...) LOG_WITH_TIME(ANDROID_LOG_INFO, fmt, ##__VA_ARGS__)
#define LOGD_TIME(fmt, ...) LOG_WITH_TIME(ANDROID_LOG_DEBUG, fmt, ##__VA_ARGS__)
// Nanodev file
int fd;
@@ -65,8 +60,14 @@ bool watchdog_enabled = true;
// Add this near the top of the file, after the global variables
#define CONFIG_PATH "/data/local/tmp/xiaomi_keyboard.conf"
// Configuration loading function
// Add a default config that can be used instead of parsing a file
const bool DEFAULT_WATCHDOG_ENABLED = true;
// Simplify configuration loading
void load_configuration() {
// Set defaults
watchdog_enabled = DEFAULT_WATCHDOG_ENABLED;
FILE* config_file = fopen(CONFIG_PATH, "r");
if (!config_file) {
LOGI("No configuration file found, using defaults");
@@ -117,28 +118,27 @@ char* find_keyboard_input_path() {
char device_name[256];
struct dirent* entry;
// Enhanced detection with more keywords
// Simplified detection criteria with key terms
const char* keyboard_identifiers[] = {"xiaomi", "keyboard", "pipa", "XKBD"};
const int num_identifiers = 4;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
snprintf(name_path, sizeof(name_path),
"/sys/class/input/%s/device/name", entry->d_name);
device_file = fopen(name_path, "r");
if (device_file) {
if (fgets(device_name, sizeof(device_name), device_file)) {
// More comprehensive detection criteria
if (strstr(device_name, "xiaomi") ||
strstr(device_name, "Xiaomi") ||
strstr(device_name, "keyboard") ||
strstr(device_name, "Keyboard") ||
strstr(device_name, "pipa") ||
strstr(device_name, "Pipa") ||
strstr(device_name, "XKBD")) {
if (device_file && fgets(device_name, sizeof(device_name), device_file)) {
// Convert to lowercase for case-insensitive matching
for (char* p = device_name; *p; p++) {
*p = tolower(*p);
}
for (int i = 0; i < num_identifiers; i++) {
if (strstr(device_name, keyboard_identifiers[i])) {
snprintf(path_buffer, sizeof(path_buffer),
"/dev/input/%s", entry->d_name);
LOGI("Found keyboard at: %s - Device: %s",
path_buffer, device_name);
LOGI("Found keyboard at: %s", path_buffer);
fclose(device_file);
closedir(dir);
return path_buffer;
@@ -260,34 +260,33 @@ void *watchdog_thread_func(void *arg) {
/**
* Event handler for wake/sleep messages
*/
// Consider using a simpler mutex lock/unlock pattern
void handle_power_event(char *buffer) {
bool is_wake = (buffer[6] == 1);
pthread_mutex_lock(&kb_mutex);
if (is_wake) {
// Wake event
LOGI("Received wake event - enabling keyboard monitoring");
pthread_mutex_lock(&kb_mutex);
kb_thread_paused = false;
last_monitor_activity = time(NULL); // Reset watchdog timer
last_monitor_activity = time(NULL);
pthread_cond_signal(&kb_cond);
pthread_mutex_unlock(&kb_mutex);
// Re-check keyboard connection status immediately
} else {
kb_thread_paused = true;
}
pthread_mutex_unlock(&kb_mutex);
// Log and handle status after mutex is released
if (is_wake) {
LOGI("Received wake event - enabling keyboard monitoring");
bool keyboard_connected = (access(EVENT_PATH, F_OK) != -1);
LOGI("Wake: Keyboard %s", keyboard_connected ? "connected" : "disconnected");
// Restore keyboard state based on current connection
if (keyboard_connected) {
set_kb_state(true, true);
} else {
kb_status = false;
}
} else {
// Sleep event
LOGI("Received sleep event - pausing keyboard monitoring");
pthread_mutex_lock(&kb_mutex);
kb_thread_paused = true;
pthread_mutex_unlock(&kb_mutex);
}
}
@@ -295,47 +294,16 @@ void handle_power_event(char *buffer) {
* Main event handler - dispatches to appropriate handler based on message type
*/
void handle_event(char *buffer, ssize_t bytes_read) {
// More comprehensive validation
if (bytes_read < 7) {
LOGD("Message too short: %zd bytes", bytes_read);
return;
}
// Log message details at debug level
LOGD("Received message: type=%d, headers=[%02x,%02x]",
buffer[4], buffer[1], buffer[2]);
// Improved header validation
if (!(buffer[0] == 34 || buffer[0] == 35 || buffer[0] == 36 || buffer[0] == 38)) {
LOGD("Invalid message prefix: %02x", buffer[0]);
return;
}
if (buffer[1] != MSG_HEADER_1 || buffer[2] != MSG_HEADER_2) {
LOGD("Invalid message headers: %02x,%02x", buffer[1], buffer[2]);
// Basic validation
if (bytes_read < 7 || buffer[1] != MSG_HEADER_1 || buffer[2] != MSG_HEADER_2) {
return;
}
// Handle message based on type
switch (buffer[4]) {
case MSG_TYPE_SLEEP:
LOGD("Processing sleep message");
if (buffer[5] == 1) {
handle_power_event(buffer);
}
break;
case MSG_TYPE_WAKE:
LOGD("Processing wake message");
if (buffer[5] == 1) {
handle_power_event(buffer);
}
break;
default:
// Unknown message type with hex logging
LOGD("Unhandled message type: %d (0x%02x)", buffer[4], buffer[4]);
break;
if (buffer[4] == MSG_TYPE_SLEEP || buffer[4] == MSG_TYPE_WAKE) {
if (buffer[5] == 1) {
handle_power_event(buffer);
}
}
}
@@ -345,37 +313,27 @@ void handle_event(char *buffer, ssize_t bytes_read) {
*/
int reconnect_device() {
int attempts = 0;
const int max_attempts = 10; // Increased from 5
const int max_attempts = 5; // Reduced from 10
int new_fd = -1;
LOGI("Starting device reconnection procedure");
while (attempts < max_attempts && new_fd == -1 && !terminate) {
LOGI("Reconnect attempt %d/%d", attempts + 1, max_attempts);
new_fd = open(NANODEV_PATH, O_RDWR);
if (new_fd != -1) {
LOGI("Successfully reconnected to device");
return new_fd;
}
// Log specific error
LOGE("Reconnection attempt failed: %s", strerror(errno));
// Exponential backoff with cap
int sleep_time = (1 << attempts) * 500000; // 0.5s, 1s, 2s, 4s, 8s...
if (sleep_time > 8000000) sleep_time = 8000000; // Max 8 seconds
LOGI("Waiting %0.1f seconds before next attempt", sleep_time/1000000.0);
usleep(sleep_time);
// Simplified backoff: 1s, 2s, 4s, 4s, 4s
int sleep_time = (attempts < 3) ? (1 << attempts) : 4;
sleep(sleep_time);
attempts++;
}
if (terminate) {
LOGI("Reconnection aborted due to termination request");
} else {
LOGE("Failed to reconnect after %d attempts", max_attempts);
}
LOGE("Failed to reconnect after %d attempts", attempts);
return -1;
}
@@ -385,6 +343,26 @@ void signal_handler(int signum) {
terminate = 1;
}
// Use a cleanup function for consistent resource release
void cleanup_resources(pthread_t monitor_thread, pthread_t watchdog_thread_id) {
LOGI("Performing cleanup...");
pthread_mutex_lock(&kb_mutex);
terminate = 1;
pthread_cond_signal(&kb_cond);
pthread_mutex_unlock(&kb_mutex);
pthread_join(monitor_thread, NULL);
if (watchdog_enabled && watchdog_thread_id != 0) {
pthread_join(watchdog_thread_id, NULL);
}
if (fd != -1) {
close(fd);
fd = -1;
}
}
/**
* Main function
*/
@@ -395,9 +373,7 @@ int main() {
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
LOGI("==================================================");
LOGI("Xiaomi keyboard service starting at %s", time_str);
LOGI("==================================================");
// Load configuration
load_configuration();
@@ -406,7 +382,7 @@ int main() {
char buffer[BUFFER_SIZE];
// Initialize log
LOGI_TIME("Xiaomi keyboard service starting...");
LOG_IMPORTANT("Xiaomi keyboard service starting...");
// Dynamic path detection
EVENT_PATH = find_keyboard_input_path();
@@ -436,15 +412,27 @@ int main() {
return EXIT_FAILURE;
}
// Create watchdog thread after creating monitor thread
if (pthread_create(&watchdog_thread, NULL, watchdog_thread_func, NULL) != 0) {
LOGW("Failed to create watchdog thread - continuing without watchdog");
// At the top of main():
pthread_t watchdog_thread_id = 0;
// Replace watchdog thread creation with:
if (watchdog_enabled) {
if (pthread_create(&watchdog_thread_id, NULL, watchdog_thread_func, NULL) != 0) {
LOGW("Failed to create watchdog thread - continuing without watchdog");
watchdog_enabled = false;
}
} else {
LOGI("Watchdog disabled by configuration");
}
// Set up signal handling
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Consider adding a maximum number of recoveries
int recoveries = 0;
const int MAX_RECOVERIES = 3;
// Main loop for keyboard events
LOGI("Main loop starting, ready to receive keyboard events");
while (!terminate) {
@@ -452,6 +440,8 @@ int main() {
bytes_read = read(fd, buffer, BUFFER_SIZE);
if (bytes_read > 0) {
// Reset recovery counter after successful read
recoveries = 0;
// Process the message
handle_event(buffer, bytes_read);
}
@@ -463,6 +453,12 @@ int main() {
// Read error occurred
LOGE("Error reading device: %s", strerror(errno));
// Check if we've exceeded recovery limit
if (++recoveries > MAX_RECOVERIES) {
LOGE("Exceeded maximum recovery attempts, exiting");
break;
}
// Close the current file descriptor
close(fd);
@@ -480,22 +476,10 @@ int main() {
// Final status report before exit
time_t end_time = time(NULL);
double runtime = difftime(end_time, start_time);
LOGI("==================================================");
LOGI("Service exiting after running for %.1f seconds", runtime);
LOGI("==================================================");
// Cleanup
LOGI("Performing cleanup...");
// Replace pthread_cancel with a more compatible approach
pthread_mutex_lock(&kb_mutex);
terminate = 1; // Signal the thread to exit
pthread_cond_signal(&kb_cond); // Wake up the thread if it's waiting
pthread_mutex_unlock(&kb_mutex);
// Join the thread to wait for it to finish
pthread_join(monitor_thread, NULL);
pthread_join(watchdog_thread, NULL);
close(fd);
cleanup_resources(monitor_thread, watchdog_thread_id);
return 0;
}