feaet(core): add support for secmon on STM32U58x MCU
What changed, and why it matters
This commit adds hardware support for a new microcontroller variant (STM32U58x) in Trezor's secure monitor feature. It is a feature addition, not a security fix. There is no indication in the commit that it addresses any vulnerability or security incident.
No security action required. Treat as normal feature/enablement code for new hardware. Standard code review and hardware validation are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces STM32U58x support to the secure monitor (secmon) component of Trezor firmware. Changes include: conditionalizing USB power/clock recovery initialization so it runs in secure monitor on STM32U5 secure-mode builds; guarding TrustZone initialization steps for peripherals only present on STM32U5A9xx/U5G9xx (not U58x); and adding new linker scripts (secmon.ld, plus updates to kernel.ld and prodtest.ld) to embed the secure monitor binary at the start of the firmware region for secmon-layout models. The commit message is a feature description and explicitly notes ‘[no changelog]’.
Changed components
core/embed/io/usb/stm32/usbd_conf.ccore/embed/projects/secmon/main.ccore/embed/sec/trustzone/stm32u5/tz_init.ccore/embed/sys/linker/stm32u58/kernel.ldcore/embed/sys/linker/stm32u58/prodtest.ldcore/embed/sys/linker/stm32u58/secmon.ldInspect captured patch +165 / −2
diff --git a/core/embed/io/usb/stm32/usbd_conf.c b/core/embed/io/usb/stm32/usbd_conf.c
index 978f8c02..de5e0441 100644
--- a/core/embed/io/usb/stm32/usbd_conf.c
+++ b/core/embed/io/usb/stm32/usbd_conf.c
@@ -128,7 +128,9 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)
/* Enable USB FS Clocks */
__HAL_RCC_USB_OTG_FS_CLK_ENABLE();
-#ifdef STM32U5
+#if defined(STM32U5) && defined(SECURE_MODE)
+ // If not in secure mode, this initialization is done
+ // in secure monitor
/* Enable VDDUSB */
__HAL_RCC_PWR_CLK_ENABLE();
diff --git a/core/embed/projects/secmon/main.c b/core/embed/projects/secmon/main.c
index f972b8c7..022922f1 100644
--- a/core/embed/projects/secmon/main.c
+++ b/core/embed/projects/secmon/main.c
@@ -70,6 +70,28 @@ void usb_power_init(void) {
HAL_PWREx_EnableUSBHSTranceiverSupply();
__HAL_RCC_PWR_CLK_DISABLE();
}
+#elif defined(USE_USB_FS) || defined(USE_USB_HS_IN_FS)
+void usb_power_init(void) {
+ __HAL_RCC_PWR_CLK_ENABLE();
+ // Enable VDDUSB (the full-speed peripheral has no separate HS PHY supply)
+ HAL_PWREx_EnableVddUSB();
+ __HAL_RCC_PWR_CLK_DISABLE();
+
+ // Configure the HSI48 clock recovery system (CRS). HSI48 (already enabled
+ // during the secure clock init) and the CRS configuration live in the
+ // secure RCC domain, so they are set up here in the secure monitor rather
+ // than in the non-secure kernel. The CRS trims HSI48 against the USB SOF
+ // once the (non-secure) kernel starts the USB peripheral.
+ __HAL_RCC_CRS_CLK_ENABLE();
+ RCC_CRSInitTypeDef crs_init = {0};
+ crs_init.Prescaler = RCC_CRS_SYNC_DIV1;
+ crs_init.Source = RCC_CRS_SYNC_SOURCE_USB;
+ crs_init.Polarity = RCC_CRS_SYNC_POLARITY_RISING;
+ crs_init.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000, 1000);
+ crs_init.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT;
+ crs_init.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT;
+ HAL_RCCEx_CRSConfig(&crs_init);
+}
#else
#error Not implemented
#endif
diff --git a/core/embed/sec/trustzone/stm32u5/tz_init.c b/core/embed/sec/trustzone/stm32u5/tz_init.c
index 6b3057ae..c31a5302 100644
--- a/core/embed/sec/trustzone/stm32u5/tz_init.c
+++ b/core/embed/sec/trustzone/stm32u5/tz_init.c
@@ -89,7 +89,9 @@ static void tz_configure_sau(void) {
SET_REGION(4, NONSECURE_RAM1_START, NONSECURE_RAM1_SIZE, 0);
SET_REGION(5, NONSECURE_RAM2_START, NONSECURE_RAM2_SIZE, 0);
SET_REGION(6, PERIPH_BASE_NS, SIZE_256M, 0);
+#if defined STM32U5A9xx || defined STM32U5G9xx
SET_REGION(7, GFXMMU_VIRTUAL_BUFFERS_BASE_NS, SIZE_16M, 0);
+#endif
// clang-format on
SAU->CTRL = SAU_CTRL_ENABLE_Msk;
@@ -277,8 +279,10 @@ void tz_init(void) {
GTZC_PERIPH_ICACHE_REG, GTZC_TZSC_PERIPH_SEC | GTZC_TZSC_PERIPH_PRIV);
HAL_GTZC_TZSC_ConfigPeriphAttributes(
GTZC_PERIPH_DCACHE1_REG, GTZC_TZSC_PERIPH_SEC | GTZC_TZSC_PERIPH_PRIV);
+#if defined STM32U5A9xx || defined STM32U5G9xx
HAL_GTZC_TZSC_ConfigPeriphAttributes(
GTZC_PERIPH_DCACHE2_REG, GTZC_TZSC_PERIPH_SEC | GTZC_TZSC_PERIPH_PRIV);
+#endif
// Set all interrupts as non-secure
for (int i = 0; i < 512; i++) {
@@ -292,8 +296,10 @@ void tz_init(void) {
SYSCFG->SECCFGR |= SYSCFG_SECCFGR_FPUSEC | SYSCFG_SECCFGR_CLASSBSEC |
SYSCFG_SECCFGR_SYSCFGSEC;
+#if defined STM32U5A9xx || defined STM32U5G9xx
// Disable chaching of SRAM in DCACHE2 (used only by GPU which we do not use)
SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_SRAMCACHED;
+#endif
// All RCC peripherals secure by default
const uint32_t RCC_SECCFGR_ALL_BITS =
diff --git a/core/embed/sys/linker/stm32u58/kernel.ld b/core/embed/sys/linker/stm32u58/kernel.ld
index 836b7b5e..2cbcbba6 100644
--- a/core/embed/sys/linker/stm32u58/kernel.ld
+++ b/core/embed/sys/linker/stm32u58/kernel.ld
@@ -33,6 +33,13 @@ SECTIONS {
. = ALIGN(CODE_ALIGNMENT);
} >FLASH AT>FLASH
+ /* Secure monitor binary, embedded at the start of the firmware region by
+ the kernel build when the model uses the secmon layout. Empty (no-op) for
+ models without a secure monitor. */
+ .flash : ALIGN(CODE_ALIGNMENT) {
+ KEEP(*(.secmon));
+ } >FLASH AT>FLASH
+
.flash : ALIGN(CODE_ALIGNMENT) {
_kernel_flash_start = .;
KEEP(*(.vector_table));
diff --git a/core/embed/sys/linker/stm32u58/prodtest.ld b/core/embed/sys/linker/stm32u58/prodtest.ld
index ca6c2beb..17cceca7 100644
--- a/core/embed/sys/linker/stm32u58/prodtest.ld
+++ b/core/embed/sys/linker/stm32u58/prodtest.ld
@@ -23,7 +23,12 @@ _bss_section_end = ADDR(.bss) + SIZEOF(.bss);
_bootargs_ram_start = BOOTARGS_START;
_bootargs_ram_end = BOOTARGS_START + BOOTARGS_SIZE;
-_codelen = _image_flash_end - ADDR(.flash);
+/* `_codelen` is the firmware-signed length (covers the secmon header + body);
+ `_secmon_codelen` is the secmon-signed body length. On non-secmon models the
+ `.secmon_header`/`.padding` sections are empty, so `_codelen` is unchanged and
+ `_secmon_codelen` is unused. */
+_codelen = _image_flash_end - ADDR(.secmon_header);
+_secmon_codelen = _image_flash_end - ADDR(.padding);
SECTIONS {
.vendorheader : ALIGN(4) {
@@ -34,6 +39,15 @@ SECTIONS {
KEEP(*(.header));
} >FLASH AT>FLASH
+ .secmon_header : ALIGN(CODE_ALIGNMENT) {
+ KEEP(*(.secmon_header));
+ } >FLASH AT>FLASH
+
+ .padding : ALIGN(4) {
+ . = ALIGN(4);
+ . = ALIGN(CODE_ALIGNMENT);
+ } >FLASH AT>FLASH
+
.flash : ALIGN(CODE_ALIGNMENT) {
KEEP(*(.vector_table));
. = ALIGN(4);
diff --git a/core/embed/sys/linker/stm32u58/secmon.ld b/core/embed/sys/linker/stm32u58/secmon.ld
new file mode 100644
index 00000000..cc9f54d3
--- /dev/null
+++ b/core/embed/sys/linker/stm32u58/secmon.ld
@@ -0,0 +1,112 @@
+ENTRY(reset_handler)
+
+MEMORY {
+ FLASH (rx) : ORIGIN = FIRMWARE_START_S, LENGTH = FIRMWARE_MAXSIZE
+ RAM (rw) : ORIGIN = SECMON_RAM_START, LENGTH = SECMON_RAM_SIZE
+ BOOT_ARGS (rw) : ORIGIN = BOOTARGS_START, LENGTH = BOOTARGS_SIZE
+ FB1_RAM (rw) : ORIGIN = FB1_RAM_START, LENGTH = FB1_RAM_SIZE
+ FB2_RAM (rw) : ORIGIN = FB2_RAM_START, LENGTH = FB2_RAM_SIZE
+}
+
+_sgstubs_section_start = ADDR(.gnu.sgstubs);
+_sgstubs_section_end = ADDR(.gnu.sgstubs) + SIZEOF(.gnu.sgstubs);
+
+_stack_section_start = ADDR(.stack);
+_stack_section_end = ADDR(.stack) + SIZEOF(.stack);
+
+_data_section_loadaddr = LOADADDR(.data);
+_data_section_start = ADDR(.data);
+_data_section_end = ADDR(.data) + SIZEOF(.data);
+
+_bss_section_start = ADDR(.bss);
+_bss_section_end = ADDR(.bss) + SIZEOF(.bss);
+
+_bootargs_ram_start = BOOTARGS_START;
+_bootargs_ram_end = BOOTARGS_START + BOOTARGS_SIZE;
+
+_codelen = _secmon_flash_end - _secmon_code_start;
+_secmon_size = _secmon_flash_end - ORIGIN(FLASH);
+
+SECTIONS {
+ .vendorheader : ALIGN(4) {
+ KEEP(*(.vendorheader))
+ } >FLASH
+
+ .header : ALIGN(4) {
+ . += 1K;
+ . = ALIGN(CODE_ALIGNMENT);
+ } >FLASH
+
+ .secmon_header : ALIGN(4) {
+ KEEP(*(.secmon_header));
+ } >FLASH
+
+ .padding : ALIGN(4) {
+ _secmon_code_start = .;
+ . = ALIGN(CODE_ALIGNMENT);
+ } >FLASH AT>FLASH
+
+ .flash : ALIGN(CODE_ALIGNMENT) {
+ KEEP(*(.vector_table));
+ . = ALIGN(4);
+ } >FLASH
+
+ .gnu.sgstubs : ALIGN (32) {
+ . = ALIGN(32);
+ KEEP(*(.gnu.sgstubs*));
+ . = ALIGN(32);
+ } >FLASH
+
+ .flash : {
+ *(.text*);
+ . = ALIGN(4);
+ *(.rodata*);
+ . = ALIGN(4);
+ } >FLASH
+
+ /* .data and .bss are intentionally placed before the .stack
+ section to ensure they are located in SRAM2, which is
+ cleared on security events */
+
+ .data : ALIGN(4) {
+ *(.data*);
+ . = ALIGN(4);
+ } >RAM AT>FLASH
+
+ .bss : ALIGN(4) {
+ *(.bss*);
+ . = ALIGN(4);
+ } >RAM
+
+ .stack : ALIGN(8) {
+ . = 80K; /* Overflow causes UsageFault */
+ } >RAM
+
+ .fb1 : ALIGN(4) {
+ *(.fb1*);
+ . = ALIGN(4);
+ } >FB1_RAM
+
+ .fb2 : ALIGN(4) {
+ *(.fb2*);
+ . = ALIGN(4);
+ } >FB2_RAM
+
+ .boot_args : ALIGN(8) {
+ *(.boot_command*);
+ . = ALIGN(8);
+ *(.boot_args*);
+ . = ALIGN(8);
+ } >BOOT_ARGS
+
+ /DISCARD/ : {
+ *(.ARM.exidx*);
+ }
+
+ .flash : {
+ BYTE(0x00)
+ FILL(0x00)
+ . = ALIGN(8K);
+ _secmon_flash_end = .;
+ } >FLASH
+}
Why this scored 12/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.