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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user