From cb2c4d17ebb37719a90f89635a84adc0c3eaa601 Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 20 Mar 2025 15:29:14 +0100 Subject: [PATCH] feat(core): add event polling to button driver [no changelog] --- core/embed/io/button/inc/io/button.h | 4 - core/embed/io/button/stm32/button.c | 218 +++++++++++------- core/embed/io/button/unix/button.c | 10 +- core/embed/projects/bootloader/bootui.c | 4 - core/embed/projects/bootloader/main.c | 2 - .../projects/prodtest/cmd/prodtest_button.c | 8 - 6 files changed, 147 insertions(+), 99 deletions(-) diff --git a/core/embed/io/button/inc/io/button.h b/core/embed/io/button/inc/io/button.h index 7db72ff656..4335bb278b 100644 --- a/core/embed/io/button/inc/io/button.h +++ b/core/embed/io/button/inc/io/button.h @@ -68,8 +68,4 @@ void button_deinit(void); bool button_get_event(button_event_t* event); // Checks if the specified button is currently pressed -// -// The current implementation returns the state of the button at the time -// `button_get_event()` was called. In the future, we may fix this limitation. -// For now, `button_get_event()` must be called before `button_is_down()`. bool button_is_down(button_t button); diff --git a/core/embed/io/button/stm32/button.c b/core/embed/io/button/stm32/button.c index a426ae1b9c..10546cfed1 100644 --- a/core/embed/io/button/stm32/button.c +++ b/core/embed/io/button/stm32/button.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #ifdef USE_POWERCTL #include @@ -30,19 +32,23 @@ #ifdef KERNEL_MODE +typedef struct { + // Time of last update of pressed/released data + uint64_t time; + // Button presses that were detected since last get_event call + uint32_t pressed; + // Button releases that were detected since last get_event call + uint32_t released; + // State of buttons signalled to the poller + uint32_t state; +} button_tls_t; + // Button driver state typedef struct { bool initialized; -#ifdef BTN_LEFT_PIN - bool left_down; -#endif -#ifdef BTN_RIGHT_PIN - bool right_down; -#endif -#ifdef BTN_POWER_PIN - bool power_down; -#endif + // Task local storage for button driver + button_tls_t tls[SYSTASK_MAX_TASKS]; } button_driver_t; @@ -51,7 +57,10 @@ static button_driver_t g_button_driver = { .initialized = false, }; -static void button_setup_pin(GPIO_TypeDef *port, uint16_t pin) { +// Forward declarations +static const syshandle_vmt_t g_button_handle_vmt; + +static void button_setup_pin(GPIO_TypeDef* port, uint16_t pin) { GPIO_InitTypeDef GPIO_InitStructure = {0}; GPIO_InitStructure.Mode = GPIO_MODE_INPUT; @@ -62,7 +71,7 @@ static void button_setup_pin(GPIO_TypeDef *port, uint16_t pin) { } bool button_init(void) { - button_driver_t *drv = &g_button_driver; + button_driver_t* drv = &g_button_driver; if (drv->initialized) { return true; @@ -99,106 +108,118 @@ bool button_init(void) { NVIC_EnableIRQ(BTN_EXTI_INTERRUPT_NUM); #endif // BTN_EXTI_INTERRUPT_HANDLER - drv->initialized = true; + if (!syshandle_register(SYSHANDLE_BUTTON, &g_button_handle_vmt, drv)) { + goto cleanup; + } + drv->initialized = true; return true; + +cleanup: + button_deinit(); + return false; } void button_deinit(void) { + button_driver_t* drv = &g_button_driver; + + syshandle_unregister(SYSHANDLE_BUTTON); + #ifdef BTN_EXIT_INTERRUPT_HANDLER NVIC_DisableIRQ(BTN_EXTI_INTERRUPT_NUM); #endif + + memset(drv, 0, sizeof(button_driver_t)); } -bool button_get_event(button_event_t *event) { - button_driver_t *drv = &g_button_driver; +static uint32_t button_read_state(button_driver_t* drv) { + UNUSED(drv); + uint32_t state = 0; +#ifdef BTN_LEFT_PIN + if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_LEFT_PORT, BTN_LEFT_PIN)) { + state |= (1U << BTN_LEFT); + } +#endif + +#ifdef BTN_RIGHT_PIN + if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_RIGHT_PORT, BTN_RIGHT_PIN)) { + state |= (1U << BTN_RIGHT); + } +#endif + +#ifdef BTN_POWER_PIN + if (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_POWER_PORT, BTN_POWER_PIN)) { + state |= (1U << BTN_POWER); + } +#endif + return state; +} + +bool button_get_event(button_event_t* event) { + button_driver_t* drv = &g_button_driver; memset(event, 0, sizeof(*event)); if (!drv->initialized) { return false; } -#ifdef BTN_LEFT_PIN - bool left_down = - (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_LEFT_PORT, BTN_LEFT_PIN)); + button_tls_t* tls = &drv->tls[systask_id(systask_active())]; - if (drv->left_down != left_down) { - drv->left_down = left_down; - if (left_down) { - event->button = BTN_LEFT; + uint64_t now = systick_us(); + + if ((now - tls->time) > 100000) { + // Reset the history if the button was not read for 100ms + tls->pressed = 0; + tls->released = 0; + } + + uint32_t new_state = button_read_state(drv); + + // Remember state changes and the time of the last read + tls->pressed |= new_state & ~tls->state; + tls->released |= ~new_state & tls->state; + tls->time = now; + + // Bring the automaton out of invalid states, + // in case it somehow ends up in one. + tls->released &= tls->pressed | tls->state; + tls->pressed &= tls->released | ~tls->state; + + button_t button = 0; + while (tls->pressed | tls->released) { + uint32_t mask = 1 << button; + + if ((tls->pressed & mask) != 0 && (tls->state & mask) == 0) { + // Button press was not signalled yet + tls->pressed &= ~mask; + tls->state |= mask; + event->button = button; event->event_type = BTN_EVENT_DOWN; return true; - } else { - event->button = BTN_LEFT; + } else if ((tls->released & mask) != 0 && (tls->state & mask) != 0) { + // Button release was not signalled yet + tls->released &= ~mask; + tls->state &= ~mask; + event->button = button; event->event_type = BTN_EVENT_UP; return true; } + + ++button; } -#endif -#ifdef BTN_RIGHT_PIN - bool right_down = - (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_RIGHT_PORT, BTN_RIGHT_PIN)); - - if (drv->right_down != right_down) { - drv->right_down = right_down; - if (right_down) { - event->button = BTN_RIGHT; - event->event_type = BTN_EVENT_DOWN; - return true; - } else { - event->button = BTN_RIGHT; - event->event_type = BTN_EVENT_UP; - return true; - } - } -#endif - -#ifdef BTN_POWER_PIN - bool power_down = - (GPIO_PIN_RESET == HAL_GPIO_ReadPin(BTN_POWER_PORT, BTN_POWER_PIN)); - - if (drv->power_down != power_down) { - drv->power_down = power_down; - if (power_down) { - event->button = BTN_POWER; - event->event_type = BTN_EVENT_DOWN; - return true; - } else { - event->button = BTN_POWER; - event->event_type = BTN_EVENT_UP; - return true; - } - } -#endif - - return 0; + return false; } bool button_is_down(button_t button) { - button_driver_t *drv = &g_button_driver; + button_driver_t* drv = &g_button_driver; if (!drv->initialized) { return false; } - switch (button) { -#ifdef BTN_LEFT_PIN - case BTN_LEFT: - return drv->left_down; -#endif -#ifdef BTN_RIGHT_PIN - case BTN_RIGHT: - return drv->right_down; -#endif -#ifdef BTN_POWER_PIN - case BTN_POWER: - return drv->power_down; -#endif - default: - return false; - } + return (button_read_state(drv) & (1 << button)) != 0; } #ifdef BTN_EXTI_INTERRUPT_HANDLER @@ -221,4 +242,45 @@ void BTN_EXTI_INTERRUPT_HANDLER(void) { } #endif +static void on_task_created(void* context, systask_id_t task_id) { + button_driver_t* drv = (button_driver_t*)context; + button_tls_t* tls = &drv->tls[task_id]; + memset(tls, 0, sizeof(button_tls_t)); +} + +static void on_event_poll(void* context, bool read_awaited, + bool write_awaited) { + button_driver_t* drv = (button_driver_t*)context; + + UNUSED(write_awaited); + + if (read_awaited) { + uint32_t state = button_read_state(drv); + syshandle_signal_read_ready(SYSHANDLE_BUTTON, &state); + } +} + +static bool on_check_read_ready(void* context, systask_id_t task_id, + void* param) { + button_driver_t* drv = (button_driver_t*)context; + button_tls_t* tls = &drv->tls[task_id]; + + uint32_t new_state = *(uint32_t*)param; + + // Remember state changes and the time of the last read + tls->pressed |= new_state & ~tls->state; + tls->released |= ~new_state & tls->state; + tls->time = systick_us(); + + return (tls->pressed | tls->released) != 0; +} + +static const syshandle_vmt_t g_button_handle_vmt = { + .task_created = on_task_created, + .task_killed = NULL, + .check_read_ready = on_check_read_ready, + .check_write_ready = NULL, + .poll = on_event_poll, +}; + #endif // KERNEL_MODE diff --git a/core/embed/io/button/unix/button.c b/core/embed/io/button/unix/button.c index e66c1cfd0e..c9e6cbdac0 100644 --- a/core/embed/io/button/unix/button.c +++ b/core/embed/io/button/unix/button.c @@ -112,18 +112,22 @@ bool button_is_down(button_t button) { return false; } + SDL_PumpEvents(); + + const uint8_t *keystate = SDL_GetKeyboardState(NULL); + switch (button) { #ifdef BTN_LEFT_KEY case BTN_LEFT: - return drv->left_down; + return keystate[BTN_LEFT_KEY] != 0; #endif #ifdef BTN_RIGHT_KEY case BTN_RIGHT: - return drv->right_down; + return keystate[BTN_RIGHT_KEY] != 0; #endif #ifdef BTN_POWER_KEY case BTN_POWER: - return drv->power_down; + return keystate[BTN_POWER_KEY] != 0; #endif default: return false; diff --git a/core/embed/projects/bootloader/bootui.c b/core/embed/projects/bootloader/bootui.c index c9ad6cf059..1d79041840 100644 --- a/core/embed/projects/bootloader/bootui.c +++ b/core/embed/projects/bootloader/bootui.c @@ -92,15 +92,11 @@ void ui_click(void) { void ui_click(void) { for (;;) { - button_event_t event = {0}; - button_get_event(&event); if (button_is_down(BTN_LEFT) && button_is_down(BTN_RIGHT)) { break; } } for (;;) { - button_event_t event = {0}; - button_get_event(&event); if (!button_is_down(BTN_LEFT) && !button_is_down(BTN_RIGHT)) { break; } diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c index 9e843f31c6..0907c59b35 100644 --- a/core/embed/projects/bootloader/main.c +++ b/core/embed/projects/bootloader/main.c @@ -535,8 +535,6 @@ int bootloader_main(void) { } } #elif defined USE_BUTTON - button_event_t btn_evt = {0}; - button_get_event(&btn_evt); if (button_is_down(BTN_LEFT)) { touched = 1; } diff --git a/core/embed/projects/prodtest/cmd/prodtest_button.c b/core/embed/projects/prodtest/cmd/prodtest_button.c index d899119741..c8ac570022 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_button.c +++ b/core/embed/projects/prodtest/cmd/prodtest_button.c @@ -67,10 +67,6 @@ static void test_button_combination(cli_t* cli, uint32_t timeout, button_t btn1, cli_trace(cli, "Waiting for button combination to be pressed..."); while (true) { - // Event must be read before calling `button_is_down()` - button_event_t e = {0}; - button_get_event(&e); - if (button_is_down(btn1) && button_is_down(btn2)) { break; } else if (ticks_expired(expire_time)) { @@ -84,10 +80,6 @@ static void test_button_combination(cli_t* cli, uint32_t timeout, button_t btn1, cli_trace(cli, "Waiting for buttons to be released..."); while (true) { - // Event must be read before calling `button_is_down()` - button_event_t e = {0}; - button_get_event(&e); - if (!button_is_down(btn1) && !button_is_down(btn2)) { break; } else if (ticks_expired(expire_time)) {