feat(core): introduce charging rgb led effect into the suspend module.
What changed, and why it matters
This commit changes how the Trezor hardware wallet's RGB LED behaves when the device is suspended while charging. Previously the LED driver was fully shut down; now it stays partially active to show a charging light effect. There is no obvious security bug in the change itself, but it slightly increases the amount of code that runs before the device enters its deepest sleep state.
No immediate action required. As a defensive review, consider verifying that the new charging-suspend loop cannot be abused to keep the device awake indefinitely (denial-of-sleep) and that rgb_led_suspend() and rgb_led_resume() properly save and restore state without leaking power or data. Review the RGB LED driver implementation if available.
Security signals we found
New loop in system_suspend() that delays entering low-power mode while charging
RGB LED driver kept partially active across suspend/resume instead of full deinit/reinit
No input validation or bounds checking changes visible in this diff
No secrets, crypto, or authentication logic modified
No vendor security disclosure or CVE referenced in commit message
Evidence from the diff
The patch adds a charging RGB LED effect to the STM32U5 suspend module. It introduces a pre-suspend loop that checks power-manager state and, if USB or wireless power is connected, starts a charging LED effect and waits until external power is disconnected or another wakeup event occurs. The RGB LED driver is no longer fully deinitialized during suspend; instead, its wakeup parameters are saved, the LED color is set to off, and it is resumed on wakeup. The change is gated behind USE_RGB_LED and appears to be a feature addition rather than a vulnerability fix.
Changed components
core/embed/sys/suspend/stm32u5/suspend.ccore/embed/sys/suspend/stm32u5/suspend_io.ccore/embed/sys/suspend/inc/sys/suspend_io.hRGB LED driver integration in suspend/resume pathInspect captured patch +48 / −3
diff --git a/core/embed/sys/suspend/inc/sys/suspend_io.h b/core/embed/sys/suspend/inc/sys/suspend_io.h
index e15264a64..76286a991 100644
--- a/core/embed/sys/suspend/inc/sys/suspend_io.h
+++ b/core/embed/sys/suspend/inc/sys/suspend_io.h
@@ -23,6 +23,10 @@
#include <io/ble.h>
#endif
+#ifdef USE_RGB_LED
+#include <io/rgb_led.h>
+#endif
+
/**
* @brief Switches the CPU to STOP2 low-power mode.
*
@@ -44,6 +48,10 @@ typedef struct {
/** State of the ble driver */
ble_wakeup_params_t ble;
#endif
+#ifdef USE_RGB_LED
+ /** State of the rgb_led driver */
+ rgb_led_wakeup_params_t rgb_led;
+#endif
} power_save_wakeup_params_t;
/**
diff --git a/core/embed/sys/suspend/stm32u5/suspend.c b/core/embed/sys/suspend/stm32u5/suspend.c
index fce1cfd26..b29b40726 100644
--- a/core/embed/sys/suspend/stm32u5/suspend.c
+++ b/core/embed/sys/suspend/stm32u5/suspend.c
@@ -29,6 +29,10 @@
#include <sys/pmic.h>
#include <sys/power_manager.h>
+#ifdef USE_RGB_LED
+#include <io/rgb_led.h>
+#endif
+
static wakeup_flags_t g_wakeup_flags = 0;
static void background_tasks_suspend(void);
@@ -64,12 +68,43 @@ wakeup_flags_t system_suspend(void) {
// (e.g., USB, display, touch, haptic, etc.).
suspend_drivers(&wakeup_params);
+ wakeup_flags_t wakeup_flags = 0;
+ wakeup_flags_get(&wakeup_flags);
+
+ // If the device is requested to go in suspend, but the battery is charging,
+ // Keep in this loop until the the external power got diconnected or the
+ // device is woke up. This state is signaled with RGB LED charging effect
+ bool charging_in_suspend;
+ do {
+ pm_state_t pm_state;
+ pm_get_state(&pm_state);
+
+ if (pm_state.usb_connected || pm_state.wireless_connected) {
+ charging_in_suspend = true;
+
+#ifdef USE_RGB_LED
+ if (!rgb_led_effect_ongoing()) {
+ rgb_led_effect_start(RGB_LED_EFFECT_CHARGING, 0);
+ }
+#endif
+
+ } else {
+ charging_in_suspend = false;
+ }
+
+ wakeup_flags_get(&wakeup_flags);
+
+ } while (charging_in_suspend && (wakeup_flags == 0));
+
+#ifdef USE_RGB_LED
+ rgb_led_suspend();
+#endif
+
// In the following loop, the system will attempt to enter low-power mode.
// Low-power mode may be exited for various reasons, but the loop will
// terminate only if a wakeup flag is set, indicating that user interaction
// is required or the user needs to be notified.
- wakeup_flags_t wakeup_flags = 0;
wakeup_flags_get(&wakeup_flags);
while (wakeup_flags == 0) {
diff --git a/core/embed/sys/suspend/stm32u5/suspend_io.c b/core/embed/sys/suspend/stm32u5/suspend_io.c
index 79ad28857..eccea633a 100644
--- a/core/embed/sys/suspend/stm32u5/suspend_io.c
+++ b/core/embed/sys/suspend/stm32u5/suspend_io.c
@@ -125,7 +125,9 @@ void suspend_drivers(power_save_wakeup_params_t *wakeup_params) {
haptic_deinit();
#endif
#ifdef USE_RGB_LED
- rgb_led_deinit();
+ // Just store the wakeup params and turn off the LED
+ rgb_led_set_wakeup_params(&wakeup_params->rgb_led);
+ rgb_led_set_color(RGBLED_OFF);
#endif
#ifdef USE_TOUCH
touch_deinit();
@@ -152,7 +154,7 @@ void resume_drivers(const power_save_wakeup_params_t *wakeup_params) {
haptic_init();
#endif
#ifdef USE_RGB_LED
- rgb_led_init();
+ rgb_led_resume(&wakeup_params->rgb_led);
#endif
#ifdef USE_USB
usb_start(NULL);
Why this scored 18/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.