From c1bfd5a8b3f1aa62b48ba68dcc96abeb8f1359d1 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 8 Jul 2025 20:13:45 +0200 Subject: [PATCH] feat(core/bootloader): bootloader button power off logic [no changelog] --- core/embed/projects/bootloader/bootui.c | 2 - core/embed/projects/bootloader/bootui.h | 2 + .../bootloader/workflow/wf_host_control.c | 62 +++++++++++++++++++ core/embed/rust/build.rs | 1 + core/embed/rust/rust_ui_bootloader.h | 3 + core/embed/rust/src/trezorhal/haptic.rs | 1 + .../embed/rust/src/trezorhal/power_manager.rs | 4 ++ core/embed/rust/src/ui/api/bootloader_c.rs | 9 +++ core/embed/rust/src/ui/layout/simplified.rs | 56 ++++++++++++++++- .../src/ui/layout_eckhart/ui_bootloader.rs | 14 ++++- core/embed/rust/src/ui/ui_bootloader.rs | 5 ++ 11 files changed, 155 insertions(+), 4 deletions(-) diff --git a/core/embed/projects/bootloader/bootui.c b/core/embed/projects/bootloader/bootui.c index 3b90dbbacf..9e8334d76c 100644 --- a/core/embed/projects/bootloader/bootui.c +++ b/core/embed/projects/bootloader/bootui.c @@ -27,8 +27,6 @@ #include "rust_ui_bootloader.h" #include "version.h" -#define BACKLIGHT_NORMAL 150 - #define TOIF_LENGTH(ptr) ((*(uint32_t *)((ptr) + 8)) + 12) // common shared functions diff --git a/core/embed/projects/bootloader/bootui.h b/core/embed/projects/bootloader/bootui.h index 6ce5207cb3..ac9f2e912a 100644 --- a/core/embed/projects/bootloader/bootui.h +++ b/core/embed/projects/bootloader/bootui.h @@ -25,6 +25,8 @@ #include "rust_ui_bootloader.h" +#define BACKLIGHT_NORMAL 150 + // Displays a warning screen before jumping to the untrusted firmware // // Shows vendor image, vendor string and firmware version diff --git a/core/embed/projects/bootloader/workflow/wf_host_control.c b/core/embed/projects/bootloader/workflow/wf_host_control.c index d30b561138..0e03b531b6 100644 --- a/core/embed/projects/bootloader/workflow/wf_host_control.c +++ b/core/embed/projects/bootloader/workflow/wf_host_control.c @@ -31,10 +31,18 @@ #include "wire/wire_iface_usb.h" #include "workflow.h" +#ifdef USE_HAPTIC +#include +#endif + #ifdef USE_BLE #include #endif +#ifdef USE_BUTTON +#include +#endif + #ifdef USE_POWER_MANAGER #include #include @@ -53,6 +61,10 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, workflow_result_t result = WF_ERROR_FATAL; #ifdef USE_POWER_MANAGER + uint32_t button_deadline = 0; +#ifdef USE_HAPTIC + bool button_haptic_played = false; +#endif uint32_t fade_deadline = ticks_timeout(FADE_TIME_MS); uint32_t suspend_deadline = ticks_timeout(SUSPEND_TIME_MS); bool faded = false; @@ -86,6 +98,16 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, sysevents_poll(&awaited, &signalled, ticks_timeout(100)); #ifdef USE_POWER_MANAGER + +#ifdef USE_HAPTIC + if (button_deadline != 0 && !button_haptic_played && + ticks_expired(button_deadline)) { + // we reached hibernation time + haptic_play(HAPTIC_BOOTLOADER_ENTRY); + button_haptic_played = true; + } +#endif + if (signalled.read_ready == 0) { pm_state_t pm_state = {0}; @@ -106,7 +128,9 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, if (ticks_expired(suspend_deadline)) { pm_suspend(NULL); + screen_render(wait_layout); display_fade(display_get_backlight(), fade_value, 200); + button_deadline = 0; faded = false; fade_deadline = ticks_timeout(FADE_TIME_MS); suspend_deadline = ticks_timeout(SUSPEND_TIME_MS); @@ -120,6 +144,44 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr, display_fade(display_get_backlight(), fade_value, 200); faded = false; } + + // in case of battery powered device, power button is handled by eventloop + if (signalled.read_ready & (1 << SYSHANDLE_BUTTON)) { + button_event_t btn_event = {0}; + // todo this eats all button events, not only power button, so it needs to + // be handled differently for button-based battery powered devices. + if (button_get_event(&btn_event) && btn_event.button == BTN_POWER) { + if (btn_event.event_type == BTN_EVENT_DOWN) { + button_deadline = ticks_timeout(3000); +#ifdef USE_HAPTIC + button_haptic_played = false; +#endif + } else if (btn_event.event_type == BTN_EVENT_UP && + button_deadline != 0) { + display_fade(display_get_backlight(), 0, 200); + if (ticks_expired(button_deadline)) { + // power button pressed for 3 seconds, we hibernate + +#ifdef USE_HAPTIC + if (!button_haptic_played) { + haptic_play(HAPTIC_BOOTLOADER_ENTRY); + button_haptic_played = true; + } +#endif + pm_hibernate(); + } else { + pm_suspend(NULL); + button_deadline = 0; + screen_render(wait_layout); + display_fade(display_get_backlight(), BACKLIGHT_NORMAL, 200); + faded = false; + fade_deadline = ticks_timeout(FADE_TIME_MS); + suspend_deadline = ticks_timeout(SUSPEND_TIME_MS); + } + } + } + } + #else if (signalled.read_ready == 0) { continue; diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index da2fec8074..b00f5e3eb6 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -456,6 +456,7 @@ fn generate_trezorhal_bindings() { .allowlist_function("pm_get_events") .allowlist_function("pm_get_state") .allowlist_function("pm_suspend") + .allowlist_function("pm_hibernate") // irq .allowlist_function("irq_lock_fn") .allowlist_function("irq_unlock_fn") diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index a8dfe880e7..ccb3a5c2f7 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -11,6 +11,9 @@ // common event function for screens that need UI + communication uint32_t screen_event(c_layout_t* layout, sysevents_t* signalled); +// common render event +void screen_render(c_layout_t* layout); + // result screens void screen_wipe_success(void); void screen_wipe_fail(void); diff --git a/core/embed/rust/src/trezorhal/haptic.rs b/core/embed/rust/src/trezorhal/haptic.rs index a1f75700e3..663d71a4bc 100644 --- a/core/embed/rust/src/trezorhal/haptic.rs +++ b/core/embed/rust/src/trezorhal/haptic.rs @@ -4,6 +4,7 @@ use super::ffi; pub enum HapticEffect { ButtonPress = ffi::haptic_effect_t_HAPTIC_BUTTON_PRESS as _, HoldToConfirm = ffi::haptic_effect_t_HAPTIC_HOLD_TO_CONFIRM as _, + BootloaderEntry = ffi::haptic_effect_t_HAPTIC_BOOTLOADER_ENTRY as _, } pub fn play(effect: HapticEffect) { diff --git a/core/embed/rust/src/trezorhal/power_manager.rs b/core/embed/rust/src/trezorhal/power_manager.rs index cba2e0a315..5a2aff3d5b 100644 --- a/core/embed/rust/src/trezorhal/power_manager.rs +++ b/core/embed/rust/src/trezorhal/power_manager.rs @@ -51,3 +51,7 @@ pub fn is_usb_connected() -> bool { pub fn suspend() { unsafe { ffi::pm_suspend(null_mut()) }; } + +pub fn hibernate() { + unsafe { ffi::pm_hibernate() }; +} diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 595017fece..75729f9e15 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -23,6 +23,15 @@ extern "C" fn screen_event(layout: *mut c_layout_t, signalled: &sysevents_t) -> } } +#[no_mangle] +extern "C" fn screen_render(layout: *mut c_layout_t) { + unsafe { + let mut layout = LayoutBuffer::<::CLayoutType>::new(layout); + let layout = layout.get_mut(); + layout.render() + } +} + #[no_mangle] extern "C" fn screen_welcome(layout: *mut c_layout_t) { let mut screen = ::CLayoutType::init_welcome(); diff --git a/core/embed/rust/src/ui/layout/simplified.rs b/core/embed/rust/src/ui/layout/simplified.rs index a66abdc558..3833f05bc0 100644 --- a/core/embed/rust/src/ui/layout/simplified.rs +++ b/core/embed/rust/src/ui/layout/simplified.rs @@ -16,10 +16,14 @@ use crate::trezorhal::sysevent::{sysevents_poll, Syshandle}; #[cfg(feature = "power_manager")] use crate::{ time::Instant, - trezorhal::power_manager::{is_usb_connected, suspend}, + trezorhal::power_manager::{hibernate, is_usb_connected, suspend}, ui::display::fade_backlight_duration, + ui::event::PhysicalButton, }; +#[cfg(all(feature = "haptic", feature = "power_manager"))] +use crate::trezorhal::haptic::{play, HapticEffect}; + use heapless::Vec; use num_traits::ToPrimitive; @@ -98,6 +102,10 @@ pub fn run(frame: &mut impl Component) -> u32 { render(frame); ModelUI::fadein(); + #[cfg(all(feature = "power_manager", feature = "haptic"))] + let mut haptic_played = false; + #[cfg(feature = "power_manager")] + let mut button_pressed_time = None; #[cfg(feature = "power_manager")] let mut start = Instant::now(); let mut faded = false; @@ -132,6 +140,39 @@ pub fn run(frame: &mut impl Component) -> u32 { #[cfg(feature = "power_manager")] { start = Instant::now(); + + if e == Event::Button(ButtonEvent::ButtonPressed(PhysicalButton::Power)) { + button_pressed_time = Some(Instant::now()); + + #[cfg(feature = "haptic")] + { + haptic_played = false; + } + } else if e == Event::Button(ButtonEvent::ButtonReleased(PhysicalButton::Power)) { + if let Some(t) = button_pressed_time { + if let Some(elapsed) = Instant::now().checked_duration_since(t) { + ModelUI::fadeout(); + if elapsed.to_secs() >= 3 { + #[cfg(feature = "haptic")] + { + if !haptic_played { + play(HapticEffect::BootloaderEntry); + haptic_played = true; + } + } + hibernate(); + } else { + suspend(); + render(frame); + ModelUI::fadein(); + + faded = false; + button_pressed_time = None; + start = Instant::now(); + } + } + } + } } let mut ctx = EventCtx::new(); @@ -145,6 +186,18 @@ pub fn run(frame: &mut impl Component) -> u32 { } else { #[cfg(feature = "power_manager")] { + #[cfg(feature = "haptic")] + { + if let Some(t) = button_pressed_time { + if let Some(elapsed) = Instant::now().checked_duration_since(t) { + if elapsed.to_secs() >= 3 && !haptic_played { + play(HapticEffect::BootloaderEntry); + haptic_played = true; + } + } + } + } + if is_usb_connected() { continue; } @@ -164,6 +217,7 @@ pub fn run(frame: &mut impl Component) -> u32 { faded = false; } start = Instant::now(); + button_pressed_time = None; } } } 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 ec685a5ac7..ec65f0fdcf 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs @@ -2,7 +2,7 @@ use crate::ui::{ component::{Event, Label}, display::{self, toif::Toif, Color}, geometry::{Alignment, Alignment2D, Offset, Point, Rect}, - layout::simplified::{process_frame_event, run, show}, + layout::simplified::{process_frame_event, render, run, show}, shape::{self, render_on_display}, ui_bootloader::{BootloaderLayoutType, BootloaderUI}, CommonUI, @@ -109,6 +109,18 @@ impl BootloaderLayoutType for BootloaderLayout { } } + fn render(&mut self) { + match self { + BootloaderLayout::Welcome(f) => render(f), + BootloaderLayout::Menu(f) => render(f), + BootloaderLayout::Connect(f) => render(f), + #[cfg(feature = "ble")] + BootloaderLayout::PairingMode(f) => render(f), + #[cfg(feature = "ble")] + BootloaderLayout::WirelessSetup(f) => render(f), + } + } + fn show(&mut self) { match self { BootloaderLayout::Welcome(f) => show(f, true), diff --git a/core/embed/rust/src/ui/ui_bootloader.rs b/core/embed/rust/src/ui/ui_bootloader.rs index 2eb1e52264..7b64456a47 100644 --- a/core/embed/rust/src/ui/ui_bootloader.rs +++ b/core/embed/rust/src/ui/ui_bootloader.rs @@ -2,6 +2,11 @@ use crate::ui::component::Event; pub trait BootloaderLayoutType { fn event(&mut self, event: Option) -> u32; + + fn render(&mut self) { + unimplemented!(); + } + fn show(&mut self); fn init_welcome() -> Self; fn init_menu(initial_setup: bool) -> Self;