feat(core): introduce interaction-less upgrade

pull/3363/head
cepetr 7 months ago committed by matejcik
parent 804874c7b9
commit ba83a7e644

@ -0,0 +1 @@
Support interaction-less upgrade

@ -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'],

@ -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'],

@ -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 */

@ -0,0 +1 @@
Minimize risk of losing seed when upgrading firmware

@ -0,0 +1 @@
Support interaction-less upgrade

@ -0,0 +1,21 @@
#ifndef BOOT_INTERNAL_H
#define BOOT_INTERNAL_H
#include <stdint.h>
#include <boot_args.h>
// 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

@ -2,6 +2,7 @@
#include <unistd.h>
#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') {

@ -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 <stdio.h>
extern uint8_t *FIRMWARE_START;
extern uint32_t stay_in_bootloader_flag;
void emulator_poll_events(void);
void set_core_clock(int);

@ -20,6 +20,7 @@
#include <string.h>
#include <sys/types.h>
#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;

@ -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);

@ -24,6 +24,7 @@
#include <pb_encode.h>
#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(&current_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);

@ -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 {

@ -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

@ -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,

@ -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);

@ -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;

@ -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

@ -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),

@ -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) {

@ -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);

@ -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

@ -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;
}

@ -21,6 +21,7 @@
#define __TREZORHAL_IMAGE_H__
#include <stdint.h>
#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

@ -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)"); }

@ -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);

@ -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

@ -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);

@ -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

@ -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;

@ -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);

@ -5,7 +5,7 @@ use crate::ui::{
pub struct Pad {
pub area: Rect,
color: Color,
pub color: Color,
clear: bool,
}

@ -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,

@ -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,
};

@ -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,
};

@ -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<ButtonEvent> {
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);

@ -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,

@ -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<T: StringType, U> {
bg: Pad,
bg_color: Color,
title: &'static str,
message: Child<Label<&'a str>>,
alert: Option<Label<&'a str>>,
info_title: Option<&'static str>,
info_text: Option<Label<&'a str>>,
button_text: &'static str,
buttons: ButtonController<&'static str>,
message: Child<Label<U>>,
alert: Option<Label<T>>,
info_title: Option<T>,
info_text: Option<Label<U>>,
button_text: T,
buttons: ButtonController<T>,
/// Whether we are on the info screen (optional extra screen)
showing_info_screen: bool,
two_btn_confirm: bool,
}
impl<'a> Confirm<'a> {
impl<T, U> Confirm<T, U>
where
T: StringType + Clone,
U: AsRef<str>,
{
pub fn new(
bg_color: Color,
title: &'static str,
message: Label<&'a str>,
alert: Option<Label<&'a str>>,
button_text: &'static str,
message: Label<U>,
alert: Option<Label<T>>,
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<U>) -> 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<T> {
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<T> {
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<T, U> Component for Confirm<T, U>
where
T: StringType + Clone,
U: AsRef<str>,
{
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<T, U> crate::trace::Trace for Confirm<T, U>
where
T: StringType + Clone,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("BlConfirm");
}
}

@ -1,3 +1,4 @@
pub mod bl_confirm;
mod button;
mod button_controller;
mod common;

@ -30,7 +30,7 @@ use crate::{
},
TextStyle,
},
ComponentExt, FormattedText, Timeout,
ComponentExt, FormattedText, Label, LineBreaking, Timeout,
},
display, geometry,
layout::{
@ -263,6 +263,19 @@ where
}
}
impl<T, U> ComponentMsgObj for super::component::bl_confirm::Confirm<T, U>
where
T: StringType + Clone,
U: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
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(),
};

@ -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();

@ -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,
);
}
}

@ -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())

@ -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())

@ -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);
}

@ -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,
},
};

@ -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<T> {
Text(Label<T>),
Icon(Icon),
}
pub struct ConfirmInfo<'a> {
pub title: Child<Label<&'a str>>,
pub text: Child<Label<&'a str>>,
pub struct ConfirmInfo<T> {
pub title: Child<Label<T>>,
pub text: Child<Label<T>>,
pub info_button: Child<Button<&'static str>>,
pub close_button: Child<Button<&'static str>>,
}
pub struct Confirm<'a> {
pub struct Confirm<T> {
bg: Pad,
content_pad: Pad,
bg_color: Color,
title: ConfirmTitle<'a>,
message: Child<Label<&'a str>>,
alert: Option<Child<Label<&'a str>>>,
title: ConfirmTitle<T>,
message: Child<Label<T>>,
alert: Option<Child<Label<T>>>,
left_button: Child<Button<&'static str>>,
right_button: Child<Button<&'static str>>,
info: Option<ConfirmInfo<'a>>,
info: Option<ConfirmInfo<T>>,
show_info: bool,
}
impl<'a> Confirm<'a> {
impl<T> Confirm<T>
where
T: AsRef<str>,
{
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<Label<&'a str>>,
info: Option<(&'a str, &'a str)>,
menu_button: ButtonStyleSheet,
title: ConfirmTitle<T>,
message: Label<T>,
alert: Option<Label<T>>,
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<T> Component for Confirm<T>
where
T: AsRef<str>,
{
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<T> crate::trace::Trace for Confirm<T>
where
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("BlConfirm");
}
}

@ -357,7 +357,7 @@ pub enum ButtonContent<T> {
IconBlend(Icon, Icon, Offset),
}
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct ButtonStyleSheet {
pub normal: &'static ButtonStyle,
pub active: &'static ButtonStyle,

@ -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;

@ -1,4 +1,5 @@
mod address_details;
pub mod bl_confirm;
mod button;
mod coinjoin_progress;
mod dialog;

@ -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;

@ -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<T> ComponentMsgObj for super::component::bl_confirm::Confirm<T>
where
T: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
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)]

@ -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,

@ -1,3 +1,5 @@
pub mod bootloader;
use crate::{
time::Duration,
ui::{

@ -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

@ -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);

@ -0,0 +1,112 @@
#include STM32_HAL_H
#include <model.h>
#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

@ -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 <string.h>
#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()) {

@ -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

@ -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."""

@ -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:
"""

@ -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",

@ -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

@ -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

@ -0,0 +1,6 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
STOP_AND_WAIT = 0
INSTALL_UPGRADE = 1

@ -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

@ -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"]:

@ -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,
)
)

@ -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,
)
)

@ -15,6 +15,7 @@ from trezorutils import ( # noqa: F401
VERSION_MINOR,
VERSION_PATCH,
bootloader_locked,
check_firmware_header,
consteq,
firmware_hash,
firmware_vendor,

@ -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

Loading…
Cancel
Save