From 03fd6a5de8fbd6a6145cb8c821b407c1acb916a5 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Fri, 27 Jun 2025 21:40:36 +0200 Subject: [PATCH] feat(core/bootloader): prepare wireless setup bootloader workflow [no changelog] --- core/embed/io/ble/inc/io/ble.h | 3 + core/embed/io/ble/stm32/ble.c | 16 ++++ core/embed/io/ble/unix/ble.c | 4 + .../workflow/wf_ble_pairing_request.c | 95 ++++++++++++++++++- .../bootloader/workflow/wf_empty_device.c | 3 +- .../projects/bootloader/workflow/workflow.h | 4 + core/embed/rust/rust_ui_bootloader.h | 15 ++- core/embed/rust/src/ui/api/bootloader_c.rs | 35 ++++++- .../rust/src/ui/layout_bolt/bootloader/mod.rs | 12 ++- .../src/ui/layout_caesar/bootloader/mod.rs | 12 ++- .../src/ui/layout_delizia/bootloader/mod.rs | 12 ++- .../layout_eckhart/bootloader/pairing_mode.rs | 18 ++-- .../src/ui/layout_eckhart/ui_bootloader.rs | 41 +++++++- core/embed/rust/src/ui/ui_bootloader.rs | 7 +- 14 files changed, 259 insertions(+), 18 deletions(-) diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h index 2e025b7084..81dfd7686c 100644 --- a/core/embed/io/ble/inc/io/ble.h +++ b/core/embed/io/ble/inc/io/ble.h @@ -153,6 +153,9 @@ void ble_event_flush(void); // Obtains the current operational state of the BLE module. void ble_get_state(ble_state_t *state); +// Retrieves last set advertising name +void ble_get_advertising_name(char *name, size_t max_len); + // Check if write is possible bool ble_can_write(void); diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c index 7a8095b0c3..0b612a6b8d 100644 --- a/core/embed/io/ble/stm32/ble.c +++ b/core/embed/io/ble/stm32/ble.c @@ -847,6 +847,22 @@ bool ble_get_mac(uint8_t *mac, size_t max_len) { return false; } +void ble_get_advertising_name(char *name, size_t max_len) { + ble_driver_t *drv = &g_ble_driver; + + if (max_len < sizeof(drv->adv_cmd.name)) { + memset(name, 0, max_len); + return; + } + + if (!drv->initialized) { + memset(name, 0, max_len); + return; + } + + memcpy(name, drv->adv_cmd.name, sizeof(drv->adv_cmd.name)); +} + static void on_ble_iface_event_poll(void *context, bool read_awaited, bool write_awaited) { UNUSED(context); diff --git a/core/embed/io/ble/unix/ble.c b/core/embed/io/ble/unix/ble.c index d486d2f46e..cd1777fa79 100644 --- a/core/embed/io/ble/unix/ble.c +++ b/core/embed/io/ble/unix/ble.c @@ -28,3 +28,7 @@ uint32_t ble_read(uint8_t *data, uint16_t max_len) { return 0; } bool ble_get_mac(uint8_t *mac, size_t max_len) { return false; } void ble_event_flush(void) {} + +void ble_get_advertising_name(char *name, size_t max_len) { + memset(name, 0, max_len); +} diff --git a/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c b/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c index caad29d7cc..deb6b9daea 100644 --- a/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c +++ b/core/embed/projects/bootloader/workflow/wf_ble_pairing_request.c @@ -45,9 +45,13 @@ workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr, if (!ble_iface_start_pairing()) { return WF_OK_PAIRING_FAILED; } + + char name[BLE_ADV_NAME_LEN + 1] = {0}; + ble_get_advertising_name(name, sizeof(name)); + c_layout_t layout; memset(&layout, 0, sizeof(layout)); - screen_pairing_mode(ui_get_initial_setup(), &layout); + screen_pairing_mode(ui_get_initial_setup(), name, strlen(name), &layout); uint32_t code = 0; workflow_result_t res = @@ -117,4 +121,93 @@ workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr, return WF_OK_PAIRING_COMPLETED; } +workflow_result_t workflow_wireless_setup(const vendor_header *const vhdr, + const image_header *const hdr, + protob_ios_t *ios) { + ble_iface_start_pairing(); + + char name[BLE_ADV_NAME_LEN + 1] = {0}; + ble_get_advertising_name(name, sizeof(name)); + + c_layout_t layout; + memset(&layout, 0, sizeof(layout)); + screen_wireless_setup(name, strlen(name), &layout); + + uint32_t code = 0; + workflow_result_t res = workflow_host_control(vhdr, hdr, &layout, &code, ios); + + if (res != WF_OK_UI_ACTION) { + ble_iface_end_pairing(); + return res; + } + + if (code == WIRELESS_SETUP_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, ticks_timeout(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; + } + } + + memset(&layout, 0, sizeof(layout)); + screen_wireless_setup_final(&layout); + + uint32_t ui_result = 0; + res = workflow_host_control(vhdr, hdr, &layout, &ui_result, ios); + + if (ui_result == WIRELESS_SETUP_FINAL_CANCEL) { + return WF_OK_PAIRING_COMPLETED; + } + + return res; +} + #endif diff --git a/core/embed/projects/bootloader/workflow/wf_empty_device.c b/core/embed/projects/bootloader/workflow/wf_empty_device.c index c59c568b02..5744d19ba6 100644 --- a/core/embed/projects/bootloader/workflow/wf_empty_device.c +++ b/core/embed/projects/bootloader/workflow/wf_empty_device.c @@ -54,12 +54,13 @@ workflow_result_t workflow_empty_device(void) { res = workflow_host_control(NULL, NULL, &layout, &ui_result, &ios); #ifdef USE_BLE if (res == WF_OK_UI_ACTION && ui_result == WELCOME_PAIRING_MODE) { - res = workflow_ble_pairing_request(NULL, NULL); + res = workflow_wireless_setup(NULL, NULL, &ios); if (res == WF_OK_PAIRING_COMPLETED || res == WF_OK_PAIRING_FAILED) { res = WF_CANCELLED; ui_result = WELCOME_CANCEL; continue; } + return res; } #endif if (res == WF_OK_UI_ACTION && ui_result == WELCOME_MENU) { diff --git a/core/embed/projects/bootloader/workflow/workflow.h b/core/embed/projects/bootloader/workflow/workflow.h index 5676e653f3..defe9d2db7 100644 --- a/core/embed/projects/bootloader/workflow/workflow.h +++ b/core/embed/projects/bootloader/workflow/workflow.h @@ -81,6 +81,10 @@ workflow_result_t workflow_auto_update(const vendor_header *const vhdr, #ifdef USE_BLE workflow_result_t workflow_ble_pairing_request(const vendor_header *const vhdr, const image_header *const hdr); + +workflow_result_t workflow_wireless_setup(const vendor_header *const vhdr, + const image_header *const hdr, + protob_ios_t *ios); #endif void workflow_ifaces_init(secbool usb21_landing, protob_ios_t *ios); diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index ac26423a2b..cff98f5137 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -88,4 +88,17 @@ 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); +void screen_pairing_mode(bool initial_setup, const char* name, size_t name_len, + c_layout_t* layout); + +typedef enum { + // 0 - 999999 - pairing code + WIRELESS_SETUP_CANCEL = 1000000, +} wireless_setup_result_t; +void screen_wireless_setup(const char* name, size_t name_len, + c_layout_t* layout); + +typedef enum { + WIRELESS_SETUP_FINAL_CANCEL = 1, +} wireless_setup_final_result_t; +void screen_wireless_setup_final(c_layout_t* layout); diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 1df69f9f00..cef66ceceb 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -181,8 +181,39 @@ extern "C" fn screen_confirm_pairing(code: u32, initial_setup: bool) -> u32 { #[cfg(feature = "ble")] #[no_mangle] -extern "C" fn screen_pairing_mode(initial_setup: bool, layout: *mut c_layout_t) { - let mut screen = ::CLayoutType::init_pairing_mode(initial_setup); +extern "C" fn screen_pairing_mode( + initial_setup: bool, + name: *const cty::c_char, + name_len: usize, + layout: *mut c_layout_t, +) { + let name = unsafe { from_c_array(name, name_len).unwrap_or("") }; + let mut screen = ::CLayoutType::init_pairing_mode(initial_setup, name); + screen.show(); + // SAFETY: calling code is supposed to give us exclusive access to the layout + let mut layout = unsafe { LayoutBuffer::new(layout) }; + layout.store(screen); +} + +#[cfg(feature = "ble")] +#[no_mangle] +extern "C" fn screen_wireless_setup( + name: *const cty::c_char, + name_len: usize, + layout: *mut c_layout_t, +) { + let name = unsafe { from_c_array(name, name_len).unwrap_or("") }; + let mut screen = ::CLayoutType::init_wireless_setup(name); + screen.show(); + // SAFETY: calling code is supposed to give us exclusive access to the layout + let mut layout = unsafe { LayoutBuffer::new(layout) }; + layout.store(screen); +} + +#[cfg(feature = "ble")] +#[no_mangle] +extern "C" fn screen_wireless_setup_final(layout: *mut c_layout_t) { + let mut screen = ::CLayoutType::init_wireless_setup_final(); screen.show(); // SAFETY: calling code is supposed to give us exclusive access to the layout let mut layout = unsafe { LayoutBuffer::new(layout) }; 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 6cd00d8f19..4bf18d99d0 100644 --- a/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs @@ -190,7 +190,7 @@ impl BootloaderLayoutType for BootloaderLayout { } #[cfg(feature = "ble")] - fn init_pairing_mode(initial_setup: bool) -> Self { + fn init_pairing_mode(initial_setup: bool, _name: &'static str) -> Self { let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG }; let btn = if initial_setup { @@ -208,6 +208,16 @@ impl BootloaderLayoutType for BootloaderLayout { ); Self::PairingMode(frame) } + + #[cfg(feature = "ble")] + fn init_wireless_setup(_name: &'static str) -> Self { + unimplemented!() + } + + #[cfg(feature = "ble")] + fn init_wireless_setup_final() -> Self { + unimplemented!() + } } impl BootloaderUI for UIBolt { diff --git a/core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs b/core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs index 2bf3858d01..ea9370dcee 100644 --- a/core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs +++ b/core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs @@ -136,7 +136,17 @@ impl BootloaderLayoutType for BootloaderLayout { } #[cfg(feature = "ble")] - fn init_pairing_mode(_initial_setup: bool) -> Self { + fn init_pairing_mode(_initial_setup: bool, _name: &'static str) -> Self { + unimplemented!() + } + + #[cfg(feature = "ble")] + fn init_wireless_setup(_name: &'static str) -> Self { + unimplemented!() + } + + #[cfg(feature = "ble")] + fn init_wireless_setup_final() -> Self { unimplemented!() } } diff --git a/core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs b/core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs index c6fc15e7b3..d34f0bfeb0 100644 --- a/core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs +++ b/core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs @@ -171,7 +171,17 @@ impl BootloaderLayoutType for BootloaderLayout { } #[cfg(feature = "ble")] - fn init_pairing_mode(_initial_setup: bool) -> Self { + fn init_pairing_mode(_initial_setup: bool, _name: &'static str) -> Self { + unimplemented!() + } + + #[cfg(feature = "ble")] + fn init_wireless_setup(_name: &'static str) -> Self { + unimplemented!() + } + + #[cfg(feature = "ble")] + fn init_wireless_setup_final() -> Self { unimplemented!() } } diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs index bac2123a1c..9f3cc0b49f 100644 --- a/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs +++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs @@ -1,3 +1,7 @@ +use super::{ + super::{cshape::ScreenBorder, theme}, + BldActionBar, BldActionBarMsg, +}; use crate::{ strutil::TString, ui::{ @@ -9,11 +13,6 @@ use crate::{ }, }; -use super::{ - super::{cshape::ScreenBorder, theme}, - BldActionBar, BldActionBarMsg, -}; - #[repr(u32)] pub enum PairingMsg { Cancel, @@ -31,14 +30,16 @@ impl ReturnToC for PairingMsg { pub struct PairingModeScreen { message: Label<'static>, + name: Label<'static>, action_bar: Option, screen_border: ScreenBorder, } impl PairingModeScreen { - pub fn new(message: TString<'static>) -> Self { + pub fn new(message: TString<'static>, name: TString<'static>) -> Self { Self { message: Label::new(message, Alignment::Center, theme::TEXT_NORMAL), + name: Label::new(name, Alignment::Center, theme::TEXT_NORMAL), action_bar: None, screen_border: ScreenBorder::new(theme::BLUE), } @@ -56,8 +57,9 @@ impl Component for PairingModeScreen { fn place(&mut self, bounds: Rect) -> Rect { let (_header_area, rest) = bounds.split_top(theme::HEADER_HEIGHT); let (rest, action_bar_area) = rest.split_bottom(theme::ACTION_BAR_HEIGHT); - let content_area = rest.inset(theme::SIDE_INSETS); + let (content_area, name_area) = rest.inset(theme::SIDE_INSETS).split_top(70); self.message.place(content_area); + self.name.place(name_area); self.action_bar.place(action_bar_area); bounds } @@ -83,6 +85,7 @@ impl Component for PairingModeScreen { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { self.message.render(target); + self.name.render(target); self.action_bar.render(target); self.screen_border.render(u8::MAX, target); } @@ -93,5 +96,6 @@ impl crate::trace::Trace for PairingModeScreen { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.component("PairingMode"); t.string("message", *self.message.text()); + t.string("name", *self.name.text()); } } diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs index 8aa70fedda..22ff372d01 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs @@ -93,6 +93,10 @@ pub enum BootloaderLayout { Connect(ConnectScreen), #[cfg(feature = "ble")] PairingMode(PairingModeScreen), + #[cfg(feature = "ble")] + WirelessSetup(PairingModeScreen), + #[cfg(feature = "ble")] + WirelessSetupFinal(ConnectScreen), } impl BootloaderLayoutType for BootloaderLayout { @@ -103,6 +107,14 @@ impl BootloaderLayoutType for BootloaderLayout { BootloaderLayout::Connect(f) => process_frame_event::(f, event), #[cfg(feature = "ble")] BootloaderLayout::PairingMode(f) => process_frame_event::(f, event), + #[cfg(feature = "ble")] + BootloaderLayout::WirelessSetup(f) => { + process_frame_event::(f, event) + } + #[cfg(feature = "ble")] + BootloaderLayout::WirelessSetupFinal(f) => { + process_frame_event::(f, event) + } } } @@ -113,10 +125,16 @@ impl BootloaderLayoutType for BootloaderLayout { BootloaderLayout::Connect(f) => show(f, true), #[cfg(feature = "ble")] BootloaderLayout::PairingMode(f) => show(f, true), + #[cfg(feature = "ble")] + BootloaderLayout::WirelessSetup(f) => show(f, true), + #[cfg(feature = "ble")] + BootloaderLayout::WirelessSetupFinal(f) => show(f, true), } } fn init_welcome() -> Self { + // TODO: different UI. needs to decide based on some host already paired: + // peer_count() > 0 let screen = BldWelcomeScreen::new(); Self::Welcome(screen) } @@ -138,13 +156,32 @@ impl BootloaderLayoutType for BootloaderLayout { } #[cfg(feature = "ble")] - fn init_pairing_mode(_initial_setup: bool) -> Self { + fn init_pairing_mode(_initial_setup: bool, name: &'static str) -> Self { // TODO: different style for initial setup let btn = Button::with_text("Cancel".into()).styled(button_default()); - let screen = PairingModeScreen::new("Waiting for pairing...".into()) + let screen = PairingModeScreen::new("Waiting for pairing...".into(), name.into()) .with_action_bar(BldActionBar::new_single(btn)); Self::PairingMode(screen) } + + #[cfg(feature = "ble")] + fn init_wireless_setup(name: &'static str) -> Self { + // todo implement correct UI + let btn = Button::with_text("Cancel".into()).styled(button_default()); + + let screen = PairingModeScreen::new("QR_CODE".into(), name.into()) + .with_action_bar(BldActionBar::new_single(btn)); + Self::WirelessSetup(screen) + } + + #[cfg(feature = "ble")] + fn init_wireless_setup_final() -> Self { + // todo implement correct UI + let btn = Button::with_text("Cancel".into()).styled(button_default()); + let screen = + ConnectScreen::new("WAIT".into()).with_action_bar(BldActionBar::new_single(btn)); + Self::WirelessSetupFinal(screen) + } } impl BootloaderUI for UIEckhart { diff --git a/core/embed/rust/src/ui/ui_bootloader.rs b/core/embed/rust/src/ui/ui_bootloader.rs index 74320e4286..bbe3a0287d 100644 --- a/core/embed/rust/src/ui/ui_bootloader.rs +++ b/core/embed/rust/src/ui/ui_bootloader.rs @@ -7,7 +7,12 @@ pub trait BootloaderLayoutType { fn init_menu(initial_setup: bool) -> Self; fn init_connect(initial_setup: bool, auto_update: bool) -> Self; #[cfg(feature = "ble")] - fn init_pairing_mode(initial_setup: bool) -> Self; + fn init_pairing_mode(initial_setup: bool, name: &'static str) -> Self; + #[cfg(feature = "ble")] + fn init_wireless_setup(name: &'static str) -> Self; + + #[cfg(feature = "ble")] + fn init_wireless_setup_final() -> Self; } pub trait BootloaderUI {