feat(core): Introduce led effects into rgb_led driver.
What changed, and why it matters
This commit adds new LED lighting effects (a slow blue 'breathing' light for the bootloader and a yellow pulsing light for charging) to the RGB LED driver used in some Trezor hardware wallets. It is a feature addition, not a security fix or vulnerability patch. There is no indication it addresses any security issue.
No security action required. Treat as normal feature code review for embedded driver quality and power/IRQ behavior.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces an effects subsystem into the STM32U5 low-power RGB LED driver. It adds a periodic system timer callback that interpolates LED colors over time for two predefined effects, plus start/stop APIs. The driver is wired into the T3W1 revB and revC board builds. No security-relevant bug fixes, bounds-checking changes, or incident references are present.
Changed components
core/embed/io/rgb_led/stm32u5/rgb_led_lp.ccore/embed/io/rgb_led/stm32u5/rgb_led_effects.ccore/embed/io/rgb_led/stm32u5/rgb_led_internal.hcore/embed/io/rgb_led/inc/io/rgb_led.hcore/site_scons/models/T3W1/trezor_t3w1_revB.pycore/site_scons/models/T3W1/trezor_t3w1_revC.pyInspect captured patch +269 / −17
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 824f5ce48..3185cb34d 100644
--- a/core/embed/io/rgb_led/inc/io/rgb_led.h
+++ b/core/embed/io/rgb_led/inc/io/rgb_led.h
@@ -17,13 +17,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef TREZORHAL_RGB_LED_H
-#define TREZORHAL_RGB_LED_H
+#pragma once
#include <trezor_types.h>
#ifdef KERNEL_MODE
+#define RGB_EXTRACT_RED(color) (((color) >> 16) & 0xFF)
+#define RGB_EXTRACT_GREEN(color) (((color) >> 8) & 0xFF)
+#define RGB_EXTRACT_BLUE(color) ((color) & 0xFF)
+
+#define RGB_COMPOSE_COLOR(red, green, blue) \
+ (((red) & 0xFF) << 16 | ((green) & 0xFF) << 8 | ((blue) & 0xFF))
+
+typedef enum {
+ RGB_LED_STATUS_OK = 0,
+ RGB_LED_NOT_INITIALIZED,
+ RGB_LED_INVALID_ARGUMENT,
+} rgb_led_status_t;
+
+typedef enum {
+ RGB_LED_EFFECT_BOOTLOADER_BREATHE = 0,
+ RGB_LED_EFFECT_CHARGING,
+ RGB_LED_NUM_OF_EFFECTS,
+} rgb_led_effect_type_t;
+
// Initialize RGB LED driver
void rgb_led_init(void);
@@ -39,13 +57,20 @@ void rgb_led_set_enabled(bool enabled);
// Get RGB LED enabled state
bool rgb_led_get_enabled(void);
+#define RGBLED_WHITE RGB_COMPOSE_COLOR(35, 35, 32)
+#define RGBLED_GREEN RGB_COMPOSE_COLOR(0, 255, 0)
+#define RGBLED_GREEN_LIGHT RGB_COMPOSE_COLOR(4, 13, 4)
+#define RGBLED_GREEN_LIME RGB_COMPOSE_COLOR(35, 75, 10)
+#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_OFF 0x000000
+
// Set RGB LED color
// color: 24-bit RGB color, 0x00RRGGBB
void rgb_led_set_color(uint32_t color);
-#define RGBLED_GREEN 0x040D04
-#define RGBLED_RED 0x640603
-#define RGBLED_BLUE 0x050532
-#define RGBLED_YELLOW 0x161000
+void rgb_led_effect_start(rgb_led_effect_type_t effect_type);
-#endif // TREZORHAL_RGB_LED_H
+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
new file mode 100644
index 000000000..329e20341
--- /dev/null
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_effects.c
@@ -0,0 +1,117 @@
+/*
+ * 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/>.
+ */
+
+#include <sys/systick.h>
+#include <trezor_rtl.h>
+
+#include "rgb_led_internal.h"
+
+// Effects constants
+#define EFFECT_BOOTLOADER_BREATHE_UP_MS 2000
+#define EFFECT_BOOTLOADER_BREATHE_DOWN_MS 800
+#define EFFECT_BOOTLOADER_BREATHE_CYCLE_MS \
+ (EFFECT_BOOTLOADER_BREATHE_UP_MS + EFFECT_BOOTLOADER_BREATHE_DOWN_MS)
+
+#define EFFECT_CHARGING_UP_MS 200
+#define EFFECT_CHARGING_DOWN_MS 500
+#define EFFECT_CHARGING_CYCLE_MS \
+ (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_effects_callbacks[])(uint32_t elapsed_ms) = {
+ [RGB_LED_EFFECT_BOOTLOADER_BREATHE] = rgb_led_effect_bootloader_breathe,
+ [RGB_LED_EFFECT_CHARGING] = rgb_led_effect_charging,
+};
+
+static inline uint32_t linear_interpolate(uint32_t y0, uint32_t y1, uint32_t x,
+ uint32_t x1) {
+ int32_t diff = (int32_t)y1 - (int32_t)y0;
+ return (uint32_t)(y0 + (diff * (int32_t)x / (int32_t)x1));
+}
+
+static uint32_t rgb_led_linear_effect(uint32_t c_start, uint32_t c_end,
+ uint32_t elapsed_ms, uint32_t total_ms) {
+ if (elapsed_ms >= total_ms) {
+ return c_end;
+ }
+
+ uint32_t start_r = RGB_EXTRACT_RED(c_start);
+ uint32_t start_g = RGB_EXTRACT_GREEN(c_start);
+ uint32_t start_b = RGB_EXTRACT_BLUE(c_start);
+
+ uint32_t end_r = RGB_EXTRACT_RED(c_end);
+ uint32_t end_g = RGB_EXTRACT_GREEN(c_end);
+ uint32_t end_b = RGB_EXTRACT_BLUE(c_end);
+
+ uint32_t r = linear_interpolate(start_r, end_r, elapsed_ms, total_ms);
+ uint32_t g = linear_interpolate(start_g, end_g, elapsed_ms, total_ms);
+ uint32_t b = linear_interpolate(start_b, end_b, elapsed_ms, total_ms);
+
+ return RGB_COMPOSE_COLOR(r, g, b);
+}
+
+bool rgb_led_assign_effect(rgb_led_effect_t *effect,
+ rgb_led_effect_type_t effect_type) {
+ if (effect_type < 0 || effect_type >= RGB_LED_NUM_OF_EFFECTS) {
+ return false;
+ }
+
+ // Clear effect structure
+ memset(effect, 0, sizeof(rgb_led_effect_t));
+
+ effect->type = effect_type;
+ effect->callback = rgb_led_effects_callbacks[effect_type];
+
+ return true;
+}
+
+static uint32_t rgb_led_effect_bootloader_breathe(uint32_t elapsed_ms) {
+ uint32_t effect_time = elapsed_ms % EFFECT_BOOTLOADER_BREATHE_CYCLE_MS;
+
+ if (effect_time < EFFECT_BOOTLOADER_BREATHE_UP_MS) {
+ return rgb_led_linear_effect(RGBLED_OFF, RGBLED_BLUE, effect_time,
+ EFFECT_BOOTLOADER_BREATHE_UP_MS);
+ } else if (effect_time < EFFECT_BOOTLOADER_BREATHE_CYCLE_MS) {
+ return rgb_led_linear_effect(RGBLED_BLUE, RGBLED_OFF,
+ effect_time - EFFECT_BOOTLOADER_BREATHE_UP_MS,
+ EFFECT_BOOTLOADER_BREATHE_DOWN_MS);
+ } else {
+ // Should not happen
+ return RGBLED_OFF;
+ }
+}
+
+static uint32_t rgb_led_effect_charging(uint32_t elapsed_ms) {
+ uint32_t effect_time = elapsed_ms % EFFECT_CHARGING_CYCLE_MS;
+
+ if (effect_time < EFFECT_CHARGING_UP_MS) {
+ return rgb_led_linear_effect(RGBLED_OFF, RGBLED_YELLOW, effect_time,
+ EFFECT_CHARGING_UP_MS);
+ } else if (effect_time < EFFECT_CHARGING_CYCLE_MS) {
+ return rgb_led_linear_effect(RGBLED_YELLOW, RGBLED_OFF,
+ effect_time - EFFECT_CHARGING_UP_MS,
+ EFFECT_CHARGING_DOWN_MS);
+ } else {
+ // Should not happen
+ return RGBLED_OFF;
+ }
+}
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
new file mode 100644
index 000000000..40660b2dc
--- /dev/null
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_internal.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <trezor_bsp.h>
+#include <trezor_types.h>
+
+#include <io/rgb_led.h>
+#include <sys/systimer.h>
+
+typedef struct {
+ rgb_led_effect_type_t type;
+ uint32_t start_time_ms;
+ uint32_t (*callback)(uint32_t elapsed_ms);
+} rgb_led_effect_t;
+
+typedef struct {
+ LPTIM_HandleTypeDef tim_1;
+ LPTIM_HandleTypeDef tim_3;
+ bool initialized;
+ bool enabled;
+
+ bool ongoing_effect;
+ systimer_t *effect_timer;
+ rgb_led_effect_t effect;
+} rgb_led_t;
+
+bool rgb_led_assign_effect(rgb_led_effect_t *effect,
+ rgb_led_effect_type_t effect_type);
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 5454794b2..6e89d47ce 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
@@ -24,7 +24,9 @@
#include <trezor_rtl.h>
#include <io/rgb_led.h>
+#include <sys/systimer.h>
+#include "rgb_led_internal.h"
#include "sys/systick.h"
#define LED_SWITCHING_FREQUENCY_HZ 20000
@@ -42,15 +44,13 @@
#define RGB_LED_BLUE_PORT GPIOB
#define RGB_LED_BLUE_CLK_ENA __HAL_RCC_GPIOB_CLK_ENABLE
-typedef struct {
- LPTIM_HandleTypeDef tim_1;
- LPTIM_HandleTypeDef tim_3;
- bool initialized;
- bool enabled;
-} rgb_led_t;
+#define RGB_LED_EFFECT_TIMER_PERIOD_MS 20
static rgb_led_t g_rgb_led = {0};
+static void rgb_led_apply_color(rgb_led_t* drv, uint32_t color);
+static void rgb_led_systimer_callback(void* context);
+
static void rgb_led_set_default_pin_state(void) {
HAL_GPIO_DeInit(RGB_LED_RED_PORT, RGB_LED_RED_PIN);
HAL_GPIO_DeInit(RGB_LED_GREEN_PORT, RGB_LED_GREEN_PIN);
@@ -164,6 +164,7 @@ void rgb_led_init(void) {
GPIO_InitStructure.Alternate = GPIO_AF4_LPTIM3;
HAL_GPIO_Init(RGB_LED_BLUE_PORT, &GPIO_InitStructure);
+ drv->effect_timer = systimer_create(rgb_led_systimer_callback, NULL);
drv->initialized = true;
drv->enabled = true;
}
@@ -174,6 +175,9 @@ void rgb_led_deinit(void) {
return;
}
+ systimer_delete(drv->effect_timer);
+ drv->effect_timer = NULL;
+
rgb_led_set_default_pin_state();
HAL_LPTIM_PWM_Stop(&drv->tim_1, LPTIM_CHANNEL_1);
@@ -186,7 +190,6 @@ void rgb_led_deinit(void) {
__HAL_RCC_LPTIM1_CLK_DISABLE();
__HAL_RCC_LPTIM1_FORCE_RESET();
__HAL_RCC_LPTIM1_RELEASE_RESET();
-
__HAL_RCC_LPTIM3_CLK_DISABLE();
__HAL_RCC_LPTIM3_FORCE_RESET();
__HAL_RCC_LPTIM3_RELEASE_RESET();
@@ -229,9 +232,56 @@ void rgb_led_set_color(uint32_t color) {
return;
}
- uint32_t red = (color >> 16) & 0xFF;
- uint32_t green = (color >> 8) & 0xFF;
- uint32_t blue = color & 0xFF;
+ if (drv->ongoing_effect) {
+ // Override the effect with a direct color setting
+ rgb_led_effect_stop();
+ }
+
+ rgb_led_apply_color(drv, color);
+}
+
+void rgb_led_effect_start(rgb_led_effect_type_t effect_type) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ if (effect_type >= RGB_LED_NUM_OF_EFFECTS) {
+ // Invalid effect type
+ return;
+ }
+
+ if (!rgb_led_assign_effect(&drv->effect, effect_type)) {
+ return;
+ }
+
+ systimer_set_periodic(drv->effect_timer, RGB_LED_EFFECT_TIMER_PERIOD_MS);
+ drv->effect.start_time_ms = systick_ms();
+
+ drv->ongoing_effect = true;
+
+ return;
+}
+
+void rgb_led_effect_stop(void) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ drv->ongoing_effect = false;
+ systimer_unset(drv->effect_timer);
+
+ // Reset the LED to default state
+ rgb_led_apply_color(drv, RGBLED_OFF); // Turn off the LED
+}
+
+static void rgb_led_apply_color(rgb_led_t* drv, uint32_t color) {
+ uint32_t red = RGB_EXTRACT_RED(color);
+ uint32_t green = RGB_EXTRACT_GREEN(color);
+ uint32_t blue = RGB_EXTRACT_BLUE(color);
if (red != 0) {
__HAL_LPTIM_CAPTURE_COMPARE_ENABLE(&drv->tim_1, LPTIM_CHANNEL_1);
@@ -259,4 +309,16 @@ void rgb_led_set_color(uint32_t color) {
TIMER_PERIOD - (blue * (TIMER_PERIOD) / 255));
}
+static void rgb_led_systimer_callback(void* context) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized || !drv->ongoing_effect) {
+ return;
+ }
+
+ uint32_t elapsed_ms = systick_ms() - drv->effect.start_time_ms;
+ uint32_t color = drv->effect.callback(elapsed_ms);
+ rgb_led_apply_color(drv, color);
+}
+
#endif
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revB.py b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
index 6c0625bbe..14c119e40 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revB.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
@@ -232,6 +232,7 @@ def configure(
if "rgb_led" in features_wanted:
sources += ["embed/io/rgb_led/stm32u5/rgb_led_lp.c"]
+ sources += ["embed/io/rgb_led/stm32u5/rgb_led_effects.c"]
paths += ["embed/io/rgb_led/inc"]
features_available.append("rgb_led")
defines += [("USE_RGB_LED", "1")]
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index f901e5d05..1fbf4c88f 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -231,6 +231,7 @@ def configure(
if "rgb_led" in features_wanted:
sources += ["embed/io/rgb_led/stm32u5/rgb_led_lp.c"]
+ sources += ["embed/io/rgb_led/stm32u5/rgb_led_effects.c"]
paths += ["embed/io/rgb_led/inc"]
features_available.append("rgb_led")
defines += [("USE_RGB_LED", "1")]
Why this scored 15/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.