#define pr_fmt(fmt) "[hui][button] " fmt #include "hui_internal.h" #include "events.h" #include #include #include #include #include "button.h" #include "log.h" static inline void button_handle_timeout(unsigned long j); static DEFINE_TIMER(button_timer, button_handle_timeout, 0, 0); #define BUTTON_ACTIONS(...) \ (struct button_action[]) \ { \ __VA_ARGS__ \ { \ 0 \ } \ } #include "../def/button.c" static struct kset *button_kset; int avm_hui_send_button_event(unsigned int button_id, unsigned int pressed_time) { struct _avm_event_push_button *event; event = kzalloc(sizeof(*event), GFP_ATOMIC); if (!event) return -ENOMEM; event->id = avm_event_id_push_button; event->key = button_id; event->pressed = pressed_time; hui_log_button_event(button_id, pressed_time); avm_event_source_trigger(event_source, avm_event_id_push_button, sizeof(*event), event); return 0; } static inline const char *button_event_to_string(enum button_event event) { switch (event) { case button_event_none: return "none"; case button_event_down: return "down"; case button_event_up: return "up"; case button_event_cancelled: return "cancelled"; } return NULL; } /*! * Counts all buttons currently in an action similar to the specified. */ static inline int button_multi_count(const struct button_action *action) { struct button *button; int count = 0; list_for_each_entry (button, &button_kset->list, kobj.entry) { const struct button_action *a; // Not pressed if (button->current_action == -1) continue; a = button->actions + button->current_action; // Not a multi-button if (!(a->flags & BUTTON_FLAG_MULTI)) continue; // Actions are not the same if (a->ms != action->ms || a->down != action->down || a->up != action->up || a->flags != action->flags) continue; // Found matching action count++; } return count; } /*! * The default handler for button actions. * * It handles two factor authentication and multi button presses. * * As final result it sends a button event. */ static inline void button_default_handler(struct button *button, const struct button_action *action, enum button_event event, unsigned long elapsed) { if (event == button_event_cancelled) return; // Any press should trigger a two factor press if (event_get_value(two_factor) == two_factor_start) { avm_hui_send_button_event(avm_event_push_button_2fa_success, elapsed); avm_hui_send_event(event_two_factor_success, 1); // Mark this button to be done for now button->jiffies_down = MAX_JIFFY_OFFSET; return; } if (event_get_value(button_events) == button_events_disable && (action->flags & BUTTON_FLAG_IGNORE_LOCK) == 0) { pr_info("Discard disabled button event: %s, %d, %s\n", kobject_name(&button->kobj), action->ms, button_event_to_string(event)); return; } // Require at least 2 pressed buttons (including this one) if ((action->flags & BUTTON_FLAG_MULTI) && button_multi_count(action) < 2) { pr_info("Ignore mutli event: %s, %d, %s\n", kobject_name(&button->kobj), action->ms, button_event_to_string(event)); return; } if (event == button_event_down && action->down) { /* * Zero here denotes that the button is just pressed and may be * cancelled. The cancellation is not signalled. */ avm_hui_send_button_event(action->down, 0); } else if (event == button_event_up && action->up) { avm_hui_send_button_event(action->up, elapsed); } } int button_init(void) { int i, j; for (i = 0; i < ARRAY_SIZE(button_defs); i++) { struct button_def *def = button_defs + i; for (j = 0; def->actions[j].ms != 0; j++) { struct button_action *action = def->actions + j; if (!action->handler) action->handler = button_default_handler; } } button_kset = kset_create_and_add("button", NULL, &hui_kobj->obj.kobj); if (!button_kset) return -ENOMEM; return 0; } void button_exit(void) { del_timer_sync(&button_timer); } static inline struct button_def *lookup_button_def(const char *name) { int i; for (i = 0; i < ARRAY_SIZE(button_defs); i++) { struct button_def *def = button_defs + i; if (strcmp(def->name, name) == 0) return def; } return NULL; } static inline void deliver_button_event(struct button *button, enum button_event event); /* * This function looks the currently active actions * and calculates when it should transition to the next action. * * The lowest timeout is then used for the button timer. * * The list lock must be held before calling this function. It does * not depend on the current time. */ static inline void update_button_timer(void) { struct button *button; unsigned long timeout = MAX_JIFFY_OFFSET; list_for_each_entry (button, &button_kset->list, kobj.entry) { const struct button_action *next; unsigned long b_timeout; /* Button not pressed */ if (button->jiffies_down == MAX_JIFFY_OFFSET) continue; next = &button->actions[button->current_action + 1]; /* End marker for the action list */ if (next->ms == 0) continue; b_timeout = button->jiffies_down + msecs_to_jiffies(next->ms); if (time_after(timeout, b_timeout)) timeout = b_timeout; } if (timeout != MAX_JIFFY_OFFSET) mod_timer(&button_timer, timeout); } static inline void button_handle_timeout(unsigned long j) { struct button *button; spin_lock(&button_kset->list_lock); list_for_each_entry (button, &button_kset->list, kobj.entry) { if (button->jiffies_down != MAX_JIFFY_OFFSET) deliver_button_event(button, button_event_none); } update_button_timer(); spin_unlock(&button_kset->list_lock); } /* * This function used the current time to calculate in which action the button * should be in. * * If the calculate action is different from the current action, it will call * the handlers to let them know and change the current action. Otherwise * nothing happens. * * If the last action is reached, the up event is triggered immediately. */ static inline void deliver_button_event(struct button *button, enum button_event event) { int elapsed = jiffies_to_msecs(jiffies - button->jiffies_down); int i, new_action = button->current_action; pr_debug("Handle button %s: %s (elapsed=%d)\n", button_name(button), button_event_to_string(event), elapsed); // Find the action we should be in for (i = button->current_action + 1; button->actions[i].ms != 0; i++) { const struct button_action *action = button->actions + i; if (elapsed >= action->ms) { new_action = i; } } // Notify old handler that it has been cancelled, if we changed it if (button->current_action != new_action && button->current_action != -1) { const struct button_action *action = button->actions + button->current_action; action->handler(button, action, button_event_cancelled, elapsed); } button->current_action = new_action; if (button->current_action != -1) { const struct button_action *action = button->actions + button->current_action; // A timer triggered us, so fake a down if (event == button_event_none) action->handler(button, action, button_event_down, elapsed); else action->handler(button, action, event, elapsed); } // In last action, immediately trigger up event if (button->actions[button->current_action + 1].ms == 0) { const struct button_action *action = button->actions + button->current_action; action->handler(button, action, button_event_up, elapsed); // Reset, so we don't trigger up twice button->jiffies_down = MAX_JIFFY_OFFSET; // Do not reset current_action, as others may want to inspect // were we are at } } void *avm_hui_add_button(const char *name, const char *type) { struct button_def *def; struct button *button; int err; def = lookup_button_def(type); if (!def) return ERR_PTR(-ENOENT); button = kzalloc(sizeof(struct button), GFP_KERNEL); if (!button) return ERR_PTR(-ENOMEM); button->kobj.kset = button_kset; button->actions = def->actions; button->current_action = -1; button->jiffies_down = MAX_JIFFY_OFFSET; err = kobject_init_and_add(&button->kobj, &button_ktype, NULL, "%s", name); if (err) return ERR_PTR(err); return button; } EXPORT_SYMBOL(avm_hui_add_button); void avm_hui_remove_button(void *handle) { struct button *button = handle; if (!button || IS_ERR(button)) return; kobject_put(&button->kobj); } EXPORT_SYMBOL(avm_hui_remove_button); void avm_hui_button_input(void *handle, int pressed) { struct button *button = handle; spin_lock(&button_kset->list_lock); if (pressed) { hui_log_button(button, button_event_down); if (button->jiffies_down != MAX_JIFFY_OFFSET) { pr_err("Ignore duplicated down for %s\n", button_name(button)); spin_unlock(&button_kset->list_lock); return; } /* * Temporarily enable led display */ avm_hui_send_event(event_display_suspend_on_idle, 1); button->jiffies_down = jiffies; deliver_button_event(button, button_event_down); } else { hui_log_button(button, button_event_up); if (button->jiffies_down == MAX_JIFFY_OFFSET) { pr_err("Ignore duplicated up for %s\n", button_name(button)); button->current_action = -1; spin_unlock(&button_kset->list_lock); return; } deliver_button_event(button, button_event_up); button->jiffies_down = MAX_JIFFY_OFFSET; button->current_action = -1; } update_button_timer(); spin_unlock(&button_kset->list_lock); } EXPORT_SYMBOL(avm_hui_button_input);