From ba83a7e644632089f144f56048feea5245277545 Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 20 Oct 2023 14:58:32 +0200 Subject: [PATCH] feat(core): introduce interaction-less upgrade --- core/.changelog.d/2919.added | 1 + core/SConscript.firmware | 1 + core/SConscript.prodtest | 3 +- core/embed/boardloader/memory.ld | 6 +- core/embed/bootloader/.changelog.d/2794.added | 1 + core/embed/bootloader/.changelog.d/2919.added | 1 + core/embed/bootloader/boot_internal.h | 21 ++++ core/embed/bootloader/emulator.c | 10 +- core/embed/bootloader/emulator.h | 2 - core/embed/bootloader/main.c | 59 +++++---- core/embed/bootloader/memory.ld | 13 +- core/embed/bootloader/messages.c | 64 ++++++++-- core/embed/bootloader/messages.h | 3 + core/embed/bootloader/startup.s | 15 ++- core/embed/bootloader_ci/main.c | 17 --- core/embed/bootloader_ci/memory.ld | 12 +- core/embed/bootloader_ci/messages.c | 2 - core/embed/bootloader_ci/startup.s | 2 +- .../extmod/modtrezorutils/modtrezorutils.c | 85 ++++++++++++- core/embed/firmware/main.c | 77 ------------ core/embed/firmware/memory_T.ld | 14 ++- core/embed/firmware/startup.S | 5 + core/embed/lib/image.c | 64 ++++++++++ core/embed/lib/image.h | 21 ++++ core/embed/prodtest/main.c | 24 ---- core/embed/prodtest/memory.ld | 11 +- core/embed/prodtest/startup.s | 5 + core/embed/reflash/memory.ld | 11 +- core/embed/reflash/startup.s | 5 + core/embed/rust/librust_qstr.h | 2 + core/embed/rust/rust_ui.h | 2 +- core/embed/rust/src/ui/component/pad.rs | 2 +- .../src/ui/model_tr/bootloader/connect.rs | 2 +- .../rust/src/ui/model_tr/bootloader/intro.rs | 6 +- .../rust/src/ui/model_tr/bootloader/menu.rs | 6 +- .../rust/src/ui/model_tr/bootloader/mod.rs | 59 ++++----- .../src/ui/model_tr/bootloader/welcome.rs | 2 +- .../confirm.rs => component/bl_confirm.rs} | 82 +++++++------ .../rust/src/ui/model_tr/component/mod.rs | 1 + core/embed/rust/src/ui/model_tr/layout.rs | 50 +++++++- .../theme.rs => theme/bootloader.rs} | 0 .../ui/model_tr/{theme.rs => theme/mod.rs} | 2 + .../src/ui/model_tt/bootloader/connect.rs | 10 +- .../rust/src/ui/model_tt/bootloader/intro.rs | 10 +- .../rust/src/ui/model_tt/bootloader/menu.rs | 14 ++- .../rust/src/ui/model_tt/bootloader/mod.rs | 51 ++++---- .../src/ui/model_tt/bootloader/welcome.rs | 6 +- .../confirm.rs => component/bl_confirm.rs} | 75 ++++++++---- .../rust/src/ui/model_tt/component/button.rs | 2 +- .../rust/src/ui/model_tt/component/error.rs | 2 +- .../rust/src/ui/model_tt/component/mod.rs | 1 + .../ui/model_tt/component/welcome_screen.rs | 2 +- core/embed/rust/src/ui/model_tt/layout.rs | 55 ++++++++- .../theme.rs => theme/bootloader.rs} | 22 ++-- .../ui/model_tt/{theme.rs => theme/mod.rs} | 2 + core/embed/trezorhal/boot_args.h | 20 ++++ core/embed/trezorhal/common.h | 5 - core/embed/trezorhal/stm32f4/supervise.c | 112 ++++++++++++++++++ core/embed/trezorhal/stm32f4/supervise.h | 29 +---- core/embed/trezorhal/stm32f4/util.s | 2 +- core/mocks/generated/trezorui2.pyi | 18 +++ core/mocks/generated/trezorutils.pyi | 20 +++- core/site_scons/boards/stm32f4_common.py | 1 + core/src/all_modules.py | 2 + .../apps/management/reboot_to_bootloader.py | 58 +++++++-- core/src/trezor/enums/BootCommand.py | 6 + core/src/trezor/enums/__init__.py | 4 + core/src/trezor/messages.py | 11 ++ core/src/trezor/ui/layouts/tr/__init__.py | 14 +++ core/src/trezor/ui/layouts/tt/__init__.py | 14 +++ core/src/trezor/utils.py | 1 + python/src/trezorlib/device.py | 12 +- 72 files changed, 956 insertions(+), 396 deletions(-) create mode 100644 core/.changelog.d/2919.added create mode 100644 core/embed/bootloader/.changelog.d/2794.added create mode 100644 core/embed/bootloader/.changelog.d/2919.added create mode 100644 core/embed/bootloader/boot_internal.h rename core/embed/rust/src/ui/model_tr/{bootloader/confirm.rs => component/bl_confirm.rs} (80%) rename core/embed/rust/src/ui/model_tr/{bootloader/theme.rs => theme/bootloader.rs} (100%) rename core/embed/rust/src/ui/model_tr/{theme.rs => theme/mod.rs} (99%) rename core/embed/rust/src/ui/model_tt/{bootloader/confirm.rs => component/bl_confirm.rs} (81%) rename core/embed/rust/src/ui/model_tt/{bootloader/theme.rs => theme/bootloader.rs} (96%) rename core/embed/rust/src/ui/model_tt/{theme.rs => theme/mod.rs} (99%) create mode 100644 core/embed/trezorhal/boot_args.h create mode 100644 core/embed/trezorhal/stm32f4/supervise.c create mode 100644 core/src/trezor/enums/BootCommand.py diff --git a/core/.changelog.d/2919.added b/core/.changelog.d/2919.added new file mode 100644 index 0000000000..708f3fe44d --- /dev/null +++ b/core/.changelog.d/2919.added @@ -0,0 +1 @@ +Support interaction-less upgrade \ No newline at end of file diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 72527c1491..f64076312a 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -445,6 +445,7 @@ env.Replace( 'FIRMWARE', 'TREZOR_MODEL_'+TREZOR_MODEL, 'USE_HAL_DRIVER', + 'ARM_USER_MODE', UI_LAYOUT, ] + CPPDEFINES_MOD + CPPDEFINES_HAL, ASFLAGS=env.get('ENV')['CPU_ASFLAGS'], diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index 2cafbd9a16..ec71b3cc73 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -109,7 +109,7 @@ tools.add_font('DEMIBOLD', FONT_DEMIBOLD, CPPDEFINES_MOD, SOURCE_MOD) tools.add_font('MONO', FONT_MONO, CPPDEFINES_MOD, SOURCE_MOD) tools.add_font('BIG', FONT_BIG, CPPDEFINES_MOD, SOURCE_MOD) -env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0')), CONSTRAINTS=["limited_util_s"]) +env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0'))) FEATURES_AVAILABLE = tools.configure_board(TREZOR_MODEL, FEATURES_WANTED, env, CPPDEFINES_HAL, SOURCE_HAL, PATH_HAL) @@ -151,6 +151,7 @@ env.Replace( CPPDEFINES=[ 'TREZOR_PRODTEST', 'TREZOR_MODEL_'+TREZOR_MODEL, + 'ARM_USER_MODE', 'USE_HAL_DRIVER', ] + CPPDEFINES_MOD + CPPDEFINES_HAL, ASFLAGS=env.get('ENV')['CPU_ASFLAGS'], diff --git a/core/embed/boardloader/memory.ld b/core/embed/boardloader/memory.ld index 328bcd0351..5dbf852a0f 100644 --- a/core/embed/boardloader/memory.ld +++ b/core/embed/boardloader/memory.ld @@ -3,9 +3,9 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 48K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 48K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(CCMRAM) + LENGTH(CCMRAM); /* 8-byte aligned full descending stack */ diff --git a/core/embed/bootloader/.changelog.d/2794.added b/core/embed/bootloader/.changelog.d/2794.added new file mode 100644 index 0000000000..0c881be08b --- /dev/null +++ b/core/embed/bootloader/.changelog.d/2794.added @@ -0,0 +1 @@ +Minimize risk of losing seed when upgrading firmware diff --git a/core/embed/bootloader/.changelog.d/2919.added b/core/embed/bootloader/.changelog.d/2919.added new file mode 100644 index 0000000000..708f3fe44d --- /dev/null +++ b/core/embed/bootloader/.changelog.d/2919.added @@ -0,0 +1 @@ +Support interaction-less upgrade \ No newline at end of file diff --git a/core/embed/bootloader/boot_internal.h b/core/embed/bootloader/boot_internal.h new file mode 100644 index 0000000000..74fb1541af --- /dev/null +++ b/core/embed/bootloader/boot_internal.h @@ -0,0 +1,21 @@ +#ifndef BOOT_INTERNAL_H +#define BOOT_INTERNAL_H + +#include +#include + +// The 'g_boot_command' variable stores the 'command' passed to the +// function 'svc_reboot_to_bootloader()'. It may be one of the +// 'BOOT_COMMAND_xxx' values defined in the enumeration, or it could +// be any other value that should be treated as a non-special action, +// in which case the bootloader should behave as if the device was +// just powered up. The variable is set before the main() is called. +extern boot_command_t g_boot_command; + + +// The 'g_boot_args' array stores extra arguments passed +// function 'svc_reboot_to_bootloader()' +extern uint8_t g_boot_args[BOOT_ARGS_SIZE]; + + +#endif // BOOT_INTERNAL_H diff --git a/core/embed/bootloader/emulator.c b/core/embed/bootloader/emulator.c index 0a82e800d8..05b1c9ad40 100644 --- a/core/embed/bootloader/emulator.c +++ b/core/embed/bootloader/emulator.c @@ -2,6 +2,7 @@ #include #include TREZOR_BOARD +#include "boot_internal.h" #include "bootui.h" #include "common.h" #include "display.h" @@ -17,7 +18,12 @@ #undef FIRMWARE_START uint8_t *FIRMWARE_START = 0; -uint32_t stay_in_bootloader_flag; + +// Simulation of a boot command normally grabbed during reset processing +boot_command_t g_boot_command = BOOT_COMMAND_NONE; +// Simulation of a boot args normally sitting at the BOOT_ARGS region +uint8_t g_boot_args[BOOT_ARGS_SIZE]; + void set_core_clock(int) {} @@ -47,7 +53,7 @@ __attribute__((noreturn)) int main(int argc, char **argv) { if (argc == 2) { if (argv[1][0] == 's') { // Run the firmware - stay_in_bootloader_flag = STAY_IN_BOOTLOADER_FLAG; + g_boot_command = BOOT_COMMAND_STOP_AND_WAIT; } #ifdef USE_OPTIGA else if (argv[1][0] == 'l') { diff --git a/core/embed/bootloader/emulator.h b/core/embed/bootloader/emulator.h index c8dea36c14..d73b8bed4a 100644 --- a/core/embed/bootloader/emulator.h +++ b/core/embed/bootloader/emulator.h @@ -2,7 +2,6 @@ #define __EMULATOR_H__ #define CLOCK_180_MHZ 0 -#define STAY_IN_BOOTLOADER_FLAG 0x0FC35A96 #define mini_snprintf snprintf #undef FIRMWARE_START @@ -11,7 +10,6 @@ #include extern uint8_t *FIRMWARE_START; -extern uint32_t stay_in_bootloader_flag; void emulator_poll_events(void); void set_core_clock(int); diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index fd6a6e5d45..f9fed5cc0f 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -20,6 +20,7 @@ #include #include +#include "boot_internal.h" #include "common.h" #include "display.h" #include "flash.h" @@ -68,19 +69,6 @@ #include "platform.h" #endif -const uint8_t BOOTLOADER_KEY_M = 2; -const uint8_t BOOTLOADER_KEY_N = 3; -static const uint8_t * const BOOTLOADER_KEYS[] = { -#if !PRODUCTION - /*** DEVEL/QA KEYS ***/ - (const uint8_t *)"\xd7\x59\x79\x3b\xbc\x13\xa2\x81\x9a\x82\x7c\x76\xad\xb6\xfb\xa8\xa4\x9a\xee\x00\x7f\x49\xf2\xd0\x99\x2d\x99\xb8\x25\xad\x2c\x48", - (const uint8_t *)"\x63\x55\x69\x1c\x17\x8a\x8f\xf9\x10\x07\xa7\x47\x8a\xfb\x95\x5e\xf7\x35\x2c\x63\xe7\xb2\x57\x03\x98\x4c\xf7\x8b\x26\xe2\x1a\x56", - (const uint8_t *)"\xee\x93\xa4\xf6\x6f\x8d\x16\xb8\x19\xbb\x9b\xeb\x9f\xfc\xcd\xfc\xdc\x14\x12\xe8\x7f\xee\x6a\x32\x4c\x2a\x99\xa1\xe0\xe6\x71\x48", -#else - MODEL_BOOTLOADER_KEYS -#endif -}; - #define USB_IFACE_NUM 0 typedef enum { @@ -251,11 +239,6 @@ static usb_result_t bootloader_usb_loop(const vendor_header *const vhdr, } } -secbool check_vendor_header_keys(const vendor_header *const vhdr) { - return check_vendor_header_sig(vhdr, BOOTLOADER_KEY_M, BOOTLOADER_KEY_N, - BOOTLOADER_KEYS); -} - static secbool check_vendor_header_lock(const vendor_header *const vhdr) { uint8_t lock[FLASH_OTP_BLOCK_SIZE]; ensure(flash_otp_read(FLASH_OTP_BLOCK_VENDOR_HEADER_LOCK, 0, lock, @@ -376,12 +359,10 @@ void real_jump_to_firmware(void) { #ifndef TREZOR_EMULATOR int main(void) { - // grab "stay in bootloader" flag as soon as possible - register uint32_t r11 __asm__("r11"); - volatile uint32_t stay_in_bootloader_flag = r11; #else int bootloader_main(void) { #endif + secbool stay_in_bootloader = secfalse; random_delays_init(); // display_init_seq(); @@ -413,6 +394,7 @@ int bootloader_main(void) { volatile secbool header_present = secfalse; volatile secbool firmware_present = secfalse; volatile secbool firmware_present_backup = secfalse; + volatile secbool auto_upgrade = secfalse; vhdr_present = read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr); @@ -482,10 +464,19 @@ int bootloader_main(void) { check_bootloader_version(); #endif - // was there reboot with request to stay in bootloader? - secbool stay_in_bootloader = secfalse; - if (stay_in_bootloader_flag == STAY_IN_BOOTLOADER_FLAG) { - stay_in_bootloader = sectrue; + switch (g_boot_command) { + case BOOT_COMMAND_STOP_AND_WAIT: + // firmare requested to stay in bootloader + stay_in_bootloader = sectrue; + break; + case BOOT_COMMAND_INSTALL_UPGRADE: + if (firmware_present == sectrue) { + // continue without user interaction + auto_upgrade = sectrue; + } + break; + default: + break; } ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup), @@ -521,12 +512,20 @@ int bootloader_main(void) { // start the bootloader ... // ... if user touched the screen on start // ... or we have stay_in_bootloader flag to force it + // ... or strict upgrade was confirmed in the firmware (auto_upgrade flag) // ... or there is no valid firmware - if (touched || stay_in_bootloader == sectrue || firmware_present != sectrue) { + if (touched || stay_in_bootloader == sectrue || firmware_present != sectrue || + auto_upgrade == sectrue) { screen_t screen; + ui_set_initial_setup(true); if (header_present == sectrue) { - ui_set_initial_setup(false); - screen = SCREEN_INTRO; + if (auto_upgrade == sectrue) { + screen = SCREEN_WAIT_FOR_HOST; + } else { + ui_set_initial_setup(false); + screen = SCREEN_INTRO; + } + } else { screen = SCREEN_WELCOME; @@ -534,8 +533,6 @@ int bootloader_main(void) { ensure(flash_area_erase_bulk(STORAGE_AREAS, STORAGE_AREAS_COUNT, NULL), NULL); - ui_set_initial_setup(true); - // keep the model screen up for a while #ifndef USE_BACKLIGHT hal_delay(1500); @@ -613,7 +610,7 @@ int bootloader_main(void) { } break; case SCREEN_WAIT_FOR_HOST: - screen_connect(); + screen_connect(auto_upgrade == sectrue); switch (bootloader_usb_loop(&vhdr, hdr)) { case CONTINUE_TO_FIRMWARE: continue_to_firmware = sectrue; diff --git a/core/embed/bootloader/memory.ld b/core/embed/bootloader/memory.ld index bec4f50803..b96888c7e5 100644 --- a/core/embed/bootloader/memory.ld +++ b/core/embed/bootloader/memory.ld @@ -3,9 +3,10 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 128K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 128K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - 0x100 + BOOT_ARGS (wal) : ORIGIN = 0x1000FF00, LENGTH = 0x100 + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(CCMRAM) + SIZEOF(.stack) ; /* 8-byte aligned full descending stack */ @@ -23,8 +24,10 @@ ccmram_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); sram_start = ORIGIN(SRAM); sram_end = ORIGIN(SRAM) + LENGTH(SRAM); -/* IMAGE_HEADER_SIZE is 0x400, this is for interaction-less firmware update start */ -firmware_header_start = ccmram_end - 0x400; +/* reserve 256 bytes for bootloader arguments */ +boot_args_start = ORIGIN(BOOT_ARGS); +boot_args_end = ORIGIN(BOOT_ARGS) + LENGTH(BOOT_ARGS); +g_boot_args = boot_args_start; _codelen = SIZEOF(.flash) + SIZEOF(.data); diff --git a/core/embed/bootloader/messages.c b/core/embed/bootloader/messages.c index 9226b1b809..84d23e8e5e 100644 --- a/core/embed/bootloader/messages.c +++ b/core/embed/bootloader/messages.c @@ -24,6 +24,7 @@ #include #include "messages.pb.h" +#include "boot_internal.h" #include "common.h" #include "flash.h" #include "image.h" @@ -426,8 +427,6 @@ static bool _read_payload(pb_istream_t *stream, const pb_field_t *field, return true; } -secbool check_vendor_header_keys(const vendor_header *const vhdr); - static int version_compare(uint32_t vera, uint32_t verb) { int a, b; a = vera & 0xFF; @@ -448,11 +447,12 @@ static void detect_installation(const vendor_header *current_vhdr, const image_header *current_hdr, const vendor_header *const new_vhdr, const image_header *const new_hdr, - secbool *is_new, secbool *is_upgrade, - secbool *is_newvendor) { + secbool *is_new, secbool *keep_seed, + secbool *is_newvendor, secbool *is_upgrade) { *is_new = secfalse; - *is_upgrade = secfalse; + *keep_seed = secfalse; *is_newvendor = secfalse; + *is_upgrade = secfalse; if (sectrue != check_vendor_header_keys(current_vhdr)) { *is_new = sectrue; return; @@ -477,7 +477,11 @@ static void detect_installation(const vendor_header *current_vhdr, if (version_compare(new_hdr->version, current_hdr->fix_version) < 0) { return; } - *is_upgrade = sectrue; + if (version_compare(new_hdr->version, current_hdr->version) > 0) { + *is_upgrade = sectrue; + } + + *keep_seed = sectrue; } static int firmware_upload_chunk_retry = FIRMWARE_UPLOAD_CHUNK_RETRY_COUNT; @@ -577,9 +581,51 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, secbool should_keep_seed = secfalse; secbool is_newvendor = secfalse; + secbool is_upgrade = secfalse; if (is_new == secfalse) { detect_installation(¤t_vhdr, current_hdr, &vhdr, &hdr, &is_new, - &should_keep_seed, &is_newvendor); + &should_keep_seed, &is_newvendor, &is_upgrade); + } + + secbool is_ilu = secfalse; // interaction-less update + + if (g_boot_command == BOOT_COMMAND_INSTALL_UPGRADE) { + BLAKE2S_CTX ctx; + uint8_t hash[BLAKE2S_DIGEST_LENGTH]; + blake2s_Init(&ctx, BLAKE2S_DIGEST_LENGTH); + blake2s_Update(&ctx, CHUNK_BUFFER_PTR, + vhdr.hdrlen + received_hdr->hdrlen); + blake2s_Final(&ctx, hash, BLAKE2S_DIGEST_LENGTH); + + // the firmware must be the same as confirmed by the user + if (memcmp(&g_boot_args[0], hash, sizeof(hash)) != 0) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Firmware mismatch"); + MSG_SEND(Failure); + return UPLOAD_ERR_FIRMWARE_MISMATCH; + } + + // the firmware must be from the same vendor + // the firmware must be newer + if (is_upgrade != sectrue || is_newvendor != secfalse) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Not a firmware upgrade"); + MSG_SEND(Failure); + return UPLOAD_ERR_NOT_FIRMWARE_UPGRADE; + } + + if ((vhdr.vtrust & VTRUST_ALL) != VTRUST_ALL) { + MSG_SEND_INIT(Failure); + MSG_SEND_ASSIGN_VALUE(code, FailureType_Failure_ProcessError); + MSG_SEND_ASSIGN_STRING(message, "Not a full-trust image"); + MSG_SEND(Failure); + return UPLOAD_ERR_NOT_FULLTRUST_IMAGE; + } + + // upload the firmware without confirmation + is_ilu = sectrue; } #ifdef USE_OPTIGA @@ -593,8 +639,8 @@ int process_msg_FirmwareUpload(uint8_t iface_num, uint32_t msg_size, #endif uint32_t response = INPUT_CANCEL; - if (sectrue == is_new) { - // new installation - auto confirm + if (sectrue == is_new || sectrue == is_ilu) { + // new installation or interaction less updated - auto confirm response = INPUT_CONFIRM; } else { int version_cmp = version_compare(hdr.version, current_hdr->version); diff --git a/core/embed/bootloader/messages.h b/core/embed/bootloader/messages.h index 45a128560b..1b69508634 100644 --- a/core/embed/bootloader/messages.h +++ b/core/embed/bootloader/messages.h @@ -42,6 +42,9 @@ enum { UPLOAD_ERR_FIRMWARE_TOO_BIG = -8, UPLOAD_ERR_INVALID_CHUNK_HASH = -9, UPLOAD_ERR_BOOTLOADER_LOCKED = -10, + UPLOAD_ERR_FIRMWARE_MISMATCH = -11, + UPLOAD_ERR_NOT_FIRMWARE_UPGRADE = -12, + UPLOAD_ERR_NOT_FULLTRUST_IMAGE = -13, }; enum { diff --git a/core/embed/bootloader/startup.s b/core/embed/bootloader/startup.s index 20044d5a9b..bb2d3ef6ce 100644 --- a/core/embed/bootloader/startup.s +++ b/core/embed/bootloader/startup.s @@ -7,7 +7,7 @@ reset_handler: // setup environment for subsequent stage of code ldr r0, =ccmram_start // r0 - point to beginning of CCMRAM - ldr r1, =firmware_header_start // r1 - point to byte where firmware image header might start + ldr r1, =ccmram_end // r1 - point to byte where BOOT_ARGS region starts ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg @@ -33,9 +33,22 @@ reset_handler: // subsequent operations, it is not necessary to insert a memory barrier instruction." cpsie f + // r11 contains argument passed to reboot_to_bootloader() + // function called when the firmware rebooted to the bootloader + ldr r0, =g_boot_command + str r11, [r0] + // enter the application code bl main b shutdown_privileged + .bss + + .global g_boot_command +g_boot_command: + .word 0 + + .end + diff --git a/core/embed/bootloader_ci/main.c b/core/embed/bootloader_ci/main.c index 3810d68643..8285a99af7 100644 --- a/core/embed/bootloader_ci/main.c +++ b/core/embed/bootloader_ci/main.c @@ -40,18 +40,6 @@ #include "model.h" // #include "mpu.h" -const uint8_t BOOTLOADER_KEY_M = 2; -const uint8_t BOOTLOADER_KEY_N = 3; -static const uint8_t * const BOOTLOADER_KEYS[] = { - (const uint8_t *)"\xc2\xc8\x7a\x49\xc5\xa3\x46\x09\x77\xfb\xb2\xec\x9d\xfe\x60\xf0\x6b\xd6\x94\xdb\x82\x44\xbd\x49\x81\xfe\x3b\x7a\x26\x30\x7f\x3f", - (const uint8_t *)"\x80\xd0\x36\xb0\x87\x39\xb8\x46\xf4\xcb\x77\x59\x30\x78\xde\xb2\x5d\xc9\x48\x7a\xed\xcf\x52\xe3\x0b\x4f\xb7\xcd\x70\x24\x17\x8a", - (const uint8_t *)"\xb8\x30\x7a\x71\xf5\x52\xc6\x0a\x4c\xbb\x31\x7f\xf4\x8b\x82\xcd\xbf\x6b\x6b\xb5\xf0\x4c\x92\x0f\xec\x7b\xad\xf0\x17\x88\x37\x51", -// comment the lines above and uncomment the lines below to use a custom signed vendorheader -// (const uint8_t *)"\xd7\x59\x79\x3b\xbc\x13\xa2\x81\x9a\x82\x7c\x76\xad\xb6\xfb\xa8\xa4\x9a\xee\x00\x7f\x49\xf2\xd0\x99\x2d\x99\xb8\x25\xad\x2c\x48", -// (const uint8_t *)"\x63\x55\x69\x1c\x17\x8a\x8f\xf9\x10\x07\xa7\x47\x8a\xfb\x95\x5e\xf7\x35\x2c\x63\xe7\xb2\x57\x03\x98\x4c\xf7\x8b\x26\xe2\x1a\x56", -// (const uint8_t *)"\xee\x93\xa4\xf6\x6f\x8d\x16\xb8\x19\xbb\x9b\xeb\x9f\xfc\xcd\xfc\xdc\x14\x12\xe8\x7f\xee\x6a\x32\x4c\x2a\x99\xa1\xe0\xe6\x71\x48", -}; - #define USB_IFACE_NUM 0 static void usb_init_all(secbool usb21_landing) { @@ -166,11 +154,6 @@ static secbool bootloader_usb_loop(const vendor_header *const vhdr, } } -secbool check_vendor_header_keys(vendor_header *const vhdr) { - return check_vendor_header_sig(vhdr, BOOTLOADER_KEY_M, BOOTLOADER_KEY_N, - BOOTLOADER_KEYS); -} - static secbool check_vendor_header_lock(const vendor_header *const vhdr) { uint8_t lock[FLASH_OTP_BLOCK_SIZE]; ensure(flash_otp_read(FLASH_OTP_BLOCK_VENDOR_HEADER_LOCK, 0, lock, diff --git a/core/embed/bootloader_ci/memory.ld b/core/embed/bootloader_ci/memory.ld index 9c89cc6347..8a05c75c46 100644 --- a/core/embed/bootloader_ci/memory.ld +++ b/core/embed/bootloader_ci/memory.ld @@ -3,9 +3,10 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 128K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08020000, LENGTH = 128K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - 0x100 + BOOT_ARGS (wal) : ORIGIN = 0x1000FF00, LENGTH = 0x100 + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(CCMRAM) + LENGTH(CCMRAM); /* 8-byte aligned full descending stack */ @@ -23,8 +24,9 @@ ccmram_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); sram_start = ORIGIN(SRAM); sram_end = ORIGIN(SRAM) + LENGTH(SRAM); -/* IMAGE_HEADER_SIZE is 0x400, this is for interaction-less firmware update start */ -firmware_header_start = ccmram_end - 0x400; +/* reserve 256 bytes for bootloader arguments */ +boot_args_start = ORIGIN(BOOT_ARGS); +boot_args_end = ORIGIN(BOOT_ARGS) + LENGTH(BOOT_ARGS); _codelen = SIZEOF(.flash) + SIZEOF(.data); diff --git a/core/embed/bootloader_ci/messages.c b/core/embed/bootloader_ci/messages.c index 4695fb8482..d3af48627c 100644 --- a/core/embed/bootloader_ci/messages.c +++ b/core/embed/bootloader_ci/messages.c @@ -402,8 +402,6 @@ static bool _read_payload(pb_istream_t *stream, const pb_field_t *field, return true; } -secbool check_vendor_header_keys(const vendor_header *const vhdr); - static int version_compare(uint32_t vera, uint32_t verb) { int a, b; a = vera & 0xFF; diff --git a/core/embed/bootloader_ci/startup.s b/core/embed/bootloader_ci/startup.s index 101e222c51..59a80d3c18 100644 --- a/core/embed/bootloader_ci/startup.s +++ b/core/embed/bootloader_ci/startup.s @@ -7,7 +7,7 @@ reset_handler: // setup environment for subsequent stage of code ldr r0, =ccmram_start // r0 - point to beginning of CCMRAM - ldr r1, =firmware_header_start // r1 - point to byte where firmware header starts + ldr r1, =ccmram_end // r1 - point to byte where BOOT_ARGS region starts ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg diff --git a/core/embed/extmod/modtrezorutils/modtrezorutils.c b/core/embed/extmod/modtrezorutils/modtrezorutils.c index 55281b5062..4fd54d301d 100644 --- a/core/embed/extmod/modtrezorutils/modtrezorutils.c +++ b/core/embed/extmod/modtrezorutils/modtrezorutils.c @@ -23,6 +23,7 @@ #include "supervise.h" #endif +#include "image.h" #include "version.h" #if MICROPY_PY_TREZORUTILS @@ -245,18 +246,90 @@ STATIC mp_obj_t mod_trezorutils_unit_btconly(void) { STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_unit_btconly_obj, mod_trezorutils_unit_btconly); -/// def reboot_to_bootloader() -> None: +/// def reboot_to_bootloader( +/// boot_command : int = 0, +/// boot_args : bytes | None = None, +/// ) -> None: /// """ /// Reboots to bootloader. /// """ -STATIC mp_obj_t mod_trezorutils_reboot_to_bootloader() { +STATIC mp_obj_t mod_trezorutils_reboot_to_bootloader(size_t n_args, + const mp_obj_t *args) { #ifndef TREZOR_EMULATOR - svc_reboot_to_bootloader(); + boot_command_t boot_command = BOOT_COMMAND_NONE; + mp_buffer_info_t boot_args = {0}; + + if (n_args > 0 && args[0] != mp_const_none) { + mp_int_t value = mp_obj_get_int(args[0]); + + switch (value) { + case 0: + boot_command = BOOT_COMMAND_STOP_AND_WAIT; + break; + case 1: + boot_command = BOOT_COMMAND_INSTALL_UPGRADE; + break; + default: + mp_raise_ValueError("Invalid value."); + break; + } + } + + if (n_args > 1 && args[1] != mp_const_none) { + mp_get_buffer_raise(args[1], &boot_args, MP_BUFFER_READ); + } + + svc_reboot_to_bootloader(boot_command, boot_args.buf, boot_args.len); #endif return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_reboot_to_bootloader_obj, - mod_trezorutils_reboot_to_bootloader); + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mod_trezorutils_reboot_to_bootloader_obj, 0, 2, + mod_trezorutils_reboot_to_bootloader); + +/// def check_firmware_header( +/// header : bytes +/// ) -> dict: +/// """ +/// Checks firmware image and vendor header and returns +/// { "version": (major, minor, patch), +/// "vendor": string, +/// "fingerprint": bytes, +/// "hash": bytes +/// } +/// """ +STATIC mp_obj_t mod_trezorutils_check_firmware_header(mp_obj_t header) { + mp_buffer_info_t header_buf = {0}; + mp_get_buffer_raise(header, &header_buf, MP_BUFFER_READ); + + firmware_header_info_t info; + + if (sectrue == check_firmware_header(header_buf.buf, header_buf.len, &info)) { + mp_obj_t version[3] = {mp_obj_new_int(info.ver_major), + mp_obj_new_int(info.ver_minor), + mp_obj_new_int(info.ver_patch)}; + + mp_obj_t result = mp_obj_new_dict(4); + mp_obj_dict_store(result, MP_ROM_QSTR(MP_QSTR_version), + mp_obj_new_tuple(MP_ARRAY_SIZE(version), version)); + mp_obj_dict_store( + result, MP_ROM_QSTR(MP_QSTR_vendor), + mp_obj_new_str_copy(&mp_type_str, info.vstr, info.vstr_len)); + mp_obj_dict_store( + result, MP_ROM_QSTR(MP_QSTR_fingerprint), + mp_obj_new_bytes(info.fingerprint, sizeof(info.fingerprint))); + mp_obj_dict_store(result, MP_ROM_QSTR(MP_QSTR_hash), + mp_obj_new_bytes(info.hash, sizeof(info.hash))); + + return result; + } + + mp_raise_ValueError("Invalid value."); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_check_firmware_header_obj, + mod_trezorutils_check_firmware_header); /// def bootloader_locked() -> bool | None: /// """ @@ -328,6 +401,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = { MP_ROM_PTR(&mod_trezorutils_firmware_vendor_obj)}, {MP_ROM_QSTR(MP_QSTR_reboot_to_bootloader), MP_ROM_PTR(&mod_trezorutils_reboot_to_bootloader_obj)}, + {MP_ROM_QSTR(MP_QSTR_check_firmware_header), + MP_ROM_PTR(&mod_trezorutils_check_firmware_header_obj)}, {MP_ROM_QSTR(MP_QSTR_bootloader_locked), MP_ROM_PTR(&mod_trezorutils_bootloader_locked_obj)}, {MP_ROM_QSTR(MP_QSTR_unit_color), diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 304699f88c..4a95ca39b3 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -244,83 +244,6 @@ void BusFault_Handler(void) { error_shutdown("INTERNAL ERROR", "(BF)"); } void UsageFault_Handler(void) { error_shutdown("INTERNAL ERROR", "(UF)"); } -__attribute__((noreturn)) void reboot_to_bootloader() { - mpu_config_bootloader(); - jump_to_with_flag(BOOTLOADER_START + IMAGE_HEADER_SIZE, - STAY_IN_BOOTLOADER_FLAG); - for (;;) - ; -} - -void copy_image_header_for_bootloader(const uint8_t *image_header) { - memcpy(&firmware_header_start, image_header, IMAGE_HEADER_SIZE); -} - -void SVC_C_Handler(uint32_t *stack) { - uint8_t svc_number = ((uint8_t *)stack[6])[-2]; - bool clear_firmware_header = true; - switch (svc_number) { - case SVC_ENABLE_IRQ: - HAL_NVIC_EnableIRQ(stack[0]); - break; - case SVC_DISABLE_IRQ: - HAL_NVIC_DisableIRQ(stack[0]); - break; - case SVC_SET_PRIORITY: - NVIC_SetPriority(stack[0], stack[1]); - break; -#ifdef SYSTEM_VIEW - case SVC_GET_DWT_CYCCNT: - cyccnt_cycles = *DWT_CYCCNT_ADDR; - break; -#endif - case SVC_SHUTDOWN: - shutdown_privileged(); - for (;;) - ; - break; - case SVC_REBOOT_COPY_IMAGE_HEADER: - copy_image_header_for_bootloader((uint8_t *)stack[0]); - clear_firmware_header = false; - // break is omitted here because we want to continue to reboot below - case SVC_REBOOT_TO_BOOTLOADER: - // if not going from copy image header & reboot, clean preventively this - // part of CCMRAM - if (clear_firmware_header) { - explicit_bzero(&firmware_header_start, IMAGE_HEADER_SIZE); - } - - ensure_compatible_settings(); - - __asm__ volatile("msr control, %0" ::"r"(0x0)); - __asm__ volatile("isb"); - // See stack layout in - // https://developer.arm.com/documentation/ka004005/latest We are changing - // return address in PC to land into reboot to avoid any bug with ROP and - // raising privileges. - stack[6] = (uintptr_t)reboot_to_bootloader; - return; - case SVC_GET_SYSTICK_VAL: { - systick_val_copy = SysTick->VAL; - } break; - default: - stack[0] = 0xffffffff; - break; - } -} - -__attribute__((naked)) void SVC_Handler(void) { - __asm volatile( - " tst lr, #4 \n" // Test Bit 3 to see which stack pointer we should - // use. - " ite eq \n" // Tell the assembler that the nest 2 instructions - // are if-then-else - " mrseq r0, msp \n" // Make R0 point to main stack pointer - " mrsne r0, psp \n" // Make R0 point to process stack pointer - " b SVC_C_Handler \n" // Off to C land - ); -} - // MicroPython builtin stubs mp_import_stat_t mp_import_stat(const char *path) { diff --git a/core/embed/firmware/memory_T.ld b/core/embed/firmware/memory_T.ld index 82008aaade..fa557dccd1 100644 --- a/core/embed/firmware/memory_T.ld +++ b/core/embed/firmware/memory_T.ld @@ -3,10 +3,11 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K - FLASH2 (r) : ORIGIN = 0x08120000, LENGTH = 896K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K + FLASH2 (r) : ORIGIN = 0x08120000, LENGTH = 896K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - 0x100 + BOOT_ARGS (wal) : ORIGIN = 0x1000FF00, LENGTH = 0x100 + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(SRAM) + SIZEOF(.stack); /* 8-byte aligned full descending stack */ @@ -22,8 +23,9 @@ data_size = SIZEOF(.data); ccmram_start = ORIGIN(CCMRAM); ccmram_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); -/* IMAGE_HEADER_SIZE is 0x400, this is for interaction-less firmware update start */ -firmware_header_start = ccmram_end - 0x400; +/* reserve 256 bytes for bootloader arguments */ +boot_args_start = ORIGIN(BOOT_ARGS); +boot_args_end = ORIGIN(BOOT_ARGS) + LENGTH(BOOT_ARGS); /* used by the startup code to wipe memory */ sram_start = ORIGIN(SRAM); diff --git a/core/embed/firmware/startup.S b/core/embed/firmware/startup.S index 7b44f5f196..dbed46bb25 100644 --- a/core/embed/firmware/startup.S +++ b/core/embed/firmware/startup.S @@ -28,6 +28,11 @@ reset_handler: ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg + ldr r0, =boot_args_start // r0 - point to beginning of BOOT_ARGS + ldr r1, =boot_args_end // r1 - point to byte after the end of BOOT_ARGS + ldr r2, =0 // r2 - the word-sized value to be written + bl memset_reg + ldr r0, =sram_start // r0 - point to beginning of SRAM ldr r1, =sram_end // r1 - point to byte after the end of SRAM ldr r2, =0 // r2 - the word-sized value to be written diff --git a/core/embed/lib/image.c b/core/embed/lib/image.c index c3fce9a42a..e6e098d893 100644 --- a/core/embed/lib/image.c +++ b/core/embed/lib/image.c @@ -25,6 +25,20 @@ #include "common.h" #include "flash.h" #include "image.h" +#include "model.h" + +const uint8_t BOOTLOADER_KEY_M = 2; +const uint8_t BOOTLOADER_KEY_N = 3; +static const uint8_t * const BOOTLOADER_KEYS[] = { +#if !PRODUCTION + /*** DEVEL/QA KEYS ***/ + (const uint8_t *)"\xd7\x59\x79\x3b\xbc\x13\xa2\x81\x9a\x82\x7c\x76\xad\xb6\xfb\xa8\xa4\x9a\xee\x00\x7f\x49\xf2\xd0\x99\x2d\x99\xb8\x25\xad\x2c\x48", + (const uint8_t *)"\x63\x55\x69\x1c\x17\x8a\x8f\xf9\x10\x07\xa7\x47\x8a\xfb\x95\x5e\xf7\x35\x2c\x63\xe7\xb2\x57\x03\x98\x4c\xf7\x8b\x26\xe2\x1a\x56", + (const uint8_t *)"\xee\x93\xa4\xf6\x6f\x8d\x16\xb8\x19\xbb\x9b\xeb\x9f\xfc\xcd\xfc\xdc\x14\x12\xe8\x7f\xee\x6a\x32\x4c\x2a\x99\xa1\xe0\xe6\x71\x48", +#else + MODEL_BOOTLOADER_KEYS +#endif +}; static secbool compute_pubkey(uint8_t sig_m, uint8_t sig_n, const uint8_t *const *pub, uint8_t sigmask, @@ -203,6 +217,11 @@ secbool check_vendor_header_sig(const vendor_header *const vhdr, uint8_t key_m, *(const ed25519_signature *)vhdr->sig)); } +secbool check_vendor_header_keys(const vendor_header *const vhdr) { + return check_vendor_header_sig(vhdr, BOOTLOADER_KEY_M, BOOTLOADER_KEY_N, + BOOTLOADER_KEYS); +} + void vendor_header_hash(const vendor_header *const vhdr, uint8_t *hash) { BLAKE2S_CTX ctx; blake2s_Init(&ctx, BLAKE2S_DIGEST_LENGTH); @@ -257,3 +276,48 @@ secbool check_image_contents(const image_header *const hdr, uint32_t firstskip, return sectrue; } + +secbool check_firmware_header(const uint8_t *header, size_t header_size, + firmware_header_info_t *info) { + // parse and check vendor header + vendor_header vhdr; + if (sectrue != read_vendor_header(header, &vhdr)) { + return secfalse; + } + if (sectrue != check_vendor_header_keys(&vhdr)) { + return secfalse; + } + + // parse and check image header + const image_header *ihdr; + if ((ihdr = read_image_header(header + vhdr.hdrlen, FIRMWARE_IMAGE_MAGIC, + FIRMWARE_IMAGE_MAXSIZE)) == NULL) { + return secfalse; + } + if (sectrue != + check_image_header_sig(ihdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub)) { + return secfalse; + } + + // copy vendor string + info->vstr_len = MIN(sizeof(info->vstr), vhdr.vstr_len); + if (info->vstr_len > 0) { + memcpy(info->vstr, vhdr.vstr, info->vstr_len); + } + + // copy firmware version + info->ver_major = ihdr->version & 0xFF; + info->ver_minor = (ihdr->version >> 8) & 0xFF; + info->ver_patch = (ihdr->version >> 16) & 0xFF; + + // calculate and copy the image fingerprint + get_image_fingerprint(ihdr, info->fingerprint); + + // calculate hash of both vendor and image headers + BLAKE2S_CTX ctx; + blake2s_Init(&ctx, BLAKE2S_DIGEST_LENGTH); + blake2s_Update(&ctx, header, vhdr.hdrlen + ihdr->hdrlen); + blake2s_Final(&ctx, &info->hash, BLAKE2S_DIGEST_LENGTH); + + return sectrue; +} diff --git a/core/embed/lib/image.h b/core/embed/lib/image.h index 57313e2bf0..561c0b830c 100644 --- a/core/embed/lib/image.h +++ b/core/embed/lib/image.h @@ -21,6 +21,7 @@ #define __TREZORHAL_IMAGE_H__ #include +#include "blake2s.h" #include "flash.h" #include "model.h" #include "secbool.h" @@ -78,6 +79,21 @@ typedef struct { const uint8_t *origin; // pointer to the underlying data } vendor_header; +typedef struct { + // vendor string + uint8_t vstr[64]; + // vendor string length + size_t vstr_len; + // firmware version + uint8_t ver_major; + uint8_t ver_minor; + uint8_t ver_patch; + // firmware fingerprint + uint8_t fingerprint[BLAKE2S_DIGEST_LENGTH]; + // hash of vendor and image header + uint8_t hash[BLAKE2S_DIGEST_LENGTH]; +} firmware_header_info_t; + const image_header *read_image_header(const uint8_t *const data, const uint32_t magic, const uint32_t maxsize); @@ -95,6 +111,8 @@ secbool __wur check_vendor_header_sig(const vendor_header *const vhdr, uint8_t key_m, uint8_t key_n, const uint8_t *const *keys); +secbool check_vendor_header_keys(const vendor_header *const vhdr); + void vendor_header_hash(const vendor_header *const vhdr, uint8_t *hash); secbool __wur check_single_hash(const uint8_t *const hash, @@ -106,4 +124,7 @@ secbool __wur check_image_contents(const image_header *const hdr, void get_image_fingerprint(const image_header *const hdr, uint8_t *const out); +secbool check_firmware_header(const uint8_t *header, size_t header_size, + firmware_header_info_t *info); + #endif diff --git a/core/embed/prodtest/main.c b/core/embed/prodtest/main.c index b0422f8e5f..b1121f9f6a 100644 --- a/core/embed/prodtest/main.c +++ b/core/embed/prodtest/main.c @@ -667,28 +667,4 @@ int main(void) { return 0; } -void SVC_C_Handler(uint32_t *stack) { - uint8_t svc_number = ((uint8_t *)stack[6])[-2]; - switch (svc_number) { - case SVC_GET_SYSTICK_VAL: { - systick_val_copy = SysTick->VAL; - } break; - default: - stack[0] = 0xffffffff; - break; - } -} - -__attribute__((naked)) void SVC_Handler(void) { - __asm volatile( - " tst lr, #4 \n" // Test Bit 3 to see which stack pointer we should - // use. - " ite eq \n" // Tell the assembler that the nest 2 instructions - // are if-then-else - " mrseq r0, msp \n" // Make R0 point to main stack pointer - " mrsne r0, psp \n" // Make R0 point to process stack pointer - " b SVC_C_Handler \n" // Off to C land - ); -} - void HardFault_Handler(void) { error_shutdown("INTERNAL ERROR!", "(HF)"); } diff --git a/core/embed/prodtest/memory.ld b/core/embed/prodtest/memory.ld index f364d5c021..afbe0aa3bb 100644 --- a/core/embed/prodtest/memory.ld +++ b/core/embed/prodtest/memory.ld @@ -3,9 +3,10 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - 0x100 + BOOT_ARGS (wal) : ORIGIN = 0x1000FF00, LENGTH = 0x100 + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(SRAM) + LENGTH(SRAM); /* 8-byte aligned full descending stack */ @@ -20,6 +21,10 @@ data_size = SIZEOF(.data); ccmram_start = ORIGIN(CCMRAM); ccmram_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); +/* reserve 256 bytes for bootloader arguments */ +boot_args_start = ORIGIN(BOOT_ARGS); +boot_args_end = ORIGIN(BOOT_ARGS) + LENGTH(BOOT_ARGS); + /* used by the startup code to wipe memory */ sram_start = ORIGIN(SRAM); sram_end = ORIGIN(SRAM) + LENGTH(SRAM); diff --git a/core/embed/prodtest/startup.s b/core/embed/prodtest/startup.s index 5104f0db9b..ad8bc096ee 100644 --- a/core/embed/prodtest/startup.s +++ b/core/embed/prodtest/startup.s @@ -11,6 +11,11 @@ reset_handler: ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg + ldr r0, =boot_args_start // r0 - point to beginning of BOOT_ARGS + ldr r1, =boot_args_end // r1 - point to byte after the end of BOOT_ARGS + ldr r2, =0 // r2 - the word-sized value to be written + bl memset_reg + ldr r0, =sram_start // r0 - point to beginning of SRAM ldr r1, =sram_end // r1 - point to byte after the end of SRAM ldr r2, =0 // r2 - the word-sized value to be written diff --git a/core/embed/reflash/memory.ld b/core/embed/reflash/memory.ld index 326c64edb5..89b38a8bac 100644 --- a/core/embed/reflash/memory.ld +++ b/core/embed/reflash/memory.ld @@ -3,9 +3,10 @@ ENTRY(reset_handler) MEMORY { - FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K - CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K + FLASH (rx) : ORIGIN = 0x08040000, LENGTH = 768K + CCMRAM (wal) : ORIGIN = 0x10000000, LENGTH = 64K - 0x100 + BOOT_ARGS (wal) : ORIGIN = 0x1000FF00, LENGTH = 0x100 + SRAM (wal) : ORIGIN = 0x20000000, LENGTH = 192K } main_stack_base = ORIGIN(SRAM) + LENGTH(SRAM); /* 8-byte aligned full descending stack */ @@ -20,6 +21,10 @@ data_size = SIZEOF(.data); ccmram_start = ORIGIN(CCMRAM); ccmram_end = ORIGIN(CCMRAM) + LENGTH(CCMRAM); +/* reserve 256 bytes for bootloader arguments */ +boot_args_start = ORIGIN(BOOT_ARGS); +boot_args_end = ORIGIN(BOOT_ARGS) + LENGTH(BOOT_ARGS); + /* used by the startup code to wipe memory */ sram_start = ORIGIN(SRAM); sram_end = ORIGIN(SRAM) + LENGTH(SRAM); diff --git a/core/embed/reflash/startup.s b/core/embed/reflash/startup.s index 5104f0db9b..ad8bc096ee 100644 --- a/core/embed/reflash/startup.s +++ b/core/embed/reflash/startup.s @@ -11,6 +11,11 @@ reset_handler: ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg + ldr r0, =boot_args_start // r0 - point to beginning of BOOT_ARGS + ldr r1, =boot_args_end // r1 - point to byte after the end of BOOT_ARGS + ldr r2, =0 // r2 - the word-sized value to be written + bl memset_reg + ldr r0, =sram_start // r0 - point to beginning of SRAM ldr r1, =sram_end // r1 - point to byte after the end of SRAM ldr r2, =0 // r2 - the word-sized value to be written diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 0473c416a6..35e4626497 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -46,6 +46,7 @@ static void _librust_qstrs(void) { MP_QSTR_confirm_emphasized; MP_QSTR_confirm_ethereum_tx; MP_QSTR_confirm_fido; + MP_QSTR_confirm_firmware_update; MP_QSTR_confirm_homescreen; MP_QSTR_confirm_joint_total; MP_QSTR_confirm_modify_fee; @@ -73,6 +74,7 @@ static void _librust_qstrs(void) { MP_QSTR_fee_amount; MP_QSTR_fee_label; MP_QSTR_fee_rate_amount; + MP_QSTR_fingerprint; MP_QSTR_hold; MP_QSTR_hold_danger; MP_QSTR_horizontal; diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h index fb79f8de59..e95efaa1ad 100644 --- a/core/embed/rust/rust_ui.h +++ b/core/embed/rust/rust_ui.h @@ -15,7 +15,7 @@ uint32_t screen_intro(const char* bld_version_str, const char* vendor_str, uint8_t vendor_str_len, const char* version_str, bool fw_ok); uint32_t screen_menu(secbool firmware_present); -void screen_connect(void); +void screen_connect(bool initial_setup); void screen_fatal_error_rust(const char* title, const char* msg, const char* footer); void screen_wipe_success(void); diff --git a/core/embed/rust/src/ui/component/pad.rs b/core/embed/rust/src/ui/component/pad.rs index cf13ab452c..b26d90d77d 100644 --- a/core/embed/rust/src/ui/component/pad.rs +++ b/core/embed/rust/src/ui/component/pad.rs @@ -5,7 +5,7 @@ use crate::ui::{ pub struct Pad { pub area: Rect, - color: Color, + pub color: Color, clear: bool, } diff --git a/core/embed/rust/src/ui/model_tr/bootloader/connect.rs b/core/embed/rust/src/ui/model_tr/bootloader/connect.rs index 94143c0111..ba2e5701ec 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/connect.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/connect.rs @@ -4,7 +4,7 @@ use crate::ui::{ geometry::{Offset, Rect}, }; -use super::theme::{BLD_BG, BLD_FG}; +use super::super::theme::bootloader::{BLD_BG, BLD_FG}; pub struct Connect { bg: Pad, diff --git a/core/embed/rust/src/ui/model_tr/bootloader/intro.rs b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs index b7b157904b..e3a5af4a5b 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/intro.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs @@ -6,9 +6,11 @@ use crate::ui::{ use super::{ super::{ component::{ButtonController, ButtonControllerMsg::Triggered, ButtonLayout, ButtonPos}, - theme::{BUTTON_HEIGHT, ICON_WARN_TITLE, TITLE_AREA_HEIGHT}, + theme::{ + bootloader::{BLD_BG, BLD_FG, TEXT_NORMAL}, + BUTTON_HEIGHT, ICON_WARN_TITLE, TITLE_AREA_HEIGHT, + }, }, - theme::{BLD_BG, BLD_FG, TEXT_NORMAL}, ReturnToC, }; diff --git a/core/embed/rust/src/ui/model_tr/bootloader/menu.rs b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs index a8d61b6b0b..73afbfc290 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/menu.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs @@ -12,8 +12,10 @@ use crate::{ }; use super::{ - super::component::{Choice, ChoiceFactory, ChoicePage}, - theme::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH}, + super::{ + component::{Choice, ChoiceFactory, ChoicePage}, + theme::bootloader::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH}, + }, ReturnToC, }; diff --git a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs index aa92083483..ea951b66f2 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs @@ -14,11 +14,9 @@ use heapless::String; use super::component::{ResultScreen, WelcomeScreen}; -mod confirm; mod connect; mod intro; mod menu; -mod theme; mod welcome; use crate::{ @@ -27,14 +25,18 @@ use crate::{ constant, constant::HEIGHT, geometry::Point, - model_tr::theme::{ICON_ARM_LEFT, ICON_ARM_RIGHT, WHITE}, + model_tr::{ + component::bl_confirm::{Confirm, ConfirmMsg}, + theme::{ + bootloader::{BLD_BG, BLD_FG, ICON_ALERT, ICON_SPINNER, ICON_SUCCESS}, + ICON_ARM_LEFT, ICON_ARM_RIGHT, TEXT_BOLD, TEXT_NORMAL, WHITE, + }, + }, }, }; -use confirm::Confirm; use connect::Connect; use intro::Intro; use menu::Menu; -use theme::{BLD_BG, BLD_FG, ICON_ALERT, ICON_SPINNER, ICON_SUCCESS}; use welcome::Welcome; pub type BootloaderString = String<128>; @@ -55,6 +57,12 @@ impl ReturnToC for () { } } +impl ReturnToC for ConfirmMsg { + fn return_to_c(self) -> u32 { + self as u32 + } +} + fn button_eval() -> Option { let event = io_button_read(); if event == 0 { @@ -145,18 +153,15 @@ extern "C" fn screen_install_confirm( "DOWNGRADE FW" }; - let message = - Label::left_aligned(version_str.as_str(), theme::TEXT_NORMAL).vertically_centered(); + let message = Label::left_aligned(version_str.as_str(), TEXT_NORMAL).vertically_centered(); let fingerprint = Label::left_aligned( fingerprint_str, - theme::TEXT_NORMAL.with_line_breaking(BreakWordsNoHyphen), + TEXT_NORMAL.with_line_breaking(BreakWordsNoHyphen), ) .vertically_centered(); - let alert = (!should_keep_seed).then_some(Label::left_aligned( - "Seed will be erased!", - theme::TEXT_NORMAL, - )); + let alert = + (!should_keep_seed).then_some(Label::left_aligned("Seed will be erased!", TEXT_NORMAL)); let mut frame = Confirm::new(BLD_BG, title_str, message, alert, "INSTALL", false) .with_info_screen("FW FINGERPRINT", fingerprint); @@ -165,8 +170,8 @@ extern "C" fn screen_install_confirm( #[no_mangle] extern "C" fn screen_wipe_confirm() -> u32 { - let message = Label::left_aligned("Seed and firmware will be erased!", theme::TEXT_NORMAL) - .vertically_centered(); + let message = + Label::left_aligned("Seed and firmware will be erased!", TEXT_NORMAL).vertically_centered(); let mut frame = Confirm::new(BLD_BG, "FACTORY RESET", message, None, "RESET", false); @@ -175,8 +180,8 @@ extern "C" fn screen_wipe_confirm() -> u32 { #[no_mangle] extern "C" fn screen_unlock_bootloader_confirm() -> u32 { - let message = Label::left_aligned("This action cannot be undone!", theme::TEXT_NORMAL) - .vertically_centered(); + let message = + Label::left_aligned("This action cannot be undone!", TEXT_NORMAL).vertically_centered(); let mut frame = Confirm::new(BLD_BG, "UNLOCK BOOTLOADER?", message, None, "UNLOCK", true); @@ -185,10 +190,10 @@ extern "C" fn screen_unlock_bootloader_confirm() -> u32 { #[no_mangle] extern "C" fn screen_unlock_bootloader_success() { - let title = Label::centered("Bootloader unlocked", theme::TEXT_BOLD).vertically_centered(); + let title = Label::centered("Bootloader unlocked", TEXT_BOLD).vertically_centered(); let content = - Label::centered("Please reconnect the\ndevice", theme::TEXT_NORMAL).vertically_centered(); + Label::centered("Please reconnect the\ndevice", TEXT_NORMAL).vertically_centered(); let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true); show(&mut frame); @@ -295,17 +300,17 @@ extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) { } #[no_mangle] -extern "C" fn screen_connect() { +extern "C" fn screen_connect(_initial_setup: bool) { let mut frame = Connect::new("Waiting for host..."); show(&mut frame); } #[no_mangle] extern "C" fn screen_wipe_success() { - let title = Label::centered("Trezor Reset", theme::TEXT_BOLD).vertically_centered(); + let title = Label::centered("Trezor Reset", TEXT_BOLD).vertically_centered(); let content = - Label::centered("Please reconnect\nthe device", theme::TEXT_NORMAL).vertically_centered(); + Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered(); let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true); show(&mut frame); @@ -313,10 +318,10 @@ extern "C" fn screen_wipe_success() { #[no_mangle] extern "C" fn screen_wipe_fail() { - let title = Label::centered("Reset failed", theme::TEXT_BOLD).vertically_centered(); + let title = Label::centered("Reset failed", TEXT_BOLD).vertically_centered(); let content = - Label::centered("Please reconnect\nthe device", theme::TEXT_NORMAL).vertically_centered(); + Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered(); let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true); show(&mut frame); @@ -332,10 +337,10 @@ extern "C" fn screen_boot_empty(_fading: bool) { #[no_mangle] extern "C" fn screen_install_fail() { - let title = Label::centered("Install failed", theme::TEXT_BOLD).vertically_centered(); + let title = Label::centered("Install failed", TEXT_BOLD).vertically_centered(); let content = - Label::centered("Please reconnect\nthe device", theme::TEXT_NORMAL).vertically_centered(); + Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered(); let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true); show(&mut frame); @@ -358,9 +363,9 @@ extern "C" fn screen_install_success( unwrap!(reboot_msg.push_str("Reconnect the device")); } - let title = Label::centered("Firmware installed", theme::TEXT_BOLD).vertically_centered(); + let title = Label::centered("Firmware installed", TEXT_BOLD).vertically_centered(); - let content = Label::centered(reboot_msg.as_str(), theme::TEXT_NORMAL).vertically_centered(); + let content = Label::centered(reboot_msg.as_str(), TEXT_NORMAL).vertically_centered(); let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, complete_draw); show(&mut frame); diff --git a/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs b/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs index e4d7706e16..0a5e03a297 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs @@ -4,7 +4,7 @@ use crate::ui::{ geometry::{Offset, Rect}, }; -use super::theme::{BLD_BG, BLD_FG}; +use super::super::theme::bootloader::{BLD_BG, BLD_FG}; pub struct Welcome { bg: Pad, diff --git a/core/embed/rust/src/ui/model_tr/bootloader/confirm.rs b/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs similarity index 80% rename from core/embed/rust/src/ui/model_tr/bootloader/confirm.rs rename to core/embed/rust/src/ui/model_tr/component/bl_confirm.rs index 40b26ede1c..2163078bea 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/confirm.rs +++ b/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs @@ -1,16 +1,15 @@ -use crate::ui::{ - component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad}, - display::{self, Color, Font}, - geometry::{Point, Rect}, +use crate::{ + strutil::StringType, + ui::{ + component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad}, + display::{self, Color, Font}, + geometry::{Point, Rect}, + }, }; use super::{ - super::{ - component::{ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos}, - theme::{BUTTON_HEIGHT, TITLE_AREA_HEIGHT}, - }, - theme::WHITE, - ReturnToC, + theme::{BUTTON_HEIGHT, TITLE_AREA_HEIGHT, WHITE}, + ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos, }; const ALERT_AREA_START: i16 = 39; @@ -21,38 +20,36 @@ pub enum ConfirmMsg { Confirm = 2, } -impl ReturnToC for ConfirmMsg { - fn return_to_c(self) -> u32 { - self as u32 - } -} - -pub struct Confirm<'a> { +pub struct Confirm { bg: Pad, bg_color: Color, title: &'static str, - message: Child>, - alert: Option>, - info_title: Option<&'static str>, - info_text: Option>, - button_text: &'static str, - buttons: ButtonController<&'static str>, + message: Child>, + alert: Option>, + info_title: Option, + info_text: Option>, + button_text: T, + buttons: ButtonController, /// Whether we are on the info screen (optional extra screen) showing_info_screen: bool, two_btn_confirm: bool, } -impl<'a> Confirm<'a> { +impl Confirm +where + T: StringType + Clone, + U: AsRef, +{ pub fn new( bg_color: Color, title: &'static str, - message: Label<&'a str>, - alert: Option>, - button_text: &'static str, + message: Label, + alert: Option>, + button_text: T, two_btn_confirm: bool, ) -> Self { let btn_layout = - Self::get_button_layout_general(false, button_text, false, two_btn_confirm); + Self::get_button_layout_general(false, button_text.clone(), false, two_btn_confirm); Self { bg: Pad::with_background(bg_color).with_clear(), bg_color, @@ -69,7 +66,7 @@ impl<'a> Confirm<'a> { } /// Adding optional info screen - pub fn with_info_screen(mut self, info_title: &'static str, info_text: Label<&'a str>) -> Self { + pub fn with_info_screen(mut self, info_title: T, info_text: Label) -> Self { self.info_title = Some(info_title); self.info_text = Some(info_text); self.buttons = ButtonController::new(self.get_button_layout()); @@ -80,10 +77,10 @@ impl<'a> Confirm<'a> { self.info_title.is_some() } - fn get_button_layout(&self) -> ButtonLayout<&'static str> { + fn get_button_layout(&self) -> ButtonLayout { Self::get_button_layout_general( self.showing_info_screen, - self.button_text, + self.button_text.clone(), self.has_info_screen(), self.two_btn_confirm, ) @@ -92,10 +89,10 @@ impl<'a> Confirm<'a> { /// Not relying on self here, to call it in constructor. fn get_button_layout_general( showing_info_screen: bool, - button_text: &'static str, + button_text: T, has_info_screen: bool, two_btn_confirm: bool, - ) -> ButtonLayout<&'static str> { + ) -> ButtonLayout { if showing_info_screen { ButtonLayout::arrow_none_none() } else if has_info_screen { @@ -124,7 +121,11 @@ impl<'a> Confirm<'a> { } } -impl<'a> Component for Confirm<'a> { +impl Component for Confirm +where + T: StringType + Clone, + U: AsRef, +{ type Msg = ConfirmMsg; fn place(&mut self, bounds: Rect) -> Rect { @@ -209,7 +210,7 @@ impl<'a> Component for Confirm<'a> { // We are either on the info screen or on the "main" screen if self.showing_info_screen { - display_top_left(unwrap!(self.info_title)); + display_top_left(unwrap!(self.info_title.clone()).as_ref()); self.info_text.paint(); } else { display_top_left(self.title); @@ -224,3 +225,14 @@ impl<'a> Component for Confirm<'a> { self.buttons.bounds(sink); } } + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Confirm +where + T: StringType + Clone, + U: AsRef, +{ + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("BlConfirm"); + } +} diff --git a/core/embed/rust/src/ui/model_tr/component/mod.rs b/core/embed/rust/src/ui/model_tr/component/mod.rs index 50592b5a89..4b0e9469a2 100644 --- a/core/embed/rust/src/ui/model_tr/component/mod.rs +++ b/core/embed/rust/src/ui/model_tr/component/mod.rs @@ -1,3 +1,4 @@ +pub mod bl_confirm; mod button; mod button_controller; mod common; diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 03e2336bf4..f967907efd 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -30,7 +30,7 @@ use crate::{ }, TextStyle, }, - ComponentExt, FormattedText, Timeout, + ComponentExt, FormattedText, Label, LineBreaking, Timeout, }, display, geometry, layout::{ @@ -263,6 +263,19 @@ where } } +impl ComponentMsgObj for super::component::bl_confirm::Confirm +where + T: StringType + Clone, + U: AsRef, +{ + fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { + match msg { + super::component::bl_confirm::ConfirmMsg::Cancel => Ok(CANCELLED.as_obj()), + super::component::bl_confirm::ConfirmMsg::Confirm => Ok(CONFIRMED.as_obj()), + } + } +} + /// Function to create and call a `ButtonPage` dialog based on paginable content /// (e.g. `Paragraphs` or `FormattedText`). /// Has optional title (supply empty `StrBuffer` for that) and hold-to-confirm @@ -1609,6 +1622,33 @@ extern "C" fn draw_welcome_screen() -> Obj { Obj::const_none() } +extern "C" fn new_confirm_firmware_update( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + use super::component::bl_confirm::Confirm; + let block = move |_args: &[Obj], kwargs: &Map| { + let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + let fingerprint: StrBuffer = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; + + let title = "UPDATE FIRMWARE"; + let message = Label::left_aligned(description, theme::TEXT_NORMAL).vertically_centered(); + let fingerprint = Label::left_aligned( + fingerprint, + theme::TEXT_NORMAL.with_line_breaking(LineBreaking::BreakWordsNoHyphen), + ) + .vertically_centered(); + + let obj = LayoutObj::new( + Confirm::new(theme::BG, title, message, None, "INSTALL".into(), false) + .with_info_screen(StrBuffer::from("FW FINGERPRINT"), fingerprint), + )?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + #[no_mangle] pub static mp_module_trezorui2: Module = obj_module! { Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), @@ -2019,4 +2059,12 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def draw_welcome_screen() -> None: /// """Show logo icon with the model name at the bottom and return.""" Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(), + + /// def confirm_firmware_update( + /// *, + /// description: str, + /// fingerprint: str, + /// ) -> None: + /// """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" + Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(), }; diff --git a/core/embed/rust/src/ui/model_tr/bootloader/theme.rs b/core/embed/rust/src/ui/model_tr/theme/bootloader.rs similarity index 100% rename from core/embed/rust/src/ui/model_tr/bootloader/theme.rs rename to core/embed/rust/src/ui/model_tr/theme/bootloader.rs diff --git a/core/embed/rust/src/ui/model_tr/theme.rs b/core/embed/rust/src/ui/model_tr/theme/mod.rs similarity index 99% rename from core/embed/rust/src/ui/model_tr/theme.rs rename to core/embed/rust/src/ui/model_tr/theme/mod.rs index 917bffb3d3..c1a61b59b8 100644 --- a/core/embed/rust/src/ui/model_tr/theme.rs +++ b/core/embed/rust/src/ui/model_tr/theme/mod.rs @@ -9,6 +9,8 @@ use crate::ui::{ use num_traits::FromPrimitive; +pub mod bootloader; + // Color palette. pub const WHITE: Color = Color::white(); pub const BLACK: Color = Color::black(); diff --git a/core/embed/rust/src/ui/model_tt/bootloader/connect.rs b/core/embed/rust/src/ui/model_tt/bootloader/connect.rs index 379f7cad21..5f23b0c643 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/connect.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/connect.rs @@ -1,9 +1,9 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Pad}, constant::screen, - display::{self, Font}, + display::{self, Color, Font}, geometry::{Offset, Rect}, - model_tt::bootloader::theme::{BLD_BG, BLD_TITLE_COLOR}, + model_tt::theme::bootloader::BLD_TITLE_COLOR, }; pub struct Connect { @@ -12,9 +12,9 @@ pub struct Connect { } impl Connect { - pub fn new(message: &'static str) -> Self { + pub fn new(message: &'static str, bg: Color) -> Self { let mut instance = Self { - bg: Pad::with_background(BLD_BG), + bg: Pad::with_background(bg), message, }; @@ -44,7 +44,7 @@ impl Component for Connect { self.message, Font::NORMAL, BLD_TITLE_COLOR, - BLD_BG, + self.bg.color, ); } } diff --git a/core/embed/rust/src/ui/model_tt/bootloader/intro.rs b/core/embed/rust/src/ui/model_tt/bootloader/intro.rs index 81e99f2dd0..b113c9c6a8 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/intro.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/intro.rs @@ -4,12 +4,12 @@ use crate::ui::{ display::Icon, geometry::{Alignment, Insets, Point, Rect}, model_tt::{ - bootloader::theme::{ - button_bld, button_bld_menu, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING, - CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_TITLE, TEXT_WARNING, TITLE_AREA, - }, component::{Button, ButtonMsg::Clicked}, constant::WIDTH, + theme::bootloader::{ + button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT, + CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_WARNING, TITLE_AREA, + }, }, }; @@ -33,7 +33,7 @@ impl<'a> Intro<'a> { pub fn new(title: &'a str, content: &'a str, fw_ok: bool) -> Self { Self { bg: Pad::with_background(BLD_BG).with_clear(), - title: Child::new(Label::left_aligned(title, TEXT_TITLE).vertically_centered()), + title: Child::new(Label::left_aligned(title, text_title(BLD_BG)).vertically_centered()), menu: Child::new( Button::with_icon(Icon::new(MENU32)) .styled(button_bld_menu()) diff --git a/core/embed/rust/src/ui/model_tt/bootloader/menu.rs b/core/embed/rust/src/ui/model_tt/bootloader/menu.rs index 3c1a441610..9cbf43df1b 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/menu.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/menu.rs @@ -6,12 +6,12 @@ use crate::{ display::Icon, geometry::{Insets, Point, Rect}, model_tt::{ - bootloader::theme::{ - button_bld, button_bld_menu, BLD_BG, BUTTON_HEIGHT, CONTENT_PADDING, - CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, FIRE24, REFRESH24, TEXT_TITLE, - TITLE_AREA, X32, - }, component::{Button, ButtonMsg::Clicked, IconText}, + theme::bootloader::{ + button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_HEIGHT, CONTENT_PADDING, + CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, FIRE24, REFRESH24, TITLE_AREA, + X32, + }, }, }, }; @@ -42,7 +42,9 @@ impl Menu { let mut instance = Self { bg: Pad::with_background(BLD_BG), - title: Child::new(Label::left_aligned("BOOTLOADER", TEXT_TITLE).vertically_centered()), + title: Child::new( + Label::left_aligned("BOOTLOADER", text_title(BLD_BG)).vertically_centered(), + ), close: Child::new( Button::with_icon(Icon::new(X32)) .styled(button_bld_menu()) diff --git a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs index c6eff2353d..1d44d66e1a 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs @@ -8,19 +8,22 @@ use crate::{ event::TouchEvent, geometry::Point, model_tt::{ - bootloader::{ - confirm::ConfirmTitle, - connect::Connect, - theme::{ - button_bld, button_confirm, button_wipe_cancel, button_wipe_confirm, BLD_BG, - BLD_FG, BLD_WIPE_COLOR, CHECK24, CHECK40, DOWNLOAD32, FIRE32, FIRE40, - TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24, - }, - welcome::Welcome, + bootloader::{connect::Connect, welcome::Welcome}, + component::{ + bl_confirm::{Confirm, ConfirmTitle}, + Button, ResultScreen, WelcomeScreen, }, - component::{Button, ResultScreen, WelcomeScreen}, constant, - theme::{BACKLIGHT_DIM, BACKLIGHT_NORMAL, FG, WHITE}, + theme::{ + bootloader::{ + button_bld, button_bld_menu, button_confirm, button_wipe_cancel, + button_wipe_confirm, BLD_BG, BLD_FG, BLD_WIPE_COLOR, CHECK24, CHECK40, + DOWNLOAD32, FIRE32, FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, + TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, + WELCOME_COLOR, X24, + }, + BACKLIGHT_DIM, BACKLIGHT_NORMAL, FG, WHITE, + }, }, util::{from_c_array, from_c_str}, }, @@ -28,20 +31,15 @@ use crate::{ use heapless::String; use num_traits::ToPrimitive; -pub mod confirm; mod connect; pub mod intro; pub mod menu; -pub mod theme; pub mod welcome; use crate::{trezorhal::secbool::secbool, ui::model_tt::theme::BLACK}; -use confirm::Confirm; use intro::Intro; use menu::Menu; -use self::theme::{RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE}; - pub type BootloaderString = String<128>; const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE"; @@ -163,12 +161,10 @@ extern "C" fn screen_install_confirm( } else { "DOWNGRADE FW" }; - let title = Label::left_aligned(title_str, theme::TEXT_BOLD).vertically_centered(); - let msg = Label::left_aligned(version_str.as_ref(), theme::TEXT_NORMAL); - let alert = (!should_keep_seed).then_some(Label::left_aligned( - "SEED WILL BE ERASED!", - theme::TEXT_BOLD, - )); + let title = Label::left_aligned(title_str, TEXT_BOLD).vertically_centered(); + let msg = Label::left_aligned(version_str.as_ref(), TEXT_NORMAL); + let alert = + (!should_keep_seed).then_some(Label::left_aligned("SEED WILL BE ERASED!", TEXT_BOLD)); let (left, right) = if should_keep_seed { let l = Button::with_text("CANCEL").styled(button_bld()); @@ -184,6 +180,7 @@ extern "C" fn screen_install_confirm( BLD_BG, left, right, + button_bld_menu(), ConfirmTitle::Text(title), msg, alert, @@ -210,6 +207,7 @@ extern "C" fn screen_wipe_confirm() -> u32 { BLD_WIPE_COLOR, left, right, + button_bld_menu(), ConfirmTitle::Icon(icon), msg, Some(alert), @@ -298,15 +296,16 @@ extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) { "Resetting Trezor", progress, initialize, - theme::BLD_FG, + BLD_FG, BLD_WIPE_COLOR, - Some((Icon::new(FIRE32), theme::BLD_FG)), + Some((Icon::new(FIRE32), BLD_FG)), ) } #[no_mangle] -extern "C" fn screen_connect() { - let mut frame = Connect::new("Waiting for host..."); +extern "C" fn screen_connect(initial_setup: bool) { + let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG }; + let mut frame = Connect::new("Waiting for host...", bg); show(&mut frame, true); } diff --git a/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs b/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs index 83c771236c..5a84bccfea 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs @@ -3,9 +3,9 @@ use crate::ui::{ constant::screen, display::{self, Font, Icon}, geometry::{Alignment2D, Offset, Rect}, - model_tt::{ - bootloader::theme::{START_URL, WELCOME_COLOR}, - theme::{BLACK, GREY_MEDIUM, WHITE}, + model_tt::theme::{ + bootloader::{START_URL, WELCOME_COLOR}, + BLACK, GREY_MEDIUM, WHITE, }, }; diff --git a/core/embed/rust/src/ui/model_tt/bootloader/confirm.rs b/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs similarity index 81% rename from core/embed/rust/src/ui/model_tt/bootloader/confirm.rs rename to core/embed/rust/src/ui/model_tt/component/bl_confirm.rs index 0f935dff11..4c434f707c 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/confirm.rs +++ b/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs @@ -5,13 +5,15 @@ use crate::ui::{ display::{Color, Icon}, geometry::{Alignment2D, Insets, Offset, Point, Rect}, model_tt::{ - bootloader::theme::{ - button_bld_menu, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING, CORNER_BUTTON_AREA, - CORNER_BUTTON_TOUCH_EXPANSION, INFO32, TEXT_FINGERPRINT, TEXT_TITLE, TITLE_AREA, X32, - }, - component::{Button, ButtonMsg::Clicked}, + component::{Button, ButtonMsg::Clicked, ButtonStyleSheet}, constant::WIDTH, - theme::WHITE, + theme::{ + bootloader::{ + text_fingerprint, text_title, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING, + CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, INFO32, TITLE_AREA, X32, + }, + WHITE, + }, }, }; @@ -29,40 +31,44 @@ pub enum ConfirmMsg { Confirm = 2, } -pub enum ConfirmTitle<'a> { - Text(Label<&'a str>), +pub enum ConfirmTitle { + Text(Label), Icon(Icon), } -pub struct ConfirmInfo<'a> { - pub title: Child>, - pub text: Child>, +pub struct ConfirmInfo { + pub title: Child>, + pub text: Child>, pub info_button: Child>, pub close_button: Child>, } -pub struct Confirm<'a> { +pub struct Confirm { bg: Pad, content_pad: Pad, bg_color: Color, - title: ConfirmTitle<'a>, - message: Child>, - alert: Option>>, + title: ConfirmTitle, + message: Child>, + alert: Option>>, left_button: Child>, right_button: Child>, - info: Option>, + info: Option>, show_info: bool, } -impl<'a> Confirm<'a> { +impl Confirm +where + T: AsRef, +{ pub fn new( bg_color: Color, left_button: Button<&'static str>, right_button: Button<&'static str>, - title: ConfirmTitle<'a>, - message: Label<&'a str>, - alert: Option>, - info: Option<(&'a str, &'a str)>, + menu_button: ButtonStyleSheet, + title: ConfirmTitle, + message: Label, + alert: Option>, + info: Option<(T, T)>, ) -> Self { Self { bg: Pad::with_background(bg_color).with_clear(), @@ -74,16 +80,20 @@ impl<'a> Confirm<'a> { left_button: Child::new(left_button), right_button: Child::new(right_button), info: info.map(|(title, text)| ConfirmInfo { - title: Child::new(Label::left_aligned(title, TEXT_TITLE).vertically_centered()), - text: Child::new(Label::left_aligned(text, TEXT_FINGERPRINT).vertically_centered()), + title: Child::new( + Label::left_aligned(title, text_title(bg_color)).vertically_centered(), + ), + text: Child::new( + Label::left_aligned(text, text_fingerprint(bg_color)).vertically_centered(), + ), info_button: Child::new( Button::with_icon(Icon::new(INFO32)) - .styled(button_bld_menu()) + .styled(menu_button) .with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)), ), close_button: Child::new( Button::with_icon(Icon::new(X32)) - .styled(button_bld_menu()) + .styled(menu_button) .with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)), ), }), @@ -92,7 +102,10 @@ impl<'a> Confirm<'a> { } } -impl<'a> Component for Confirm<'a> { +impl Component for Confirm +where + T: AsRef, +{ type Msg = ConfirmMsg; fn place(&mut self, bounds: Rect) -> Rect { @@ -220,3 +233,13 @@ impl<'a> Component for Confirm<'a> { self.right_button.bounds(sink); } } + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Confirm +where + T: AsRef, +{ + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("BlConfirm"); + } +} diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index 01354296fa..e8e31ebdc1 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -357,7 +357,7 @@ pub enum ButtonContent { IconBlend(Icon, Icon, Offset), } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub struct ButtonStyleSheet { pub normal: &'static ButtonStyle, pub active: &'static ButtonStyle, diff --git a/core/embed/rust/src/ui/model_tt/component/error.rs b/core/embed/rust/src/ui/model_tt/component/error.rs index a9d1000408..739770f4f4 100644 --- a/core/embed/rust/src/ui/model_tt/component/error.rs +++ b/core/embed/rust/src/ui/model_tt/component/error.rs @@ -15,7 +15,7 @@ const TITLE_AREA_START: i16 = 70; const MESSAGE_AREA_START: i16 = 116; #[cfg(feature = "bootloader")] -const STYLE: &ResultStyle = &crate::ui::model_tt::bootloader::theme::RESULT_WIPE; +const STYLE: &ResultStyle = &crate::ui::model_tt::theme::bootloader::RESULT_WIPE; #[cfg(not(feature = "bootloader"))] const STYLE: &ResultStyle = &super::theme::RESULT_ERROR; diff --git a/core/embed/rust/src/ui/model_tt/component/mod.rs b/core/embed/rust/src/ui/model_tt/component/mod.rs index b2f1be3f49..56e5918951 100644 --- a/core/embed/rust/src/ui/model_tt/component/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/mod.rs @@ -1,4 +1,5 @@ mod address_details; +pub mod bl_confirm; mod button; mod coinjoin_progress; mod dialog; diff --git a/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs b/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs index 482dd53e2d..cf6e789b6f 100644 --- a/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs +++ b/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs @@ -4,7 +4,7 @@ use crate::ui::{ model_tt::theme, }; #[cfg(feature = "bootloader")] -use crate::ui::{display::Icon, model_tt::bootloader::theme::DEVICE_NAME}; +use crate::ui::{display::Icon, model_tt::theme::bootloader::DEVICE_NAME}; const TEXT_BOTTOM_MARGIN: i16 = 24; // matching the homescreen label margin const ICON_TOP_MARGIN: i16 = 48; diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 1c0d4ccc0f..c239809655 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -31,7 +31,7 @@ use crate::{ }, TextStyle, }, - Border, Component, Empty, FormattedText, Never, Qr, Timeout, + Border, Component, Empty, FormattedText, Label, Never, Qr, Timeout, }, display::{self, tjpgd::jpeg_info}, geometry, @@ -363,6 +363,18 @@ where } } +impl ComponentMsgObj for super::component::bl_confirm::Confirm +where + T: AsRef, +{ + fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { + match msg { + super::component::bl_confirm::ConfirmMsg::Cancel => Ok(CANCELLED.as_obj()), + super::component::bl_confirm::ConfirmMsg::Confirm => Ok(CONFIRMED.as_obj()), + } + } +} + extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; @@ -1586,6 +1598,39 @@ extern "C" fn draw_welcome_screen() -> Obj { Obj::const_none() } +#[no_mangle] +extern "C" fn new_confirm_firmware_update( + n_args: usize, + args: *const Obj, + kwargs: *mut Map, +) -> Obj { + use super::component::bl_confirm::{Confirm, ConfirmTitle}; + let block = move |_args: &[Obj], kwargs: &Map| { + let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; + let fingerprint: StrBuffer = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?; + + let title_str = StrBuffer::from("UPDATE FIRMWARE"); + let title = Label::left_aligned(title_str, theme::TEXT_BOLD).vertically_centered(); + let msg = Label::left_aligned(description, theme::TEXT_NORMAL); + + let left = Button::with_text("CANCEL").styled(theme::button_default()); + let right = Button::with_text("INSTALL").styled(theme::button_confirm()); + + let obj = LayoutObj::new(Confirm::new( + theme::BG, + left, + right, + theme::button_moreinfo(), + ConfirmTitle::Text(title), + msg, + None, + Some(("FW FINGERPRINT".into(), fingerprint)), + ))?; + Ok(obj.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + #[no_mangle] pub static mp_module_trezorui2: Module = obj_module! { Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(), @@ -2006,6 +2051,14 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def draw_welcome_screen() -> None: /// """Show logo icon with the model name at the bottom and return.""" Qstr::MP_QSTR_draw_welcome_screen => obj_fn_0!(draw_welcome_screen).as_obj(), + + /// def confirm_firmware_update( + /// *, + /// description: str, + /// fingerprint: str, + /// ) -> None: + /// """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" + Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(), }; #[cfg(test)] diff --git a/core/embed/rust/src/ui/model_tt/bootloader/theme.rs b/core/embed/rust/src/ui/model_tt/theme/bootloader.rs similarity index 96% rename from core/embed/rust/src/ui/model_tt/bootloader/theme.rs rename to core/embed/rust/src/ui/model_tt/theme/bootloader.rs index 6c34ba3d97..cfc47a793d 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/theme.rs +++ b/core/embed/rust/src/ui/model_tt/theme/bootloader.rs @@ -231,13 +231,15 @@ pub fn button_bld() -> ButtonStyleSheet { } } -pub const TEXT_TITLE: TextStyle = TextStyle::new( - Font::BOLD, - BLD_TITLE_COLOR, - BLD_BG, - BLD_TITLE_COLOR, - BLD_TITLE_COLOR, -); +pub const fn text_title(bg: Color) -> TextStyle { + TextStyle::new( + Font::BOLD, + BLD_TITLE_COLOR, + bg, + BLD_TITLE_COLOR, + BLD_TITLE_COLOR, + ) +} pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG); pub const TEXT_WARNING: TextStyle = TextStyle::new( @@ -247,9 +249,9 @@ pub const TEXT_WARNING: TextStyle = TextStyle::new( BLD_WARN_COLOR, BLD_WARN_COLOR, ); -pub const TEXT_FINGERPRINT: TextStyle = - TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG) - .with_line_breaking(BreakWordsNoHyphen); +pub const fn text_fingerprint(bg: Color) -> TextStyle { + TextStyle::new(Font::NORMAL, BLD_FG, bg, BLD_FG, BLD_FG).with_line_breaking(BreakWordsNoHyphen) +} pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, BLD_FG, BLD_BG, BLD_FG, BLD_FG); pub const TEXT_WIPE_BOLD: TextStyle = TextStyle::new( Font::BOLD, diff --git a/core/embed/rust/src/ui/model_tt/theme.rs b/core/embed/rust/src/ui/model_tt/theme/mod.rs similarity index 99% rename from core/embed/rust/src/ui/model_tt/theme.rs rename to core/embed/rust/src/ui/model_tt/theme/mod.rs index d9835e814a..8110f17578 100644 --- a/core/embed/rust/src/ui/model_tt/theme.rs +++ b/core/embed/rust/src/ui/model_tt/theme/mod.rs @@ -1,3 +1,5 @@ +pub mod bootloader; + use crate::{ time::Duration, ui::{ diff --git a/core/embed/trezorhal/boot_args.h b/core/embed/trezorhal/boot_args.h new file mode 100644 index 0000000000..44f7966cf3 --- /dev/null +++ b/core/embed/trezorhal/boot_args.h @@ -0,0 +1,20 @@ +#ifndef TREZORHAL_BOOT_ARGS_H +#define TREZORHAL_BOOT_ARGS_H + + +// Defines boot command for 'svc_reboot_to_bootloader()' function +typedef enum { + // Normal boot sequence + BOOT_COMMAND_NONE = 0x00000000, + // Stop and wait for further instructions + BOOT_COMMAND_STOP_AND_WAIT = 0x0FC35A96, + // Do not ask anything, install an upgrade + BOOT_COMMAND_INSTALL_UPGRADE = 0xFA4A5C8D, +} boot_command_t; + +// Maximum size of extra arguments passed to +// 'svc_reboot_to_bootloader()' function +#define BOOT_ARGS_SIZE 256 + + +#endif // TREZORHAL_BOOT_ARGS_H diff --git a/core/embed/trezorhal/common.h b/core/embed/trezorhal/common.h index d032a27318..6cc8e46eb2 100644 --- a/core/embed/trezorhal/common.h +++ b/core/embed/trezorhal/common.h @@ -51,12 +51,7 @@ }) #endif -#define STAY_IN_BOOTLOADER_FLAG 0x0FC35A96 -// from linker script -extern uint8_t firmware_header_start; -extern uint8_t ccmram_start; -extern uint8_t ccmram_end; void __attribute__((noreturn)) trezor_shutdown(void); diff --git a/core/embed/trezorhal/stm32f4/supervise.c b/core/embed/trezorhal/stm32f4/supervise.c new file mode 100644 index 0000000000..f8a7067114 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/supervise.c @@ -0,0 +1,112 @@ +#include STM32_HAL_H + +#include + +#include "../mpu.h" +#include "common.h" +#include "supervise.h" + +#ifdef ARM_USER_MODE + + +// Saves extra parameters for the bootloader +static void _copy_boot_args(const void *args, size_t args_size) { + + // symbols imported from the linker script + extern uint8_t boot_args_start; + extern uint8_t boot_args_end; + + uint8_t *p = &boot_args_start; + + if (args != NULL && args_size > 0) { + size_t max_size = &boot_args_end - &boot_args_start; + size_t copy_size = MIN(args_size, max_size); + memcpy(p, args, copy_size); + p += args_size; + } + + if (p < &boot_args_end) { + memset(p, 0, &boot_args_end - p); + } +} + +__attribute__((noreturn)) static void _reboot_to_bootloader( + boot_command_t boot_command) { + mpu_config_bootloader(); + jump_to_with_flag(BOOTLOADER_START + IMAGE_HEADER_SIZE, boot_command); + for (;;) + ; +} + +void svc_reboot_to_bootloader(boot_command_t boot_command, const void *args, + size_t args_size) { + _copy_boot_args(args, args_size); + if (is_mode_unprivileged() && !is_mode_handler()) { + register uint32_t r0 __asm__("r0") = boot_command; + __asm__ __volatile__("svc %0" ::"i"(SVC_REBOOT_TO_BOOTLOADER), "r"(r0) + : "memory"); + } else { + ensure_compatible_settings(); + _reboot_to_bootloader(boot_command); + } +} + +void SVC_C_Handler(uint32_t *stack) { + uint8_t svc_number = ((uint8_t *)stack[6])[-2]; + switch (svc_number) { + case SVC_ENABLE_IRQ: + HAL_NVIC_EnableIRQ(stack[0]); + break; + case SVC_DISABLE_IRQ: + HAL_NVIC_DisableIRQ(stack[0]); + break; + case SVC_SET_PRIORITY: + NVIC_SetPriority(stack[0], stack[1]); + break; +#ifdef SYSTEM_VIEW + case SVC_GET_DWT_CYCCNT: + cyccnt_cycles = *DWT_CYCCNT_ADDR; + break; +#endif + case SVC_SHUTDOWN: + shutdown_privileged(); + for (;;) + ; + break; + case SVC_REBOOT_TO_BOOTLOADER: + ensure_compatible_settings(); + + __asm__ volatile("msr control, %0" ::"r"(0x0)); + __asm__ volatile("isb"); + + __asm__ volatile( + "mov r0, %[boot_command]" ::[boot_command] "r"(stack[0])); + + // See stack layout in + // https://developer.arm.com/documentation/ka004005/latest We are changing + // return address in PC to land into reboot to avoid any bug with ROP and + // raising privileges. + stack[6] = (uintptr_t)_reboot_to_bootloader; + return; + case SVC_GET_SYSTICK_VAL: + systick_val_copy = SysTick->VAL; + break; + default: + stack[0] = 0xffffffff; + break; + } +} + +__attribute__((naked)) void SVC_Handler(void) { + __asm volatile( + " tst lr, #4 \n" // Test Bit 3 to see which stack pointer we should + // use. + " ite eq \n" // Tell the assembler that the nest 2 instructions + // are if-then-else + " mrseq r0, msp \n" // Make R0 point to main stack pointer + " mrsne r0, psp \n" // Make R0 point to process stack pointer + " b SVC_C_Handler \n" // Off to C land + ); +} + +#endif // ARM_USER_MODE diff --git a/core/embed/trezorhal/stm32f4/supervise.h b/core/embed/trezorhal/stm32f4/supervise.h index 27699c5c73..95965ccf08 100644 --- a/core/embed/trezorhal/stm32f4/supervise.h +++ b/core/embed/trezorhal/stm32f4/supervise.h @@ -5,10 +5,10 @@ #define SVC_SET_PRIORITY 2 #define SVC_SHUTDOWN 4 #define SVC_REBOOT_TO_BOOTLOADER 5 -#define SVC_REBOOT_COPY_IMAGE_HEADER 6 -#define SVC_GET_SYSTICK_VAL 7 +#define SVC_GET_SYSTICK_VAL 6 #include +#include "boot_args.h" #include "common.h" #include "image.h" @@ -17,8 +17,6 @@ extern uint32_t systick_val_copy; // from util.s extern void shutdown_privileged(void); -extern void reboot_to_bootloader(void); -extern void copy_image_header_for_bootloader(const uint8_t *image_header); extern void ensure_compatible_settings(void); static inline uint32_t is_mode_unprivileged(void) { @@ -70,27 +68,8 @@ static inline void svc_shutdown(void) { } } -static inline void svc_reboot_to_bootloader(void) { - explicit_bzero(&firmware_header_start, IMAGE_HEADER_SIZE); - if (is_mode_unprivileged() && !is_mode_handler()) { - __asm__ __volatile__("svc %0" ::"i"(SVC_REBOOT_TO_BOOTLOADER) : "memory"); - } else { - ensure_compatible_settings(); - reboot_to_bootloader(); - } -} - -static inline void svc_reboot_copy_image_header(const uint8_t *image_address) { - if (is_mode_unprivileged() && !is_mode_handler()) { - register const uint8_t *r0 __asm__("r0") = image_address; - __asm__ __volatile__("svc %0" ::"i"(SVC_REBOOT_COPY_IMAGE_HEADER), "r"(r0) - : "memory"); - } else { - copy_image_header_for_bootloader(image_address); - ensure_compatible_settings(); - reboot_to_bootloader(); - } -} +void svc_reboot_to_bootloader(boot_command_t boot_command, const void* args, + size_t args_size); static inline uint32_t svc_get_systick_val(void) { if (is_mode_unprivileged() && !is_mode_handler()) { diff --git a/core/embed/trezorhal/stm32f4/util.s b/core/embed/trezorhal/stm32f4/util.s index 1e39846d0a..5052206211 100644 --- a/core/embed/trezorhal/stm32f4/util.s +++ b/core/embed/trezorhal/stm32f4/util.s @@ -43,7 +43,7 @@ jump_to_with_flag: // wipe memory at the end of the current stage of code bl clear_otg_hs_memory ldr r0, =ccmram_start // r0 - point to beginning of CCMRAM - ldr r1, =firmware_header_start // r1 - point to byte after the end of CCMRAM + ldr r1, =boot_args_start // r1 - point to byte after the end of CCMRAM ldr r2, =0 // r2 - the word-sized value to be written bl memset_reg ldr r0, =sram_start // r0 - point to beginning of SRAM diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 58f30c93f5..32f4610582 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -442,6 +442,15 @@ def show_lockscreen( # rust/src/ui/model_tr/layout.rs def draw_welcome_screen() -> None: """Show logo icon with the model name at the bottom and return.""" + + +# rust/src/ui/model_tr/layout.rs +def confirm_firmware_update( + *, + description: str, + fingerprint: str, +) -> None: + """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" CONFIRMED: object CANCELLED: object INFO: object @@ -895,3 +904,12 @@ def show_lockscreen( # rust/src/ui/model_tt/layout.rs def draw_welcome_screen() -> None: """Show logo icon with the model name at the bottom and return.""" + + +# rust/src/ui/model_tt/layout.rs +def confirm_firmware_update( + *, + description: str, + fingerprint: str, +) -> None: + """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader.""" diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi index ff2fa9dcc0..400aff2c50 100644 --- a/core/mocks/generated/trezorutils.pyi +++ b/core/mocks/generated/trezorutils.pyi @@ -75,12 +75,30 @@ def unit_btconly() -> bool | None: # extmod/modtrezorutils/modtrezorutils.c -def reboot_to_bootloader() -> None: +def reboot_to_bootloader( + boot_command : int = 0, + boot_args : bytes | None = None, +) -> None: """ Reboots to bootloader. """ +# extmod/modtrezorutils/modtrezorutils.c +def check_firmware_header( + header : bytes +) -> dict: + """ + Checks firmware image and vendor header and returns + { "version": (major, minor, patch), + "vendor": string, + "full_trust": bool, + "fingerprint": bytes, + "hash": bytes + } + """ + + # extmod/modtrezorutils/modtrezorutils.c def bootloader_locked() -> bool | None: """ diff --git a/core/site_scons/boards/stm32f4_common.py b/core/site_scons/boards/stm32f4_common.py index 329de344cb..b47e45c8f3 100644 --- a/core/site_scons/boards/stm32f4_common.py +++ b/core/site_scons/boards/stm32f4_common.py @@ -43,6 +43,7 @@ def stm32f4_common_files(env, defines, sources, paths): "embed/trezorhal/stm32f4/mpu.c", "embed/trezorhal/stm32f4/platform.c", "embed/trezorhal/stm32f4/systick.c", + "embed/trezorhal/stm32f4/supervise.c", "embed/trezorhal/stm32f4/random_delays.c", "embed/trezorhal/stm32f4/rng.c", "embed/trezorhal/stm32f4/vectortable.s", diff --git a/core/src/all_modules.py b/core/src/all_modules.py index f67f747be0..6883d01604 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -93,6 +93,8 @@ trezor.enums.AmountUnit import trezor.enums.AmountUnit trezor.enums.BackupType import trezor.enums.BackupType +trezor.enums.BootCommand +import trezor.enums.BootCommand trezor.enums.ButtonRequestType import trezor.enums.ButtonRequestType trezor.enums.Capability diff --git a/core/src/apps/management/reboot_to_bootloader.py b/core/src/apps/management/reboot_to_bootloader.py index c5f0e42636..c7373e02ae 100644 --- a/core/src/apps/management/reboot_to_bootloader.py +++ b/core/src/apps/management/reboot_to_bootloader.py @@ -7,20 +7,60 @@ if TYPE_CHECKING: async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn: - from trezor import io, loop, utils + from ubinascii import hexlify + + from trezor import io, loop, utils, wire + from trezor.enums import BootCommand from trezor.messages import Success - from trezor.ui.layouts import confirm_action + from trezor.ui.layouts import confirm_action, confirm_firmware_update from trezor.wire.context import get_context - await confirm_action( - "reboot", - "Go to bootloader", - "Do you want to restart Trezor in bootloader mode?", - verb="Restart", - ) + if ( + msg.boot_command == BootCommand.INSTALL_UPGRADE + and msg.firmware_header is not None + ): + # check and parse received firmware header + hdr = utils.check_firmware_header(msg.firmware_header) + if hdr is None: + raise wire.DataError("Invalid firmware header.") + else: + # vendor must be the same + if hdr["vendor"] != utils.firmware_vendor(): + raise wire.DataError("Different firmware vendor.") + + current_version = ( + int(utils.VERSION_MAJOR), + int(utils.VERSION_MINOR), + int(utils.VERSION_PATCH), + ) + + # firmware must be newer + if hdr["version"] <= current_version: + raise wire.DataError("Not a firmware upgrade.") + + version_str = ".".join(map(str, hdr["version"])) + + await confirm_firmware_update( + description=f"Firmware version {version_str}\nby {hdr['vendor']}", + fingerprint=hexlify(hdr["fingerprint"]).decode(), + ) + boot_command = BootCommand.INSTALL_UPGRADE + boot_args = hdr["hash"] + + else: + await confirm_action( + "reboot", + "Go to bootloader", + "Do you want to restart Trezor in bootloader mode?", + verb="Restart", + ) + boot_command = BootCommand.STOP_AND_WAIT + boot_args = None + ctx = get_context() await ctx.write(Success(message="Rebooting")) # make sure the outgoing USB buffer is flushed await loop.wait(ctx.iface.iface_num() | io.POLL_WRITE) - utils.reboot_to_bootloader() + # reboot to the bootloader, pass the firmware header hash if any + utils.reboot_to_bootloader(boot_command, boot_args) raise RuntimeError diff --git a/core/src/trezor/enums/BootCommand.py b/core/src/trezor/enums/BootCommand.py new file mode 100644 index 0000000000..a6a9df15c7 --- /dev/null +++ b/core/src/trezor/enums/BootCommand.py @@ -0,0 +1,6 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +STOP_AND_WAIT = 0 +INSTALL_UPGRADE = 1 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index d2c31237c9..bf062cc92a 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -450,6 +450,10 @@ if TYPE_CHECKING: Matrix9 = 1 Matrix6 = 2 + class BootCommand(IntEnum): + STOP_AND_WAIT = 0 + INSTALL_UPGRADE = 1 + class DebugSwipeDirection(IntEnum): UP = 0 DOWN = 1 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 8ef1f8b98a..e901e92f7e 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: from trezor.enums import BinanceOrderSide # noqa: F401 from trezor.enums import BinanceOrderType # noqa: F401 from trezor.enums import BinanceTimeInForce # noqa: F401 + from trezor.enums import BootCommand # noqa: F401 from trezor.enums import ButtonRequestType # noqa: F401 from trezor.enums import Capability # noqa: F401 from trezor.enums import CardanoAddressType # noqa: F401 @@ -2617,6 +2618,16 @@ if TYPE_CHECKING: return isinstance(msg, cls) class RebootToBootloader(protobuf.MessageType): + boot_command: "BootCommand" + firmware_header: "bytes | None" + + def __init__( + self, + *, + boot_command: "BootCommand | None" = None, + firmware_header: "bytes | None" = None, + ) -> None: + pass @classmethod def is_type_of(cls, msg: Any) -> TypeGuard["RebootToBootloader"]: diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index 87ea605ec0..f2954e1bad 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -1349,3 +1349,17 @@ async def confirm_set_new_pin( "CONTINUE", br_code, ) + + +async def confirm_firmware_update(description: str, fingerprint: str) -> None: + await raise_if_not_confirmed( + interact( + RustLayout( + trezorui2.confirm_firmware_update( + description=description, fingerprint=fingerprint + ) + ), + "firmware_update", + BR_TYPE_OTHER, + ) + ) diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index e03e34ac22..48b2a72c2c 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -1368,3 +1368,17 @@ async def confirm_set_new_pin( br_code, ) ) + + +async def confirm_firmware_update(description: str, fingerprint: str) -> None: + await raise_if_not_confirmed( + interact( + RustLayout( + trezorui2.confirm_firmware_update( + description=description, fingerprint=fingerprint + ) + ), + "firmware_update", + BR_TYPE_OTHER, + ) + ) diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index 8b8a83f419..eb90879f1a 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -15,6 +15,7 @@ from trezorutils import ( # noqa: F401 VERSION_MINOR, VERSION_PATCH, bootloader_locked, + check_firmware_header, consteq, firmware_hash, firmware_vendor, diff --git a/python/src/trezorlib/device.py b/python/src/trezorlib/device.py index 930b78ab33..1fa33dea6d 100644 --- a/python/src/trezorlib/device.py +++ b/python/src/trezorlib/device.py @@ -238,8 +238,16 @@ def unlock_path(client: "TrezorClient", n: "Address") -> "MessageType": @session @expect(messages.Success, field="message", ret_type=str) -def reboot_to_bootloader(client: "TrezorClient") -> "MessageType": - return client.call(messages.RebootToBootloader()) +def reboot_to_bootloader( + client: "TrezorClient", + boot_command: messages.BootCommand = messages.BootCommand.STOP_AND_WAIT, + firmware_header: Optional[bytes] = None, +) -> "MessageType": + return client.call( + messages.RebootToBootloader( + boot_command=boot_command, firmware_header=firmware_header + ) + ) @session