diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index e4b2203807..6f7d0a9bd4 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -11,7 +11,7 @@ PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1' HW_REVISION = ARGUMENTS.get('HW_REVISION', None) UI_DEBUG_OVERLAY = ARGUMENTS.get('UI_DEBUG_OVERLAY', '0') == '1' -FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d"] +FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d", "ble"] CCFLAGS_MOD = '' CPPPATH_MOD = [] @@ -105,7 +105,6 @@ SOURCE_BOOTLOADER = [ 'embed/projects/bootloader/header.S', 'embed/projects/bootloader/bootui.c', 'embed/projects/bootloader/main.c', - 'embed/projects/bootloader/poll.c', 'embed/projects/bootloader/antiglitch.c', 'embed/projects/bootloader/workflow/wf_firmware_update.c', 'embed/projects/bootloader/workflow/wf_wipe_device.c', @@ -116,8 +115,10 @@ SOURCE_BOOTLOADER = [ 'embed/projects/bootloader/workflow/wf_empty_device.c', 'embed/projects/bootloader/workflow/wf_auto_update.c', 'embed/projects/bootloader/workflow/wf_host_control.c', + 'embed/projects/bootloader/workflow/wf_ble_pairing_request.c', 'embed/projects/bootloader/wire/codec_v1.c', 'embed/projects/bootloader/wire/wire_iface_usb.c', + 'embed/projects/bootloader/wire/wire_iface_ble.c', 'embed/projects/bootloader/protob/protob.c', 'embed/projects/bootloader/protob/pb/messages.pb.c', 'embed/projects/bootloader/version_check.c', diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu index e2a3198d49..3d8a1d64f8 100644 --- a/core/SConscript.bootloader_emu +++ b/core/SConscript.bootloader_emu @@ -99,7 +99,6 @@ SOURCE_NANOPB = [ SOURCE_BOOTLOADER = [ 'embed/projects/bootloader/bootui.c', 'embed/projects/bootloader/main.c', - 'embed/projects/bootloader/poll.c', 'embed/projects/bootloader/antiglitch.c', 'embed/projects/bootloader/workflow/wf_firmware_update.c', 'embed/projects/bootloader/workflow/wf_wipe_device.c', diff --git a/core/embed/projects/bootloader/.changelog.d/4833.added b/core/embed/projects/bootloader/.changelog.d/4833.added new file mode 100644 index 0000000000..87be1b1898 --- /dev/null +++ b/core/embed/projects/bootloader/.changelog.d/4833.added @@ -0,0 +1 @@ +Add cancel button to Connect to host screen. diff --git a/core/embed/projects/bootloader/bootui.c b/core/embed/projects/bootloader/bootui.c index eb4d94f51b..b952941dfa 100644 --- a/core/embed/projects/bootloader/bootui.c +++ b/core/embed/projects/bootloader/bootui.c @@ -24,7 +24,7 @@ #include #include "bootui.h" -#include "rust_ui.h" +#include "rust_ui_bootloader.h" #include "version.h" #define BACKLIGHT_NORMAL 150 @@ -47,6 +47,8 @@ static bool initial_setup = true; void ui_set_initial_setup(bool initial) { initial_setup = initial; } +bool ui_get_initial_setup(void) { return initial_setup; } + #if defined USE_TOUCH #include @@ -97,10 +99,6 @@ void ui_screen_boot(const vendor_header *const vhdr, vimg_len, wait); } -// welcome UI - -void ui_screen_welcome(void) { screen_welcome(); } - uint32_t ui_screen_intro(const vendor_header *const vhdr, const image_header *const hdr, bool fw_ok) { char bld_ver[32]; @@ -111,19 +109,14 @@ uint32_t ui_screen_intro(const vendor_header *const vhdr, return screen_intro(bld_ver, vhdr->vstr, vhdr->vstr_len, ver_str, fw_ok); } -uint32_t ui_screen_menu(secbool firmware_present) { - return screen_menu(firmware_present); -} - -void ui_screen_connect(void) { screen_connect(initial_setup); } - // install UI -ui_result_t ui_screen_install_confirm(const vendor_header *const vhdr, - const image_header *const hdr, - secbool should_keep_seed, - secbool is_newvendor, - secbool is_newinstall, int version_cmp) { +confirm_result_t ui_screen_install_confirm(const vendor_header *const vhdr, + const image_header *const hdr, + secbool should_keep_seed, + secbool is_newvendor, + secbool is_newinstall, + int version_cmp) { uint8_t fingerprint[32]; char ver_str[64]; get_image_fingerprint(hdr, fingerprint); @@ -149,7 +142,7 @@ void ui_screen_install_progress_upload(int pos) { // wipe UI -ui_result_t ui_screen_wipe_confirm(void) { return screen_wipe_confirm(); } +confirm_result_t ui_screen_wipe_confirm(void) { return screen_wipe_confirm(); } void ui_screen_wipe(void) { screen_wipe_progress(0, true); } @@ -180,3 +173,9 @@ void ui_screen_install_restricted(void) { screen_install_fail(); } void ui_fadein(void) { display_fade(0, BACKLIGHT_NORMAL, 1000); } void ui_fadeout(void) { display_fade(BACKLIGHT_NORMAL, 0, 500); } + +#ifdef USE_BLE +uint32_t ui_screen_confirm_pairing(uint32_t code) { + return screen_confirm_pairing(code, initial_setup); +} +#endif diff --git a/core/embed/projects/bootloader/bootui.h b/core/embed/projects/bootloader/bootui.h index 8dd4c556d8..8fb8cd0e35 100644 --- a/core/embed/projects/bootloader/bootui.h +++ b/core/embed/projects/bootloader/bootui.h @@ -23,24 +23,7 @@ #include -// todo: use bindgen to tie this to rust -typedef enum { - UI_RESULT_CANCEL = 1, - UI_RESULT_CONFIRM = 2, -} ui_result_t; - -// todo: use bindgen to tie this to rust -typedef enum { - MENU_EXIT = 0xAABBCCDD, - MENU_REBOOT = 0x11223344, - MENU_WIPE = 0x55667788, -} menu_result_t; - -// todo: use bindgen to tie this to rust -typedef enum { - INTRO_MENU = 1, - INTRO_HOST = 2, -} intro_result_t; +#include "rust_ui_bootloader.h" // Displays a warning screen before jumping to the untrusted firmware // @@ -60,25 +43,20 @@ void ui_screen_boot(const vendor_header* const vhdr, // the user presses a button, touches the display void ui_click(void); -void ui_screen_welcome(void); - uint32_t ui_screen_intro(const vendor_header* const vhdr, const image_header* const hdr, bool fw_ok); -uint32_t ui_screen_menu(secbool firmware_present); - -void ui_screen_connect(void); - -ui_result_t ui_screen_install_confirm(const vendor_header* const vhdr, - const image_header* const hdr, - secbool shold_keep_seed, - secbool is_newvendor, - secbool is_newinstall, int version_cmp); +confirm_result_t ui_screen_install_confirm(const vendor_header* const vhdr, + const image_header* const hdr, + secbool shold_keep_seed, + secbool is_newvendor, + secbool is_newinstall, + int version_cmp); void ui_screen_install_start(); void ui_screen_install_progress_erase(int pos, int len); void ui_screen_install_progress_upload(int pos); -ui_result_t ui_screen_wipe_confirm(void); +confirm_result_t ui_screen_wipe_confirm(void); void ui_screen_wipe(void); void ui_screen_wipe_progress(int pos, int len); @@ -89,9 +67,14 @@ void ui_screen_fail(void); void ui_fadein(void); void ui_fadeout(void); void ui_set_initial_setup(bool initial); +bool ui_get_initial_setup(void); void ui_screen_boot_stage_1(bool fading); #ifdef USE_OPTIGA uint32_t ui_screen_unlock_bootloader_confirm(void); #endif + +#ifdef USE_BLE +uint32_t ui_screen_confirm_pairing(uint32_t code); +#endif diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c index 65a4934146..c8b78c6704 100644 --- a/core/embed/projects/bootloader/main.c +++ b/core/embed/projects/bootloader/main.c @@ -59,10 +59,18 @@ #ifdef USE_TAMPER #include #endif +#ifdef USE_BLE +#include +#endif + +#ifdef USE_BLE +#include "wire/wire_iface_ble.h" +#endif #include "antiglitch.h" #include "bootui.h" #include "version_check.h" +#include "wire/wire_iface_usb.h" #include "workflow/workflow.h" #ifdef TREZOR_EMULATOR @@ -122,6 +130,9 @@ static void drivers_init(secbool *touch_initialized) { #ifdef USE_RGB_LED rgb_led_init(); #endif +#ifdef USE_BLE + ble_init(); +#endif } static void drivers_deinit(void) { @@ -132,6 +143,9 @@ static void drivers_deinit(void) { #ifdef USE_RGB_LED rgb_led_deinit(); #endif +#ifdef USE_BLE + ble_deinit(); +#endif #endif display_deinit(DISPLAY_JUMP_BEHAVIOR); } @@ -381,7 +395,7 @@ int bootloader_main(void) { jump_reset(); if (header_present == sectrue) { - if (auto_upgrade == sectrue) { + if (auto_upgrade == sectrue && firmware_present == sectrue) { result = workflow_auto_update(&vhdr, hdr); } else { result = workflow_bootloader(&vhdr, hdr, firmware_present); @@ -390,6 +404,11 @@ int bootloader_main(void) { result = workflow_empty_device(); } + usb_iface_deinit(); +#ifdef USE_BLE + ble_iface_deinit(); +#endif + switch (result) { case WF_OK_FIRMWARE_INSTALLED: firmware_present = sectrue; diff --git a/core/embed/projects/bootloader/poll.c b/core/embed/projects/bootloader/poll.c deleted file mode 100644 index 6d6bc70e73..0000000000 --- a/core/embed/projects/bootloader/poll.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of the Trezor project, https://trezor.io/ - * - * Copyright (c) SatoshiLabs - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "poll.h" - -#include -#include - -#ifdef TREZOR_EMULATOR -#include "SDL.h" -#endif - -uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, - poll_event_t* event, uint32_t timeout_ms) { - uint32_t deadline = ticks_timeout(timeout_ms); - - while (!ticks_expired(deadline)) { -#ifdef TREZOR_EMULATOR - // Ensures that SDL events are processed. This prevents the emulator from - // freezing when the user interacts with the window. - SDL_PumpEvents(); -#endif - - for (size_t i = 0; i < ifaces_num; i++) { - uint8_t iface_num = ifaces[i] & 0xFF; - if (iface_num < IFACE_USB_MAX) { - if ((ifaces[i] & MODE_READ) == MODE_READ) { - // check if USB can read - if (sectrue == usb_webusb_can_read(iface_num)) { - event->type = EVENT_USB_CAN_READ; - return iface_num; - } - } - } - } - -#ifndef TREZOR_EMULATOR - __WFI(); -#endif - } - - event->type = EVENT_NONE; - return 0; -} diff --git a/core/embed/projects/bootloader/wire/codec_v1.h b/core/embed/projects/bootloader/wire/codec_v1.h index 82450250ac..b1882165fa 100644 --- a/core/embed/projects/bootloader/wire/codec_v1.h +++ b/core/embed/projects/bootloader/wire/codec_v1.h @@ -26,6 +26,8 @@ #define MAX_PACKET_SIZE 256 typedef struct { + // initialized flag + bool initialized; // identifier of the interface used for polling communication events uint8_t poll_iface_id; // size of TX packet diff --git a/core/embed/projects/bootloader/wire/wire_iface_ble.c b/core/embed/projects/bootloader/wire/wire_iface_ble.c new file mode 100644 index 0000000000..a2eb6fdd28 --- /dev/null +++ b/core/embed/projects/bootloader/wire/wire_iface_ble.c @@ -0,0 +1,208 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifdef USE_BLE + +#include +#include + +#include "wire_iface_ble.h" + +#include +#include +#include + +static wire_iface_t g_ble_iface = {0}; + +static bool is_connected(void) { + ble_state_t state = {0}; + ble_get_state(&state); + return state.connected; +} + +static bool ble_write_(uint8_t* data, size_t size) { + if (size != BLE_TX_PACKET_SIZE) { + return false; + } + + uint32_t deadline = ticks_timeout(500); + + while (true) { + if (ticks_expired(deadline)) { + return false; + } + if (!is_connected()) { + return false; + } + if (ble_can_write()) { + break; + } + } + + return ble_write(data, size); +} + +static int ble_read_(uint8_t* buffer, size_t buffer_size) { + if (buffer_size != BLE_RX_PACKET_SIZE) { + return -1; + } + + uint32_t deadline = ticks_timeout(500); + + while (true) { + if (ticks_expired(deadline)) { + return 0; + } + if (!is_connected()) { + return 0; + } + if (ble_can_read()) { + break; + } + } + + int r = ble_read(buffer, buffer_size); + + return r; +} + +static void ble_error(void) { + error_shutdown_ex("BLE ERROR", + "Error reading from BLE. Try different BLE cable.", NULL); +} + +wire_iface_t* ble_iface_init(void) { + wire_iface_t* iface = &g_ble_iface; + + if (iface->initialized) { + return iface; + } + + memset(iface, 0, sizeof(wire_iface_t)); + + iface->poll_iface_id = SYSHANDLE_BLE_IFACE_0; + iface->tx_packet_size = BLE_TX_PACKET_SIZE; + iface->rx_packet_size = BLE_RX_PACKET_SIZE; + iface->write = &ble_write_; + iface->read = &ble_read_; + iface->error = &ble_error; + + ble_start(); + + ble_state_t state = {0}; + + ble_get_state(&state); + + if (!state.connectable && !state.pairing) { + if (state.peer_count > 0) { + ble_command_t cmd = { + .cmd_type = BLE_SWITCH_ON, + .data = {.adv_start = + { + .name = "Trezor Bootloader", + .static_mac = false, + }}, + }; + ble_issue_command(&cmd); + } + } + + iface->initialized = true; + + return iface; +} + +void ble_iface_deinit(void) { + wire_iface_t* iface = &g_ble_iface; + + if (!iface->initialized) { + return; + } + + ble_command_t cmd = { + .cmd_type = BLE_SWITCH_OFF, + }; + ble_issue_command(&cmd); + + ble_stop(); + + memset(iface, 0, sizeof(wire_iface_t)); +} + +void ble_iface_end_pairing(void) { + ble_state_t state = {0}; + + ble_get_state(&state); + + if (state.peer_count > 0) { + ble_command_t cmd = {.cmd_type = BLE_SWITCH_ON}; + ble_issue_command(&cmd); + } else { + ble_command_t cmd = {.cmd_type = BLE_SWITCH_OFF}; + ble_issue_command(&cmd); + } +} + +bool ble_iface_start_pairing(void) { + ble_state_t state = {0}; + + ble_get_state(&state); + + uint16_t retry_cnt = 0; + + while (state.connected && retry_cnt < 10) { + ble_command_t cmd_disconnect = { + .cmd_type = BLE_DISCONNECT, + }; + ble_issue_command(&cmd_disconnect); + systick_delay_ms(20); + ble_get_state(&state); + retry_cnt++; + } + + if (state.connected) { + return false; + } + + ble_command_t cmd = { + .cmd_type = BLE_PAIRING_MODE, + .data = {.adv_start = + { + .name = "Trezor Bootloader", + .static_mac = false, + }}, + }; + ble_issue_command(&cmd); + + retry_cnt = 0; + ble_get_state(&state); + while (!state.pairing && retry_cnt < 10) { + systick_delay_ms(20); + ble_get_state(&state); + retry_cnt++; + } + + if (!state.pairing) { + ble_iface_end_pairing(); + return false; + } + + return true; +} + +#endif diff --git a/core/embed/projects/bootloader/poll.h b/core/embed/projects/bootloader/wire/wire_iface_ble.h similarity index 65% rename from core/embed/projects/bootloader/poll.h rename to core/embed/projects/bootloader/wire/wire_iface_ble.h index 61d7d4e79c..7e1855fd94 100644 --- a/core/embed/projects/bootloader/poll.h +++ b/core/embed/projects/bootloader/wire/wire_iface_ble.h @@ -19,21 +19,12 @@ #pragma once -#include +#include "codec_v1.h" -#define IFACE_USB_MAX (15) // 0-15 reserved for USB +wire_iface_t* ble_iface_init(void); -#define MODE_READ 0x0000 -#define MODE_WRITE 0x0100 +void ble_iface_deinit(void); -typedef enum { - EVENT_NONE = 0, - EVENT_USB_CAN_READ = 0x01, -} poll_event_type_t; +bool ble_iface_start_pairing(void); -typedef struct { - poll_event_type_t type; -} poll_event_t; - -uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, - poll_event_t* event, uint32_t timeout_ms); +void ble_iface_end_pairing(void); diff --git a/core/embed/projects/bootloader/wire/wire_iface_usb.c b/core/embed/projects/bootloader/wire/wire_iface_usb.c index 0a10fa52fe..86fae4a52f 100644 --- a/core/embed/projects/bootloader/wire/wire_iface_usb.c +++ b/core/embed/projects/bootloader/wire/wire_iface_usb.c @@ -23,19 +23,22 @@ #include "wire_iface_usb.h" #include +#include #define USB_TIMEOUT 500 #define USB_PACKET_SIZE 64 -#define USB_IFACE_NUM 0 _Static_assert(USB_PACKET_SIZE <= MAX_PACKET_SIZE, "USB_PACKET_SIZE too large"); +static wire_iface_t g_usb_iface = {0}; + static bool usb_write(uint8_t* data, size_t size) { if (size != USB_PACKET_SIZE) { return false; } - int r = usb_webusb_write_blocking(USB_IFACE_NUM, data, size, USB_TIMEOUT); + int r = + usb_webusb_write_blocking(SYSHANDLE_USB_IFACE_0, data, size, USB_TIMEOUT); return r == size; } @@ -45,8 +48,8 @@ static int usb_read(uint8_t* buffer, size_t buffer_size) { return -1; } - int r = usb_webusb_read_blocking(USB_IFACE_NUM, buffer, USB_PACKET_SIZE, - USB_TIMEOUT); + int r = usb_webusb_read_blocking(SYSHANDLE_USB_IFACE_0, buffer, + USB_PACKET_SIZE, USB_TIMEOUT); return r; } @@ -75,7 +78,7 @@ static void usb_init_all(secbool usb21_landing) { static uint8_t rx_buffer[USB_PACKET_SIZE]; static const usb_webusb_info_t webusb_info = { - .iface_num = USB_IFACE_NUM, + .iface_num = SYSHANDLE_USB_IFACE_0, #ifdef TREZOR_EMULATOR .emu_port = 21324, #else @@ -96,20 +99,35 @@ static void usb_init_all(secbool usb21_landing) { ensure(usb_start(), NULL); } -void usb_iface_init(wire_iface_t* iface, secbool usb21_landing) { +wire_iface_t* usb_iface_init(secbool usb21_landing) { + wire_iface_t* iface = &g_usb_iface; + + if (iface->initialized) { + return iface; + } + usb_init_all(usb21_landing); memset(iface, 0, sizeof(wire_iface_t)); - iface->poll_iface_id = USB_IFACE_NUM; + iface->poll_iface_id = SYSHANDLE_USB_IFACE_0; iface->tx_packet_size = USB_PACKET_SIZE; iface->rx_packet_size = USB_PACKET_SIZE; iface->write = &usb_write; iface->read = &usb_read; iface->error = &usb_error; + iface->initialized = true; + + return iface; } -void usb_iface_deinit(wire_iface_t* iface) { +void usb_iface_deinit(void) { + wire_iface_t* iface = &g_usb_iface; + + if (!iface->initialized) { + return; + } + memset(iface, 0, sizeof(wire_iface_t)); usb_deinit(); } diff --git a/core/embed/projects/bootloader/wire/wire_iface_usb.h b/core/embed/projects/bootloader/wire/wire_iface_usb.h index 6b2057071c..dda659fb9b 100644 --- a/core/embed/projects/bootloader/wire/wire_iface_usb.h +++ b/core/embed/projects/bootloader/wire/wire_iface_usb.h @@ -21,6 +21,6 @@ #include "codec_v1.h" -void usb_iface_init(wire_iface_t* iface, secbool usb21_landing); +wire_iface_t* usb_iface_init(secbool usb21_landing); -void usb_iface_deinit(wire_iface_t* iface); +void usb_iface_deinit(void); diff --git a/core/embed/projects/bootloader/workflow/wf_auto_update.c b/core/embed/projects/bootloader/workflow/wf_auto_update.c index d5a4e64260..0e21fadbdd 100644 --- a/core/embed/projects/bootloader/workflow/wf_auto_update.c +++ b/core/embed/projects/bootloader/workflow/wf_auto_update.c @@ -17,12 +17,15 @@ * along with this program. If not, see . */ +#include +#include #include #include #include #include "bootui.h" +#include "rust_ui_bootloader.h" #include "workflow.h" workflow_result_t workflow_auto_update(const vendor_header *const vhdr, @@ -30,8 +33,19 @@ workflow_result_t workflow_auto_update(const vendor_header *const vhdr, ui_set_initial_setup(true); workflow_result_t res = WF_CANCELLED; - while (res == WF_CANCELLED) { - res = workflow_host_control(vhdr, hdr, ui_screen_connect); + uint32_t ui_result = CONNECT_CANCEL; + + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + screen_connect(true, false, &layout); + res = workflow_host_control(vhdr, hdr, &layout, &ui_result); + + if (res == WF_OK_UI_ACTION && ui_result == CONNECT_CANCEL) { + bootargs_set(BOOT_COMMAND_NONE, NULL, 0); + jump_allow_1(); + jump_allow_2(); + return WF_OK_REBOOT_SELECTED; } + return res; } diff --git a/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c b/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c new file mode 100644 index 0000000000..0a5576e9fe --- /dev/null +++ b/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c @@ -0,0 +1,118 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifdef USE_BLE + +#include +#include + +#include + +#include "bootui.h" +#include "rust_ui_bootloader.h" +#include "wire/wire_iface_ble.h" +#include "workflow.h" + +static bool encode_pairing_code(uint32_t code, uint8_t *outbuf) { + if (code > 999999) { + return false; + } + for (size_t i = 0; i < BLE_PAIRING_CODE_LEN; i++) { + outbuf[BLE_PAIRING_CODE_LEN - i - 1] = '0' + (code % 10); + code /= 10; + } + return true; +} + +workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr, + const image_header *const hdr) { + if (!ble_iface_start_pairing()) { + return WF_OK_PAIRING_FAILED; + } + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + screen_pairing_mode(ui_get_initial_setup(), &layout); + + uint32_t code = 0; + workflow_result_t res = workflow_host_control(vhdr, hdr, &layout, &code); + + if (res != WF_OK_UI_ACTION) { + ble_iface_end_pairing(); + return res; + } + + if (code == PAIRING_MODE_CANCEL) { + ble_iface_end_pairing(); + return WF_OK_PAIRING_FAILED; + } + + uint32_t result = ui_screen_confirm_pairing(code); + + uint8_t pairing_code[BLE_PAIRING_CODE_LEN] = {0}; + + if (result != CONFIRM || !encode_pairing_code(code, pairing_code)) { + ble_command_t cmd = { + .cmd_type = BLE_REJECT_PAIRING, + }; + ble_issue_command(&cmd); + return WF_OK_PAIRING_FAILED; + } + + ble_command_t cmd = { + .cmd_type = BLE_ALLOW_PAIRING, + .data_len = sizeof(pairing_code), + }; + memcpy(cmd.data.raw, pairing_code, sizeof(pairing_code)); + ble_issue_command(&cmd); + + bool skip_finalization = false; + + sysevents_t awaited = {0}; + sysevents_t signalled = {0}; + awaited.read_ready |= 1 << SYSHANDLE_BLE; + + sysevents_poll(&awaited, &signalled, 500); + + if (signalled.read_ready == 1 << SYSHANDLE_BLE) { + ble_event_t event = {0}; + if (ble_get_event(&event)) { + if (event.type == BLE_PAIRING_COMPLETED) { + skip_finalization = true; + } + } + } + + if (!skip_finalization) { + pairing_mode_finalization_result_t r = + screen_pairing_mode_finalizing(ui_get_initial_setup()); + if (r == PAIRING_FINALIZATION_FAILED) { + ble_iface_end_pairing(); + return WF_OK_PAIRING_FAILED; + } + if (r == PAIRING_FINALIZATION_CANCEL) { + ble_command_t disconnect = {.cmd_type = BLE_DISCONNECT}; + ble_issue_command(&disconnect); + ble_iface_end_pairing(); + return WF_OK_PAIRING_FAILED; + } + } + + return WF_OK_PAIRING_COMPLETED; +} + +#endif diff --git a/core/embed/projects/bootloader/workflow/wf_bootloader.c b/core/embed/projects/bootloader/workflow/wf_bootloader.c index 0ecddc86f8..ef35362acb 100644 --- a/core/embed/projects/bootloader/workflow/wf_bootloader.c +++ b/core/embed/projects/bootloader/workflow/wf_bootloader.c @@ -25,74 +25,151 @@ #include "antiglitch.h" #include "bootui.h" +#include "rust_ui_bootloader.h" #include "workflow.h" +workflow_result_t workflow_menu(const vendor_header* const vhdr, + const image_header* const hdr, + secbool firmware_present) { + while (true) { + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + screen_menu(ui_get_initial_setup(), firmware_present, &layout); + uint32_t ui_result = 0; + workflow_result_t result = + workflow_host_control(vhdr, hdr, &layout, &ui_result); + + if (result != WF_OK_UI_ACTION) { + return result; + } + + menu_result_t menu_result = (menu_result_t)ui_result; + + if (menu_result == MENU_EXIT) { // exit menu + return WF_OK; + } +#ifdef USE_BLE + if (menu_result == MENU_BLUETOOTH) { + workflow_ble_pairing_request(vhdr, hdr); + continue; + } +#endif + if (menu_result == MENU_REBOOT) { // reboot + jump_allow_1(); + jump_allow_2(); + return WF_OK_REBOOT_SELECTED; + } + if (menu_result == MENU_WIPE) { // wipe + workflow_result_t r = workflow_wipe_device(NULL); + if (r == WF_ERROR || r == WF_OK_DEVICE_WIPED || r == WF_CANCELLED) { + return r; + } + } + return WF_ERROR_FATAL; + } +} + typedef enum { SCREEN_INTRO, SCREEN_MENU, SCREEN_WAIT_FOR_HOST, + SCREEN_DONE, } screen_t; -workflow_result_t workflow_bootloader(const vendor_header *const vhdr, - const image_header *const hdr, - secbool firmware_present) { - ui_set_initial_setup(false); +// Each handler returns either a next screen, or SCREEN_DONE and an out‑param +// for the result. +static screen_t handle_intro(const vendor_header* vhdr, const image_header* hdr, + secbool firmware_present, + workflow_result_t* out_result) { + intro_result_t ui = ui_screen_intro(vhdr, hdr, firmware_present); + if (ui == INTRO_MENU) return SCREEN_MENU; + if (ui == INTRO_HOST) return SCREEN_WAIT_FOR_HOST; + // no other valid INTRO result -> fatal + *out_result = WF_ERROR_FATAL; + return SCREEN_DONE; +} - screen_t screen = SCREEN_INTRO; - - while (true) { - switch (screen) { - case SCREEN_INTRO: { - intro_result_t ui_result = ui_screen_intro(vhdr, hdr, firmware_present); - if (ui_result == INTRO_MENU) { - screen = SCREEN_MENU; - } - if (ui_result == INTRO_HOST) { - screen = SCREEN_WAIT_FOR_HOST; - } - } break; - case SCREEN_MENU: { - menu_result_t menu_result = ui_screen_menu(firmware_present); - if (menu_result == MENU_EXIT) { // exit menu - screen = SCREEN_INTRO; - } - if (menu_result == MENU_REBOOT) { // reboot -#ifndef USE_HASH_PROCESSOR - ui_screen_boot_stage_1(true); -#endif - jump_allow_1(); - jump_allow_2(); - return WF_OK_REBOOT_SELECTED; - } - if (menu_result == MENU_WIPE) { // wipe - workflow_result_t r = workflow_wipe_device(NULL); - if (r == WF_ERROR) { - return r; - } - if (r == WF_OK_DEVICE_WIPED) { - return r; - } - if (r == WF_CANCELLED) { - screen = SCREEN_MENU; - continue; - } - return WF_ERROR_FATAL; - } - } break; - case SCREEN_WAIT_FOR_HOST: { - workflow_result_t res = - workflow_host_control(vhdr, hdr, ui_screen_connect); - switch (res) { - case WF_CANCELLED: - screen = SCREEN_INTRO; - continue; - default: - return res; - } - } break; - default: - return WF_ERROR_FATAL; - break; - } +static screen_t handle_menu(const vendor_header* vhdr, const image_header* hdr, + secbool firmware_present, + workflow_result_t* out_result) { + workflow_result_t res = workflow_menu(vhdr, hdr, firmware_present); + switch (res) { + case WF_OK: + return SCREEN_INTRO; // back to intro + case WF_CANCELLED: + return SCREEN_MENU; // re‑show menu + default: + *out_result = res; // final result + return SCREEN_DONE; } } + +static screen_t handle_wait_for_host(const vendor_header* vhdr, + const image_header* hdr, + secbool firmware_present, + workflow_result_t* out_result) { + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + uint32_t ui_res = 0; + + screen_connect(false, true, &layout); + workflow_result_t res = workflow_host_control(vhdr, hdr, &layout, &ui_res); + + switch (res) { + case WF_OK_UI_ACTION: { + switch (ui_res) { + case CONNECT_CANCEL: + return SCREEN_INTRO; +#ifdef USE_BLE + case CONNECT_PAIRING_MODE: { + workflow_result_t ble = workflow_ble_pairing_request(vhdr, hdr); + if (ble == WF_OK_PAIRING_COMPLETED || ble == WF_OK_PAIRING_FAILED) + return SCREEN_WAIT_FOR_HOST; + if (ble == WF_CANCELLED) return SCREEN_INTRO; + *out_result = ble; + return SCREEN_DONE; + } +#endif + case CONNECT_MENU: + return SCREEN_MENU; + default: + *out_result = WF_ERROR_FATAL; + return SCREEN_DONE; + } + } + + case WF_CANCELLED: { + return SCREEN_INTRO; + } + default: + *out_result = res; + return SCREEN_DONE; + } +} + +workflow_result_t workflow_bootloader(const vendor_header* vhdr, + const image_header* hdr, + secbool firmware_present) { + ui_set_initial_setup(false); + screen_t screen = SCREEN_INTRO; + workflow_result_t final_res = WF_ERROR_FATAL; + + while (screen != SCREEN_DONE) { + switch (screen) { + case SCREEN_INTRO: + screen = handle_intro(vhdr, hdr, firmware_present, &final_res); + break; + case SCREEN_MENU: + screen = handle_menu(vhdr, hdr, firmware_present, &final_res); + break; + case SCREEN_WAIT_FOR_HOST: + screen = handle_wait_for_host(vhdr, hdr, firmware_present, &final_res); + break; + default: + // shouldn’t happen + return WF_ERROR_FATAL; + } + } + + return final_res; +} diff --git a/core/embed/projects/bootloader/workflow/wf_empty_device.c b/core/embed/projects/bootloader/workflow/wf_empty_device.c index 136e6f5617..4e236dd45e 100644 --- a/core/embed/projects/bootloader/workflow/wf_empty_device.c +++ b/core/embed/projects/bootloader/workflow/wf_empty_device.c @@ -30,6 +30,7 @@ #endif #include "bootui.h" +#include "rust_ui_bootloader.h" #include "workflow.h" workflow_result_t workflow_empty_device(void) { @@ -50,8 +51,35 @@ workflow_result_t workflow_empty_device(void) { #endif workflow_result_t res = WF_CANCELLED; - while (res == WF_CANCELLED) { - res = workflow_host_control(NULL, NULL, ui_screen_welcome); + uint32_t ui_result = WELCOME_CANCEL; + while (res == WF_CANCELLED || + (res == WF_OK_UI_ACTION && ui_result == WELCOME_CANCEL)) { + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + screen_welcome(&layout); + res = workflow_host_control(NULL, NULL, &layout, &ui_result); +#ifdef USE_BLE + if (res == WF_OK_UI_ACTION && ui_result == WELCOME_PAIRING_MODE) { + res = workflow_ble_pairing_request(NULL, NULL); + if (res == WF_OK_PAIRING_COMPLETED || res == WF_OK_PAIRING_FAILED) { + res = WF_CANCELLED; + ui_result = WELCOME_CANCEL; + continue; + } + } +#endif + if (res == WF_OK_UI_ACTION && ui_result == WELCOME_MENU) { + do { + res = workflow_menu(NULL, NULL, false); + } while (res == WF_CANCELLED); + + if (res == WF_OK) { + res = WF_CANCELLED; + ui_result = WELCOME_CANCEL; + continue; + } + return res; + } } return res; } diff --git a/core/embed/projects/bootloader/workflow/wf_firmware_update.c b/core/embed/projects/bootloader/workflow/wf_firmware_update.c index fdcaf22446..5e84154096 100644 --- a/core/embed/projects/bootloader/workflow/wf_firmware_update.c +++ b/core/embed/projects/bootloader/workflow/wf_firmware_update.c @@ -29,8 +29,6 @@ #include #endif -#include - #include "bootui.h" #include "protob/protob.h" #include "version_check.h" @@ -311,12 +309,12 @@ static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface, } #endif - ui_result_t response = UI_RESULT_CANCEL; + confirm_result_t response = CANCEL; if (((vhdr.vtrust & VTRUST_NO_WARNING) == VTRUST_NO_WARNING) && (sectrue == is_new || sectrue == is_ilu)) { // new installation or interaction less updated - auto confirm // only allowed for full-trust images - response = UI_RESULT_CONFIRM; + response = CONFIRM; } else { if (sectrue != is_new) { int version_cmp = version_compare(hdr.version, current_hdr->version); @@ -328,7 +326,7 @@ static upload_status_t process_msg_FirmwareUpload(protob_io_t *iface, } } - if (UI_RESULT_CONFIRM != response) { + if (CONFIRM != response) { send_user_abort(iface, "Firmware install cancelled"); return UPLOAD_ERR_USER_ABORT; } @@ -519,11 +517,14 @@ workflow_result_t workflow_firmware_update(protob_io_t *iface) { upload_status_t s = UPLOAD_IN_PROGRESS; while (true) { - uint16_t ifaces[1] = {protob_get_iface_flag(iface) | MODE_READ}; - poll_event_t e = {0}; - uint8_t i = poll_events(ifaces, 1, &e, 100); + sysevents_t awaited = {0}; + sysevents_t signalled = {0}; - if (e.type == EVENT_NONE || i != protob_get_iface_flag(iface)) { + awaited.read_ready = 1 << protob_get_iface_flag(iface); + + sysevents_poll(&awaited, &signalled, 100); + + if (awaited.read_ready != signalled.read_ready) { continue; } diff --git a/core/embed/projects/bootloader/workflow/wf_host_control.c b/core/embed/projects/bootloader/workflow/wf_host_control.c index cbec24575e..82ae26cd74 100644 --- a/core/embed/projects/bootloader/workflow/wf_host_control.c +++ b/core/embed/projects/bootloader/workflow/wf_host_control.c @@ -17,57 +17,100 @@ * along with this program. If not, see . */ +#include +#include #include #include +#include #include #include #include #include "antiglitch.h" -#include "poll.h" #include "protob/protob.h" #include "wire/wire_iface_usb.h" #include "workflow.h" +#ifdef USE_BLE +#include +#endif + workflow_result_t workflow_host_control(const vendor_header *const vhdr, const image_header *const hdr, - void (*redraw_wait_screen)(void)) { - wire_iface_t usb_iface = {0}; + c_layout_t *wait_layout, + uint32_t *ui_action_result) { protob_io_t protob_usb_iface = {0}; - redraw_wait_screen(); - // if both are NULL, we don't have a firmware installed // let's show a webusb landing page in this case - usb_iface_init(&usb_iface, - (vhdr == NULL && hdr == NULL) ? sectrue : secfalse); + wire_iface_t *usb_iface = + usb_iface_init((vhdr == NULL && hdr == NULL) ? sectrue : secfalse); - protob_init(&protob_usb_iface, &usb_iface); + protob_init(&protob_usb_iface, usb_iface); + +#ifdef USE_BLE + wire_iface_t *ble_iface = ble_iface_init(); + protob_io_t protob_ble_iface = {0}; + + protob_init(&protob_ble_iface, ble_iface); +#endif workflow_result_t result = WF_ERROR_FATAL; - for (;;) { - uint16_t ifaces[1] = {protob_get_iface_flag(&protob_usb_iface) | MODE_READ}; - poll_event_t e = {0}; + sysevents_t awaited = {0}; - uint8_t i = poll_events(ifaces, 1, &e, 100); + awaited.read_ready |= 1 << protob_get_iface_flag(&protob_usb_iface); + +#ifdef USE_BLE + awaited.read_ready |= 1 << protob_get_iface_flag(&protob_ble_iface); + awaited.read_ready |= 1 << SYSHANDLE_BLE; +#endif +#ifdef USE_BUTTON + awaited.read_ready |= 1 << SYSHANDLE_BUTTON; +#endif +#ifdef USE_TOUCH + awaited.read_ready |= 1 << SYSHANDLE_TOUCH; +#endif + + for (;;) { + sysevents_t signalled = {0}; + + sysevents_poll(&awaited, &signalled, 100); + + if (signalled.read_ready == 0) { + continue; + } uint16_t msg_id = 0; protob_io_t *active_iface = NULL; - switch (e.type) { - case EVENT_USB_CAN_READ: - if (i == protob_get_iface_flag(&protob_usb_iface) && - sectrue == protob_get_msg_header(&protob_usb_iface, &msg_id)) { - active_iface = &protob_usb_iface; - } else { - continue; + if (signalled.read_ready == + (1 << protob_get_iface_flag(&protob_usb_iface)) && + sectrue == protob_get_msg_header(&protob_usb_iface, &msg_id)) { + active_iface = &protob_usb_iface; + } + +#ifdef USE_BLE + if (signalled.read_ready == + (1 << protob_get_iface_flag(&protob_ble_iface)) && + sectrue == protob_get_msg_header(&protob_ble_iface, &msg_id)) { + active_iface = &protob_ble_iface; + } +#endif + + // no data, lets pass the event signal to UI + if (active_iface == NULL) { + uint32_t res = screen_event(wait_layout, &signalled); + + if (res != 0) { + if (ui_action_result != NULL) { + *ui_action_result = res; } - break; - case EVENT_NONE: - default: - continue; + result = WF_OK_UI_ACTION; + goto exit_host_control; + } + continue; } switch (msg_id) { @@ -109,6 +152,9 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, exit_host_control: systick_delay_ms(100); - usb_iface_deinit(&usb_iface); + usb_iface_deinit(); +#ifdef USE_BLE + ble_iface_deinit(); +#endif return result; } diff --git a/core/embed/projects/bootloader/workflow/wf_unlock_bootloader.c b/core/embed/projects/bootloader/workflow/wf_unlock_bootloader.c index 02f7e1bcce..93a65fb20d 100644 --- a/core/embed/projects/bootloader/workflow/wf_unlock_bootloader.c +++ b/core/embed/projects/bootloader/workflow/wf_unlock_bootloader.c @@ -24,12 +24,12 @@ #include "bootui.h" #include "protob.h" -#include "rust_ui.h" +#include "rust_ui_bootloader.h" #include "workflow.h" workflow_result_t workflow_unlock_bootloader(protob_io_t *iface) { - ui_result_t response = ui_screen_unlock_bootloader_confirm(); - if (UI_RESULT_CONFIRM != response) { + confirm_result_t response = ui_screen_unlock_bootloader_confirm(); + if (CONFIRM != response) { send_user_abort(iface, "Bootloader unlock cancelled"); return WF_CANCELLED; } diff --git a/core/embed/projects/bootloader/workflow/wf_wipe_device.c b/core/embed/projects/bootloader/workflow/wf_wipe_device.c index d60183ec2d..15ed2c0001 100644 --- a/core/embed/projects/bootloader/workflow/wf_wipe_device.c +++ b/core/embed/projects/bootloader/workflow/wf_wipe_device.c @@ -22,18 +22,69 @@ #include +#ifdef USE_BLE +#include +#endif + +#include + #include "bootui.h" #include "protob.h" -#include "rust_ui.h" +#include "rust_ui_bootloader.h" #include "workflow.h" -workflow_result_t workflow_wipe_device(protob_io_t *iface) { +static void send_error_conditionally(protob_io_t* iface, char* msg) { + if (iface != NULL) { + send_msg_failure(iface, FailureType_Failure_ProcessError, + "Could not read BLE status"); + } +} + +#ifdef USE_BLE +static bool wipe_bonds(protob_io_t* iface) { + ble_state_t state = {0}; + ble_get_state(&state); + + if (!state.state_known) { + send_error_conditionally(iface, "Could not read BLE status"); + screen_wipe_fail(); + return false; + } + + ble_command_t ble_command = {0}; + ble_command.cmd_type = BLE_ERASE_BONDS; + if (!ble_issue_command(&ble_command)) { + send_error_conditionally(iface, "Could not issue BLE command"); + screen_wipe_fail(); + return false; + } + + uint32_t deadline = ticks_timeout(100); + + while (true) { + ble_get_state(&state); + if (state.peer_count == 0) { + break; + } + if (ticks_expired(deadline)) { + send_error_conditionally(iface, "Could not erase bonds"); + screen_wipe_fail(); + return false; + } + } + + return true; +} +#endif + +workflow_result_t workflow_wipe_device(protob_io_t* iface) { WipeDevice msg_recv; if (iface != NULL) { recv_msg_wipe_device(iface, &msg_recv); } - ui_result_t response = ui_screen_wipe_confirm(); - if (UI_RESULT_CONFIRM != response) { + + confirm_result_t response = ui_screen_wipe_confirm(); + if (CONFIRM != response) { if (iface != NULL) { send_user_abort(iface, "Wipe cancelled"); } @@ -42,11 +93,14 @@ workflow_result_t workflow_wipe_device(protob_io_t *iface) { ui_screen_wipe(); secbool wipe_result = erase_device(ui_screen_wipe_progress); +#ifdef USE_BLE + if (!wipe_bonds(iface)) { + return WF_ERROR; + } +#endif + if (sectrue != wipe_result) { - if (iface != NULL) { - send_msg_failure(iface, FailureType_Failure_ProcessError, - "Could not erase flash"); - } + send_error_conditionally(iface, "Could not erase flash"); screen_wipe_fail(); return WF_ERROR; } diff --git a/core/embed/projects/bootloader/workflow/workflow.h b/core/embed/projects/bootloader/workflow/workflow.h index 2a45dac4b1..16e53ec390 100644 --- a/core/embed/projects/bootloader/workflow/workflow.h +++ b/core/embed/projects/bootloader/workflow/workflow.h @@ -21,9 +21,11 @@ #include +#include #include #include "protob/protob.h" +#include "rust_ui_bootloader.h" typedef enum { WF_ERROR_FATAL = 0, @@ -33,6 +35,9 @@ typedef enum { WF_OK_FIRMWARE_INSTALLED = 0x04D9D07F, WF_OK_DEVICE_WIPED = 0x30DC3841, WF_OK_BOOTLOADER_UNLOCKED = 0x23FCBD03, + WF_OK_UI_ACTION = 0xAABBCCEE, + WF_OK_PAIRING_COMPLETED = 0xAABBCCEF, + WF_OK_PAIRING_FAILED = 0xAABBCCF0, WF_CANCELLED = 0x55667788, } workflow_result_t; @@ -54,6 +59,9 @@ workflow_result_t workflow_get_features(protob_io_t *iface, const vendor_header *const vhdr, const image_header *const hdr); +workflow_result_t workflow_menu(const vendor_header *const vhdr, + const image_header *const hdr, + secbool firmware_present); workflow_result_t workflow_bootloader(const vendor_header *const vhdr, const image_header *const hdr, secbool firmware_present); @@ -62,7 +70,13 @@ workflow_result_t workflow_empty_device(void); workflow_result_t workflow_host_control(const vendor_header *const vhdr, const image_header *const hdr, - void (*redraw_wait_screen)(void)); + c_layout_t *wait_layout, + uint32_t *ui_action_result); workflow_result_t workflow_auto_update(const vendor_header *const vhdr, const image_header *const hdr); + +#ifdef USE_BLE +workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr, + const image_header *const hdr); +#endif diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index e87549b73f..5cdde2e752 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -44,6 +44,7 @@ const DEFAULT_BINDGEN_MACROS_COMMON: &[&str] = &[ "-I../io/usb/inc", "-I../sec/entropy/inc", "-I../sys/time/inc", + "-I../sys/task/inc", "-I../util/flash/inc", "-I../util/translations/inc", "-I../models", @@ -407,16 +408,19 @@ fn generate_trezorhal_bindings() { .allowlist_var("BLE_TX_PACKET_SIZE") .allowlist_var("BLE_ADV_NAME_LEN") .allowlist_function("ble_get_state") + .allowlist_function("ble_get_event") .allowlist_function("ble_issue_command") .allowlist_function("ble_start") .allowlist_function("ble_write") .allowlist_function("ble_read") .allowlist_type("ble_command_t") .allowlist_type("ble_state_t") + .allowlist_type("ble_event_t") // touch .allowlist_function("touch_get_event") // button .allowlist_type("button_t") + .allowlist_type("button_event_t") .allowlist_function("button_get_event") // haptic .allowlist_type("haptic_effect_t") @@ -434,7 +438,13 @@ fn generate_trezorhal_bindings() { .allowlist_function("jpegdec_process") .allowlist_function("jpegdec_get_info") .allowlist_function("jpegdec_get_slice_rgba8888") - .allowlist_function("jpegdec_get_slice_mono8"); + .allowlist_function("jpegdec_get_slice_mono8") + // sysevent + .allowlist_type("syshandle_t") + .allowlist_type("sysevents_t") + .allowlist_function("sysevents_poll") + // c_layout + .allowlist_type("c_layout_t"); // Write the bindings to a file in the OUR_DIR. bindings diff --git a/core/embed/rust/rust_types.h b/core/embed/rust/rust_types.h new file mode 100644 index 0000000000..fda46961cf --- /dev/null +++ b/core/embed/rust/rust_types.h @@ -0,0 +1,6 @@ +#pragma once + +// additional type for C-rust interface +typedef struct { + uint8_t buf[1024]; +} c_layout_t; diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h deleted file mode 100644 index 8a736eb04e..0000000000 --- a/core/embed/rust/rust_ui.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "rust_ui_bootloader.h" -#include "rust_ui_common.h" diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index b0b4c22322..7402d2b8b5 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -1,29 +1,91 @@ +#pragma once + #include +#include + +#include "rust_types.h" + +// todo: use bindgen to bind return values to rust + +// common event function for screens that need UI + communication +uint32_t screen_event(c_layout_t* layout, sysevents_t* signalled); + +// result screens +void screen_wipe_success(void); +void screen_wipe_fail(void); +uint32_t screen_install_success(uint8_t restart_seconds, bool initial_setup, + bool complete_draw); +uint32_t screen_install_fail(void); +void screen_unlock_bootloader_success(void); + +// progress screens +void screen_install_progress(int16_t progress, bool initialize, + bool initial_setup); +void screen_wipe_progress(int16_t progress, bool initialize); + +// simple screens with no interaction + +void screen_boot_stage_1(bool fading); +void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len, + uint32_t version, const void* vendor_img, + size_t vendor_img_len, int wait); + +// confirm screens +typedef enum { + CANCEL = 1, + CONFIRM = 2, +} confirm_result_t; uint32_t screen_install_confirm(const char* vendor_str, uint8_t vendor_str_len, const char* version_str, const uint8_t* fingerprint, bool should_keep_seed, bool is_newvendor, bool is_newinstall, int version_cmp); uint32_t screen_wipe_confirm(void); -void screen_install_progress(int16_t progress, bool initialize, - bool initial_setup); -void screen_wipe_progress(int16_t progress, bool initialize); +uint32_t screen_confirm_pairing(uint32_t code, bool initial_setup); +uint32_t screen_unlock_bootloader_confirm(void); + +// screens with UI but no communication +typedef enum { + INTRO_MENU = 1, + INTRO_HOST = 2, +} intro_result_t; 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(bool initial_setup); -void screen_wipe_success(void); -void screen_wipe_fail(void); -uint32_t screen_install_success(uint8_t restart_seconds, bool initial_setup, - bool complete_draw); -uint32_t screen_install_fail(void); -void screen_welcome(void); -void screen_boot_stage_1(bool fading); -uint32_t screen_unlock_bootloader_confirm(void); -void screen_unlock_bootloader_success(void); -void bld_continue_label(uint16_t bg_color); -void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len, - uint32_t version, const void* vendor_img, - size_t vendor_img_len, int wait); + +typedef enum { + PAIRING_FINALIZATION_COMPLETED = 1, + PAIRING_FINALIZATION_CANCEL = 2, + PAIRING_FINALIZATION_FAILED = 3, +} pairing_mode_finalization_result_t; +uint32_t screen_pairing_mode_finalizing(bool initial_setup); + +// screens with UI and communication interactions +typedef enum { + MENU_EXIT = 0xAABBCCDD, + MENU_REBOOT = 0x11223344, + MENU_WIPE = 0x55667788, + MENU_BLUETOOTH = 0x99AABBCC, +} menu_result_t; +void screen_menu(bool initial_setup, secbool firmware_present, + c_layout_t* layout); + +typedef enum { + CONNECT_CANCEL = 1, + CONNECT_PAIRING_MODE = 2, + CONNECT_MENU = 3, +} connect_result_t; +void screen_connect(bool initial_setup, bool auto_update, c_layout_t* layout); +typedef enum { + WELCOME_CANCEL = 1, + WELCOME_PAIRING_MODE = 2, + WELCOME_MENU = 3, +} welcome_result_t; +void screen_welcome(c_layout_t* layout); + +typedef enum { + // 0 - 999999 - pairing code + PAIRING_MODE_CANCEL = 1000000, +} pairing_mode_result_t; +void screen_pairing_mode(bool initial_setup, c_layout_t* layout); diff --git a/core/embed/rust/src/trezorhal/ble/micropython.rs b/core/embed/rust/src/trezorhal/ble/micropython.rs index f46959622a..894bc1cc75 100644 --- a/core/embed/rust/src/trezorhal/ble/micropython.rs +++ b/core/embed/rust/src/trezorhal/ble/micropython.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{super::model, *}; use crate::{ error::Error, micropython::{ diff --git a/core/embed/rust/src/trezorhal/ble/mod.rs b/core/embed/rust/src/trezorhal/ble/mod.rs index 9e7b83bd99..dc1c6c98d0 100644 --- a/core/embed/rust/src/trezorhal/ble/mod.rs +++ b/core/embed/rust/src/trezorhal/ble/mod.rs @@ -1,10 +1,10 @@ #[cfg(feature = "micropython")] mod micropython; -use crate::error::Error; +use crate::{error::Error, ui::event::BLEEvent}; use core::mem::size_of; -use super::{ffi, model}; +use super::ffi; pub const ADV_NAME_LEN: usize = ffi::BLE_ADV_NAME_LEN as usize; pub const PAIRING_CODE_LEN: usize = ffi::BLE_PAIRING_CODE_LEN as usize; @@ -23,6 +23,25 @@ fn prefix_utf8_bytes(text: &str, max_len: usize) -> &[u8] { &text.as_bytes()[..i] } +pub fn ble_parse_event(event: ffi::ble_event_t) -> BLEEvent { + match event.type_ { + ffi::ble_event_type_t_BLE_CONNECTED => BLEEvent::Connected, + ffi::ble_event_type_t_BLE_DISCONNECTED => BLEEvent::Disconnected, + ffi::ble_event_type_t_BLE_PAIRING_REQUEST => { + let code: u32 = event + .data + .iter() + .take(6) + .map(|&b| (b - b'0')) + .fold(0, |acc, d| acc * 10 + d as u32); + BLEEvent::PairingRequest(code) + } + ffi::ble_event_type_t_BLE_PAIRING_CANCELLED => BLEEvent::PairingCanceled, + ffi::ble_event_type_t_BLE_PAIRING_COMPLETED => BLEEvent::PairingCompleted, + _ => panic!(), + } +} + fn state() -> ffi::ble_state_t { let mut state = ffi::ble_state_t { connected: false, diff --git a/core/embed/rust/src/trezorhal/button.rs b/core/embed/rust/src/trezorhal/button.rs index 5379a2907a..1079c62bad 100644 --- a/core/embed/rust/src/trezorhal/button.rs +++ b/core/embed/rust/src/trezorhal/button.rs @@ -17,6 +17,13 @@ pub enum PhysicalButtonEvent { Up = ffi::button_event_type_t_BTN_EVENT_UP as _, } +pub fn button_parse_event(event: ffi::button_event_t) -> (PhysicalButton, PhysicalButtonEvent) { + ( + unwrap!(PhysicalButton::from_u8(event.button as _)), + unwrap!(PhysicalButtonEvent::from_u8(event.event_type as _)), + ) +} + pub fn button_get_event() -> Option<(PhysicalButton, PhysicalButtonEvent)> { unsafe { let mut e = ffi::button_event_t { @@ -24,10 +31,7 @@ pub fn button_get_event() -> Option<(PhysicalButton, PhysicalButtonEvent)> { button: ffi::button_t_BTN_LEFT, }; if ffi::button_get_event(&mut e as _) { - Some(( - unwrap!(PhysicalButton::from_u8(e.button as _)), - unwrap!(PhysicalButtonEvent::from_u8(e.event_type as _)), - )) + Some(button_parse_event(e)) } else { None } diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index 5785d9de36..4114840404 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -35,3 +35,9 @@ pub mod wordlist; pub mod secbool; pub mod time; + +#[cfg(feature = "ui")] +pub mod sysevent; + +#[cfg(feature = "bootloader")] +pub use ffi::c_layout_t; diff --git a/core/embed/rust/src/trezorhal/sysevent.rs b/core/embed/rust/src/trezorhal/sysevent.rs new file mode 100644 index 0000000000..16af8332da --- /dev/null +++ b/core/embed/rust/src/trezorhal/sysevent.rs @@ -0,0 +1,84 @@ +#![allow(unused_imports)] + +use super::ffi; +use core::mem; + +use crate::ui::component::Event; +pub use ffi::{sysevents_t, syshandle_t}; + +#[cfg(feature = "ble")] +use crate::trezorhal::ble::ble_parse_event; +#[cfg(feature = "ble")] +use crate::ui::event::BLEEvent; + +#[cfg(feature = "touch")] +use crate::trezorhal::touch::touch_get_event; +#[cfg(feature = "touch")] +use crate::ui::event::TouchEvent; + +#[cfg(feature = "button")] +use crate::trezorhal::button::button_parse_event; +#[cfg(feature = "button")] +use crate::trezorhal::ffi::button_get_event; +#[cfg(feature = "button")] +use crate::ui::event::ButtonEvent; + +#[derive(PartialEq, Debug, Eq, Clone, Copy)] +pub enum Syshandle { + Button = ffi::syshandle_t_SYSHANDLE_BUTTON as _, + Touch = ffi::syshandle_t_SYSHANDLE_TOUCH as _, + Ble = ffi::syshandle_t_SYSHANDLE_BLE as _, +} + +pub fn parse_event(signalled: &sysevents_t) -> Option { + #[cfg(feature = "ble")] + if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_BLE) != 0 { + let mut ble_event: ffi::ble_event_t = unsafe { mem::zeroed() }; + let event_available = unsafe { ffi::ble_get_event(&mut ble_event) }; + if event_available { + let ble_event = ble_parse_event(ble_event); + return Some(Event::BLE(ble_event)); + } + } + #[cfg(feature = "button")] + if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_BUTTON) != 0 { + let mut button_event: ffi::button_event_t = unsafe { mem::zeroed() }; + let event_available = unsafe { button_get_event(&mut button_event) }; + if event_available { + let (btn, evt) = button_parse_event(button_event); + return Some(Event::Button(unwrap!(ButtonEvent::new(evt, btn)))); + } + } + #[cfg(feature = "touch")] + if signalled.read_ready & (1 << ffi::syshandle_t_SYSHANDLE_TOUCH) != 0 { + let touch_event = touch_get_event(); + + if touch_event != 0 { + let (event_type, ex, ey) = { + let event_type = touch_event >> 24; + + let ex = (touch_event >> 12) & 0xFFF; + let ey = touch_event & 0xFFF; + (event_type, ex, ey) + }; + return Some(Event::Touch(unwrap!(TouchEvent::new(event_type, ex, ey)))); + } + } + + None +} + +pub fn sysevents_poll(ifaces: &[Syshandle]) -> Option { + let mut awaited: sysevents_t = unsafe { mem::zeroed() }; + + for i in ifaces { + let bit: u32 = 1 << *i as u32; + awaited.read_ready |= bit; + } + + let mut signalled: sysevents_t = unsafe { mem::zeroed() }; + + unsafe { ffi::sysevents_poll(&awaited as _, &mut signalled as _, 100) }; + + parse_event(&signalled) +} diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 30590d6ec8..61c5df9369 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -1,6 +1,9 @@ use crate::{ strutil::hexlify, - trezorhal::secbool::secbool, + trezorhal::{ + secbool::secbool, + sysevent::{parse_event, sysevents_t}, + }, ui::{ ui_bootloader::BootloaderUI, util::{from_c_array, from_c_str}, @@ -8,9 +11,21 @@ use crate::{ }, }; +use super::super::super::trezorhal::c_layout_t; + #[no_mangle] -extern "C" fn screen_welcome() { - ModelUI::screen_welcome(); +extern "C" fn screen_event(layout: *mut c_layout_t, signalled: &sysevents_t) -> u32 { + let e = parse_event(signalled); + + let layout = unsafe { &mut *(layout) }; + + ModelUI::screen_event(&mut layout.buf, e) +} + +#[no_mangle] +extern "C" fn screen_welcome(layout: *mut c_layout_t) { + let layout = unsafe { &mut *(layout) }; + ModelUI::screen_welcome(&mut layout.buf); } #[no_mangle] @@ -75,8 +90,9 @@ extern "C" fn screen_unlock_bootloader_success() { } #[no_mangle] -extern "C" fn screen_menu(firmware_present: secbool) -> u32 { - ModelUI::screen_menu(firmware_present) +extern "C" fn screen_menu(initial_setup: bool, firmware_present: secbool, layout: *mut c_layout_t) { + let layout = unsafe { &mut *(layout) }; + ModelUI::screen_menu(initial_setup, firmware_present, &mut layout.buf); } #[no_mangle] @@ -131,8 +147,9 @@ extern "C" fn screen_install_progress(progress: u16, initialize: bool, initial_s } #[no_mangle] -extern "C" fn screen_connect(initial_setup: bool) { - ModelUI::screen_connect(initial_setup) +extern "C" fn screen_connect(initial_setup: bool, auto_update: bool, layout: *mut c_layout_t) { + let layout = unsafe { &mut *(layout) }; + ModelUI::screen_connect(initial_setup, auto_update, &mut layout.buf) } #[no_mangle] @@ -144,3 +161,22 @@ extern "C" fn screen_wipe_success() { extern "C" fn screen_wipe_fail() { ModelUI::screen_wipe_fail() } + +#[cfg(feature = "ble")] +#[no_mangle] +extern "C" fn screen_confirm_pairing(code: u32, initial_setup: bool) -> u32 { + ModelUI::screen_confirm_pairing(code, initial_setup) +} + +#[cfg(feature = "ble")] +#[no_mangle] +extern "C" fn screen_pairing_mode(initial_setup: bool, layout: *mut c_layout_t) { + let layout = unsafe { &mut *(layout) }; + ModelUI::screen_pairing_mode(initial_setup, &mut layout.buf); +} + +#[cfg(feature = "ble")] +#[no_mangle] +extern "C" fn screen_pairing_mode_finalizing(initial_setup: bool) -> u32 { + ModelUI::screen_pairing_mode_finalizing(initial_setup) +} diff --git a/core/embed/rust/src/ui/layout/mod.rs b/core/embed/rust/src/ui/layout/mod.rs index 63b9ed1c3f..42c3f6bd3e 100644 --- a/core/embed/rust/src/ui/layout/mod.rs +++ b/core/embed/rust/src/ui/layout/mod.rs @@ -5,6 +5,7 @@ pub mod obj; #[cfg(feature = "micropython")] pub mod result; + pub mod simplified; #[cfg(feature = "micropython")] diff --git a/core/embed/rust/src/ui/layout/simplified.rs b/core/embed/rust/src/ui/layout/simplified.rs index e4e1667f27..29c14f0388 100644 --- a/core/embed/rust/src/ui/layout/simplified.rs +++ b/core/embed/rust/src/ui/layout/simplified.rs @@ -1,19 +1,23 @@ #[cfg(feature = "button")] use crate::trezorhal::button::button_get_event; -#[cfg(feature = "touch")] -use crate::trezorhal::touch::touch_get_event; #[cfg(feature = "button")] use crate::ui::event::ButtonEvent; #[cfg(feature = "touch")] use crate::ui::event::TouchEvent; use crate::ui::{ - component::{Component, Event, EventCtx, Never}, + component::{Component, EventCtx, Never}, display, CommonUI, ModelUI, }; +use core::{ + mem::{align_of, size_of, MaybeUninit}, + ptr, +}; +use crate::ui::{component::Event, display::color::Color, shape::render_on_display}; use num_traits::ToPrimitive; -use crate::ui::{display::color::Color, shape::render_on_display}; +use crate::trezorhal::sysevent::{sysevents_poll, Syshandle}; +use heapless::Vec; pub trait ReturnToC { fn return_to_c(self) -> u32; @@ -57,7 +61,7 @@ pub fn touch_unpack(event: u32) -> Option { TouchEvent::new(event_type, ex as _, ey as _).ok() } -fn render(frame: &mut impl Component) { +pub(crate) fn render(frame: &mut impl Component) { display::sync(); render_on_display(None, Some(Color::black()), |target| { frame.render(target); @@ -65,26 +69,98 @@ fn render(frame: &mut impl Component) { display::refresh(); } +fn convert_layout(buf: &mut [u8]) -> *mut MaybeUninit { + let required_size = size_of::(); + let required_align = align_of::(); + + let ptr = buf.as_mut_ptr(); + let addr = ptr as usize; + + // Check alignment and size + if addr % required_align != 0 || buf.len() < required_size { + panic!("Buffer is not aligned or too small"); + } + + // SAFETY: we have just verified alignment and size + ptr as *mut MaybeUninit +} + +pub fn get_layout(buf: &mut [u8]) -> &mut A { + let required_size = size_of::(); + let required_align = align_of::(); + + let ptr = buf.as_mut_ptr(); + let addr = ptr as usize; + + // Check alignment and size + if addr % required_align != 0 || buf.len() < required_size { + panic!("Buffer is not aligned or too small"); + } + + // SAFETY: we have just verified alignment and size + let layout = ptr as *mut MaybeUninit; + + unsafe { &mut *layout.cast::() } +} + +pub fn init_layout(buf: &mut [u8], frame: A) -> &mut A { + let frame_ptr: *mut MaybeUninit = convert_layout(buf); + + unsafe { + // Construct in-place + ptr::write(frame_ptr as *mut A, frame); + + // Get mutable reference to initialized object + &mut *frame_ptr.cast::() + } +} + +pub fn process_frame_event(frame: &mut A, event: Option) -> u32 +where + A: Component, + A::Msg: ReturnToC, +{ + if let Some(event) = event { + let mut ctx = EventCtx::new(); + let msg = frame.event(&mut ctx, event); + if let Some(message) = msg { + return message.return_to_c(); + } + } + + render(frame); + + 0 +} + pub fn run(frame: &mut impl Component) -> u32 { frame.place(ModelUI::SCREEN); ModelUI::fadeout(); render(frame); ModelUI::fadein(); + // flush any pending events #[cfg(feature = "button")] while button_eval().is_some() {} + let mut ifaces: Vec = Vec::new(); + + #[cfg(feature = "ble")] + unwrap!(ifaces.push(Syshandle::Ble)); + + #[cfg(feature = "button")] + unwrap!(ifaces.push(Syshandle::Button)); + + #[cfg(feature = "touch")] + unwrap!(ifaces.push(Syshandle::Touch)); + loop { - #[cfg(all(feature = "button", not(feature = "touch")))] - let event = button_eval(); - #[cfg(feature = "touch")] - let event = touch_unpack(touch_get_event()); + let event = sysevents_poll(ifaces.as_slice()); + if let Some(e) = event { let mut ctx = EventCtx::new(); - #[cfg(all(feature = "button", not(feature = "touch")))] - let msg = frame.event(&mut ctx, Event::Button(e)); - #[cfg(feature = "touch")] - let msg = frame.event(&mut ctx, Event::Touch(e)); + + let msg = frame.event(&mut ctx, e); if let Some(message) = msg { return message.return_to_c(); diff --git a/core/embed/rust/src/ui/layout_bolt/bootloader/connect.rs b/core/embed/rust/src/ui/layout_bolt/bootloader/connect.rs new file mode 100644 index 0000000000..2085ec2a81 --- /dev/null +++ b/core/embed/rust/src/ui/layout_bolt/bootloader/connect.rs @@ -0,0 +1,132 @@ +use crate::{ + strutil::TString, + ui::{ + component::{Component, Event, EventCtx, Pad}, + display::{Color, Font, Icon}, + geometry::{Alignment, Insets, Offset, Point, Rect}, + layout_bolt::{ + component::{Button, ButtonMsg}, + constant::WIDTH, + theme::bootloader::{ + button_bld, button_bld_menu, button_initial, BLD_BG, BUTTON_AREA_START, + BUTTON_HEIGHT, CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, WELCOME_COLOR, + }, + }, + shape::{self, Renderer}, + }, +}; + +#[repr(u32)] +#[derive(Copy, Clone, ToPrimitive)] +pub enum ConnectMsg { + Cancel = 1, + PairingMode = 2, + Menu = 3, +} + +pub struct Connect { + fg: Color, + bg: Pad, + message: TString<'static>, + font: Font, + button: Button, + menu: Option