feat(core): add effect cycle limitation.
What changed, and why it matters
This commit adds a cycle limit to the RGB LED animation effects on Trezor hardware wallets. Previously, LED effects like the charging or bootloader breathing animation would run indefinitely until explicitly stopped. Now, callers can request a specific number of cycles, and the effect will automatically stop after that many cycles. The change also slightly adjusts the blue LED color definition. There is no direct security vulnerability visible in the diff; it appears to be a feature or hardening improvement for LED behavior.
No immediate security action required. Treat as a normal firmware feature/hardening change. Review callers of `rgb_led_effect_start()` to ensure they pass sensible cycle limits and handle the new parameter correctly during code review.
Security signals we found
New automatic termination condition for LED effects based on cycle count
Effect callback signature change to pass internal state
Color definition change for blue LED (no security relevance evident)
Evidence from the diff
The patch modifies the STM32U5 RGB LED driver to support a requested_cycles parameter in rgb_led_effect_start(). It introduces an rgb_led_effect_data_t struct carrying cycles (computed from elapsed time divided by effect cycle period) and requested_cycles. Effect callbacks now receive this struct and update the cycle count. The periodic timer callback checks whether requested_cycles is non-zero and whether cycles >= requested_cycles, then calls rgb_led_effect_stop() to terminate the animation. Additionally, RGBLED_BLUE is changed from (5,5,50) to (0,0,50). No input validation, buffer handling, or privileged operations are involved.
Changed components
core/embed/io/rgb_led/inc/io/rgb_led.hcore/embed/io/rgb_led/stm32u5/rgb_led_effects.ccore/embed/io/rgb_led/stm32u5/rgb_led_internal.hcore/embed/io/rgb_led/stm32u5/rgb_led_lp.cInspect captured patch +33 / −10
diff --git a/core/embed/io/rgb_led/inc/io/rgb_led.h b/core/embed/io/rgb_led/inc/io/rgb_led.h
index 3185cb34d..69753d3f6 100644
--- a/core/embed/io/rgb_led/inc/io/rgb_led.h
+++ b/core/embed/io/rgb_led/inc/io/rgb_led.h
@@ -64,13 +64,14 @@ bool rgb_led_get_enabled(void);
#define RGBLED_ORANGE RGB_COMPOSE_COLOR(188, 42, 6)
#define RGBLED_RED RGB_COMPOSE_COLOR(100, 6, 3)
#define RGBLED_YELLOW RGB_COMPOSE_COLOR(22, 16, 0)
-#define RGBLED_BLUE RGB_COMPOSE_COLOR(5, 5, 50)
+#define RGBLED_BLUE RGB_COMPOSE_COLOR(0, 0, 50)
#define RGBLED_OFF 0x000000
// Set RGB LED color
// color: 24-bit RGB color, 0x00RRGGBB
void rgb_led_set_color(uint32_t color);
-void rgb_led_effect_start(rgb_led_effect_type_t effect_type);
+void rgb_led_effect_start(rgb_led_effect_type_t effect_type,
+ uint32_t requested_cycles);
void rgb_led_effect_stop(void);
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c b/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
index 329e20341..c9a0013ea 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
@@ -34,10 +34,13 @@
(EFFECT_CHARGING_UP_MS + EFFECT_CHARGING_DOWN_MS)
// Effect callback function prototypes
-static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms);
-static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms);
+static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data);
+static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data);
-static uint32_t (*rgb_led_effects_callbacks[])(uint32_t elapsed_ms) = {
+static uint32_t (*rgb_led_effects_callbacks[])(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data) = {
[RGB_LED_EFFECT_BOOTLOADER_BREATHE] = rgb_led_effect_bootloader_breathe,
[RGB_LED_EFFECT_CHARGING] = rgb_led_effect_charging,
};
@@ -84,7 +87,9 @@ bool rgb_led_assign_effect(rgb_led_effect_t *effect,
return true;
}
-static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms) {
+static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data) {
+ data->cycles = elapsed_ms / EFFECT_BOOTLOADER_BREATHE_CYCLE_MS;
uint32_t effect_time = elapsed_ms % EFFECT_BOOTLOADER_BREATHE_CYCLE_MS;
if (effect_time < EFFECT_BOOTLOADER_BREATHE_UP_MS) {
@@ -100,7 +105,9 @@ static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms) {
}
}
-static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms) {
+static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms,
+ rgb_led_effect_data_t *data) {
+ data->cycles = elapsed_ms / EFFECT_CHARGING_CYCLE_MS;
uint32_t effect_time = elapsed_ms % EFFECT_CHARGING_CYCLE_MS;
if (effect_time < EFFECT_CHARGING_UP_MS) {
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
index 40660b2dc..e2651b868 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
@@ -25,10 +25,16 @@
#include <io/rgb_led.h>
#include <sys/systimer.h>
+typedef struct {
+ uint32_t cycles;
+ uint32_t requested_cycles;
+} rgb_led_effect_data_t;
+
typedef struct {
rgb_led_effect_type_t type;
uint32_t start_time_ms;
- uint32_t (*callback)(uint32_t elapsed_ms);
+ rgb_led_effect_data_t data;
+ uint32_t (*callback)(uint32_t elapsed_ms, rgb_led_effect_data_t *data);
} rgb_led_effect_t;
typedef struct {
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
index 6e89d47ce..5ae41c4ed 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
@@ -240,7 +240,8 @@ void rgb_led_set_color(uint32_t color) {
rgb_led_apply_color(drv, color);
}
-void rgb_led_effect_start(rgb_led_effect_type_t effect_type) {
+void rgb_led_effect_start(rgb_led_effect_type_t effect_type,
+ uint32_t requested_cycles) {
rgb_led_t* drv = &g_rgb_led;
if (!drv->initialized) {
@@ -256,6 +257,8 @@ void rgb_led_effect_start(rgb_led_effect_type_t effect_type) {
return;
}
+ drv->effect.data.requested_cycles = requested_cycles;
+
systimer_set_periodic(drv->effect_timer, RGB_LED_EFFECT_TIMER_PERIOD_MS);
drv->effect.start_time_ms = systick_ms();
@@ -317,8 +320,14 @@ static void rgb_led_systimer_callback(void* context) {
}
uint32_t elapsed_ms = systick_ms() - drv->effect.start_time_ms;
- uint32_t color = drv->effect.callback(elapsed_ms);
+ uint32_t color = drv->effect.callback(elapsed_ms, &drv->effect.data);
rgb_led_apply_color(drv, color);
+
+ if (drv->effect.data.requested_cycles &&
+ drv->effect.data.cycles >= drv->effect.data.requested_cycles) {
+ // Stop the effect if the requested cycles have been reached
+ rgb_led_effect_stop();
+ }
}
#endif
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.