1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-22 18:19:03 +00:00

feat(core): add event polling to button driver

[no changelog]
This commit is contained in:
cepetr 2025-04-01 09:06:10 +02:00 committed by cepetr
parent b9d15cb343
commit 8d7a25e5eb
15 changed files with 367 additions and 179 deletions

View File

@ -0,0 +1,86 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef KERNEL_MODE
#include <trezor_rtl.h>
#include <sys/systick.h>
#include "button_fsm.h"
void button_fsm_init(button_fsm_t* fsm) {
memset(fsm, 0, sizeof(button_fsm_t));
}
bool button_fsm_event_ready(button_fsm_t* fsm, uint32_t new_state) {
// Remember state changes
fsm->pressed |= new_state & ~fsm->state;
fsm->released |= ~new_state & fsm->state;
fsm->time = systick_us();
// Return true if there are any state changes
return (fsm->pressed | fsm->released) != 0;
}
bool button_fsm_get_event(button_fsm_t* fsm, uint32_t new_state,
button_event_t* event) {
uint64_t now = systick_us();
if ((now - fsm->time) > 100000) {
// Reset the history if the button was not read for 100ms
fsm->pressed = 0;
fsm->released = 0;
}
// Remember state changes and the time of the last read
fsm->pressed |= new_state & ~fsm->state;
fsm->released |= ~new_state & fsm->state;
// Bring the automaton out of invalid states,
// in case it somehow ends up in one.
fsm->released &= fsm->pressed | fsm->state;
fsm->pressed &= fsm->released | ~fsm->state;
uint8_t button_idx = 0;
while (fsm->pressed | fsm->released) {
uint32_t mask = 1 << button_idx;
if ((fsm->pressed & mask) != 0 && (fsm->state & mask) == 0) {
// Button press was not signalled yet
fsm->pressed &= ~mask;
fsm->state |= mask;
event->button = (button_t)button_idx;
event->event_type = BTN_EVENT_DOWN;
return true;
} else if ((fsm->released & mask) != 0 && (fsm->state & mask) != 0) {
// Button release was not signalled yet
fsm->released &= ~mask;
fsm->state &= ~mask;
event->button = (button_t)button_idx;
event->event_type = BTN_EVENT_UP;
return true;
}
++button_idx;
}
return false;
}
#endif // KERNEL_MODE

View File

@ -0,0 +1,59 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <io/button.h>
// This module is a simple finite state machine for buttons.
//
// It is designed to be used in a polling loop, where the state of the buttons
// is read periodically. The module keeps track of the state changes and
// provides a simple interface to get the events that happened since the last
// call to button_fsm_get_event().
//
// The structure is designed to be used in a multi-threaded environment, where
// each thread has its own state machine. The state machines are stored in an
// array indexed by the task ID.
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_fsm_t;
// Initializes button finite state machine
void button_fsm_init(button_fsm_t* fsm);
// Checks if button_fsm_get_event() would return `true` on the next call
bool button_fsm_event_ready(button_fsm_t* fsm, uint32_t new_state);
// Processes the new_state of the button and fills the event structure.
//
// `new_state` is the current state of the buttons - each bit represents
// the state of one button (up to 32 buttons can be handled simultaneously).
//
// Returns `true` if the event structure was filled.
bool button_fsm_get_event(button_fsm_t* fsm, uint32_t new_state,
button_event_t* event);

View File

@ -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);

View File

@ -23,6 +23,9 @@
#include <io/button.h>
#include <sys/irq.h>
#include <sys/mpu.h>
#include <sys/sysevent_source.h>
#include "../button_fsm.h"
#ifdef USE_POWERCTL
#include <sys/wakeup_flags.h>
@ -34,15 +37,8 @@
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
// Each task has its own state machine
button_fsm_t tls[SYSTASK_MAX_TASKS];
} button_driver_t;
@ -51,7 +47,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 +61,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 +98,76 @@ 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));
uint32_t new_state = button_read_state(drv);
if (drv->left_down != left_down) {
drv->left_down = left_down;
if (left_down) {
event->button = BTN_LEFT;
event->event_type = BTN_EVENT_DOWN;
return true;
} else {
event->button = BTN_LEFT;
event->event_type = BTN_EVENT_UP;
return true;
}
}
#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;
button_fsm_t* fsm = &drv->tls[systask_id(systask_active())];
return button_fsm_get_event(fsm, new_state, event);
}
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 +190,40 @@ 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_fsm_t* fsm = &drv->tls[task_id];
button_fsm_init(fsm);
}
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_fsm_t* fsm = &drv->tls[task_id];
uint32_t new_state = *(uint32_t*)param;
return button_fsm_event_ready(fsm, new_state);
}
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

View File

