1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-29 18:08:19 +00:00

feat(core/bootloader): bootloader button power off logic

[no changelog]
This commit is contained in:
tychovrahe 2025-07-08 20:13:45 +02:00 committed by TychoVrahe
parent 0e6052dc25
commit c1bfd5a8b3
11 changed files with 155 additions and 4 deletions

View File

@ -27,8 +27,6 @@
#include "rust_ui_bootloader.h" #include "rust_ui_bootloader.h"
#include "version.h" #include "version.h"
#define BACKLIGHT_NORMAL 150
#define TOIF_LENGTH(ptr) ((*(uint32_t *)((ptr) + 8)) + 12) #define TOIF_LENGTH(ptr) ((*(uint32_t *)((ptr) + 8)) + 12)
// common shared functions // common shared functions

View File

@ -25,6 +25,8 @@
#include "rust_ui_bootloader.h" #include "rust_ui_bootloader.h"
#define BACKLIGHT_NORMAL 150
// Displays a warning screen before jumping to the untrusted firmware // Displays a warning screen before jumping to the untrusted firmware
// //
// Shows vendor image, vendor string and firmware version // Shows vendor image, vendor string and firmware version

View File

@ -31,10 +31,18 @@
#include "wire/wire_iface_usb.h" #include "wire/wire_iface_usb.h"
#include "workflow.h" #include "workflow.h"
#ifdef USE_HAPTIC
#include <io/haptic.h>
#endif
#ifdef USE_BLE #ifdef USE_BLE
#include <wire/wire_iface_ble.h> #include <wire/wire_iface_ble.h>
#endif #endif
#ifdef USE_BUTTON
#include <io/button.h>
#endif
#ifdef USE_POWER_MANAGER #ifdef USE_POWER_MANAGER
#include <io/display.h> #include <io/display.h>
#include <io/display_utils.h> #include <io/display_utils.h>
@ -53,6 +61,10 @@ workflow_result_t workflow_host_control(const vendor_header *const vhdr,
workflow_result_t result = WF_ERROR_FATAL; workflow_result_t result = WF_ERROR_FATAL;
#ifdef USE_POWER_MANAGER #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 fade_deadline = ticks_timeout(FADE_TIME_MS);
uint32_t suspend_deadline = ticks_timeout(SUSPEND_TIME_MS); uint32_t suspend_deadline = ticks_timeout(SUSPEND_TIME_MS);
bool faded = false; 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)); sysevents_poll(&awaited, &signalled, ticks_timeout(100));
#ifdef USE_POWER_MANAGER #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) { if (signalled.read_ready == 0) {
pm_state_t pm_state = {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)) { if (ticks_expired(suspend_deadline)) {
pm_suspend(NULL); pm_suspend(NULL);
screen_render(wait_layout);
display_fade(display_get_backlight(), fade_value, 200); display_fade(display_get_backlight(), fade_value, 200);
button_deadline = 0;
faded = false; faded = false;
fade_deadline = ticks_timeout(FADE_TIME_MS); fade_deadline = ticks_timeout(FADE_TIME_MS);
suspend_deadline = ticks_timeout(SUSPEND_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); display_fade(display_get_backlight(), fade_value, 200);
faded = false; 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 #else
if (signalled.read_ready == 0) { if (signalled.read_ready == 0) {
continue; continue;

View File

@ -456,6 +456,7 @@ fn generate_trezorhal_bindings() {
.allowlist_function("pm_get_events") .allowlist_function("pm_get_events")
.allowlist_function("pm_get_state") .allowlist_function("pm_get_state")
.allowlist_function("pm_suspend") .allowlist_function("pm_suspend")
.allowlist_function("pm_hibernate")
// irq // irq
.allowlist_function("irq_lock_fn") .allowlist_function("irq_lock_fn")
.allowlist_function("irq_unlock_fn") .allowlist_function("irq_unlock_fn")

View File

@ -11,6 +11,9 @@
// common event function for screens that need UI + communication // common event function for screens that need UI + communication
uint32_t screen_event(c_layout_t* layout, sysevents_t* signalled); uint32_t screen_event(c_layout_t* layout, sysevents_t* signalled);
// common render event
void screen_render(c_layout_t* layout);
// result screens // result screens
void screen_wipe_success(void); void screen_wipe_success(void);
void screen_wipe_fail(void); void screen_wipe_fail(void);

View File

@ -4,6 +4,7 @@ use super::ffi;
pub enum HapticEffect { pub enum HapticEffect {
ButtonPress = ffi::haptic_effect_t_HAPTIC_BUTTON_PRESS as _, ButtonPress = ffi::haptic_effect_t_HAPTIC_BUTTON_PRESS as _,
HoldToConfirm = ffi::haptic_effect_t_HAPTIC_HOLD_TO_CONFIRM 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) { pub fn play(effect: HapticEffect) {

View File

@ -51,3 +51,7 @@ pub fn is_usb_connected() -> bool {
pub fn suspend() { pub fn suspend() {
unsafe { ffi::pm_suspend(null_mut()) }; unsafe { ffi::pm_suspend(null_mut()) };
} }
pub fn hibernate() {
unsafe { ffi::pm_hibernate() };
}

View File

@ -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::<<ModelUI as BootloaderUI>::CLayoutType>::new(layout);
let layout = layout.get_mut();
layout.render()
}
}
#[no_mangle] #[no_mangle]
extern "C" fn screen_welcome(layout: *mut c_layout_t) { extern "C" fn screen_welcome(layout: *mut c_layout_t) {
let mut screen = <ModelUI as BootloaderUI>::CLayoutType::init_welcome(); let mut screen = <ModelUI as BootloaderUI>::CLayoutType::init_welcome();

View File

@ -16,10 +16,14 @@ use crate::trezorhal::sysevent::{sysevents_poll, Syshandle};
#[cfg(feature = "power_manager")] #[cfg(feature = "power_manager")]
use crate::{ use crate::{
time::Instant, time::Instant,
trezorhal::power_manager::{is_usb_connected, suspend}, trezorhal::power_manager::{hibernate, is_usb_connected, suspend},
ui::display::fade_backlight_duration, 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 heapless::Vec;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
@ -98,6 +102,10 @@ pub fn run(frame: &mut impl Component<Msg = impl ReturnToC>) -> u32 {
render(frame); render(frame);
ModelUI::fadein(); 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")] #[cfg(feature = "power_manager")]
let mut start = Instant::now(); let mut start = Instant::now();
let mut faded = false; let mut faded = false;
@ -132,6 +140,39 @@ pub fn run(frame: &mut impl Component<Msg = impl ReturnToC>) -> u32 {
#[cfg(feature = "power_manager")] #[cfg(feature = "power_manager")]
{ {
start = Instant::now(); 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(); let mut ctx = EventCtx::new();
@ -145,6 +186,18 @@ pub fn run(frame: &mut impl Component<Msg = impl ReturnToC>) -> u32 {
} else { } else {
#[cfg(feature = "power_manager")] #[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() { if is_usb_connected() {
continue; continue;
} }
@ -164,6 +217,7 @@ pub fn run(frame: &mut impl Component<Msg = impl ReturnToC>) -> u32 {
faded = false; faded = false;
} }
start = Instant::now(); start = Instant::now();
button_pressed_time = None;
} }
} }
} }

View File

@ -2,7 +2,7 @@ use crate::ui::{
component::{Event, Label}, component::{Event, Label},
display::{self, toif::Toif, Color}, display::{self, toif::Toif, Color},
geometry::{Alignment, Alignment2D, Offset, Point, Rect}, 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}, shape::{self, render_on_display},
ui_bootloader::{BootloaderLayoutType, BootloaderUI}, ui_bootloader::{BootloaderLayoutType, BootloaderUI},
CommonUI, 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) { fn show(&mut self) {
match self { match self {
BootloaderLayout::Welcome(f) => show(f, true), BootloaderLayout::Welcome(f) => show(f, true),

View File

@ -2,6 +2,11 @@ use crate::ui::component::Event;
pub trait BootloaderLayoutType { pub trait BootloaderLayoutType {
fn event(&mut self, event: Option<Event>) -> u32; fn event(&mut self, event: Option<Event>) -> u32;
fn render(&mut self) {
unimplemented!();
}
fn show(&mut self); fn show(&mut self);
fn init_welcome() -> Self; fn init_welcome() -> Self;
fn init_menu(initial_setup: bool) -> Self; fn init_menu(initial_setup: bool) -> Self;