feat(core): introduce jump_to_next_stage with arguments
What changed, and why it matters
This commit adds a new mechanism for passing small data structures (called 'startup arguments') from one boot stage to the next inside Trezor hardware wallets. It changes how the device hands control from the boardloader/bootloader to the next firmware stage. The change is a feature addition, not a fix for a known bug or vulnerability. There is no evidence in the commit message or diff that this is a security patch or that it addresses a disclosed issue.
Treat as a normal feature commit. Review the new startup-args parsing code for robustness against malformed buffers (find_entry already has bounds checks), verify that the argument block is excluded from memory wiping on all paths, and ensure the assembly copy in `jump_to_vectbl` correctly handles overlap and alignment. No urgent security action is indicated by the supplied materials.
Security signals we found
New inter-stage data channel introduced (startup arguments passed via stack/R0)
Low-level assembly changes to stack setup and register clearing in `jump_to_vectbl`
Memory wipe logic in `jump_to_next_stage_phase_2` updated to preserve the argument block
No changelog entry and no security-related wording in commit message or code comments
Evidence from the diff
The commit introduces startup_args_t, a typed key/value buffer, plus startup_args_add/export/import/get APIs. It extends jump_to_next_stage() and the low-level jump_to_vectbl() assembly to optionally copy an argument block to the top of the target stack and pass its address in R0. The boardloader keeps the legacy no-arguments signature, while the bootloader now exports startup args when jumping to firmware. STM32U5 reset handlers import the args; STM32F4 reset handlers leave the import commented out. A new error code TS_EEXIST is added. The change touches startup, boot utilities, build files, and the emulator stub.
Changed components
core/embed/sys/startup/startup_args.ccore/embed/sys/startup/stm32/bootutils.ccore/embed/sys/startup/stm32/sysutils.ccore/embed/sys/startup/stm32f4/startup_init.ccore/embed/sys/startup/stm32u5/startup_init.ccore/embed/projects/boardloader/main.ccore/embed/projects/bootloader/main.ccore/embed/projects/bootloader_ci/main.ccore/embed/projects/bootloader/emulator.ccore/embed/rtl/error_handling.ccore/site_scons/models/stm32f4_common.pycore/site_scons/models/stm32u5_common.pycore/site_scons/models/unix_common.pyInspect captured patch +402 / −36
diff --git a/core/embed/projects/boardloader/main.c b/core/embed/projects/boardloader/main.c
index bd1e45e1..8a16ea5e 100644
--- a/core/embed/projects/boardloader/main.c
+++ b/core/embed/projects/boardloader/main.c
@@ -353,7 +353,7 @@ int main(void) {
system_deinit();
// Jump to bootloader code
- jump_to_next_stage(next_stage_addr);
+ jump_to_next_stage(next_stage_addr, NULL);
// We should never reach this point, but if we do,
// we can return anything, as the system will reset anyway.
diff --git a/core/embed/projects/bootloader/emulator.c b/core/embed/projects/bootloader/emulator.c
index e53076df..2c1b588f 100644
--- a/core/embed/projects/bootloader/emulator.c
+++ b/core/embed/projects/bootloader/emulator.c
@@ -232,7 +232,7 @@ int main(int argc, char **argv) {
return 0;
}
-void jump_to_next_stage(uint32_t address) {
+void jump_to_next_stage(uint32_t address, const startup_args_t *args) {
bool storage_is_erased =
storage_empty(&STORAGE_AREAS[0]) && storage_empty(&STORAGE_AREAS[1]);
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index ea95aae9..67668a63 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -32,6 +32,7 @@
#include <sys/bootargs.h>
#include <sys/bootutils.h>
#include <sys/flash_utils.h>
+#include <sys/startup_args.h>
#include <sys/system.h>
#include <sys/systick.h>
#include <sys/types.h>
@@ -496,9 +497,11 @@ void real_jump_to_firmware(void) {
system_deinit();
- jump_to_next_stage(
+ uint32_t vectbl_addr =
IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE) +
- secmon_code_offset);
+ secmon_code_offset;
+
+ jump_to_next_stage(vectbl_addr, startup_args_export());
}
__attribute__((noreturn)) void reboot_with_fade(void) {
diff --git a/core/embed/projects/bootloader_ci/main.c b/core/embed/projects/bootloader_ci/main.c
index a47f44b6..fa186999 100644
--- a/core/embed/projects/bootloader_ci/main.c
+++ b/core/embed/projects/bootloader_ci/main.c
@@ -263,8 +263,10 @@ int main(void) {
system_deinit();
- jump_to_next_stage(
- IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE));
+ uint32_t vectbl_addr =
+ IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen + IMAGE_HEADER_SIZE);
+
+ jump_to_next_stage(vectbl_addr, NULL);
return 0;
}
diff --git a/core/embed/projects/secmon/main.c b/core/embed/projects/secmon/main.c
index d7aa2f1f..e3a50f37 100644
--- a/core/embed/projects/secmon/main.c
+++ b/core/embed/projects/secmon/main.c
@@ -118,7 +118,7 @@ static void drivers_init(void) {
// Secure monitor panic handler
// (may be called from interrupt context)
-static void secmon_panic(const systask_postmortem_t *pminfo) {
+static void secmon_panic(const systask_postmortem_t* pminfo) {
// Since the system state is unreliable, enter emergency mode,
// store the postmortem info into bootargs and reboot.
system_emergency_rescue(NULL, pminfo);
diff --git a/core/embed/rtl/error_handling.c b/core/embed/rtl/error_handling.c
index 99df8e66..aad8e6a4 100644
--- a/core/embed/rtl/error_handling.c
+++ b/core/embed/rtl/error_handling.c
@@ -50,6 +50,8 @@ const char *ts_string(ts_t status) {
return "EBADMSG";
} else if (ts_eq(status, TS_EACCES)) {
return "EACCES";
+ } else if (ts_eq(status, TS_EEXIST)) {
+ return "EEXIST";
// Trezor-specific error codes
} else if (ts_eq(status, TS_ENOINIT)) {
return "ENOINIT";
diff --git a/core/embed/rtl/inc/rtl/error_handling.h b/core/embed/rtl/inc/rtl/error_handling.h
index ddad16cc..0a3468fe 100644
--- a/core/embed/rtl/inc/rtl/error_handling.h
+++ b/core/embed/rtl/inc/rtl/error_handling.h
@@ -46,6 +46,7 @@ typedef struct {
#define TS_EIO ts_make(EIO)
#define TS_EBADMSG ts_make(EBADMSG)
#define TS_EACCES ts_make(EACCES)
+#define TS_EEXIST ts_make(EEXIST)
/** List of Trezor-specific error codes with offset from 2000 to avoid mixing
* with standard errno codes */
diff --git a/core/embed/sys/startup/inc/sys/bootutils.h b/core/embed/sys/startup/inc/sys/bootutils.h
index 70def5cf..9d523026 100644
--- a/core/embed/sys/startup/inc/sys/bootutils.h
+++ b/core/embed/sys/startup/inc/sys/bootutils.h
@@ -19,6 +19,7 @@
#pragma once
+#include <sys/startup_args.h>
#include <sys/systask.h>
#ifdef STM32F4
@@ -83,6 +84,12 @@ void __attribute__((noreturn)) reboot_or_halt_after_rsod(void);
// Jumps to the next booting stage (e.g. bootloader to firmware).
// `vectbl_address` points to the flash at the vector table of the next stage.
//
+// Optionally, `args` can point to a structure with additional arguments for
+// the next stage of booting. `args` must point to a global or static variable
+// because the function will switch to a new stack before jumping and
+// smash the old stack contents.
+//
// Before jumping, the function disables all interrupts and clears the
// memory and registers that could contain sensitive information.
-void __attribute__((noreturn)) jump_to_next_stage(uint32_t vectbl_address);
+void __attribute__((noreturn)) jump_to_next_stage(uint32_t vectbl_address,
+ const startup_args_t *args);
diff --git a/core/embed/sys/startup/inc/sys/startup_args.h b/core/embed/sys/startup/inc/sys/startup_args.h
new file mode 100644
index 00000000..5cdf717a
--- /dev/null
+++ b/core/embed/sys/startup/inc/sys/startup_args.h
@@ -0,0 +1,99 @@
+/*
+ * 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_types.h>
+
+/** Structure passed to the next stage of the boot process */
+typedef struct {
+ /** Version of the structure, used for backward compatibility handling */
+ uint32_t version;
+ /** Size of payload data in bytes (not including the header) */
+ uint32_t size;
+ /** Payload passed to the next stage */
+ uint8_t data[0];
+} startup_args_t;
+
+/**
+ * Enum representing system-wide argument types.
+ *
+ * The actual values and their meaning are defined by the caller; the
+ * startup_args module does not interpret them.
+ *
+ * The enum values are part of the binary interface and must remain stable to
+ * preserve compatibility.
+ */
+typedef enum {
+ /** Invalid argument type */
+ STARTUP_ARGS_TYPE_INVALID = 0,
+
+} startup_args_type_t;
+
+/*
+ * @brief Adds an argument into the output buffer to be passed to the next
+ * stage of the boot process.
+ *
+ * @note The output structure is stored in a preallocated global static buffer.
+ * If the function is not called at all, the linker will optimize it out and
+ * the buffer will not be included in the final binary.
+ *
+ * @param type Argument type (defined by the caller)
+ * @param value Pointer to the argument value
+ * @param size Size of the argument value in bytes
+ * @return TS_OK on success, or an error code on failure:
+ * TS_ENOMEM if the output buffer does not have enough space
+ * TS_EEXIST if an entry with the same type already exists in the buffer
+ */
+ts_t startup_args_add(startup_args_type_t type, const void* value, size_t size);
+
+/*
+ * @brief Retrieves the pointer to the output arguments structure that can
+ * be passed to the next stage of the boot process.
+ *
+ * @return Pointer to the output arguments structure, or NULL if no arguments
+ */
+const startup_args_t* startup_args_export(void);
+
+/*
+ * @brief Initializes the input buffer used to retrieve arguments passed from
+ * the previous stage of the boot process. Call this function before
+ * `startup_args_get()`.
+ *
+ * @param args Pointer to the input arguments structure passed from the previous
+ * stage
+ * @return Status code indicating success or error
+ */
+ts_t startup_args_import(const startup_args_t* args);
+
+/*
+ * @brief Retrieves an argument from the input buffer initialized from the
+ * previous stage of the boot process.
+ *
+ * @param type Argument type (defined by the caller)
+ * @param value Output pointer to store the address of the argument value. May
+ * be NULL if the caller is not interested in the value.
+ * @param size Output pointer to store the size of the argument value in
+ * bytes. May be NULL if the caller is not interested in the size.
+ * @return TS_OK on success, or an error code on failure:
+ * TS_ENOINIT if the input buffer has not been initialized
+ * TS_ENOENT if no entry with the specified type exists in the buffer
+ */
+ts_t startup_args_get(startup_args_type_t type, const void** value,
+ size_t* size);
diff --git a/core/embed/sys/startup/inc/sys/sysutils.h b/core/embed/sys/startup/inc/sys/sysutils.h
index 7b54b3b8..d79f8bca 100644
--- a/core/embed/sys/startup/inc/sys/sysutils.h
+++ b/core/embed/sys/startup/inc/sys/sysutils.h
@@ -72,7 +72,12 @@ void reset_peripherals_and_interrupts(void);
//
// The target binary is called with interrupts disabled, and all registers
// are cleared except R11, which is set to the specified value.
-__attribute((noreturn)) void jump_to_vectbl(uint32_t vectbl_addr, uint32_t r11);
+//
+// If args_size is non-zero, args_size bytes from args are moved to the top
+// of the target stack (overlap-safe) and R0 points to the moved block.
+// If args_size is zero, R0 is set to NULL.
+__attribute((noreturn)) void jump_to_vectbl(uint32_t vectbl_addr, uint32_t r11,
+ void* args, size_t args_size);
#ifdef SECMON
// Jumps to the NON-SECURE binary using its vector table.
diff --git a/core/embed/sys/startup/startup_args.c b/core/embed/sys/startup/startup_args.c
new file mode 100644
index 00000000..6251fd12
--- /dev/null
+++ b/core/embed/sys/startup/startup_args.c
@@ -0,0 +1,179 @@
+/*
+ * 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 <trezor_rtl.h>
+
+#include <rtl/sizedefs.h>
+#include <sys/startup_args.h>
+
+// Version of the startup_args_t structure used in this implementation.
+// This should be incremented whenever the structure changes.
+#define STARTUP_ARGS_VERSION_V1 1
+
+// Maximum size of the output buffer for startup arguments.
+#define STARTUP_ARGS_BUFFER_SIZE 4096
+
+// Size of the startup_args_t header
+#define STARTUP_ARGS_HEADER_SIZE offsetof(startup_args_t, data)
+
+// Alignment requirement for individual argument entries in the buffer.
+#define ARG_ENTRY_ALIGNMENT 4
+
+// Calculates the total size of an argument entry in the buffer,
+// including padding for alignment.
+#define ARG_ENTRY_SIZE(value_size) \
+ ALIGN_UP(sizeof(arg_entry_t) + (value_size), ARG_ENTRY_ALIGNMENT)
+
+// Initial offset for the first argument entry in the buffer, accounting for
+// the startup_args_t header and alignment. Should evaluate to zero if the
+// header size is already aligned.
+#define ARG_ENTRY_INITIAL_OFFSET \
+ (ALIGN_UP(STARTUP_ARGS_HEADER_SIZE, ARG_ENTRY_ALIGNMENT) - \
+ STARTUP_ARGS_HEADER_SIZE)
+
+// Output buffer for startup arguments, populated with `startup_args_add()` and
+// retrieved with `startup_args_export()`.
+static struct {
+ startup_args_t header;
+ uint8_t buffer[STARTUP_ARGS_BUFFER_SIZE];
+} g_args_buffer;
+
+// Pointer to the current output arguments structure, initialized on the first
+// call to `startup_args_add()`. If `startup_args_add()` is not called at all,
+// this will remain NULL and the output buffer and `g_args_buffer`
+// will be optimized out by the linker.
+startup_args_t* g_args_out_ptr;
+
+// Input arguments passed from the previous stage, initialized with
+// `startup_args_import()` and accessed with `startup_args_get()`.
+static const startup_args_t* g_args_in;
+
+// Internal structure of an individual argument entry in the input/output
+// buffer.
+typedef struct {
+ uint16_t type;
+ uint16_t size;
+ uint8_t value[0];
+} arg_entry_t;
+
+// forward declaration
+static const arg_entry_t* find_entry(const startup_args_t* args,
+ startup_args_type_t type);
+
+ts_t startup_args_add(startup_args_type_t type, const void* value,
+ size_t size) {
+ TSH_DECLARE;
+
+ startup_args_t* args = g_args_out_ptr;
+
+ if (args == NULL) {
+ // First call to `startup_args_add()`, initialize the output buffer header.
+ args = &g_args_buffer.header;
+ args->version = STARTUP_ARGS_VERSION_V1;
+ args->size = ARG_ENTRY_INITIAL_OFFSET;
+ g_args_out_ptr = args;
+ }
+
+ TSH_CHECK_ARG(size <= UINT16_MAX);
+ TSH_CHECK_ARG(type != STARTUP_ARGS_TYPE_INVALID);
+ TSH_CHECK_ARG(size == 0 || value != NULL);
+
+ TSH_CHECK(find_entry(args, type) == NULL, TS_EEXIST);
+
+ uint32_t entry_size = ARG_ENTRY_SIZE(size);
+ TSH_CHECK(args->size + entry_size <= STARTUP_ARGS_BUFFER_SIZE, TS_ENOMEM);
+
+ arg_entry_t* entry = (arg_entry_t*)(args->data + args->size);
+ entry->type = type;
+ entry->size = size;
+ if (size > 0) {
+ memcpy(entry->value, value, size);
+ }
+
+ args->size += entry_size;
+
+cleanup:
+ TSH_RETURN;
+}
+
+const startup_args_t* startup_args_export(void) { return g_args_out_ptr; }
+
+ts_t startup_args_import(const startup_args_t* args) {
+ TSH_DECLARE;
+
+ TSH_CHECK_ARG(args == NULL || args->version == STARTUP_ARGS_VERSION_V1);
+
+ g_args_in = args;
+
+cleanup:
+ TSH_RETURN;
+}
+
+ts_t startup_args_get(startup_args_type_t type, const void** value,
+ size_t* size) {
+ TSH_DECLARE;
+
+ const startup_args_t* args = g_args_in;
+
+ TSH_CHECK(args != NULL, TS_ENOINIT);
+ TSH_CHECK_ARG(type != STARTUP_ARGS_TYPE_INVALID);
+
+ const arg_entry_t* entry = find_entry(args, type);
+
+ if (value != NULL) {
+ *value = entry != NULL ? entry->value : NULL;
+ }
+
+ if (size != NULL) {
+ *size = entry != NULL ? entry->size : 0;
+ }
+
+ TSH_CHECK(entry != NULL, TS_ENOENT);
+
+cleanup:
+ TSH_RETURN;
+}
+
+// Finds the argument entry with the specified type in the given arguments
+static const arg_entry_t* find_entry(const startup_args_t* args,
+ startup_args_type_t type) {
+ uintptr_t offset = ARG_ENTRY_INITIAL_OFFSET;
+
+ while (offset + sizeof(arg_entry_t) <= args->size) {
+ const arg_entry_t* entry = (const arg_entry_t*)(args->data + offset);
+
+ if (offset + ARG_ENTRY_SIZE(0) > args->size) {
+ // Malformed buffer, entry header size exceeds total size
+ break;
+ }
+
+ offset += ARG_ENTRY_SIZE(entry->size);
+
+ if (offset > args->size) {
+ // Malformed buffer, entry total size exceeds total size
+ break;
+ }
+
+ if (entry->type == type) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
diff --git a/core/embed/sys/startup/stm32/bootutils.c b/core/embed/sys/startup/stm32/bootutils.c
index e69ab341..c55aa8c9 100644
--- a/core/embed/sys/startup/stm32/bootutils.c
+++ b/core/embed/sys/startup/stm32/bootutils.c
@@ -173,7 +173,8 @@ static void reboot_with_args_phase_2(uint32_t arg1, uint32_t arg2) {
SysTick_Config(HAL_RCC_GetSysClockFreq() / 1000U);
NVIC_SetPriority(SysTick_IRQn, 0);
#endif
- jump_to_vectbl(BOOTLOADER_START + BOOTLOADER_VECTBL_OFFSET, command);
+ jump_to_vectbl(BOOTLOADER_START + BOOTLOADER_VECTBL_OFFSET, command, NULL,
+ 0);
}
#else
#error Unsupported platform
@@ -239,7 +240,12 @@ __attribute__((noreturn)) void reboot_or_halt_after_rsod(void) {
#endif // SECURE_MODE
+// args1 - vector table address of the next stage
+// args2 - pointer to the startup_args_t struct
static void jump_to_next_stage_phase_2(uint32_t arg1, uint32_t arg2) {
+ const startup_args_t* args = (const startup_args_t*)arg2;
+ size_t args_size = (args != NULL) ? sizeof(startup_args_t) + args->size : 0;
+
// We are now running on a new stack. We cannot be sure about
// any variables in the .bss and .data sections, so we must
// be careful and avoid using them altogether.
@@ -255,13 +261,17 @@ static void jump_to_next_stage_phase_2(uint32_t arg1, uint32_t arg2) {
memregion_t region = MEMREGION_ALL_RUNTIME_RAM;
MEMREGION_DEL_SECTION(®ion, _stack_section);
MEMREGION_DEL_SECTION(®ion, _bootargs_ram);
+ if (args_size != 0) {
+ memregion_del_range(®ion, (uint8_t*)args, (uint8_t*)args + args_size);
+ }
memregion_fill(®ion, 0);
// Jump to reset vector of the next stage
- jump_to_vectbl(arg1, 0);
+ jump_to_vectbl(arg1, 0, (void*)args, args_size);
}
-void __attribute__((noreturn)) jump_to_next_stage(uint32_t vectbl_address) {
+void __attribute__((noreturn)) jump_to_next_stage(uint32_t vectbl_address,
+ const startup_args_t* args) {
#ifdef STM32F4
// Ensure the display is properly deinitialized, CPU frequency is
// properly set. It's needed for backward compatibility with the older
@@ -271,8 +281,8 @@ void __attribute__((noreturn)) jump_to_next_stage(uint32_t vectbl_address) {
#endif
// Disable interrupts, MPU, clear all registers and set up a new stack
- // (on STM32U5 it also clear all CPU secrets and SRAM2).
- call_with_new_stack(vectbl_address, 0, false, jump_to_next_stage_phase_2);
+ call_with_new_stack(vectbl_address, (uint32_t)args, false,
+ jump_to_next_stage_phase_2);
}
#endif // KERNEL_MODE
diff --git a/core/embed/sys/startup/stm32/sysutils.c b/core/embed/sys/startup/stm32/sysutils.c
index ee729e81..9c732ee4 100644
--- a/core/embed/sys/startup/stm32/sysutils.c
+++ b/core/embed/sys/startup/stm32/sysutils.c
@@ -228,36 +228,71 @@ void ensure_compatible_settings(void) {
}
__attribute((naked, noreturn, no_stack_protector)) void jump_to_vectbl(
- uint32_t vectbl_addr, uint32_t r11) {
+ uint32_t vectbl_addr, uint32_t r11, void* args, size_t args_size) {
__asm__ volatile(
"CPSID F \n"
- "MOV R11, R1 \n"
- "MOV LR, R0 \n"
-
- "LDR R0, =0 \n"
- "MOV R1, R0 \n"
- "MOV R2, R0 \n"
- "MOV R3, R0 \n"
- "MOV R4, R0 \n"
- "MOV R5, R0 \n"
- "MOV R6, R0 \n"
- "MOV R7, R0 \n"
- "MOV R8, R0 \n"
- "MOV R9, R0 \n"
- "MOV R10, R0 \n" // R11 is set to r11 argument
- "MOV R12, R0 \n"
+ "MOV R11, R1 \n" // Save r11
+ "MOV LR, R0 \n" // Save vectbl_addr
+
+ "LDR R12, [LR] \n" // next stack top
+ "LDR R0, =0 \n" // default args pointer (legacy)
+ "CMP R3, #0 \n"
+ "BEQ 9f \n" // No args to move
+
+ "ADD R5, R3, #7 \n"
+ "BIC R5, R5, #7 \n" // ALIGN_UP(args_size, 8)
+ "SUB R12, R12, R5 \n" // next stack top
+ "MOV R0, R12 \n" // args pointer
+ "MOV R4, R12 \n" // dst pointer
+
+ // Decide direction for overlap-safe move
+ "CMP R4, R2 \n"
+ "BLO 2f \n" // dst < src => forward
+ "BEQ 9f \n" // dst == src => done
+ "ADD R5, R2, R3 \n" // src_end
+ "CMP R4, R5 \n"
+ "BHS 2f \n" // dst >= src_end => forward
+
+ // Backward copy
+ "ADD R2, R2, R3 \n" // src_end
+ "ADD R4, R4, R3 \n" // dst_end
+ "1: \n"
+ "LDRB R5, [R2, #-1]! \n"
+ "STRB R5, [R4, #-1]! \n"
+ "SUBS R3, R3, #1 \n"
+ "BNE 1b \n"
+ "B 9f \n"
+
+ // Forward copy
+ "2: \n"
+ "LDRB R5, [R2], #1 \n"
+ "STRB R5, [R4], #1 \n"
+ "SUBS R3, R3, #1 \n"
+ "BNE 2b \n"
+
+ "9: \n"
+
+ // Clear all registers except R0 (args ptr) and R11.
+ "LDR R1, =0 \n"
+ "MOV R2, R1 \n"
+ "MOV R3, R1 \n"
+ "MOV R4, R1 \n"
+ "MOV R5, R1 \n"
+ "MOV R6, R1 \n"
+ "MOV R7, R1 \n"
+ "MOV R8, R1 \n"
+ "MOV R9, R1 \n"
+ "MOV R10, R1 \n" // R11 is set to r11 argument
#if defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8M_BASE__)
"MSR MSPLIM, R1 \n" // Disable MSPLIM
#endif
- "LDR R0, [LR] \n" // Initial MSP value
- "MSR MSP, R0 \n" // Set MSP
+ "MSR MSP, R12 \n" // Set MSP to initial stack top
+ "MOV R12, R1 \n" // Clear R12 as well
- "LDR R0, =%[_SCB_VTOR] \n" // Reset handler
- "STR LR, [R0] \n" // Set SCB->VTOR = vectb_addr
-
- "MOV R0, R1 \n" // Zero out R0
+ "LDR R1, =%[_SCB_VTOR] \n" // Reset handler
+ "STR LR, [R1] \n" // Set SCB->VTOR = vectb_addr
"LDR LR, [LR, #4] \n" // Reset handler
"BX LR \n" // Go to reset handler
diff --git a/core/embed/sys/startup/stm32f4/startup_init.c b/core/embed/sys/startup/stm32f4/startup_init.c
index 0b6b960e..fc9ed6f5 100644
--- a/core/embed/sys/startup/stm32f4/startup_init.c
+++ b/core/embed/sys/startup/stm32f4/startup_init.c
@@ -229,7 +229,12 @@ void set_core_clock(clock_settings_t settings) {
}
#endif
+#ifdef BOARDLOADER
__attribute((no_stack_protector)) void reset_handler(void) {
+#else
+__attribute((no_stack_protector)) void reset_handler(startup_args_t* args) {
+#endif
+
#ifdef BOOTLOADER
uint32_t r11_value;
// Copy the value of R11 to the local variable r11_value
@@ -280,6 +285,12 @@ __attribute((no_stack_protector)) void reset_handler(void) {
bootargs_init(r11_value);
#endif
+#ifndef BOARDLOADER
+ // Arguments passing between stages is not used on legacy models
+ // but may be enabled if needed in the future
+ // startup_args_import(args);
+#endif
+
// Enable interrupts and fault handlers
__enable_fault_irq();
diff --git a/core/embed/sys/startup/stm32u5/startup_init.c b/core/embed/sys/startup/stm32u5/startup_init.c
index 6fdf5cc0..c6ac1035 100644
--- a/core/embed/sys/startup/stm32u5/startup_init.c
+++ b/core/embed/sys/startup/stm32u5/startup_init.c
@@ -326,7 +326,12 @@ void SystemInit(void) {
__HAL_RCC_GPIOD_CLK_ENABLE();
}
+#ifdef BOARDLOADER
__attribute((no_stack_protector)) void reset_handler(void) {
+#else
+__attribute((no_stack_protector)) void reset_handler(startup_args_t* args) {
+#endif
+
// Set stack pointer limit for checking stack overflow
__set_MSPLIM((uintptr_t)&_stack_section_start + 128);
@@ -376,6 +381,10 @@ __attribute((no_stack_protector)) void reset_handler(void) {
bootargs_init(0);
#endif
+#ifndef BOARDLOADER
+ startup_args_import(args);
+#endif
+
// Enable interrupts and fault handlers
__enable_fault_irq();
diff --git a/core/site_scons/models/stm32f4_common.py b/core/site_scons/models/stm32f4_common.py
index f9302934..03c52c02 100644
--- a/core/site_scons/models/stm32f4_common.py
+++ b/core/site_scons/models/stm32f4_common.py
@@ -86,6 +86,7 @@ def stm32f4_common_files(env, features_wanted, defines, sources, paths):
"embed/sys/pvd/stm32/pvd.c",
"embed/sys/rng/stm32/rng.c",
"embed/sys/stack/stm32/stack_utils.c",
+ "embed/sys/startup/startup_args.c",
"embed/sys/startup/stm32/bootutils.c",
"embed/sys/startup/stm32/sysutils.c",
"embed/sys/startup/stm32f4/reset_flags.c",
diff --git a/core/site_scons/models/stm32u5_common.py b/core/site_scons/models/stm32u5_common.py
index 273cb022..4af6bd29 100644
--- a/core/site_scons/models/stm32u5_common.py
+++ b/core/site_scons/models/stm32u5_common.py
@@ -119,6 +119,7 @@ def stm32u5_common_files(env, features_wanted, defines, sources, paths):
"embed/sys/smcall/stm32/smcall_stubs.c",
"embed/sys/smcall/stm32/smcall_verifiers.c",
"embed/sys/stack/stm32/stack_utils.c",
+ "embed/sys/startup/startup_args.c",
"embed/sys/startup/stm32/bootutils.c",
"embed/sys/startup/stm32/sysutils.c",
"embed/sys/startup/stm32u5/reset_flags.c",
diff --git a/core/site_scons/models/unix_common.py b/core/site_scons/models/unix_common.py
index 0d215fd2..d9fb222d 100644
--- a/core/site_scons/models/unix_common.py
+++ b/core/site_scons/models/unix_common.py
@@ -50,6 +50,7 @@ def unix_common_files(env, features_wanted, defines, sources, paths):
"embed/sys/flash/unix/flash_otp.c",
"embed/sys/mpu/unix/mpu.c",
"embed/sys/rng/unix/rng.c",
+ "embed/sys/startup/startup_args.c",
"embed/sys/startup/unix/bootutils.c",
"embed/sys/task/sysevent.c",
"embed/sys/task/system.c",
Why this scored 27/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.