feat(core): implement touch wakeup in ft3168 driver and wire into suspend/resume
What changed, and why it matters
This commit adds a new power-saving feature for Trezor hardware wallets: the device can now optionally wake from suspend when the touchscreen is touched. It restructures how the display and touch drivers suspend and resume, choosing between a 'light sleep' path (screen stays powered, touch sensor stays armed) and a 'full shutdown' path. The change itself is a feature implementation, not a fix for a known vulnerability, and there is no direct evidence in the commit or references that it addresses a security bug.
Treat as a normal feature commit. Reviewers should verify that the light-suspend path correctly preserves secrets, that the touch EXTI handler cannot spuriously wake the device, and that `g_touch_wakeup_enabled` cannot be corrupted by the `touch_deinit()` memset or left in an inconsistent state across suspend/resume. No immediate security action is indicated by the diff alone.
Security signals we found
New power-management feature with two suspend paths (light vs full deinit)
Global state deliberately placed outside driver struct to survive memset teardown
IRQ-based touch EXTI wakeup path added
Refactoring of boolean comparisons from secfalse-based to sectrue-based
No changelog entry despite functional change
Evidence from the diff
The patch implements runtime-configurable touch wakeup in the FT3168 touch driver and integrates it with the STM32U5 suspend/resume framework. Key changes: (1) adds a global g_touch_wakeup_enabled static outside the driver struct so the flag survives touch_deinit()’s memset; (2) adds touch_wakeup_set_enabled() / touch_wakeup_get_enabled() APIs; (3) changes display_suspend() and display_resume() to take a touch_wakeup_enabled boolean, selecting either a light-suspend path (panel powered, touch EXTI IRQ armed) or a full deinit/reinit path; (4) updates suspend_io.c to pass the current touch-wakeup state into the display driver; (5) refactors several secfalse == x checks to sectrue != x for consistency. The prior code used a compile-time TOUCH_WAKEUP_ENABLED macro; this makes the behavior runtime selectable.
Changed components
core/embed/io/display/ltdc_dsi/display_driver.ccore/embed/io/suspend/stm32u5/suspend_io.ccore/embed/io/touch/ft3168/ft3168.ccore/embed/io/touch/unix/touch.ccore/embed/io/display/inc/io/display.hcore/embed/io/touch/inc/io/touch.hInspect captured patch +113 / −19
diff --git a/core/embed/io/display/inc/io/display.h b/core/embed/io/display/inc/io/display.h
index 3b0aa10f..683eb6dc 100644
--- a/core/embed/io/display/inc/io/display.h
+++ b/core/embed/io/display/inc/io/display.h
@@ -80,12 +80,18 @@ typedef struct {
// Suspends the display driver.
//
// Saves the current display state into `wakeup_params`.
-void display_suspend(display_wakeup_params_t *wakeup_params);
+// `touch_wakeup_enabled` controls whether a light-suspend (keep panel powered
+// for touch wakeup) or a full deinit is performed.
+void display_suspend(display_wakeup_params_t *wakeup_params,
+ bool touch_wakeup_enabled);
// Resumes the display driver.
//
// Restores the display state from `wakeup_params`.
-void display_resume(const display_wakeup_params_t *wakeup_params);
+// `touch_wakeup_enabled` must match the value passed to `display_suspend()`;
+// it selects the matching light-resume or full reinit path.
+void display_resume(const display_wakeup_params_t *wakeup_params,
+ bool touch_wakeup_enabled);
#endif // USE_SUSPEND
// Allows unprivileged access to the display framebuffer from
diff --git a/core/embed/io/display/ltdc_dsi/display_driver.c b/core/embed/io/display/ltdc_dsi/display_driver.c
index 7d0b9b32..0fd0c370 100644
--- a/core/embed/io/display/ltdc_dsi/display_driver.c
+++ b/core/embed/io/display/ltdc_dsi/display_driver.c
@@ -633,8 +633,15 @@ void display_refresh_rate_config(void) {
#endif // REFRESH_RATE_SCALING_SUPPORTED
#ifdef USE_SUSPEND
-void display_suspend(display_wakeup_params_t *wakeup_params) {
-#if TOUCH_WAKEUP_ENABLED == 1
+void display_suspend(display_wakeup_params_t *wakeup_params,
+ bool touch_wakeup_enabled) {
+#ifdef USE_TOUCH_WAKEUP
+ if (!touch_wakeup_enabled) {
+ wakeup_params->backlight_level = display_get_backlight();
+ display_deinit(DISPLAY_RESET_CONTENT);
+ return;
+ }
+
display_driver_t *drv = &g_display_driver;
memset(wakeup_params, 0, sizeof(display_wakeup_params_t));
@@ -656,13 +663,21 @@ void display_suspend(display_wakeup_params_t *wakeup_params) {
memcpy(wakeup_params, &drv->wakeup_params, sizeof(display_wakeup_params_t));
#else
+ UNUSED(touch_wakeup_enabled);
wakeup_params->backlight_level = display_get_backlight();
display_deinit(DISPLAY_RESET_CONTENT);
-#endif // TOUCH_WAKEUP_ENABLED
+#endif // USE_TOUCH_WAKEUP
}
-void display_resume(const display_wakeup_params_t *wakeup_params) {
-#if TOUCH_WAKEUP_ENABLED == 1
+void display_resume(const display_wakeup_params_t *wakeup_params,
+ bool touch_wakeup_enabled) {
+#ifdef USE_TOUCH_WAKEUP
+ if (!touch_wakeup_enabled) {
+ display_init(DISPLAY_RESET_CONTENT);
+ display_set_backlight(wakeup_params->backlight_level);
+ return;
+ }
+
display_driver_t *drv = &g_display_driver;
if (!drv->initialized) {
@@ -691,9 +706,10 @@ cleanup:
display_deinit(DISPLAY_RESET_CONTENT);
return;
#else
+ UNUSED(touch_wakeup_enabled);
display_init(DISPLAY_RESET_CONTENT);
display_set_backlight(wakeup_params->backlight_level);
-#endif // TOUCH_WAKEUP_ENABLED
+#endif // USE_TOUCH_WAKEUP
}
#endif // USE_SUSPEND
diff --git a/core/embed/io/suspend/stm32u5/suspend_io.c b/core/embed/io/suspend/stm32u5/suspend_io.c
index 306ad498..fd3986ab 100644
--- a/core/embed/io/suspend/stm32u5/suspend_io.c
+++ b/core/embed/io/suspend/stm32u5/suspend_io.c
@@ -77,7 +77,11 @@ void suspend_drivers_phase1(power_save_wakeup_params_t *wakeup_params) {
touch_suspend();
#endif
#ifdef USE_DISPLAY
- display_suspend(&wakeup_params->display);
+#ifdef USE_TOUCH_WAKEUP
+ display_suspend(&wakeup_params->display, touch_wakeup_get_enabled());
+#else
+ display_suspend(&wakeup_params->display, false);
+#endif
#endif
}
@@ -94,7 +98,11 @@ void suspend_drivers_phase2(void) {
// Reinitialize all drivers that were stopped earlier
void resume_drivers(const power_save_wakeup_params_t *wakeup_params) {
#ifdef USE_DISPLAY
- display_resume(&wakeup_params->display);
+#ifdef USE_TOUCH_WAKEUP
+ display_resume(&wakeup_params->display, touch_wakeup_get_enabled());
+#else
+ display_resume(&wakeup_params->display, false);
+#endif
#endif
#ifdef USE_TOUCH
touch_resume();
diff --git a/core/embed/io/touch/ft3168/ft3168.c b/core/embed/io/touch/ft3168/ft3168.c
index 11b55ec4..115b4403 100644
--- a/core/embed/io/touch/ft3168/ft3168.c
+++ b/core/embed/io/touch/ft3168/ft3168.c
@@ -75,6 +75,12 @@ static touch_driver_t g_touch_driver = {
.initialized = secfalse,
};
+#if defined(USE_SUSPEND) && defined(USE_TOUCH_WAKEUP)
+// Whether touch wakeup during suspend is enabled.
+// Kept outside the driver struct so it survives touch_deinit() memset.
+static secbool g_touch_wakeup_enabled = sectrue;
+#endif // USE_SUSPEND && USE_TOUCH_WAKEUP
+
// Reads a subsequent registers from the FT3168.
//
// Returns: `sectrue` if the register was read
@@ -405,10 +411,15 @@ void touch_deinit(void) {
#ifdef USE_SUSPEND
void touch_suspend(void) {
-#if TOUCH_WAKEUP_ENABLED == 1
+#ifdef USE_TOUCH_WAKEUP
+ if (sectrue != g_touch_wakeup_enabled) {
+ touch_deinit();
+ return;
+ }
+
touch_driver_t* driver = &g_touch_driver;
- if (secfalse == driver->initialized) {
+ if (sectrue != driver->initialized) {
// The driver isn't initialized, wrong control flow applied
return;
}
@@ -432,19 +443,24 @@ void touch_suspend(void) {
NVIC_EnableIRQ(TOUCH_EXTI_INTERRUPT_NUM);
#else
touch_deinit();
-#endif // TOUCH_WAKEUP_ENABLED
+#endif // USE_TOUCH_WAKEUP
}
void touch_resume(void) {
-#if TOUCH_WAKEUP_ENABLED == 1
+#ifdef USE_TOUCH_WAKEUP
+ if (sectrue != g_touch_wakeup_enabled) {
+ touch_init();
+ return;
+ }
+
touch_driver_t* driver = &g_touch_driver;
- if (secfalse == driver->initialized) {
+ if (sectrue != driver->initialized) {
// The driver isn't initialized, wrong control flow applied
return;
}
- if (secfalse == driver->suspended) {
+ if (sectrue != driver->suspended) {
// The driver isn't suspended, nothing to resume
return;
}
@@ -458,7 +474,7 @@ void touch_resume(void) {
// Configure the touch controller (the display and touch controllers share
// the same power and reset lines => we need to configure the touch controller
// again after resuming from suspend).
- if (secfalse == ft3168_configure(driver->i2c_bus)) {
+ if (sectrue != ft3168_configure(driver->i2c_bus)) {
goto cleanup;
}
@@ -475,8 +491,21 @@ cleanup:
return;
#else
touch_init();
-#endif // TOUCH_WAKEUP_ENABLED
+#endif // USE_TOUCH_WAKEUP
+}
+
+#ifdef USE_TOUCH_WAKEUP
+void touch_wakeup_set_enabled(bool enabled) {
+ irq_key_t irq_key = irq_lock();
+ g_touch_wakeup_enabled = (enabled ? sectrue : secfalse);
+ irq_unlock(irq_key);
+}
+
+bool touch_wakeup_get_enabled(void) {
+ return (sectrue == g_touch_wakeup_enabled);
}
+#endif // USE_TOUCH_WAKEUP
+
#endif // USE_SUSPEND
void touch_power_set(bool on) {
@@ -685,7 +714,7 @@ void TOUCH_EXTI_INTERRUPT_HANDLER(void) {
// Clear the EXTI line pending bit
__HAL_GPIO_EXTI_CLEAR_FLAG(TOUCH_EXTI_INTERRUPT_PIN);
- if (secfalse != driver->initialized && secfalse != driver->suspended) {
+ if (sectrue == driver->initialized && sectrue == driver->suspended) {
// Inform the powerctl module about touch press
wakeup_flags_set(WAKEUP_FLAG_TOUCH);
}
diff --git a/core/embed/io/touch/inc/io/touch.h b/core/embed/io/touch/inc/io/touch.h
index 32c1b0d6..a809fd2b 100644
--- a/core/embed/io/touch/inc/io/touch.h
+++ b/core/embed/io/touch/inc/io/touch.h
@@ -95,6 +95,14 @@ uint32_t touch_get_state(void);
// Returns `0` if there's no event or the driver is not initialized.
uint32_t touch_get_event(void);
+#if defined(USE_SUSPEND) && defined(USE_TOUCH_WAKEUP)
+// Enables or disables touch wakeup during suspend
+void touch_wakeup_set_enabled(bool enabled);
+
+// Returns whether touch wakeup during suspend is enabled
+bool touch_wakeup_get_enabled(void);
+#endif // USE_SUSPEND && USE_TOUCH_WAKEUP
+
// Touch event is packed 32-bit value
//
// 31 24 23 12 11 0
diff --git a/core/embed/io/touch/unix/touch.c b/core/embed/io/touch/unix/touch.c
index 2d69c18a..27624d3e 100644
--- a/core/embed/io/touch/unix/touch.c
+++ b/core/embed/io/touch/unix/touch.c
@@ -69,6 +69,12 @@ static touch_driver_t g_touch_driver = {
.initialized = secfalse,
};
+#if defined(USE_SUSPEND) && defined(USE_TOUCH_WAKEUP)
+// Whether touch wakeup during suspend is enabled.
+// Kept outside the driver struct so it survives touch_deinit() memset.
+static secbool g_touch_wakeup_enabled = sectrue;
+#endif // USE_SUSPEND && USE_TOUCH_WAKEUP
+
static bool is_inside_display(int x, int y) {
return x >= sdl_touch_offset_x && y >= sdl_touch_offset_y &&
x - sdl_touch_offset_x < sdl_display_res_x &&
@@ -231,6 +237,27 @@ void touch_power_set(bool on) {
// Not implemented on the emulator
}
+#ifdef USE_SUSPEND
+void touch_suspend(void) {
+ // Not implemented on the emulator
+}
+
+void touch_resume(void) {
+ // Not implemented on the emulator
+}
+
+#ifdef USE_TOUCH_WAKEUP
+void touch_wakeup_set_enabled(bool enabled) {
+ g_touch_wakeup_enabled = (enabled ? sectrue : secfalse);
+}
+
+bool touch_wakeup_get_enabled(void) {
+ return (sectrue == g_touch_wakeup_enabled);
+}
+#endif // USE_TOUCH_WAKEUP
+
+#endif // USE_SUSPEND
+
secbool touch_ready(void) {
touch_driver_t* drv = &g_touch_driver;
return drv->initialized;
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.