diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index af4d35d5e..9898f6cc1 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -111,6 +111,8 @@ fn generate_micropython_bindings() { .allowlist_var("mp_type_OverflowError") .allowlist_var("mp_type_ValueError") .allowlist_var("mp_type_TypeError") + // time + .allowlist_function("mp_hal_ticks_ms") // typ .allowlist_var("mp_type_type"); @@ -161,6 +163,7 @@ fn generate_micropython_bindings() { "-I../unix", "-I../../build/unix", "-I../../vendor/micropython", + "-I../../vendor/micropython/ports/unix", ]); } diff --git a/core/embed/rust/micropython.h b/core/embed/rust/micropython.h index eba890703..9b19e64ff 100644 --- a/core/embed/rust/micropython.h +++ b/core/embed/rust/micropython.h @@ -1,4 +1,5 @@ #include "py/gc.h" +#include "py/mphal.h" #include "py/nlr.h" #include "py/obj.h" #include "py/runtime.h" diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs index 147d78ac2..ec8678390 100644 --- a/core/embed/rust/src/lib.rs +++ b/core/embed/rust/src/lib.rs @@ -8,6 +8,7 @@ mod error; #[macro_use] mod micropython; mod protobuf; +mod time; #[cfg(feature = "ui_debug")] mod trace; mod trezorhal; diff --git a/core/embed/rust/src/micropython/mod.rs b/core/embed/rust/src/micropython/mod.rs index fe5481307..9dd469349 100644 --- a/core/embed/rust/src/micropython/mod.rs +++ b/core/embed/rust/src/micropython/mod.rs @@ -13,4 +13,5 @@ pub mod map; pub mod obj; pub mod qstr; pub mod runtime; +pub mod time; pub mod typ; diff --git a/core/embed/rust/src/micropython/time.rs b/core/embed/rust/src/micropython/time.rs new file mode 100644 index 000000000..026ba1796 --- /dev/null +++ b/core/embed/rust/src/micropython/time.rs @@ -0,0 +1,5 @@ +use super::ffi; + +pub fn ticks_ms() -> u32 { + unsafe { ffi::mp_hal_ticks_ms() as _ } +} diff --git a/core/embed/rust/src/time.rs b/core/embed/rust/src/time.rs new file mode 100644 index 000000000..7b8eeacef --- /dev/null +++ b/core/embed/rust/src/time.rs @@ -0,0 +1,147 @@ +use core::{cmp::Ordering, ops::Div}; + +use crate::micropython::time; + +const MILLIS_PER_SEC: u32 = 1000; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +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 { + Self { + millis: secs * MILLIS_PER_SEC, + } + } + + pub fn to_millis(self) -> u32 { + self.millis + } + + pub fn checked_add(self, rhs: Self) -> Option { + self.millis.checked_add(rhs.millis).map(Self::from_millis) + } + + pub fn checked_sub(self, rhs: Self) -> Option { + self.millis.checked_sub(rhs.millis).map(Self::from_millis) + } +} + +impl Div for Duration { + type Output = Self; + + fn div(self, rhs: u32) -> Self::Output { + Self::from_millis(self.millis / rhs) + } +} + +impl Div for Duration { + type Output = f32; + + fn div(self, rhs: Self) -> Self::Output { + self.to_millis() as f32 / rhs.to_millis() as f32 + } +} + +/* 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 { + if self >= earlier { + Some(Duration::from_millis( + self.millis.wrapping_sub(earlier.millis), + )) + } else { + None + } + } + + pub fn checked_add(self, duration: Duration) -> Option { + 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 { + let add_millis = duration.to_millis(); + if add_millis <= MAX_DIFFERENCE_IN_MILLIS { + Some(Self { + millis: self.millis.wrapping_sub(add_millis), + }) + } else { + None + } + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + 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_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); + } +}