@ -20,23 +20,19 @@
#include <trezor_bsp.h>
#include <trezor_rtl.h>
#include <SDL.h>
#include <io/button.h>
#include <sys/sysevent_source.h>
#include <sys/unix/sdl_event.h>
#include "../button_fsm.h"
// Button driver state
typedef struct {
bool initialized;
#ifdef BTN_LEFT_KEY
bool left_down;
#endif
#ifdef BTN_RIGHT_KEY
bool right_down;
#endif
#ifdef BTN_POWER_KEY
bool power_down;
#endif
// Global state of buttons
uint32_t state;
// Each task has its own state machine
button_fsm_t tls[SYSTASK_MAX_TASKS];
} button_driver_t;
// Button driver instance
@ -44,8 +40,12 @@ static button_driver_t g_button_driver = {
.initialized = false,
};
// Forward declarations
static const syshandle_vmt_t g_button_handle_vmt;
static void button_sdl_event_filter(void* context, SDL_Event* sdl_event);
bool button_init(void) {
button_driver_t *drv = &g_button_driver;
button_driver_t* drv = &g_button_driver;
if (drv->initialized) {
return true;
@ -53,79 +53,128 @@ bool button_init(void) {
memset(drv, 0, sizeof(button_driver_t));
if (!syshandle_register(SYSHANDLE_BUTTON, &g_button_handle_vmt, drv)) {
goto cleanup;
}
if (!sdl_events_register(button_sdl_event_filter, drv)) {
goto cleanup;
}
drv->initialized = true;
return true;
}
bool button_get_event(button_event_t *event) {
button_driver_t *drv = &g_button_driver;
memset(event, 0, sizeof(button_event_t));
if (!drv->initialized) {
return 0;
}
SDL_Event sdl_event;
if (SDL_PollEvent(&sdl_event) > 0 &&
(sdl_event.type == SDL_KEYDOWN || sdl_event.type == SDL_KEYUP) &&
!sdl_event.key.repeat) {
bool down = (sdl_event.type == SDL_KEYDOWN);
uint32_t evt_type = down ? BTN_EVENT_DOWN : BTN_EVENT_UP;
switch (sdl_event.key.keysym.sym) {
#ifdef BTN_LEFT_KEY
case BTN_LEFT_KEY:
drv->left_down = down;
event->event_type = evt_type;
event->button = BTN_LEFT;
return true;
#endif
#ifdef BTN_RIGHT_KEY
case BTN_RIGHT_KEY:
drv->right_down = down;
event->event_type = evt_type;
event->button = BTN_RIGHT;
return true;
#endif
#ifdef BTN_POWER_KEY
case BTN_POWER_KEY:
drv->power_down = down;
event->event_type = evt_type;
event->button = BTN_POWER;
return true;
#endif
default:
break;
}
}
cleanup:
button_deinit();
return false;
}
bool button_is_down(button_t button) {
button_driver_t *drv = &g_button_driver;
void button_deinit(void) {
button_driver_t* drv = &g_button_driver;
syshandle_unregister(SYSHANDLE_BUTTON);
sdl_events_unregister(button_sdl_event_filter, drv);
memset(drv, 0, sizeof(button_driver_t));
}
// Called from global event loop to filter and process SDL events
static void button_sdl_event_filter(void* context, SDL_Event* sdl_event) {
button_driver_t* drv = &g_button_driver;
if (sdl_event->type != SDL_KEYDOWN && sdl_event->type != SDL_KEYUP) {
return;
}
if (sdl_event->key.repeat) {
return;
}
button_t button;
switch (sdl_event->key.keysym.sym) {
#ifdef BTN_LEFT_KEY
case BTN_LEFT_KEY:
button = BTN_LEFT;
break;
#endif
#ifdef BTN_RIGHT_KEY
case BTN_RIGHT_KEY:
button = BTN_RIGHT;
break;
#endif
#ifdef BTN_POWER_KEY
case BTN_POWER_KEY:
button = BTN_POWER;
break;
#endif
default:
return;
}
if (sdl_event->type == SDL_KEYDOWN) {
drv->state |= (1 << button);
} else {
drv->state &= ~(1 << button);
}
}
static uint32_t button_read_state(button_driver_t* drv) {
sdl_events_poll();
return drv->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;
}
switch (button) {
#ifdef BTN_LEFT_KEY
case BTN_LEFT:
return drv->left_down;
#endif
#ifdef BTN_RIGHT_KEY
case BTN_RIGHT:
return drv->right_down;
#endif
#ifdef BTN_POWER_KEY
case BTN_POWER:
return drv->power_down;
#endif
default:
return false;
uint32_t new_state = button_read_state(drv);
button_fsm_t* fsm = &drv->tls[systask_id(systask_active())];
return button_fsm_get_event(fsm, new_state, event);
}
bool button_is_down(button_t button) {
button_driver_t* drv = &g_button_driver;
if (!drv->initialized) {
return false;
}
return (button_read_state(drv) & (1 << button)) != 0;
}
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_fsm_t* fsm = &drv->tls[task_id];
uint32_t new_state = *(uint32_t*)param;
return button_fsm_event_ready(fsm, new_state);
}
static const syshandle_vmt_t g_button_handle_vmt = {
.task_created = NULL,
.task_killed = NULL,
.check_read_ready = on_check_read_ready,
.check_write_ready = NULL,
.poll = on_event_poll,
};

View File

@ -70,15 +70,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;
}

View File

@ -362,8 +362,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;
}

View File

@ -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)) {

View File

@ -50,6 +50,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/io/button/unix/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [("USE_BUTTON", "1")]

View File

@ -50,6 +50,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/io/button/stm32/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [("USE_BUTTON", "1")]

View File

@ -50,6 +50,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/io/button/unix/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [("USE_BUTTON", "1")]

View File

@ -49,6 +49,7 @@ def configure(
if "input" in features_wanted:
sources += ["embed/io/button/stm32/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [("USE_BUTTON", "1")]

View File

@ -63,6 +63,7 @@ def configure(
paths += ["embed/io/touch/inc"]
features_available.append("touch")
sources += ["embed/io/button/stm32/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [

View File

@ -63,6 +63,7 @@ def configure(
paths += ["embed/io/touch/inc"]
features_available.append("touch")
sources += ["embed/io/button/stm32/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [

View File

@ -63,6 +63,7 @@ def configure(
paths += ["embed/io/touch/inc"]
features_available.append("touch")
sources += ["embed/io/button/stm32/button.c"]
sources += ["embed/io/button/button_fsm.c"]
paths += ["embed/io/button/inc"]
features_available.append("button")
defines += [