feat(core/sys): expose time functions to Rust
What changed, and why it matters
This commit is a large internal refactoring that moves time-related code (Duration, Instant, sleep, tick counters) from one Rust module to another shared system module so it can be reused more cleanly. It does not change what the code does; it only reorganizes where the definitions live. There is no obvious security bug introduced, but any big refactor carries a small risk of accidental behavior changes.
Treat as a routine refactor. Review the `sysevent.rs` deadline conversion for equivalence under wraparound, verify that the new `sys::time` module is compiled with the same overflow/debug-assert behavior as the old one, and run existing timing/timeout tests. No immediate security response is warranted based on this diff alone.
Security signals we found
Large refactor touching 70 files and timing primitives used by security-sensitive flows (UI timeouts, bootloader delays, SMP/THP retransmission, sysevent polling)
One functional-looking change in `sysevent.rs`: poll deadline converted from `ticks_ms().wrapping_add(100)` to `Instant::now().checked_add(Duration::from_millis(100)).unwrap().to_millis()`; semantics appear equivalent
Removal of local `trezorhal::time` wrapper and centralization of unsafe FFI calls in `sys::time`
No input validation, parsing, or cryptographic code is modified
Evidence from the diff
The change relocates time primitives from core/embed/rust/src/time.rs and core/embed/rust/src/trezorhal/time.rs into a new core/embed/sys/src/time/ module, re-exporting Duration, ShortDuration, Instant, ticks_ms, ticks_us, sleep, and measure_us. Call sites across the Rust UI, bootloader, SMP, THP, and shape rendering code are updated to import from sys::time instead of crate::time or crate::trezorhal::time. The implementation logic is preserved verbatim, including the wrapping-Instant comparison semantics and the 100 ms poll deadline calculation in sysevent.rs. A new bindgen step exposes systick_ms, systick_us, systick_delay_ms, and systick_delay_us to Rust. No new attack surface or vulnerability is visible in the diff.
Changed components
core/embed/sys/src/time/duration.rscore/embed/sys/src/time/instant.rscore/embed/sys/src/time/mod.rscore/embed/rust/src/time.rscore/embed/rust/src/trezorhal/time.rscore/embed/rust/src/trezorhal/sysevent.rscore/embed/rust/src/smp/*core/embed/rust/src/thp/*core/embed/rust/src/ui/*core/embed/sys/time/build.rsInspect captured patch +565 / −463
### core/embed/rust/src/bootloader/mod.rs
@@ -1,7 +1,7 @@
use heapless::Vec;
-
#[cfg(feature = "power_manager")]
-use crate::time::Duration;
+use sys::time::{Duration, Instant};
+
#[cfg(feature = "ble")]
use crate::trezorhal::bootloader::bootloader_process_ble;
use crate::trezorhal::bootloader::{bootloader_process_usb, BootloaderWFResult};
@@ -22,7 +22,6 @@ use crate::ui::layout::simplified::{render, ReturnToC};
use crate::ui::{CommonUI, ModelUI};
#[cfg(feature = "power_manager")]
use crate::{
- time::Instant,
trezorhal::power_manager::{hibernate, is_usb_connected, suspend},
ui::display::fade_backlight_duration,
ui::event::PhysicalButton,
### core/embed/rust/src/smp/echo.rs
@@ -1,11 +1,11 @@
use minicbor::data::Type;
use minicbor::{decode, Decoder, Encoder};
+use sys::time::Duration;
use super::{
receiver_acquire, receiver_release, send_request, wait_for_response, MsgType, SmpBuffer,
SmpHeader, SMP_CMD_ID_ECHO, SMP_GROUP_OS, SMP_HEADER_SIZE, SMP_OP_READ,
};
-use crate::time::Duration;
pub fn send(text: &str) -> bool {
let mut cbor_data = [0u8; 64];
### core/embed/rust/src/smp/image_info.rs
@@ -1,11 +1,11 @@
use minicbor::data::Type;
use minicbor::{decode, Decoder, Encoder};
+use sys::time::Duration;
use super::{
receiver_acquire, receiver_release, send_request, wait_for_response, MsgType, SmpBuffer,
SmpHeader, SMP_CMD_ID_IMAGE_STATE, SMP_GROUP_IMAGE, SMP_HEADER_SIZE, SMP_OP_READ,
};
-use crate::time::Duration;
/// MCUboot-compatible version structure matching image header format
#[derive(Clone, Copy, Debug)]
### core/embed/rust/src/smp/mod.rs
@@ -12,8 +12,8 @@ use core::convert::Infallible;
use base64::{base64_decode, base64_encode};
use crc16::crc16_itu_t;
use minicbor::encode::write::Write;
+use sys::time::{Duration, Instant};
-use crate::time::{Duration, Instant};
use crate::trezorhal::irq::{irq_lock, irq_unlock};
use crate::trezorhal::nrf::send_data;
### core/embed/rust/src/smp/upload.rs
@@ -1,10 +1,10 @@
use minicbor::Encoder;
+use sys::time::Duration;
use super::{
receiver_acquire, receiver_release, send_request, wait_for_response, MsgType, SmpBuffer,
SmpHeader, SMP_CMD_ID_IMAGE_UPLOAD, SMP_GROUP_IMAGE, SMP_HEADER_SIZE, SMP_OP_WRITE,
};
-use crate::time::Duration;
const CHUNK_SIZE: usize = 256;
const MAX_PACKET_SIZE: usize = 512;
### core/embed/rust/src/thp/mod.rs
@@ -12,6 +12,7 @@ use heapless::deque::{Deque, DequeView};
use heapless::linear_map::{Entry, LinearMap, LinearMapView};
use heapless::Vec;
use spin::{Lazy, Mutex};
+use sys::time::Instant;
use time::{least_recently_used, ChannelTiming};
use trezor_thp::channel::device::{Channel, ChannelIdAllocator, ChannelOpen, Mux};
use trezor_thp::channel::{
@@ -24,7 +25,6 @@ use trezor_thp::{ChannelIO, Error as ThpError};
use crate::error::Error;
use crate::micropython::obj::Obj;
-use crate::time::Instant;
type TrezorMux = Mux<TrezorCrypto>;
type TrezorChannelOpen = ChannelOpen<TrezorCredentialVerifier, TrezorCrypto>;
### core/embed/rust/src/thp/time.rs
@@ -1,6 +1,6 @@
use trezor_thp::channel::retransmit_after_ms;
-use crate::time::{Duration, Instant};
+use sys::time::{Duration, Instant};
const MAX_LATENCY_MS: Duration = Duration::from_millis(800);
### core/embed/rust/src/time.rs
@@ -1,274 +1,4 @@
-use core::cmp::Ordering;
-use core::ops::{Div, Mul};
-
-use crate::trezorhal::time;
-
-const MILLIS_PER_SEC: u32 = 1000;
-const MILLIS_PER_MINUTE: u32 = MILLIS_PER_SEC * 60;
-const MILLIS_PER_HOUR: u32 = MILLIS_PER_MINUTE * 60;
-const MILLIS_PER_DAY: u32 = MILLIS_PER_HOUR * 24;
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct ShortDuration {
- millis: u16,
-}
-
-impl ShortDuration {
- pub const ZERO: Self = Self::from_millis(0);
-
- pub const fn from_millis(millis: u16) -> Self {
- Self { millis }
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
-pub struct Duration {
- millis: u32,
-}
-
-impl Duration {
- pub const ZERO: Self = Self::from_millis(0);
-
- pub const fn from_millis(millis: u32) -> Self {
- Self { millis }
- }
-
- pub const fn from_secs(secs: u32) -> Self {
- // Check for potential overflow
- debug_assert!(secs < u32::MAX / MILLIS_PER_SEC);
- Self::from_millis(secs * MILLIS_PER_SEC)
- }
-
- pub const fn from_mins(mins: u32) -> Self {
- // Check for potential overflow
- debug_assert!(mins < u32::MAX / MILLIS_PER_MINUTE);
- Self::from_millis(mins * MILLIS_PER_MINUTE)
- }
-
- pub const fn from_hours(hours: u32) -> Self {
- // Check for potential overflow
- debug_assert!(hours < u32::MAX / MILLIS_PER_HOUR);
- Self::from_millis(hours * MILLIS_PER_HOUR)
- }
- pub const fn from_days(days: u32) -> Self {
- // Check for potential overflow
- debug_assert!(days < u32::MAX / MILLIS_PER_DAY);
- Self::from_millis(days * MILLIS_PER_DAY)
- }
-
- pub fn to_millis(self) -> u32 {
- self.millis
- }
-
- pub fn to_secs(self) -> u32 {
- self.millis / MILLIS_PER_SEC
- }
- pub fn to_mins(self) -> u32 {
- self.millis / MILLIS_PER_MINUTE
- }
- pub fn to_hours(self) -> u32 {
- self.millis / MILLIS_PER_HOUR
- }
- pub fn to_days(self) -> u32 {
- self.millis / MILLIS_PER_DAY
- }
-
- pub fn checked_add(self, rhs: Self) -> Option<Self> {
- self.millis.checked_add(rhs.millis).map(Self::from_millis)
- }
-
- pub fn checked_sub(self, rhs: Self) -> Option<Self> {
- self.millis.checked_sub(rhs.millis).map(Self::from_millis)
- }
-
- pub fn saturating_add(self, rhs: Self) -> Self {
- Self::from_millis(self.millis.saturating_add(rhs.millis))
- }
-
- /// Returns a new Duration containing only the largest complete time unit
- /// (days, hours, minutes, or seconds)
- ///
- /// Examples:
- /// - 1 day, 3 hours → 1 day
- /// - 3 hours, 45 minutes → 3 hours
- /// - 59 seconds → 59 seconds
- pub fn crop_to_largest_unit(self) -> Self {
- if self.millis >= MILLIS_PER_DAY {
- Duration::from_days(self.to_days())
- } else if self.millis >= MILLIS_PER_HOUR {
- Duration::from_hours(self.to_hours())
- } else if self.millis >= MILLIS_PER_MINUTE {
- Duration::from_mins(self.to_mins())
- } else {
- Duration::from_secs(self.to_secs())
- }
- }
-
- /// Increment by one unit based on the current magnitude
- ///
- /// Examples:
- /// - 59s → 1m (moves to the next unit when crossing a boundary)
- /// - 1m → 2m
- /// - 23h → 1d
- ///
- /// Returns None if addition would overflow
- pub fn increment_unit(self) -> Option<Self> {
- let base = self.crop_to_largest_unit();
-
- let step = if base.millis < MILLIS_PER_MINUTE {
- Duration::from_secs(1)
- } else if base.millis < MILLIS_PER_HOUR {
- Duration::from_mins(1)
- } else if base.millis < MILLIS_PER_DAY {
- Duration::from_hours(1)
- } else {
- Duration::from_days(1)
- };
-
- base.checked_add(step)
- }
-
- /// Decrement by one unit based on the current magnitude
- ///
- /// Examples:
- /// - 1m → 59s (moves to the previous unit at boundaries)
- /// - 2m → 1m
- /// - 1h → 59m
- /// - 1d → 23h
- ///
- /// Returns None if subtraction would result in negative duration
- pub fn decrement_unit(self) -> Option<Self> {
- let base = self.crop_to_largest_unit();
-
- let step = if base.millis <= MILLIS_PER_MINUTE {
- Duration::from_secs(1)
- } else if base.millis <= MILLIS_PER_HOUR {
- Duration::from_mins(1)
- } else if base.millis <= MILLIS_PER_DAY {
- Duration::from_hours(1)
- } else {
- Duration::from_days(1)
- };
-
- base.checked_sub(step)
- }
-}
-
-impl Mul<f32> for Duration {
- // Multiplication by float is saturating -- in particular, casting from a float
- // to an int is saturating, value larger than INT_MAX casts to INT_MAX. So
- // this operation does not need to be checked.
- type Output = Self;
-
- fn mul(self, rhs: f32) -> Self::Output {
- Self::from_millis((self.millis as f32 * rhs) as u32)
- }
-}
-
-impl Div<u32> for Duration {
- // Division by integer cannot overflow so it does not need to be checked.
- type Output = Self;
-
- fn div(self, rhs: u32) -> Self::Output {
- Self::from_millis(self.millis / rhs)
- }
-}
-
-impl Div<Duration> for Duration {
- // Division by float results in float so it does not need to be checked.
- type Output = f32;
-
- fn div(self, rhs: Self) -> Self::Output {
- self.to_millis() as f32 / rhs.to_millis() as f32
- }
-}
-
-impl From<ShortDuration> for Duration {
- fn from(value: ShortDuration) -> Self {
- Self::from_millis(value.millis.into())
- }
-}
-
-/* Instants can wrap around and we want them to be comparable even after
- * wrapping around. This works by setting a maximum allowable difference
- * between two Instants to half the range. In checked_add and checked_sub, we
- * make sure that the step from one Instant to another is at most
- * MAX_DIFFERENCE_IN_MILLIS. In the Ord implementation, if the difference is
- * more than MAX_DIFFERENCE_IN_MILLIS, we can assume that the smaller Instant
- * is actually wrapped around and so is in the future. */
-const MAX_DIFFERENCE_IN_MILLIS: u32 = u32::MAX / 2;
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub struct Instant {
- millis: u32,
-}
-
-impl Instant {
- pub fn now() -> Self {
- Self {
- millis: time::ticks_ms(),
- }
- }
-
- pub fn saturating_duration_since(self, earlier: Self) -> Duration {
- self.checked_duration_since(earlier)
- .unwrap_or(Duration::ZERO)
- }
-
- pub fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
- if self >= earlier {
- Some(Duration::from_millis(
- self.millis.wrapping_sub(earlier.millis),
- ))
- } else {
- None
- }
- }
-
- pub fn checked_add(self, duration: Duration) -> Option<Self> {
- let add_millis = duration.to_millis();
- if add_millis <= MAX_DIFFERENCE_IN_MILLIS {
- Some(Self {
- millis: self.millis.wrapping_add(add_millis),
- })
- } else {
- None
- }
- }
-
- pub fn checked_sub(self, duration: Duration) -> Option<Self> {
- let sub_millis = duration.to_millis();
- if sub_millis <= MAX_DIFFERENCE_IN_MILLIS {
- Some(Self {
- millis: self.millis.wrapping_sub(sub_millis),
- })
- } else {
- None
- }
- }
-}
-
-impl PartialOrd for Instant {
- fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
- Some(self.cmp(rhs))
- }
-}
-
-impl Ord for Instant {
- fn cmp(&self, rhs: &Self) -> Ordering {
- if self.millis == rhs.millis {
- Ordering::Equal
- } else {
- // If the difference is greater than MAX_DIFFERENCE_IN_MILLIS, we assume
- // that the larger Instant is in the past.
- // See explanation on MAX_DIFFERENCE_IN_MILLIS
- self.millis
- .wrapping_sub(rhs.millis)
- .cmp(&MAX_DIFFERENCE_IN_MILLIS)
- .reverse()
- }
- }
-}
+use sys::time::{Duration, Instant};
/// A stopwatch is a utility designed for measuring the amount of time
/// that elapses between its start and stop points. It can be used in various
@@ -342,16 +72,9 @@ impl Stopwatch {
#[cfg(test)]
mod tests {
- use super::*;
+ use sys::time::{Duration, Instant};
- #[test]
- fn instant_wraps_and_compares_correctly() {
- let milli = Duration { millis: 1 };
- let earlier = Instant { millis: u32::MAX };
- let later = earlier.checked_add(milli).unwrap();
- assert_eq!(later, Instant { millis: 0 });
- assert!(earlier < later);
- }
+ use super::*;
#[test]
fn stopwatch_builds_correctly() {
@@ -402,67 +125,4 @@ mod tests {
assert!(!sw.is_running_within(Duration::from_millis(5)));
assert!(!sw.is_running_within(Duration::from_millis(10000)));
}
-
- #[test]
- fn test_crop_to_largest_unit() {
- assert_eq!(
- Duration::from_secs(59).crop_to_largest_unit(),
- Duration::from_secs(59)
- );
- assert_eq!(
- Duration::from_secs(60).crop_to_largest_unit(),
- Duration::from_mins(1)
- );
- assert_eq!(
- Duration::from_secs(61).crop_to_largest_unit(),
- Duration::from_mins(1)
- );
- assert_eq!(
- Duration::from_secs(3600).crop_to_largest_unit(),
- Duration::from_hours(1)
- );
- assert_eq!(
- Duration::from_secs(86399).crop_to_largest_unit(),
- Duration::from_hours(23)
- );
- }
-
- #[test]
- fn test_increment_decrement_unit() {
- // Increment
- assert_eq!(
- unwrap!(Duration::from_secs(59).increment_unit()),
- Duration::from_mins(1)
- );
- assert_eq!(
- unwrap!(Duration::from_mins(1).increment_unit()),
- Duration::from_mins(2)
- );
- assert_eq!(
- unwrap!(Duration::from_secs(61).increment_unit()),
- Duration::from_mins(2)
- );
- assert_eq!(
- unwrap!(Duration::from_days(3).increment_unit()),
- Duration::from_days(4)
- );
-
- // Decrement
- assert_eq!(
- unwrap!(Duration::from_mins(1).decrement_unit()),
- Duration::from_secs(59)
- );
- assert_eq!(
- unwrap!(Duration::from_secs(61).decrement_unit()),
- Duration::from_secs(59)
- );
- assert_eq!(
- unwrap!(Duration::from_mins(3).decrement_unit()),
- Duration::from_mins(2)
- );
- assert_eq!(
- unwrap!(Duration::from_hours(1).decrement_unit()),
- Duration::from_mins(59)
- );
- }
}
### core/embed/rust/src/trezorhal/mod.rs
@@ -32,8 +32,6 @@ pub mod wordlist;
pub mod secbool;
-pub mod time;
-
#[cfg(feature = "ui")]
pub mod sysevent;
### core/embed/rust/src/trezorhal/sysevent.rs
@@ -2,9 +2,10 @@
use core::mem::{self, MaybeUninit};
-pub use ffi::{sysevents_t, syshandle_t};
+use sys::time::{Duration, Instant};
use super::ffi;
+pub use super::ffi::{sysevents_t, syshandle_t};
#[cfg(feature = "ble")]
use crate::trezorhal::ble::ble_parse_event;
#[cfg(feature = "button")]
@@ -13,7 +14,6 @@ use crate::trezorhal::button::button_parse_event;
use crate::trezorhal::ffi::button_get_event;
#[cfg(feature = "power_manager")]
use crate::trezorhal::power_manager::pm_parse_event;
-use crate::trezorhal::time::ticks_ms;
#[cfg(feature = "touch")]
use crate::trezorhal::touch::touch_get_event;
use crate::ui::component::Event;
@@ -163,12 +163,14 @@ pub fn sysevents_poll(ifaces: &[Syshandle]) -> Option<Event> {
let awaited = Sysevents::reading_from(ifaces);
let mut signalled = Sysevents::zeroed();
+ let deadline = Instant::now().checked_add(Duration::from_millis(100)).unwrap();
+
// SAFETY: safe.
unsafe {
ffi::sysevents_poll(
&awaited as _,
&mut signalled as _,
- ticks_ms().wrapping_add(100),
+ deadline.to_millis(),
)
};
### core/embed/rust/src/trezorhal/time.rs
@@ -1,28 +0,0 @@
-use super::ffi;
-use crate::time::Duration;
-
-/// Returns the current time in milliseconds since the device was reset.
-/// Time is represented as a 32-bit number that wraps around every 49.7 days.
-pub fn ticks_ms() -> u32 {
- unsafe { ffi::systick_ms() as _ }
-}
-
-/// Returns the current time in microseconds since the device was reset.
-/// Time is represented as a 64-bit number and never wraps around.
-pub fn ticks_us() -> u64 {
- unsafe { ffi::systick_us() as _ }
-}
-
-/// Sleeps for the specified duration.
-pub fn sleep(delay: Duration) {
- unsafe {
- ffi::systick_delay_ms(delay.to_millis() as _);
- }
-}
-
-/// Measures the time it takes to execute a closure in microseconds.
-pub fn measure_us(f: impl FnOnce()) -> u64 {
- let start = ticks_us();
- f();
- ticks_us() - start
-}
### core/embed/rust/src/ui/animation.rs
@@ -1,4 +1,5 @@
-use crate::time::{Duration, Instant};
+use sys::time::{Duration, Instant};
+
use crate::ui::lerp::{InvLerp, Lerp};
/// Running, time-based linear progression of a value.
### core/embed/rust/src/ui/component/base.rs
@@ -1,9 +1,9 @@
use heapless::Vec;
+use sys::time::Duration;
use super::Paginate;
use crate::error::Error;
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::ui::button_request::{ButtonRequest, ButtonRequestCode};
use crate::ui::component::{MsgMap, PageMap};
#[cfg(feature = "ble")]
### core/embed/rust/src/ui/component/marquee.rs
@@ -1,5 +1,6 @@
+use sys::time::{Duration, Instant};
+
use crate::strutil::TString;
-use crate::time::{Duration, Instant};
use crate::ui::animation::Animation;
use crate::ui::component::{Component, Event, EventCtx, Never, Timer};
use crate::ui::display::{Color, Font};
### core/embed/rust/src/ui/component/swipe_detect.rs
@@ -1,4 +1,5 @@
-use crate::time::{Duration, Instant};
+use sys::time::{Duration, Instant};
+
use crate::ui::animation::Animation;
use crate::ui::component::{Event, EventCtx};
use crate::ui::constant::screen;
### core/embed/rust/src/ui/component/timeout.rs
@@ -1,4 +1,5 @@
-use crate::time::Duration;
+use sys::time::Duration;
+
use crate::ui::component::{Component, Event, EventCtx, Timer};
use crate::ui::geometry::Rect;
use crate::ui::shape::Renderer;
### core/embed/rust/src/ui/display/mod.rs
@@ -5,6 +5,8 @@ pub mod toif;
pub use color::Color;
pub use font::{Font, Glyph, GlyphMetrics};
+#[cfg(feature = "backlight")]
+use sys::time::{sleep, Duration};
use super::geometry::{Offset, Point, Rect};
use crate::strutil::TString;
@@ -14,8 +16,6 @@ pub use crate::ui::display::toif::Icon;
#[cfg(feature = "backlight")]
use crate::ui::lerp::Lerp;
#[cfg(feature = "backlight")]
-use crate::{time::Duration, trezorhal::time};
-#[cfg(feature = "backlight")]
use crate::{time::Stopwatch, ui::util::animation_disabled};
pub const LOADER_MIN: u16 = 0;
@@ -56,7 +56,7 @@ pub fn fade_backlight_duration(target: u8, duration_ms: u32) {
}
let val = u8::lerp(current, target, elapsed / duration);
set_backlight(val);
- time::sleep(Duration::from_millis(1));
+ sleep(Duration::from_millis(1));
}
//account for imprecise rounding
set_backlight(target);
### core/embed/rust/src/ui/layout/obj.rs
@@ -6,6 +6,7 @@ use core::ops::{Deref, DerefMut};
use num_traits::FromPrimitive;
#[cfg(feature = "touch")]
use num_traits::ToPrimitive;
+use sys::time::Duration;
use super::base::{Layout, LayoutState};
use crate::error::Error;
@@ -21,7 +22,6 @@ use crate::micropython::qstr::Qstr;
use crate::micropython::simple_type::SimpleTypeObj;
use crate::micropython::typ::{FullType, Type};
use crate::micropython::util;
-use crate::time::Duration;
use crate::ui::button_request::ButtonRequest;
use crate::ui::component::base::{AttachType, TimerToken};
use crate::ui::component::{Component, Event, EventCtx, Never};
### core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs
@@ -7,10 +7,11 @@ pub mod welcome;
pub mod pairing_finalization;
use heapless::String;
-use intro::Intro;
-use menu::Menu;
+use sys::time;
use ufmt::uwrite;
+use self::intro::Intro;
+use self::menu::Menu;
use super::bootloader::connect::Connect;
use super::bootloader::welcome::Welcome;
use super::component::bl_confirm::{Confirm, ConfirmTitle};
@@ -31,8 +32,6 @@ use super::{
};
use super::{fonts, UIBolt};
use crate::bootloader::run;
-use crate::time::Duration;
-use crate::trezorhal::time;
use crate::ui::component::Label;
use crate::ui::display::toif::Toif;
use crate::ui::display::{self, Color, Icon, LOADER_MAX};
@@ -132,7 +131,7 @@ impl UIBolt {
impl BootloaderUI for UIBolt {
fn screen_welcome() -> (u32, u32) {
// let the previous screen on for some time
- time::sleep(Duration::from_millis(1000));
+ time::sleep(time::Duration::from_millis(1000));
let mut frame = Welcome::new();
run(&mut frame, true, true)
}
### core/embed/rust/src/ui/layout_bolt/component/button.rs
@@ -1,6 +1,7 @@
+use sys::time::ShortDuration;
+
use super::theme;
use crate::strutil::TString;
-use crate::time::ShortDuration;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{self, HapticEffect};
use crate::ui::component::{
### core/embed/rust/src/ui/layout_bolt/component/homescreen.rs
@@ -1,9 +1,10 @@
+use sys::time::{Duration, Instant};
+
use super::super::theme::IMAGE_HOMESCREEN;
use super::super::{constant, fonts};
use super::{theme, Loader, LoaderMsg};
use crate::io::BinaryData;
use crate::strutil::TString;
-use crate::time::{Duration, Instant};
use crate::translations::TR;
use crate::trezorhal::usb::usb_configured;
use crate::ui::component::text::TextStyle;
### core/embed/rust/src/ui/layout_bolt/component/keyboard/common.rs
@@ -1,4 +1,5 @@
-use crate::time::Duration;
+use sys::time::Duration;
+
use crate::ui::component::text::common::TextEdit;
use crate::ui::component::{Event, EventCtx, Timer};
use crate::ui::display::{Color, Font};
### core/embed/rust/src/ui/layout_bolt/component/keyboard/passphrase.rs
@@ -1,12 +1,13 @@
use core::cell::Cell;
+use sys::time::Duration;
+
use super::super::super::constant::SCREEN;
use super::super::button::{Button, ButtonContent, ButtonMsg};
use super::super::keyboard::common::{render_pending_marker, MultiTapKeyboard};
use super::super::swipe::{Swipe, SwipeDirection};
use super::super::{theme, ScrollBar};
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::ui::component::base::ComponentExt;
use crate::ui::component::text::common::TextBox;
use crate::ui::component::text::layout::{LayoutFit, LineBreaking};
### core/embed/rust/src/ui/layout_bolt/component/keyboard/pin.rs
@@ -1,11 +1,12 @@
use core::mem;
+use sys::time::Duration;
+
use super::super::super::fonts;
use super::super::button::ButtonMsg::{self, Clicked};
use super::super::button::{Button, ButtonContent};
use super::super::theme;
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::trezorhal::random;
use crate::ui::component::base::ComponentExt;
use crate::ui::component::text::TextStyle;
### core/embed/rust/src/ui/layout_bolt/component/loader.rs
@@ -1,7 +1,8 @@
+use sys::time::{Duration, Instant};
+
use super::super::constant;
use super::super::cshape::{render_loader, LoaderRange};
use super::theme;
-use crate::time::{Duration, Instant};
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{self, HapticEffect};
use crate::ui::animation::Animation;
### core/embed/rust/src/ui/layout_bolt/component/page.rs
@@ -1,12 +1,13 @@
use core::cell::Cell;
+use sys::time::Instant;
+
use super::{
theme, Button, ButtonContent, ButtonMsg, ButtonStyleSheet, Loader, LoaderMsg, ScrollBar, Swipe,
SwipeDirection,
};
use crate::error::Error;
use crate::strutil::TString;
-use crate::time::Instant;
use crate::translations::TR;
use crate::ui::component::paginated::PageMsg;
use crate::ui::component::{Component, ComponentExt, Event, EventCtx, Pad, Paginate};
### core/embed/rust/src/ui/layout_bolt/theme/mod.rs
@@ -1,9 +1,10 @@
pub mod backlight;
pub mod bootloader;
+use sys::time::ShortDuration;
+
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet, ResultStyle};
use super::fonts;
-use crate::time::ShortDuration;
use crate::ui::component::text::layout::Chunks;
use crate::ui::component::text::paragraphs::PARAGRAPH_BOTTOM_SPACE;
use crate::ui::component::text::{LineBreaking, PageBreaking, TextStyle};
### core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs
@@ -1,6 +1,18 @@
use heapless::String;
use ufmt::uwrite;
+mod intro;
+mod menu;
+mod welcome;
+
+mod connect;
+
+use sys::time;
+
+use self::connect::Connect;
+use self::intro::Intro;
+use self::menu::Menu;
+use self::welcome::Welcome;
use super::component::bl_confirm::{Confirm, ConfirmMsg};
use super::component::{ResultScreen, WelcomeScreen};
use super::theme::bootloader::{BLD_BG, BLD_FG, ICON_ALERT, ICON_SPINNER, ICON_SUCCESS};
@@ -16,20 +28,6 @@ use crate::ui::geometry::{Alignment, Alignment2D, Offset, Point};
use crate::ui::layout::simplified::{show, ReturnToC};
use crate::ui::shape;
use crate::ui::shape::render_on_display;
-
-mod intro;
-mod menu;
-mod welcome;
-
-mod connect;
-
-use connect::Connect;
-use intro::Intro;
-use menu::Menu;
-use welcome::Welcome;
-
-use crate::time::Duration;
-use crate::trezorhal::time;
use crate::ui::ui_bootloader::BootloaderUI;
use crate::ui::util::animation_disabled;
@@ -88,7 +86,7 @@ impl UICaesar {
impl BootloaderUI for UICaesar {
fn screen_welcome() -> (u32, u32) {
// let the previous screen on for some time
- time::sleep(Duration::from_millis(1500));
+ time::sleep(time::Duration::from_millis(1500));
let mut frame = Welcome::new();
run(&mut frame, true, true)
}
### core/embed/rust/src/ui/layout_caesar/component/button.rs
@@ -1,8 +1,9 @@
+use sys::time::Duration;
+
use super::super::fonts;
use super::loader::DEFAULT_DURATION_MS;
use super::theme;
use crate::strutil::TString;
-use crate::time::Duration;
use crate::ui::component::{Component, Event, EventCtx, Never};
use crate::ui::display::{Font, Icon};
use crate::ui::event::PhysicalButton;
### core/embed/rust/src/ui/layout_caesar/component/button_controller.rs
@@ -1,7 +1,8 @@
+use sys::time::{Duration, Instant};
+
use super::{
theme, Button, ButtonDetails, ButtonLayout, ButtonPos, HoldToConfirm, HoldToConfirmMsg,
};
-use crate::time::{Duration, Instant};
use crate::ui::component::base::Event;
use crate::ui::component::{Component, EventCtx, Pad, Timer};
use crate::ui::event::{ButtonEvent, PhysicalButton};
### core/embed/rust/src/ui/layout_caesar/component/hold_to_confirm.rs
@@ -1,6 +1,7 @@
+use sys::time::{Duration, Instant};
+
use super::loader::{Loader, DEFAULT_DURATION_MS};
use super::{theme, ButtonContent, ButtonDetails, LoaderMsg, LoaderStyleSheet};
-use crate::time::{Duration, Instant};
use crate::ui::component::{Component, Event, EventCtx};
use crate::ui::event::ButtonEvent;
use crate::ui::geometry::Rect;
### core/embed/rust/src/ui/layout_caesar/component/input_methods/pin.rs
@@ -1,11 +1,12 @@
+use sys::time::Duration;
+
use super::super::super::fonts;
use super::super::title::Title;
use super::super::{
theme, ButtonDetails, ButtonLayout, CancelConfirmMsg, ChangingTextLine, ChoiceControls,
ChoiceFactory, ChoiceItem, ChoiceMsg, ChoicePage,
};
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::translations::TR;
use crate::trezorhal::random;
use crate::ui::component::text::common::TextBox;
### core/embed/rust/src/ui/layout_caesar/component/loader.rs
@@ -1,6 +1,7 @@
+use sys::time::{Duration, Instant};
+
use super::{theme, Progress};
use crate::strutil::TString;
-use crate::time::{Duration, Instant};
use crate::ui::animation::Animation;
use crate::ui::component::{Child, Component, Event, EventCtx};
use crate::ui::display::{self, Color, Font, LOADER_MAX};
### core/embed/rust/src/ui/layout_caesar/component/title.rs
@@ -1,6 +1,7 @@
+use sys::time::Instant;
+
use super::super::theme;
use crate::strutil::TString;
-use crate::time::Instant;
use crate::ui::component::{Component, Event, EventCtx, Marquee, Never};
use crate::ui::geometry::{Alignment, Offset, Rect};
use crate::ui::shape;
### core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs
@@ -2,6 +2,7 @@ use connect::Connect;
use heapless::String;
use intro::Intro;
use menu::Menu;
+use sys::time;
use ufmt::uwrite;
use super::bootloader::welcome::Welcome;
@@ -17,8 +18,6 @@ use super::theme::bootloader::{
use super::theme::{backlight, GREEN_LIGHT, GREY};
use super::{fonts, UIDelizia};
use crate::bootloader::run;
-use crate::time::Duration;
-use crate::trezorhal::time;
use crate::ui::component::Label;
use crate::ui::display::toif::Toif;
use crate::ui::display::{self, Color, Icon, LOADER_MAX};
@@ -112,7 +111,7 @@ impl UIDelizia {
impl BootloaderUI for UIDelizia {
fn screen_welcome() -> (u32, u32) {
// let the previous screen on for some time
- time::sleep(Duration::from_millis(1500));
+ time::sleep(time::Duration::from_millis(1500));
let mut frame = Welcome::new();
run(&mut frame, true, true)
}
### core/embed/rust/src/ui/layout_delizia/component/button.rs
@@ -1,6 +1,7 @@
+use sys::time::ShortDuration;
+
use super::theme;
use crate::strutil::TString;
-use crate::time::ShortDuration;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
use crate::ui::component::{Component, Event, EventCtx, Timer};
### core/embed/rust/src/ui/layout_delizia/component/header.rs
@@ -1,7 +1,9 @@
+use sys::time::Duration;
+
use super::super::component::{Button, ButtonMsg, ButtonStyleSheet};
use super::super::theme::{self, TITLE_HEIGHT};
use crate::strutil::TString;
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::ui::component::text::TextStyle;
use crate::ui::component::{Component, Event, EventCtx, FlowMsg, Label};
use crate::ui::display::{Color, Icon};
### core/embed/rust/src/ui/layout_delizia/component/hold_to_confirm.rs
@@ -1,8 +1,9 @@
use pareen;
+use sys::time::{Duration, ShortDuration};
use super::theme::{self, TITLE_HEIGHT};
use super::{Button, ButtonContent, ButtonMsg};
-use crate::time::{Duration, ShortDuration, Stopwatch};
+use crate::time::Stopwatch;
use crate::translations::TR;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{self, HapticEffect};
### core/embed/rust/src/ui/layout_delizia/component/homescreen.rs
@@ -1,11 +1,13 @@
+use sys::time::{Duration, Instant};
+
use super::super::cshape::{self, UnlockOverlay};
use super::super::fonts;
use super::theme::{self, GREY_LIGHT, HOMESCREEN_ICON, ICON_KEY};
use super::{constant, Loader, LoaderMsg};
use crate::error::Error;
use crate::io::BinaryData;
use crate::strutil::TString;
-use crate::time::{Duration, Instant, Stopwatch};
+use crate::time::Stopwatch;
use crate::translations::TR;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
### core/embed/rust/src/ui/layout_delizia/component/keyboard/common.rs
@@ -1,5 +1,6 @@
+use sys::time::Duration;
+
use super::super::ButtonStyle;
-use crate::time::Duration;
use crate::ui::component::text::common::TextEdit;
use crate::ui::component::{Event, EventCtx, Timer};
use crate::ui::display::{Color, Font};
### core/embed/rust/src/ui/layout_delizia/component/keyboard/passphrase.rs
@@ -1,14 +1,14 @@
use core::cell::Cell;
use num_traits::ToPrimitive;
+use sys::time::Duration;
use super::super::super::component::button::{Button, ButtonContent, ButtonMsg};
use super::super::super::component::keyboard::common::{render_pending_marker, MultiTapKeyboard};
use super::super::super::component::theme;
use super::super::super::constant::SCREEN;
use super::super::super::cshape;
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::ui::component::base::ComponentExt;
use crate::ui::component::swipe_detect::SwipeConfig;
use crate::ui::component::text::common::TextBox;
### core/embed/rust/src/ui/layout_delizia/component/keyboard/pin.rs
@@ -1,12 +1,14 @@
use core::mem;
+use sys::time::Duration;
+
use super::super::super::component::button::ButtonMsg::{self, Clicked};
use super::super::super::component::button::{Button, ButtonContent};
use super::super::super::component::theme;
use super::super::super::cshape;
use super::super::super::fonts::FONT_MONO;
use crate::strutil::{ShortString, TString};
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::trezorhal::random;
use crate::ui::component::base::{AttachType, ComponentExt};
use crate::ui::component::text::TextStyle;
### core/embed/rust/src/ui/layout_delizia/component/loader.rs
@@ -1,6 +1,7 @@
+use sys::time::{Duration, Instant};
+
use super::super::cshape::{render_loader, LoaderRange};
use super::{constant, theme};
-use crate::time::{Duration, Instant};
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
use crate::ui::animation::Animation;
### core/embed/rust/src/ui/layout_delizia/component/status_screen.rs
@@ -1,6 +1,8 @@
+use sys::time::Duration;
+
use super::theme;
use crate::strutil::TString;
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::ui::component::paginated::SinglePage;
use crate::ui::component::{Component, Event, EventCtx, Label, Timeout};
use crate::ui::constant::screen;
### core/embed/rust/src/ui/layout_delizia/component/swipe_content.rs
@@ -1,4 +1,6 @@
-use crate::time::{Duration, Stopwatch};
+use sys::time::Duration;
+
+use crate::time::Stopwatch;
use crate::ui::component::base::{AttachType, EventPropagation};
use crate::ui::component::{Component, Event, EventCtx, Paginate};
use crate::ui::constant::screen;
### core/embed/rust/src/ui/layout_delizia/component/tap_to_confirm.rs
@@ -1,7 +1,8 @@
use pareen;
+use sys::time::Duration;
use super::{theme, Button, ButtonContent, ButtonMsg};
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::ui::component::{Component, Event, EventCtx};
use crate::ui::constant::screen;
use crate::ui::display::toif::Icon;
### core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs
@@ -1,9 +1,10 @@
use heapless::Vec;
+use sys::time::Duration;
use super::super::component::button::{Button, ButtonContent, ButtonMsg, IconText};
use super::theme;
use crate::strutil::TString;
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::ui::component::base::{AttachType, Component};
use crate::ui::component::paginated::SinglePage;
use crate::ui::component::{Event, EventCtx, Paginate};
### core/embed/rust/src/ui/layout_delizia/theme/mod.rs
@@ -2,9 +2,10 @@ pub mod bootloader;
pub mod backlight;
+use sys::time::ShortDuration;
+
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet, ResultStyle};
use super::fonts;
-use crate::time::ShortDuration;
use crate::ui::component::text::layout::Chunks;
use crate::ui::component::text::paragraphs::PARAGRAPH_BOTTOM_SPACE;
use crate::ui::component::text::{LineBreaking, PageBreaking, TextStyle};
### core/embed/rust/src/ui/layout_eckhart/component/button.rs
@@ -1,7 +1,8 @@
+use sys::time::{Duration, Instant, ShortDuration};
+
use super::super::component::ConnectionIndicator;
use super::super::theme::{self, Gradient};
use crate::strutil::TString;
-use crate::time::{Duration, Instant, ShortDuration};
#[cfg(feature = "translations")]
use crate::translations::TR;
#[cfg(feature = "haptic")]
### core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs
@@ -1,7 +1,8 @@
+use sys::time::Duration;
+
use super::super::component::{Button, ButtonMsg};
use super::{theme, HoldToConfirmAnim};
use crate::strutil::TString;
-use crate::time::Duration;
use crate::translations::TR;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{self, HapticEffect};
### core/embed/rust/src/ui/layout_eckhart/firmware/hold_to_confirm.rs
@@ -1,12 +1,13 @@
#[cfg(feature = "haptic")]
use pareen;
+use sys::time::Duration;
use super::super::cshape::ScreenBorder;
use super::super::firmware::Header;
use super::super::theme;
use super::constant::SCREEN;
use crate::strutil::TString;
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic;
use crate::ui::component::{Component, Event, EventCtx};
### core/embed/rust/src/ui/layout_eckhart/firmware/homescreen/header.rs
@@ -1,11 +1,13 @@
use core::sync::atomic::{AtomicBool, Ordering};
+use sys::time::{Duration, Instant};
+
use super::super::super::component::{ConnectionIndicator, FuelGauge};
use super::super::constant::SCREEN;
use super::super::theme;
use super::helpers::{render_pill_shaped_background, SHADOW_HEIGHT};
use crate::strutil::TString;
-use crate::time::{Duration, Instant, Stopwatch};
+use crate::time::Stopwatch;
use crate::ui::component::{Component, Event, EventCtx, Label, Never, Timer};
use crate::ui::event::TouchEvent;
use crate::ui::geometry::{Alignment2D, Offset, Point, Rect};
### core/embed/rust/src/ui/layout_eckhart/firmware/homescreen/notification_center.rs
@@ -1,10 +1,11 @@
+use sys::time::Duration;
+
use super::super::super::component::{Button, ButtonContent};
use super::super::theme::firmware::button_homebar_style;
use super::super::theme::{self, ScreenBackground};
use super::super::Hint;
use super::helpers::{render_pill_shaped_background, SHADOW_HEIGHT};
use crate::strutil::TString;
-use crate::time::Duration;
use crate::translations::TR;
use crate::ui::component::{Component, Event, EventCtx, Never, Timer};
use crate::ui::display::Color;
### core/embed/rust/src/ui/layout_eckhart/firmware/keyboard/common.rs
@@ -1,6 +1,7 @@
+use sys::time::Duration;
+
use super::super::super::component::ButtonContent;
use super::super::theme;
-use crate::time::Duration;
use crate::ui::component::text::common::TextEdit;
use crate::ui::component::{Event, EventCtx, Timer};
use crate::ui::display::{Color, Font};
### core/embed/rust/src/ui/layout_eckhart/firmware/keyboard/passphrase.rs
@@ -1,3 +1,5 @@
+use sys::time::Duration;
+
use super::super::constant::SCREEN;
use super::super::keyboard::common::{
render_pending_marker, MultiTapKeyboard, FADING_ICON_COLORS, FADING_ICON_COUNT,
@@ -6,7 +8,6 @@ use super::super::keyboard::common::{
use super::super::keyboard::keypad::{ButtonState, KeypadState};
use super::super::{theme, StringInput, StringInputMsg};
use crate::strutil::TString;
-use crate::time::Duration;
use crate::ui::component::text::common::TextBox;
use crate::ui::component::text::layout::{LayoutFit, LineBreaking};
use crate::ui::component::text::TextStyle;
### core/embed/rust/src/ui/layout_eckhart/firmware/keyboard/pin.rs
@@ -1,4 +1,5 @@
use heapless::String;
+use sys::time::Duration;
use super::super::super::component::ButtonContent;
use super::super::super::constant::SCREEN;
@@ -9,7 +10,6 @@ use super::common::{
};
use super::keypad::{ButtonState, Keypad, KeypadMsg, KeypadState};
use crate::strutil::{ShortString, TString};
-use crate::time::Duration;
use crate::ui::component::text::layout::LayoutFit;
use crate::ui::component::text::{LineBreaking, TextStyle};
use crate::ui::component::{Component, Event, EventCtx, Label, TextLayout, Timer};
### core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rs
@@ -1,9 +1,11 @@
+use sys::time::Duration;
+
use super::super::component::Button;
use super::super::constant::SCREEN;
use super::super::cshape::{render_loader_indeterminate, ScreenBorder};
use super::super::theme::{self, ScreenBackground};
use super::{ActionBar, ActionBarMsg};
-use crate::time::{Duration, Stopwatch};
+use crate::time::Stopwatch;
use crate::translations::TR;
use crate::ui::component::swipe_detect::SwipeConfig;
use crate::ui::component::{Component, Event, EventCtx, Label};
### core/embed/rust/src/ui/layout_eckhart/firmware/value_input_screen.rs
@@ -1,9 +1,10 @@
+use sys::time::Duration;
+
use super::super::super::constant::SCREEN;
use super::super::component::{Button, ButtonMsg};
use super::super::{fonts, theme};
use super::{ActionBar, ActionBarMsg, Header, HeaderMsg};
use crate::strutil::{self, plural_form, ShortString, TString};
-use crate::time::Duration;
use crate::translations::TR;
use crate::ui::component::swipe_detect::SwipeConfig;
use crate::ui::component::{Component, Event, EventCtx, Label, Maybe, Timer};
### core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu_screen.rs
@@ -1,8 +1,9 @@
+use sys::time::Instant;
+
use super::super::component::HapticMode;
use super::constant::SCREEN;
use super::{theme, Header, HeaderMsg, MenuItems, ShortMenuVec, VerticalMenu, VerticalMenuMsg};
use crate::strutil::TString;
-use crate::time::Instant;
use crate::ui::component::swipe_detect::{SwipeConfig, SwipeSettings};
use crate::ui::component::text::layout::LayoutFit;
use crate::ui::component::text::TextStyle;
### core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
@@ -1,4 +1,5 @@
use heapless::Vec;
+use sys::time::Duration;
use super::super::component::Button;
use super::super::firmware::{
@@ -11,7 +12,6 @@ use super::super::theme::gradient::Gradient;
use super::super::theme::{self};
use crate::error::{self};
use crate::strutil::TString;
-use crate::time::Duration;
use crate::translations::TR;
use crate::ui::component::text::paragraphs::{
Paragraph, ParagraphSource, ParagraphVecShort, VecExt,
### core/embed/rust/src/ui/layout_eckhart/flow/show_danger.rs
@@ -1,3 +1,5 @@
+use sys::time::Duration;
+
use super::super::component::Button;
use super::super::firmware::{
ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
@@ -6,7 +8,6 @@ use super::super::firmware::{
use super::super::theme;
use crate::error;
use crate::strutil::TString;
-use crate::time::Duration;
use crate::translations::TR;
use crate::ui::component::text::paragraphs::{Paragraph, ParagraphSource};
use crate::ui::component::ComponentExt;
### core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs
@@ -1,7 +1,8 @@
+use sys::time::ShortDuration;
+
use super::super::component::{ButtonStyle, ButtonStyleSheet};
use super::super::fonts;
use super::*;
-use crate::time::ShortDuration;
use crate::ui::component::text::layout::{Chunks, LineBreaking, PageBreaking};
use crate::ui::component::text::TextStyle;
use crate::ui::notification::NotificationLevel;
### core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
@@ -1,5 +1,7 @@
use core::cmp::Ordering;
+use sys::time::Duration;
+
use super::component::Button;
use super::firmware::{
ActionBar, Bip39Input, ConfirmHomescreen, DeviceMenuScreen, DurationInput, Header, HeaderMsg,
@@ -22,7 +24,6 @@ use crate::micropython::obj::Obj;
use crate::micropython::util;
use crate::storage;
use crate::strutil::TString;
-use crate::time::Duration;
use crate::translations::TR;
use crate::ui::component::text::op::OpTextLayout;
use crate::ui::component::text::paragraphs::{
### core/embed/rust/src/ui/shape/display/fb_rgb565.rs
@@ -1,13 +1,16 @@
+#[cfg(feature = "ui_performance_overlay")]
+use sys::time;
+
use super::bumps;
use crate::trezorhal::display;
use crate::ui::display::Color;
use crate::ui::geometry::Offset;
use crate::ui::shape::render::ScopedRenderer;
use crate::ui::shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport};
+#[cfg(feature = "ui_performance_overlay")]
+use crate::ui::PerformanceOverlay;
#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
use crate::ui::{CommonUI, ModelUI};
-#[cfg(feature = "ui_performance_overlay")]
-use crate::{trezorhal::time, ui::PerformanceOverlay};
pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgb565Canvas<'alloc>>;
### core/embed/rust/src/ui/shape/display/fb_rgba8888.rs
@@ -1,13 +1,16 @@
+#[cfg(feature = "ui_performance_overlay")]
+use sys::time;
+
use super::bumps;
use crate::trezorhal::display;
use crate::ui::display::Color;
use crate::ui::geometry::Offset;
use crate::ui::shape::render::ScopedRenderer;
use crate::ui::shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport};
+#[cfg(feature = "ui_performance_overlay")]
+use crate::ui::PerformanceOverlay;
#[cfg(any(feature = "ui_debug_overlay", feature = "ui_performance_overlay"))]
use crate::ui::{CommonUI, ModelUI};
-#[cfg(feature = "ui_performance_overlay")]
-use crate::{trezorhal::time, ui::PerformanceOverlay};
pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgba8888Canvas<'alloc>>;
### core/embed/sys/src/lib.rs
@@ -4,3 +4,5 @@ mod ffi;
#[cfg(feature = "dbg_console")]
pub mod syslog;
+
+pub mod time;
### core/embed/sys/src/time/duration.rs
@@ -0,0 +1,260 @@
+use core::ops::{Div, Mul};
+
+const MILLIS_PER_SEC: u32 = 1000;
+const MILLIS_PER_MINUTE: u32 = MILLIS_PER_SEC * 60;
+const MILLIS_PER_HOUR: u32 = MILLIS_PER_MINUTE * 60;
+const MILLIS_PER_DAY: u32 = MILLIS_PER_HOUR * 24;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct ShortDuration {
+ millis: u16,
+}
+
+impl ShortDuration {
+ pub const ZERO: Self = Self::from_millis(0);
+
+ pub const fn from_millis(millis: u16) -> Self {
+ Self { millis }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
+pub struct Duration {
+ millis: u32,
+}
+
+impl Duration {
+ pub const ZERO: Self = Self::from_millis(0);
+
+ pub const fn from_millis(millis: u32) -> Self {
+ Self { millis }
+ }
+
+ pub const fn from_secs(secs: u32) -> Self {
+ // Check for potential overflow
+ debug_assert!(secs < u32::MAX / MILLIS_PER_SEC);
+ Self::from_millis(secs * MILLIS_PER_SEC)
+ }
+
+ pub const fn from_mins(mins: u32) -> Self {
+ // Check for potential overflow
+ debug_assert!(mins < u32::MAX / MILLIS_PER_MINUTE);
+ Self::from_millis(mins * MILLIS_PER_MINUTE)
+ }
+
+ pub const fn from_hours(hours: u32) -> Self {
+ // Check for potential overflow
+ debug_assert!(hours < u32::MAX / MILLIS_PER_HOUR);
+ Self::from_millis(hours * MILLIS_PER_HOUR)
+ }
+ pub const fn from_days(days: u32) -> Self {
+ // Check for potential overflow
+ debug_assert!(days < u32::MAX / MILLIS_PER_DAY);
+ Self::from_millis(days * MILLIS_PER_DAY)
+ }
+
+ pub fn to_millis(self) -> u32 {
+ self.millis
+ }
+
+ pub fn to_secs(self) -> u32 {
+ self.millis / MILLIS_PER_SEC
+ }
+ pub fn to_mins(self) -> u32 {
+ self.millis / MILLIS_PER_MINUTE
+ }
+ pub fn to_hours(self) -> u32 {
+ self.millis / MILLIS_PER_HOUR
+ }
+ pub fn to_days(self) -> u32 {
+ self.millis / MILLIS_PER_DAY
+ }
+
+ pub fn checked_add(self, rhs: Self) -> Option<Self> {
+ self.millis.checked_add(rhs.millis).map(Self::from_millis)
+ }
+
+ pub fn checked_sub(self, rhs: Self) -> Option<Self> {
+ self.millis.checked_sub(rhs.millis).map(Self::from_millis)
+ }
+
+ pub fn saturating_add(self, rhs: Self) -> Self {
+ Self::from_millis(self.millis.saturating_add(rhs.millis))
+ }
+
+ /// Returns a new Duration containing only the largest complete time unit
+ /// (days, hours, minutes, or seconds)
+ ///
+ /// Examples:
+ /// - 1 day, 3 hours → 1 day
+ /// - 3 hours, 45 minutes → 3 hours
+ /// - 59 seconds → 59 seconds
+ pub fn crop_to_largest_unit(self) -> Self {
+ if self.millis >= MILLIS_PER_DAY {
+ Duration::from_days(self.to_days())
+ } else if self.millis >= MILLIS_PER_HOUR {
+ Duration::from_hours(self.to_hours())
+ } else if self.millis >= MILLIS_PER_MINUTE {
+ Duration::from_mins(self.to_mins())
+ } else {
+ Duration::from_secs(self.to_secs())
+ }
+ }
+
+ /// Increment by one unit based on the current magnitude
+ ///
+ /// Examples:
+ /// - 59s → 1m (moves to the next unit when crossing a boundary)
+ /// - 1m → 2m
+ /// - 23h → 1d
+ ///
+ /// Returns None if addition would overflow
+ pub fn increment_unit(self) -> Option<Self> {
+ let base = self.crop_to_largest_unit();
+
+ let step = if base.millis < MILLIS_PER_MINUTE {
+ Duration::from_secs(1)
+ } else if base.millis < MILLIS_PER_HOUR {
+ Duration::from_mins(1)
+ } else if base.millis < MILLIS_PER_DAY {
+ Duration::from_hours(1)
+ } else {
+ Duration::from_days(1)
+ };
+
+ base.checked_add(step)
+ }
+
+ /// Decrement by one unit based on the current magnitude
+ ///
+ /// Examples:
+ /// - 1m → 59s (moves to the previous unit at boundaries)
+ /// - 2m → 1m
+ /// - 1h → 59m
+ /// - 1d → 23h
+ ///
+ /// Returns None if subtraction would result in negative duration
+ pub fn decrement_unit(self) -> Option<Self> {
+ let base = self.crop_to_largest_unit();
+
+ let step = if base.millis <= MILLIS_PER_MINUTE {
+ Duration::from_secs(1)
+ } else if base.millis <= MILLIS_PER_HOUR {
+ Duration::from_mins(1)
+ } else if base.millis <= MILLIS_PER_DAY {
+ Duration::from_hours(1)
+ } else {
+ Duration::from_days(1)
+ };
+
+ base.checked_sub(step)
+ }
+}
+
+impl Mul<f32> for Duration {
+ // Multiplication by float is saturating -- in particular, casting from a float
+ // to an int is saturating, value larger than INT_MAX casts to INT_MAX. So
+ // this operation does not need to be checked.
+ type Output = Self;
+
+ fn mul(self, rhs: f32) -> Self::Output {
+ Self::from_millis((self.millis as f32 * rhs) as u32)
+ }
+}
+
+impl Div<u32> for Duration {
+ // Division by integer cannot overflow so it does not need to be checked.
+ type Output = Self;
+
+ fn div(self, rhs: u32) -> Self::Output {
+ Self::from_millis(self.millis / rhs)
+ }
+}
+
+impl Div<Duration> for Duration {
+ // Division by float results in float so it does not need to be checked.
+ type Output = f32;
+
+ fn div(self, rhs: Self) -> Self::Output {
+ self.to_millis() as f32 / rhs.to_millis() as f32
+ }
+}
+
+impl From<ShortDuration> for Duration {
+ fn from(value: ShortDuration) -> Self {
+ Self::from_millis(value.millis.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn duration_from_millis() {
+ assert_eq!(Duration::from_millis(1000), Duration::from_secs(1));
+ }
+
+ #[test]
+ fn test_crop_to_largest_unit() {
+ assert_eq!(
+ Duration::from_secs(59).crop_to_largest_unit(),
+ Duration::from_secs(59)
+ );
+ assert_eq!(
+ Duration::from_secs(60).crop_to_largest_unit(),
+ Duration::from_mins(1)
+ );
+ assert_eq!(
+ Duration::from_secs(61).crop_to_largest_unit(),
+ Duration::from_mins(1)
+ );
+ assert_eq!(
+ Duration::from_secs(3600).crop_to_largest_unit(),
+ Duration::from_hours(1)
+ );
+ assert_eq!(
+ Duration::from_secs(86399).crop_to_largest_unit(),
+ Duration::from_hours(23)
+ );
+ }
+
+ #[test]
+ fn test_increment_decrement_unit() {
+ // Increment
+ assert_eq!(
+ Duration::from_secs(59).increment_unit().unwrap(),
+ Duration::from_mins(1)
+ );
+ assert_eq!(
+ Duration::from_mins(1).increment_unit().unwrap(),
+ Duration::from_mins(2)
+ );
+ assert_eq!(
+ Duration::from_secs(61).increment_unit().unwrap(),
+ Duration::from_mins(2)
+ );
+ assert_eq!(
+ Duration::from_days(3).increment_unit().unwrap(),
+ Duration::from_days(4)
+ );
+
+ // Decrement
+ assert_eq!(
+ Duration::from_mins(1).decrement_unit().unwrap(),
+ Duration::from_secs(59)
+ );
+ assert_eq!(
+ Duration::from_secs(61).decrement_unit().unwrap(),
+ Duration::from_secs(59)
+ );
+ assert_eq!(
+ Duration::from_mins(3).decrement_unit().unwrap(),
+ Duration::from_mins(2)
+ );
+ assert_eq!(
+ Duration::from_hours(1).decrement_unit().unwrap(),
+ Duration::from_mins(59)
+ );
+ }
+}
### core/embed/sys/src/time/instant.rs
@@ -0,0 +1,108 @@
+use core::cmp::Ordering;
+
+use super::duration::Duration;
+
+/* Instants can wrap around and we want them to be comparable even after
+ * wrapping around. This works by setting a maximum allowable difference
+ * between two Instants to half the range. In checked_add and checked_sub, we
+ * make sure that the step from one Instant to another is at most
+ * MAX_DIFFERENCE_IN_MILLIS. In the Ord implementation, if the difference is
+ * more than MAX_DIFFERENCE_IN_MILLIS, we can assume that the smaller Instant
+ * is actually wrapped around and so is in the future. */
+const MAX_DIFFERENCE_IN_MILLIS: u32 = u32::MAX / 2;
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct Instant {
+ millis: u32,
+}
+
+impl Instant {
+ pub fn now() -> Self {
+ Self {
+ millis: super::ticks_ms(),
+ }
+ }
+
+ pub fn saturating_duration_since(self, earlier: Self) -> Duration {
+ self.checked_duration_since(earlier)
+ .unwrap_or(Duration::ZERO)
+ }
+
+ pub fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
+ if self >= earlier {
+ Some(Duration::from_millis(
+ self.millis.wrapping_sub(earlier.millis),
+ ))
+ } else {
+ None
+ }
+ }
+
+ pub fn checked_add(self, duration: Duration) -> Option<Self> {
+ let add_millis = duration.to_millis();
+ if add_millis <= MAX_DIFFERENCE_IN_MILLIS {
+ Some(Self {
+ millis: self.millis.wrapping_add(add_millis),
+ })
+ } else {
+ None
+ }
+ }
+
+ pub fn checked_sub(self, duration: Duration) -> Option<Self> {
+ let sub_millis = duration.to_millis();
+ if sub_millis <= MAX_DIFFERENCE_IN_MILLIS {
+ Some(Self {
+ millis: self.millis.wrapping_sub(sub_millis),
+ })
+ } else {
+ None
+ }
+ }
+
+ pub fn to_millis(self) -> u32 {
+ self.millis
+ }
+}
+
+impl PartialOrd for Instant {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(self.cmp(rhs))
+ }
+}
+
+impl Ord for Instant {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ if self.millis == rhs.millis {
+ Ordering::Equal
+ } else {
+ // If the difference is greater than MAX_DIFFERENCE_IN_MILLIS, we assume
+ // that the larger Instant is in the past.
+ // See explanation on MAX_DIFFERENCE_IN_MILLIS
+ self.millis
+ .wrapping_sub(rhs.millis)
+ .cmp(&MAX_DIFFERENCE_IN_MILLIS)
+ .reverse()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn instant_now() {
+ let instant = Instant::now();
+ assert!(instant.millis <= crate::time::ticks_ms());
+ }
+
+ #[test]
+ fn instant_wraps_and_compares_correctly() {
+ let milli = Duration::from_millis(1);
+ let earlier = Instant { millis: u32::MAX };
+ let later = earlier.checked_add(milli).unwrap();
+ assert_eq!(later, Instant { millis: 0 });
+ assert!(earlier < later);
+ }
+}
### core/embed/sys/src/time/mod.rs
@@ -0,0 +1,38 @@
+use crate::ffi;
+
+mod duration;
+mod instant;
+
+pub use duration::{Duration, ShortDuration};
+pub use instant::Instant;
+
+pub fn ticks_ms() -> u32 {
+ // SAFETY: safe
+ unsafe { ffi::systick_ms() as _ }
+}
+
+pub fn ticks_us() -> u64 {
+ // SAFETY: safe
+ unsafe { ffi::systick_us() as _ }
+}
+
+pub fn sleep_ms(ms: u32) {
+ // SAFETY: safe
+ unsafe { ffi::systick_delay_ms(ms) }
+}
+
+pub fn sleep_us(us: u64) {
+ // SAFETY: safe
+ unsafe { ffi::systick_delay_us(us) }
+}
+
+pub fn sleep(duration: Duration) {
+ sleep_ms(duration.to_millis());
+}
+
+/// Measures the time it takes to execute a closure in microseconds.
+pub fn measure_us(f: impl FnOnce()) -> u64 {
+ let start = ticks_us();
+ f();
+ ticks_us() - start
+}
### core/embed/sys/time/build.rs
@@ -2,6 +2,7 @@ use xbuild::{CLibrary, Result, bail_unsupported};
pub fn def_module(lib: &mut CLibrary) -> Result<()> {
lib.add_include("time/inc");
+ lib.add_rust_bindings(add_rust_bindings)?;
if cfg!(feature = "emulator") {
lib.add_sources(["time/unix/systick.c", "time/unix/systimer.c"]);
@@ -25,3 +26,13 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
Ok(())
}
+
+fn add_rust_bindings(builder: bindgen::Builder) -> Result<bindgen::Builder> {
+ let builder = builder
+ .header("time/inc/sys/systick.h")
+ .allowlist_function("systick_ms")
+ .allowlist_function("systick_us")
+ .allowlist_function("systick_delay_ms")
+ .allowlist_function("systick_delay_us");
+ Ok(builder)
+}Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.