From 5a68da1d2373937c8713c81782003c9111235fa1 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Mon, 3 Mar 2025 15:13:56 +0100 Subject: [PATCH] feat(core): add BLE to bootloader [no changelog] --- core/SConscript.bootloader | 4 +- core/embed/projects/bootloader/main.c | 6 + core/embed/projects/bootloader/poll.c | 50 +++++++- core/embed/projects/bootloader/poll.h | 46 ++++++- .../projects/bootloader/wire/wire_iface_ble.c | 116 ++++++++++++++++++ .../projects/bootloader/wire/wire_iface_ble.h | 28 +++++ .../workflow/wf_ble_pairing_request.c | 49 ++++++++ .../bootloader/workflow/wf_firmware_update.c | 4 +- .../bootloader/workflow/wf_host_control.c | 94 ++++++++++++-- .../projects/bootloader/workflow/workflow.h | 4 + core/embed/rust/rust_ui_bootloader.h | 2 + core/embed/rust/src/ui/api/bootloader_c.rs | 7 ++ .../rust/src/ui/layout_bolt/bootloader/mod.rs | 14 +++ core/embed/rust/src/ui/ui_bootloader.rs | 3 + 14 files changed, 402 insertions(+), 25 deletions(-) create mode 100644 core/embed/projects/bootloader/wire/wire_iface_ble.c create mode 100644 core/embed/projects/bootloader/wire/wire_iface_ble.h create mode 100644 core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index 6b9116e773..d3a7e6b5c8 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 = [] @@ -116,8 +116,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/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c index b34924182e..c1b7ff935f 100644 --- a/core/embed/projects/bootloader/main.c +++ b/core/embed/projects/bootloader/main.c @@ -59,6 +59,9 @@ #ifdef USE_TAMPER #include #endif +#ifdef USE_BLE +#include +#endif #include "antiglitch.h" #include "bootui.h" @@ -122,6 +125,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) { diff --git a/core/embed/projects/bootloader/poll.c b/core/embed/projects/bootloader/poll.c index 6d6bc70e73..67f5108fd2 100644 --- a/core/embed/projects/bootloader/poll.c +++ b/core/embed/projects/bootloader/poll.c @@ -18,17 +18,30 @@ */ #include +#include #include "poll.h" #include #include +#ifdef USE_BLE +#include +#endif + +#ifdef USE_BUTTON +#include +#endif + +#ifdef USE_TOUCH +#include +#endif + #ifdef TREZOR_EMULATOR #include "SDL.h" #endif -uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, +int16_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); @@ -45,11 +58,41 @@ uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, 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; + event->event.usb_data_event = EVENT_USB_CAN_READ; return iface_num; } } } +#ifdef USE_BLE + if (iface_num == IFACE_BLE) { + if ((ifaces[i] & MODE_READ) == MODE_READ) { + // check if BLE can read + if (ble_can_read()) { + event->event.ble_data_event = EVENT_BLE_CAN_READ; + return iface_num; + } + } + } + if (iface_num == IFACE_BLE_EVENT) { + ble_event_t ble_event = {0}; + if (ble_get_event(&ble_event)) { + memcpy(&event->event.ble_event, &ble_event, sizeof(ble_event_t)); + return iface_num; + } + } +#endif +#ifdef USE_BUTTON + if (iface_num == IFACE_BUTTON) { + uint32_t btn_event = button_get_event(); + uint32_t etype = (btn_event >> 24) & 0x3U; // button down/up + uint32_t btn_number = btn_event & 0xFFFF; + if (etype != 0) { + event->event.button_event.type = etype; + event->event.button_event.button = btn_number; + return iface_num; + } + } +#endif } #ifndef TREZOR_EMULATOR @@ -57,6 +100,5 @@ uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, #endif } - event->type = EVENT_NONE; - return 0; + return -1; } diff --git a/core/embed/projects/bootloader/poll.h b/core/embed/projects/bootloader/poll.h index 61d7d4e79c..c2c080874b 100644 --- a/core/embed/projects/bootloader/poll.h +++ b/core/embed/projects/bootloader/poll.h @@ -21,19 +21,55 @@ #include +#include + +#ifdef USE_BLE +#include +#endif +#ifdef USE_BUTTON +#include +#endif + #define IFACE_USB_MAX (15) // 0-15 reserved for USB +#define IFACE_BLE (16) +#define IFACE_BLE_EVENT (252) +#define IFACE_BUTTON (254) +#define IFACE_TOUCH (255) #define MODE_READ 0x0000 #define MODE_WRITE 0x0100 typedef enum { - EVENT_NONE = 0, - EVENT_USB_CAN_READ = 0x01, -} poll_event_type_t; + EVENT_USB_CAN_READ, +} usb_data_event_type_t; + +#ifdef USE_BLE +typedef enum { + EVENT_BLE_CAN_READ, +} ble_data_event_type_t; +#endif + +#ifdef USE_BUTTON +typedef struct { + uint32_t type; + button_t button; +} button_event_t; +#endif typedef struct { - poll_event_type_t type; + union { + usb_data_event_type_t usb_data_event; + usb_event_t usb_event; +#ifdef USE_BLE + ble_data_event_type_t ble_data_event; + ble_event_t ble_event; +#endif +#ifdef USE_BUTTON + button_event_t button_event; +#endif + + } event; } poll_event_t; -uint8_t poll_events(const uint16_t* ifaces, size_t ifaces_num, +int16_t poll_events(const uint16_t* ifaces, size_t ifaces_num, poll_event_t* event, uint32_t timeout_ms); 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..79a16bfd1f --- /dev/null +++ b/core/embed/projects/bootloader/wire/wire_iface_ble.c @@ -0,0 +1,116 @@ +/* + * 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 + +#include "wire_iface_ble.h" + +#include +#include + +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 (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 false; + } + 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); +} + +void ble_iface_init(wire_iface_t* iface) { + ble_start(); + + memset(iface, 0, sizeof(wire_iface_t)); + + iface->poll_iface_id = 16; + 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, + }; + ble_issue_command(&cmd); + } else { + ble_iface_start_pairing(); + } + } +} + +void ble_iface_deinit(wire_iface_t* iface) { ble_stop(); } + +void ble_iface_start_pairing(void) { + ble_command_t cmd = { + .cmd_type = BLE_PAIRING_MODE, + .data = {.adv_start = + { + .name = "Trezor Bootloader", + .static_mac = false, + }}, + .data_len = 0, + }; + ble_issue_command(&cmd); +} diff --git a/core/embed/projects/bootloader/wire/wire_iface_ble.h b/core/embed/projects/bootloader/wire/wire_iface_ble.h new file mode 100644 index 0000000000..98420b8207 --- /dev/null +++ b/core/embed/projects/bootloader/wire/wire_iface_ble.h @@ -0,0 +1,28 @@ +/* + * 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 . + */ + +#pragma once + +#include "codec_v1.h" + +void ble_iface_init(wire_iface_t* iface); + +void ble_iface_deinit(wire_iface_t* iface); + +void ble_iface_start_pairing(void); 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..b6d214c70b --- /dev/null +++ b/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c @@ -0,0 +1,49 @@ +/* + * 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 "workflow.h" + +workflow_result_t workflow_ble_pairing_request(const uint8_t *data) { + ui_result_t result = screen_confirm_pairing(data); + + if (result == UI_RESULT_CONFIRM) { + ble_command_t cmd = { + .cmd_type = BLE_ALLOW_PAIRING, + }; + ble_issue_command(&cmd); + } else { + ble_command_t cmd = { + .cmd_type = BLE_REJECT_PAIRING, + }; + ble_issue_command(&cmd); + } + + screen_connect(false); + return WF_OK; +} + +#endif diff --git a/core/embed/projects/bootloader/workflow/wf_firmware_update.c b/core/embed/projects/bootloader/workflow/wf_firmware_update.c index b60dbcf9ce..9576af6b7a 100644 --- a/core/embed/projects/bootloader/workflow/wf_firmware_update.c +++ b/core/embed/projects/bootloader/workflow/wf_firmware_update.c @@ -521,9 +521,9 @@ workflow_result_t workflow_firmware_update(protob_io_t *iface) { 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); + uint16_t i = poll_events(ifaces, 1, &e, 100); - if (e.type == EVENT_NONE || i != protob_get_iface_flag(iface)) { + if (i < 0 || i != protob_get_iface_flag(iface)) { continue; } diff --git a/core/embed/projects/bootloader/workflow/wf_host_control.c b/core/embed/projects/bootloader/workflow/wf_host_control.c index 5ac20be205..2356f1bbba 100644 --- a/core/embed/projects/bootloader/workflow/wf_host_control.c +++ b/core/embed/projects/bootloader/workflow/wf_host_control.c @@ -30,11 +30,21 @@ #include "wire/wire_iface_usb.h" #include "workflow.h" +#ifdef USE_BLE +#include + +#ifdef USE_BUTTON +#include +#endif +#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}; + wire_iface_t ble_iface = {0}; protob_io_t protob_usb_iface = {0}; + protob_io_t protob_ble_iface = {0}; redraw_wait_screen(); @@ -43,31 +53,89 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, usb_iface_init(&usb_iface, (vhdr == NULL && hdr == NULL) ? sectrue : secfalse); + ble_iface_init(&ble_iface); + protob_init(&protob_usb_iface, &usb_iface); + protob_init(&protob_ble_iface, &ble_iface); workflow_result_t result = WF_ERROR_FATAL; for (;;) { - uint16_t ifaces[1] = {protob_get_iface_flag(&protob_usb_iface) | MODE_READ}; + uint16_t ifaces[] = { + protob_get_iface_flag(&protob_usb_iface) | MODE_READ, +#ifdef USE_BLE + protob_get_iface_flag(&protob_ble_iface) | MODE_READ, + IFACE_BLE_EVENT, +#ifdef USE_BUTTON + IFACE_BUTTON, +#endif +#endif + }; + poll_event_t e = {0}; - uint8_t i = poll_events(ifaces, 1, &e, 100); + int16_t i = poll_events(ifaces, ARRAY_LENGTH(ifaces), &e, 100); + + if (i < 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 { + if (i < IFACE_USB_MAX) { + switch (e.event.usb_data_event) { + 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; + } + break; + default: continue; - } - break; - case EVENT_NONE: - default: - continue; + } + } +#ifdef USE_BLE + if (i == IFACE_BLE) { + switch (e.event.ble_data_event) { + case EVENT_BLE_CAN_READ: + if (i == protob_get_iface_flag(&protob_ble_iface) && + sectrue == protob_get_msg_header(&protob_ble_iface, &msg_id)) { + active_iface = &protob_ble_iface; + } else { + continue; + } + break; + default: + continue; + } + } + if (i == IFACE_BLE_EVENT) { + switch (e.event.ble_event.type) { + case BLE_PAIRING_REQUEST: + workflow_ble_pairing_request(e.event.ble_event.data); + continue; + default: + break; + } + } +#ifdef USE_BUTTON + if (i == IFACE_BUTTON) { + switch (e.event.button_event.type) { + case (BTN_EVT_DOWN >> 24): + ble_iface_start_pairing(); + default: + continue; + } + } +#endif +#endif + + if (active_iface == NULL) { + continue; + ; } switch (msg_id) { diff --git a/core/embed/projects/bootloader/workflow/workflow.h b/core/embed/projects/bootloader/workflow/workflow.h index 2a45dac4b1..d2a130c5f7 100644 --- a/core/embed/projects/bootloader/workflow/workflow.h +++ b/core/embed/projects/bootloader/workflow/workflow.h @@ -66,3 +66,7 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, 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 uint8_t *data); +#endif diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index b0b4c22322..6472e63742 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -27,3 +27,5 @@ 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); + +uint32_t screen_confirm_pairing(const uint8_t* code); diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 30590d6ec8..2724c53f70 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -144,3 +144,10 @@ extern "C" fn screen_wipe_success() { extern "C" fn screen_wipe_fail() { ModelUI::screen_wipe_fail() } + +#[no_mangle] +extern "C" fn screen_confirm_pairing(code: *const cty::c_char) -> u32 { + let code = unwrap!(unsafe { from_c_array(code, 6) }); + + ModelUI::screen_confirm_pairing(code) +} diff --git a/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs b/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs index 5476c6e9a3..14abccaa7f 100644 --- a/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs @@ -439,4 +439,18 @@ impl BootloaderUI for UIBolt { display::refresh(); } + + #[cfg(feature = "ble")] + fn screen_confirm_pairing(code: &str) -> u32 { + let title = Label::centered("Pair device".into(), TEXT_NORMAL); + + let msg = Label::centered(code.into(), TEXT_NORMAL); + + let right = Button::with_text("CONFIRM".into()).styled(button_confirm()); + let left = Button::with_text("REJECT".into()).styled(button_bld()); + + let mut frame = Confirm::new(BLD_BG, left, right, ConfirmTitle::Text(title), msg); + + run(&mut frame) + } } diff --git a/core/embed/rust/src/ui/ui_bootloader.rs b/core/embed/rust/src/ui/ui_bootloader.rs index 1a76428c95..031348521c 100644 --- a/core/embed/rust/src/ui/ui_bootloader.rs +++ b/core/embed/rust/src/ui/ui_bootloader.rs @@ -48,4 +48,7 @@ pub trait BootloaderUI { vendor_img: &'static [u8], wait: i32, ); + + #[cfg(feature = "ble")] + fn screen_confirm_pairing(code: &str) -> u32; }