Import https://github.com/tock/libtock-rs at commit 828c19d into third_party/libtock-drivers/.
This commit is contained in:
167
third_party/libtock-drivers/src/buttons.rs
vendored
Normal file
167
third_party/libtock-drivers/src/buttons.rs
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
use crate::callback::CallbackSubscription;
|
||||
use crate::callback::Consumer;
|
||||
use crate::result::OtherError;
|
||||
use crate::result::OutOfRangeError;
|
||||
use crate::result::TockResult;
|
||||
use crate::syscalls;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x00003;
|
||||
|
||||
mod command_nr {
|
||||
pub const COUNT: usize = 0;
|
||||
pub const ENABLE_INTERRUPT: usize = 1;
|
||||
pub const DISABLE_INTERRUPT: usize = 2;
|
||||
pub const READ: usize = 3;
|
||||
}
|
||||
|
||||
mod subscribe_nr {
|
||||
pub const SUBSCRIBE_CALLBACK: usize = 0;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct ButtonsDriverFactory;
|
||||
|
||||
impl ButtonsDriverFactory {
|
||||
pub fn init_driver(&mut self) -> TockResult<ButtonsDriver> {
|
||||
let buttons_driver = ButtonsDriver {
|
||||
num_buttons: syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
|
||||
lifetime: PhantomData,
|
||||
};
|
||||
Ok(buttons_driver)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ButtonsDriver<'a> {
|
||||
num_buttons: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> ButtonsDriver<'a> {
|
||||
pub fn num_buttons(&self) -> usize {
|
||||
self.num_buttons
|
||||
}
|
||||
|
||||
/// Returns the button at 0-based index `button_num`
|
||||
pub fn get(&self, button_num: usize) -> Result<Button, OutOfRangeError> {
|
||||
if button_num < self.num_buttons {
|
||||
Ok(Button {
|
||||
button_num,
|
||||
lifetime: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(OutOfRangeError)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buttons(&self) -> Buttons {
|
||||
Buttons {
|
||||
num_buttons: self.num_buttons,
|
||||
curr_button: 0,
|
||||
lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe<CB: Fn(usize, ButtonState)>(
|
||||
&self,
|
||||
callback: &'a mut CB,
|
||||
) -> TockResult<CallbackSubscription> {
|
||||
syscalls::subscribe::<ButtonsEventConsumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::SUBSCRIBE_CALLBACK,
|
||||
callback,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
struct ButtonsEventConsumer;
|
||||
|
||||
impl<CB: Fn(usize, ButtonState)> Consumer<CB> for ButtonsEventConsumer {
|
||||
fn consume(callback: &mut CB, button_num: usize, button_state: usize, _: usize) {
|
||||
let button_state = match button_state {
|
||||
0 => ButtonState::Released,
|
||||
1 => ButtonState::Pressed,
|
||||
_ => return,
|
||||
};
|
||||
callback(button_num, button_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buttons<'a> {
|
||||
num_buttons: usize,
|
||||
curr_button: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Buttons<'a> {
|
||||
type Item = Button<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr_button < self.num_buttons {
|
||||
let item = Button {
|
||||
button_num: self.curr_button,
|
||||
lifetime: PhantomData,
|
||||
};
|
||||
self.curr_button += 1;
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ButtonState {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
impl From<ButtonState> for bool {
|
||||
fn from(button_state: ButtonState) -> Self {
|
||||
match button_state {
|
||||
ButtonState::Released => false,
|
||||
ButtonState::Pressed => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Button<'a> {
|
||||
button_num: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Button<'a> {
|
||||
pub fn button_num(&self) -> usize {
|
||||
self.button_num
|
||||
}
|
||||
|
||||
pub fn read(&self) -> TockResult<ButtonState> {
|
||||
let button_state = syscalls::command(DRIVER_NUMBER, command_nr::READ, self.button_num, 0)?;
|
||||
match button_state {
|
||||
0 => Ok(ButtonState::Released),
|
||||
1 => Ok(ButtonState::Pressed),
|
||||
_ => Err(OtherError::ButtonsDriverInvalidState.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_interrupt(&self) -> TockResult<()> {
|
||||
syscalls::command(
|
||||
DRIVER_NUMBER,
|
||||
command_nr::ENABLE_INTERRUPT,
|
||||
self.button_num,
|
||||
0,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_interrupt(&self) -> TockResult<()> {
|
||||
syscalls::command(
|
||||
DRIVER_NUMBER,
|
||||
command_nr::DISABLE_INTERRUPT,
|
||||
self.button_num,
|
||||
0,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
82
third_party/libtock-drivers/src/console.rs
vendored
Normal file
82
third_party/libtock-drivers/src/console.rs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
use crate::callback::Identity0Consumer;
|
||||
use crate::executor;
|
||||
use crate::futures;
|
||||
use crate::result::TockResult;
|
||||
use crate::syscalls;
|
||||
use core::cell::Cell;
|
||||
use core::fmt;
|
||||
use core::mem;
|
||||
|
||||
const DRIVER_NUMBER: usize = 1;
|
||||
|
||||
mod command_nr {
|
||||
pub const WRITE: usize = 1;
|
||||
}
|
||||
|
||||
mod subscribe_nr {
|
||||
pub const SET_ALARM: usize = 1;
|
||||
}
|
||||
|
||||
mod allow_nr {
|
||||
pub const SHARE_BUFFER: usize = 1;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct ConsoleDriver;
|
||||
|
||||
impl ConsoleDriver {
|
||||
pub fn create_console(self) -> Console {
|
||||
Console {
|
||||
allow_buffer: [0; 64],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Console {
|
||||
allow_buffer: [u8; 64],
|
||||
}
|
||||
|
||||
impl Console {
|
||||
pub fn write<S: AsRef<[u8]>>(&mut self, text: S) -> TockResult<()> {
|
||||
let mut not_written_yet = text.as_ref();
|
||||
while !not_written_yet.is_empty() {
|
||||
let num_bytes_to_print = self.allow_buffer.len().min(not_written_yet.len());
|
||||
self.allow_buffer[..num_bytes_to_print]
|
||||
.copy_from_slice(¬_written_yet[..num_bytes_to_print]);
|
||||
self.flush(num_bytes_to_print)?;
|
||||
not_written_yet = ¬_written_yet[num_bytes_to_print..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&mut self, num_bytes_to_print: usize) -> TockResult<()> {
|
||||
let shared_memory = syscalls::allow(
|
||||
DRIVER_NUMBER,
|
||||
allow_nr::SHARE_BUFFER,
|
||||
&mut self.allow_buffer[..num_bytes_to_print],
|
||||
)?;
|
||||
|
||||
let is_written = Cell::new(false);
|
||||
let mut is_written_alarm = || is_written.set(true);
|
||||
let subscription = syscalls::subscribe::<Identity0Consumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::SET_ALARM,
|
||||
&mut is_written_alarm,
|
||||
)?;
|
||||
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::WRITE, num_bytes_to_print, 0)?;
|
||||
|
||||
unsafe { executor::block_on(futures::wait_until(|| is_written.get())) };
|
||||
|
||||
mem::drop(subscription);
|
||||
mem::drop(shared_memory);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Console {
|
||||
fn write_str(&mut self, string: &str) -> Result<(), fmt::Error> {
|
||||
self.write(string).map_err(|_| fmt::Error)
|
||||
}
|
||||
}
|
||||
161
third_party/libtock-drivers/src/leds.rs
vendored
Normal file
161
third_party/libtock-drivers/src/leds.rs
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::result::OutOfRangeError;
|
||||
use crate::result::TockResult;
|
||||
use crate::syscalls::command;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x00002;
|
||||
|
||||
mod command_nr {
|
||||
pub const COUNT: usize = 0;
|
||||
pub const ON: usize = 1;
|
||||
pub const OFF: usize = 2;
|
||||
pub const TOGGLE: usize = 3;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct LedsDriverFactory;
|
||||
|
||||
impl LedsDriverFactory {
|
||||
pub fn init_driver(&mut self) -> TockResult<LedsDriver> {
|
||||
let driver = LedsDriver {
|
||||
num_leds: command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
|
||||
lifetime: PhantomData,
|
||||
};
|
||||
Ok(driver)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LedsDriver<'a> {
|
||||
num_leds: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> LedsDriver<'a> {
|
||||
pub fn num_leds(&self) -> usize {
|
||||
self.num_leds
|
||||
}
|
||||
|
||||
pub fn leds(&self) -> Leds {
|
||||
Leds {
|
||||
num_leds: self.num_leds,
|
||||
curr_led: 0,
|
||||
lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the led at 0-based index `led_num`
|
||||
pub fn get(&self, led_num: usize) -> Result<Led, OutOfRangeError> {
|
||||
if led_num < self.num_leds {
|
||||
Ok(Led {
|
||||
led_num,
|
||||
lifetime: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(OutOfRangeError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Leds<'a> {
|
||||
num_leds: usize,
|
||||
curr_led: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Leds<'a> {
|
||||
type Item = Led<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.curr_led < self.num_leds {
|
||||
let item = Led {
|
||||
led_num: self.curr_led,
|
||||
lifetime: PhantomData,
|
||||
};
|
||||
self.curr_led += 1;
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Led<'a> {
|
||||
led_num: usize,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Led<'a> {
|
||||
pub fn led_num(&self) -> usize {
|
||||
self.led_num
|
||||
}
|
||||
|
||||
pub fn set(&self, state: impl Into<LedState>) -> TockResult<()> {
|
||||
match state.into() {
|
||||
LedState::On => self.on(),
|
||||
LedState::Off => self.off(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on(&self) -> TockResult<()> {
|
||||
command(DRIVER_NUMBER, command_nr::ON, self.led_num, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn off(&self) -> TockResult<()> {
|
||||
command(DRIVER_NUMBER, command_nr::OFF, self.led_num, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn toggle(&self) -> TockResult<()> {
|
||||
command(DRIVER_NUMBER, command_nr::TOGGLE, self.led_num, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum LedState {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
impl From<bool> for LedState {
|
||||
fn from(from_value: bool) -> Self {
|
||||
if from_value {
|
||||
LedState::On
|
||||
} else {
|
||||
LedState::Off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::command_nr;
|
||||
use super::DRIVER_NUMBER;
|
||||
use crate::result::TockResult;
|
||||
use crate::syscalls;
|
||||
use crate::syscalls::raw::Event;
|
||||
|
||||
#[test]
|
||||
pub fn single_led_can_be_enabled() {
|
||||
let events = syscalls::raw::run_recording_events::<TockResult<()>, _>(|next_return| {
|
||||
let mut drivers = unsafe { crate::drivers::retrieve_drivers_unsafe() };
|
||||
|
||||
next_return.set(1);
|
||||
|
||||
let leds_driver = drivers.leds.init_driver()?;
|
||||
next_return.set(0);
|
||||
|
||||
let led = leds_driver.get(0)?;
|
||||
led.on()?;
|
||||
Ok(())
|
||||
});
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![
|
||||
Event::Command(DRIVER_NUMBER, command_nr::COUNT, 0, 0),
|
||||
Event::Command(DRIVER_NUMBER, command_nr::ON, 0, 0),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
25
third_party/libtock-drivers/src/lib.rs
vendored
Normal file
25
third_party/libtock-drivers/src/lib.rs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
|
||||
pub mod adc;
|
||||
pub mod ble_composer;
|
||||
pub mod ble_parser;
|
||||
pub mod buttons;
|
||||
pub mod console;
|
||||
pub mod debug;
|
||||
pub mod drivers;
|
||||
pub mod electronics;
|
||||
pub mod executor;
|
||||
pub mod futures;
|
||||
pub mod gpio;
|
||||
pub mod hmac;
|
||||
pub mod leds;
|
||||
pub mod result;
|
||||
pub mod rng;
|
||||
pub mod sensors;
|
||||
pub mod simple_ble;
|
||||
pub mod temperature;
|
||||
pub mod timer;
|
||||
|
||||
pub use drivers::retrieve_drivers;
|
||||
pub use libtock_codegen::main;
|
||||
pub use libtock_core::*;
|
||||
69
third_party/libtock-drivers/src/result.rs
vendored
Normal file
69
third_party/libtock-drivers/src/result.rs
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
use core::fmt;
|
||||
|
||||
pub use libtock_core::result::*;
|
||||
|
||||
pub type TockResult<T> = Result<T, TockError>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum TockError {
|
||||
Subscribe(SubscribeError),
|
||||
Command(CommandError),
|
||||
Allow(AllowError),
|
||||
Format,
|
||||
Other(OtherError),
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "arm", target_arch = "riscv32")))]
|
||||
impl core::fmt::Debug for TockError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
writeln!(f, "impl Debug only for test builds")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubscribeError> for TockError {
|
||||
fn from(subscribe_error: SubscribeError) -> Self {
|
||||
TockError::Subscribe(subscribe_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommandError> for TockError {
|
||||
fn from(command_error: CommandError) -> Self {
|
||||
TockError::Command(command_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AllowError> for TockError {
|
||||
fn from(allow_error: AllowError) -> Self {
|
||||
TockError::Allow(allow_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for TockError {
|
||||
fn from(fmt::Error: fmt::Error) -> Self {
|
||||
TockError::Format
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum OtherError {
|
||||
ButtonsDriverInvalidState,
|
||||
GpioDriverInvalidState,
|
||||
TimerDriverDurationOutOfRange,
|
||||
TimerDriverErroneousClockFrequency,
|
||||
DriversAlreadyTaken,
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
impl From<OtherError> for TockError {
|
||||
fn from(other: OtherError) -> Self {
|
||||
TockError::Other(other)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutOfRangeError;
|
||||
|
||||
impl From<OutOfRangeError> for TockError {
|
||||
fn from(_: OutOfRangeError) -> Self {
|
||||
TockError::Other(OtherError::OutOfRange)
|
||||
}
|
||||
}
|
||||
42
third_party/libtock-drivers/src/rng.rs
vendored
Normal file
42
third_party/libtock-drivers/src/rng.rs
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::callback::Identity0Consumer;
|
||||
use crate::futures;
|
||||
use crate::result::TockResult;
|
||||
use crate::syscalls;
|
||||
use core::cell::Cell;
|
||||
use core::mem;
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x40001;
|
||||
|
||||
mod command_nr {
|
||||
pub const REQUEST_RNG: usize = 1;
|
||||
}
|
||||
|
||||
mod subscribe_nr {
|
||||
pub const BUFFER_FILLED: usize = 0;
|
||||
}
|
||||
|
||||
mod allow_nr {
|
||||
pub const SHARE_BUFFER: usize = 0;
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct RngDriver;
|
||||
|
||||
impl RngDriver {
|
||||
pub async fn fill_buffer(&mut self, buf: &mut [u8]) -> TockResult<()> {
|
||||
let buf_len = buf.len();
|
||||
let shared_memory = syscalls::allow(DRIVER_NUMBER, allow_nr::SHARE_BUFFER, buf)?;
|
||||
let is_filled = Cell::new(false);
|
||||
let mut is_filled_alarm = || is_filled.set(true);
|
||||
let subscription = syscalls::subscribe::<Identity0Consumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::BUFFER_FILLED,
|
||||
&mut is_filled_alarm,
|
||||
)?;
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::REQUEST_RNG, buf_len, 0)?;
|
||||
futures::wait_until(|| is_filled.get()).await;
|
||||
mem::drop(subscription);
|
||||
mem::drop(shared_memory);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
744
third_party/libtock-drivers/src/timer.rs
vendored
Normal file
744
third_party/libtock-drivers/src/timer.rs
vendored
Normal file
@@ -0,0 +1,744 @@
|
||||
//! Async timer driver. Can be used for (non-busy) sleeping.
|
||||
|
||||
use crate::callback::CallbackSubscription;
|
||||
use crate::callback::Consumer;
|
||||
use crate::futures;
|
||||
use crate::result::OtherError;
|
||||
use crate::result::TockError;
|
||||
use crate::result::TockResult;
|
||||
use crate::result::EALREADY;
|
||||
use crate::syscalls;
|
||||
use core::cell::Cell;
|
||||
use core::isize;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Add, AddAssign, Sub};
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x00000;
|
||||
|
||||
mod command_nr {
|
||||
pub const IS_DRIVER_AVAILABLE: usize = 0;
|
||||
pub const GET_CLOCK_FREQUENCY: usize = 1;
|
||||
pub const GET_CLOCK_VALUE: usize = 2;
|
||||
pub const STOP_ALARM: usize = 3;
|
||||
pub const SET_ALARM: usize = 4;
|
||||
}
|
||||
|
||||
mod subscribe_nr {
|
||||
pub const SUBSCRIBE_CALLBACK: usize = 0;
|
||||
}
|
||||
|
||||
pub struct WithCallback<'a, CB> {
|
||||
callback: CB,
|
||||
clock_frequency: ClockFrequency,
|
||||
phantom: PhantomData<&'a mut ()>,
|
||||
}
|
||||
|
||||
struct TimerEventConsumer;
|
||||
|
||||
impl<CB: FnMut(ClockValue, Alarm)> Consumer<WithCallback<'_, CB>> for TimerEventConsumer {
|
||||
fn consume(data: &mut WithCallback<CB>, clock_value: usize, alarm_id: usize, _: usize) {
|
||||
(data.callback)(
|
||||
ClockValue {
|
||||
num_ticks: clock_value as isize,
|
||||
clock_frequency: data.clock_frequency,
|
||||
},
|
||||
Alarm { alarm_id },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, CB: FnMut(ClockValue, Alarm)> WithCallback<'a, CB> {
|
||||
pub fn init(&'a mut self) -> TockResult<Timer<'a>> {
|
||||
let num_notifications =
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::IS_DRIVER_AVAILABLE, 0, 0)?;
|
||||
|
||||
let clock_frequency =
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_FREQUENCY, 0, 0)?;
|
||||
|
||||
if clock_frequency == 0 {
|
||||
return Err(OtherError::TimerDriverErroneousClockFrequency.into());
|
||||
}
|
||||
|
||||
let clock_frequency = ClockFrequency {
|
||||
hz: clock_frequency,
|
||||
};
|
||||
|
||||
let subscription = syscalls::subscribe::<TimerEventConsumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::SUBSCRIBE_CALLBACK,
|
||||
self,
|
||||
)?;
|
||||
|
||||
Ok(Timer {
|
||||
num_notifications,
|
||||
clock_frequency,
|
||||
subscription,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Timer<'a> {
|
||||
num_notifications: usize,
|
||||
clock_frequency: ClockFrequency,
|
||||
#[allow(dead_code)] // Used in drop
|
||||
subscription: CallbackSubscription<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Timer<'a> {
|
||||
pub fn num_notifications(&self) -> usize {
|
||||
self.num_notifications
|
||||
}
|
||||
|
||||
pub fn clock_frequency(&self) -> ClockFrequency {
|
||||
self.clock_frequency
|
||||
}
|
||||
|
||||
pub fn get_current_clock(&self) -> TockResult<ClockValue> {
|
||||
Ok(ClockValue {
|
||||
num_ticks: syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_VALUE, 0, 0)?
|
||||
as isize,
|
||||
clock_frequency: self.clock_frequency,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stop_alarm(&mut self, alarm: Alarm) -> TockResult<()> {
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::STOP_ALARM, alarm.alarm_id, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_alarm(&mut self, duration: Duration<isize>) -> TockResult<Alarm> {
|
||||
let now = self.get_current_clock()?;
|
||||
let freq = self.clock_frequency.hz();
|
||||
let duration_ms = duration.ms() as usize;
|
||||
let ticks = match duration_ms.checked_mul(freq) {
|
||||
Some(x) => x / 1000,
|
||||
None => {
|
||||
// Divide the largest of the two operands by 1000, to improve precision of the
|
||||
// result.
|
||||
if duration_ms > freq {
|
||||
match (duration_ms / 1000).checked_mul(freq) {
|
||||
Some(y) => y,
|
||||
None => return Err(OtherError::TimerDriverDurationOutOfRange.into()),
|
||||
}
|
||||
} else {
|
||||
match (freq / 1000).checked_mul(duration_ms) {
|
||||
Some(y) => y,
|
||||
None => return Err(OtherError::TimerDriverDurationOutOfRange.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let alarm_instant = now.num_ticks() as usize + ticks;
|
||||
|
||||
let alarm_id = syscalls::command(DRIVER_NUMBER, command_nr::SET_ALARM, alarm_instant, 0)?;
|
||||
|
||||
Ok(Alarm { alarm_id })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ClockFrequency {
|
||||
hz: usize,
|
||||
}
|
||||
|
||||
impl ClockFrequency {
|
||||
pub fn hz(self) -> usize {
|
||||
self.hz
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ClockValue {
|
||||
num_ticks: isize,
|
||||
clock_frequency: ClockFrequency,
|
||||
}
|
||||
|
||||
impl ClockValue {
|
||||
pub fn num_ticks(self) -> isize {
|
||||
self.num_ticks
|
||||
}
|
||||
|
||||
pub fn ms(self) -> isize {
|
||||
if self.num_ticks.abs() < isize::MAX / 1000 {
|
||||
(1000 * self.num_ticks) / self.clock_frequency.hz() as isize
|
||||
} else {
|
||||
1000 * (self.num_ticks / self.clock_frequency.hz() as isize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ms_f64(self) -> f64 {
|
||||
1000.0 * (self.num_ticks as f64) / (self.clock_frequency.hz() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Alarm {
|
||||
alarm_id: usize,
|
||||
}
|
||||
|
||||
impl Alarm {
|
||||
pub fn alarm_id(&self) -> usize {
|
||||
self.alarm_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Duration<T> {
|
||||
ms: T,
|
||||
}
|
||||
|
||||
impl<T> Duration<T> {
|
||||
pub const fn from_ms(ms: T) -> Duration<T> {
|
||||
Duration { ms }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Duration<T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
pub fn ms(&self) -> T {
|
||||
self.ms
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sub for Duration<T>
|
||||
where
|
||||
T: Sub<Output = T>,
|
||||
{
|
||||
type Output = Duration<T>;
|
||||
|
||||
fn sub(self, other: Duration<T>) -> Duration<T> {
|
||||
Duration {
|
||||
ms: self.ms - other.ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Timestamp<T> {
|
||||
ms: T,
|
||||
}
|
||||
|
||||
impl<T> Timestamp<T> {
|
||||
pub const fn from_ms(ms: T) -> Timestamp<T> {
|
||||
Timestamp { ms }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Timestamp<T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
pub fn ms(&self) -> T {
|
||||
self.ms
|
||||
}
|
||||
}
|
||||
|
||||
impl Timestamp<isize> {
|
||||
pub fn from_clock_value(value: ClockValue) -> Timestamp<isize> {
|
||||
Timestamp { ms: value.ms() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Timestamp<f64> {
|
||||
pub fn from_clock_value(value: ClockValue) -> Timestamp<f64> {
|
||||
Timestamp { ms: value.ms_f64() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Sub for Timestamp<T>
|
||||
where
|
||||
T: Sub<Output = T>,
|
||||
{
|
||||
type Output = Duration<T>;
|
||||
|
||||
fn sub(self, other: Timestamp<T>) -> Duration<T> {
|
||||
Duration::from_ms(self.ms - other.ms)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<Duration<T>> for Timestamp<T>
|
||||
where
|
||||
T: Copy + Add<Output = T>,
|
||||
{
|
||||
type Output = Timestamp<T>;
|
||||
|
||||
fn add(self, duration: Duration<T>) -> Timestamp<T> {
|
||||
Timestamp {
|
||||
ms: self.ms + duration.ms(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AddAssign<Duration<T>> for Timestamp<T>
|
||||
where
|
||||
T: Copy + AddAssign,
|
||||
{
|
||||
fn add_assign(&mut self, duration: Duration<T>) {
|
||||
self.ms += duration.ms();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct ActiveTimer {
|
||||
instant: u32,
|
||||
set_at: u32,
|
||||
}
|
||||
|
||||
/// Context for the time driver.
|
||||
/// You can create a context as follows:
|
||||
/// ```no_run
|
||||
/// # use libtock::result::TockResult;
|
||||
/// # async fn doc() -> TockResult<()> {
|
||||
/// let mut drivers = libtock::retrieve_drivers()?;
|
||||
/// let mut timer_context = drivers.timer;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[non_exhaustive]
|
||||
pub struct DriverContext {
|
||||
pub(crate) active_timer: Cell<Option<ActiveTimer>>,
|
||||
}
|
||||
|
||||
impl DriverContext {
|
||||
/// Create a driver timer from a context.
|
||||
pub fn create_timer_driver(&mut self) -> TimerDriver {
|
||||
TimerDriver {
|
||||
callback: Callback,
|
||||
context: self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_callback<CB>(&mut self, callback: CB) -> WithCallback<CB> {
|
||||
WithCallback {
|
||||
callback,
|
||||
clock_frequency: ClockFrequency { hz: 0 },
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Timer driver instance. You can create a TimerDriver from a DriverContext as follows:
|
||||
/// ```no_run
|
||||
/// # use libtock::result::TockResult;
|
||||
/// # async fn doc() -> TockResult<()> {
|
||||
/// # let mut drivers = libtock::retrieve_drivers()?;
|
||||
/// # let mut timer_context = drivers.timer;
|
||||
/// let mut timer_driver = timer_context.create_timer_driver();
|
||||
/// let timer_driver = timer_driver.activate()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct TimerDriver<'a> {
|
||||
callback: Callback,
|
||||
context: &'a DriverContext,
|
||||
}
|
||||
|
||||
struct Callback;
|
||||
|
||||
struct ParallelTimerConsumer;
|
||||
|
||||
impl<'a> Consumer<Callback> for ParallelTimerConsumer {
|
||||
fn consume(_: &mut Callback, _: usize, _: usize, _: usize) {}
|
||||
}
|
||||
|
||||
/// Activated time driver. Updates current time in the context and manages
|
||||
/// active alarms.
|
||||
/// Example usage (sleep for 1 second):
|
||||
/// ```no_run
|
||||
/// # use libtock::result::TockResult;
|
||||
/// # use libtock::timer::Duration;
|
||||
/// # async fn doc() -> TockResult<()> {
|
||||
/// # let mut drivers = libtock::retrieve_drivers()?;
|
||||
/// # let mut timer_driver = drivers.timer.create_timer_driver();
|
||||
/// let timer_driver = timer_driver.activate()?;
|
||||
/// timer_driver.sleep(Duration::from_ms(1000)).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct ParallelSleepDriver<'a> {
|
||||
_callback_subscription: CallbackSubscription<'a>,
|
||||
context: &'a DriverContext,
|
||||
}
|
||||
|
||||
impl<'a> TimerDriver<'a> {
|
||||
/// Activate the timer driver, will return a ParallelSleepDriver which
|
||||
/// can used to sleep.
|
||||
pub fn activate(&'a mut self) -> TockResult<ParallelSleepDriver<'a>> {
|
||||
let subscription = syscalls::subscribe::<ParallelTimerConsumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::SUBSCRIBE_CALLBACK,
|
||||
&mut self.callback,
|
||||
)?;
|
||||
let driver = ParallelSleepDriver {
|
||||
_callback_subscription: subscription,
|
||||
context: &self.context,
|
||||
};
|
||||
Ok(driver)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParallelSleepDriver<'a> {
|
||||
/// Sleep for the given duration
|
||||
pub async fn sleep(&self, duration: Duration<usize>) -> TockResult<()> {
|
||||
let now = get_current_ticks()?;
|
||||
let freq = get_clock_frequency()?;
|
||||
let alarm_instant = Self::compute_alarm_instant(duration.ms, now, freq)?;
|
||||
let this_alarm = ActiveTimer {
|
||||
instant: alarm_instant as u32,
|
||||
set_at: now as u32,
|
||||
};
|
||||
|
||||
let suspended_timer: Cell<Option<ActiveTimer>> = Cell::new(None);
|
||||
|
||||
futures::wait_until(|| {
|
||||
self.activate_current_timer(this_alarm, &suspended_timer)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn activate_timer(&self, timer: ActiveTimer) -> TockResult<()> {
|
||||
set_alarm_at(timer.instant as usize)?;
|
||||
let now = get_current_ticks()?;
|
||||
if !is_over(timer, now as u32) {
|
||||
self.context.active_timer.set(Some(timer));
|
||||
} else {
|
||||
self.wakeup_soon()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wakeup_soon(&self) -> TockResult<()> {
|
||||
self.context.active_timer.set(None);
|
||||
|
||||
for i in 0.. {
|
||||
let now = get_current_ticks()?;
|
||||
|
||||
let next_timer = ActiveTimer {
|
||||
instant: now as u32 + i,
|
||||
set_at: now as u32,
|
||||
};
|
||||
set_alarm_at(next_timer.instant as usize)?;
|
||||
let now = get_current_ticks()?;
|
||||
if !is_over(next_timer, now as u32) {
|
||||
break;
|
||||
} else {
|
||||
stop_alarm_at(next_timer.instant as usize)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_alarm_instant(
|
||||
duration_ms: usize,
|
||||
num_ticks: usize,
|
||||
freq: usize,
|
||||
) -> TockResult<usize> {
|
||||
let ticks = match duration_ms.checked_mul(freq) {
|
||||
Some(x) => x / 1000,
|
||||
None => {
|
||||
// Divide the largest of the two operands by 1000, to improve precision of the
|
||||
// result.
|
||||
if duration_ms > freq {
|
||||
match (duration_ms / 1000).checked_mul(freq) {
|
||||
Some(y) => y,
|
||||
None => {
|
||||
return Err(TockError::Other(OtherError::TimerDriverDurationOutOfRange))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match (freq / 1000).checked_mul(duration_ms) {
|
||||
Some(y) => y,
|
||||
None => {
|
||||
return Err(TockError::Other(OtherError::TimerDriverDurationOutOfRange))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let alarm_instant = num_ticks + ticks;
|
||||
Ok(alarm_instant)
|
||||
}
|
||||
|
||||
fn activate_current_timer(
|
||||
&self,
|
||||
this_alarm: ActiveTimer,
|
||||
suspended_timer: &Cell<Option<ActiveTimer>>,
|
||||
) -> TockResult<bool> {
|
||||
let now = get_current_ticks()?;
|
||||
|
||||
if let Some(active) = self.context.active_timer.get() {
|
||||
if left_is_later(active, this_alarm) {
|
||||
suspended_timer.set(Some(active));
|
||||
self.activate_timer(this_alarm)?;
|
||||
}
|
||||
} else {
|
||||
self.activate_timer(this_alarm)?;
|
||||
}
|
||||
if is_over(this_alarm, now as u32) {
|
||||
if let Some(paused) = suspended_timer.get() {
|
||||
self.activate_timer(paused)?;
|
||||
} else {
|
||||
self.context.active_timer.set(None);
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_ticks() -> TockResult<usize> {
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_VALUE, 0, 0).map_err(|err| err.into())
|
||||
}
|
||||
fn set_alarm_at(instant: usize) -> TockResult<()> {
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::SET_ALARM, instant, 0)
|
||||
.map(|_| ())
|
||||
.map_err(|err| err.into())
|
||||
}
|
||||
|
||||
fn stop_alarm_at(instant: usize) -> TockResult<()> {
|
||||
match syscalls::command(DRIVER_NUMBER, command_nr::STOP_ALARM, instant, 0) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => match error.return_code {
|
||||
EALREADY => Ok(()),
|
||||
_ => Err(TockError::Command(error)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_clock_frequency() -> TockResult<usize> {
|
||||
syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_FREQUENCY, 0, 0)
|
||||
.map_err(|err| err.into())
|
||||
}
|
||||
|
||||
fn is_over(timer: ActiveTimer, now: u32) -> bool {
|
||||
now.wrapping_sub(timer.set_at) >= timer.instant.wrapping_sub(timer.set_at)
|
||||
}
|
||||
|
||||
fn left_is_later(alarm_1: ActiveTimer, alarm_2: ActiveTimer) -> bool {
|
||||
if alarm_1.set_at <= alarm_1.instant && alarm_2.set_at <= alarm_2.instant {
|
||||
return alarm_1.instant > alarm_2.instant;
|
||||
}
|
||||
if alarm_1.set_at <= alarm_1.instant && alarm_2.set_at >= alarm_2.instant {
|
||||
return false;
|
||||
}
|
||||
if alarm_1.set_at >= alarm_1.instant && alarm_2.set_at <= alarm_2.instant {
|
||||
return true;
|
||||
}
|
||||
if alarm_1.set_at >= alarm_1.instant && alarm_2.set_at >= alarm_2.instant {
|
||||
return alarm_1.instant > alarm_2.instant;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
pub fn duration_bigger_than_frequency() {
|
||||
let x = ParallelSleepDriver::compute_alarm_instant(10000, 0, 1000)
|
||||
.ok()
|
||||
.unwrap();
|
||||
assert_eq!(x, 10000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn frequency_bigger_than_duration() {
|
||||
let x = ParallelSleepDriver::compute_alarm_instant(1000, 0, 10000)
|
||||
.ok()
|
||||
.unwrap();
|
||||
assert_eq!(x, 10000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn fails_if_duration_is_too_large() {
|
||||
let x =
|
||||
ParallelSleepDriver::compute_alarm_instant(core::usize::MAX, 0, core::usize::MAX - 1);
|
||||
assert!(x.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn fails_if_frequency_is_too_large() {
|
||||
let x =
|
||||
ParallelSleepDriver::compute_alarm_instant(core::usize::MAX - 1, 0, core::usize::MAX);
|
||||
assert!(x.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn alarm_before_systick_wrap_expired() {
|
||||
assert_eq!(
|
||||
super::is_over(
|
||||
super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32
|
||||
},
|
||||
3u32
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn alarm_before_systick_wrap_not_expired() {
|
||||
assert_eq!(
|
||||
super::is_over(
|
||||
super::ActiveTimer {
|
||||
instant: 3u32,
|
||||
set_at: 1u32
|
||||
},
|
||||
2u32
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn alarm_after_systick_wrap_expired() {
|
||||
assert_eq!(
|
||||
super::is_over(
|
||||
super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32
|
||||
},
|
||||
2u32
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn alarm_after_systick_wrap_time_before_systick_wrap_not_expired() {
|
||||
assert_eq!(
|
||||
super::is_over(
|
||||
super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32
|
||||
},
|
||||
4u32
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn alarm_after_systick_wrap_time_after_systick_wrap_not_expired() {
|
||||
assert_eq!(
|
||||
super::is_over(
|
||||
super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32
|
||||
},
|
||||
0u32
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn left_later_than_the_other_both_not_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 3u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn right_later_than_the_other_both_not_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 3u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn left_later_left_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn right_later_right_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 3u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn left_later_both_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn right_later_both_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 3u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn inequality_is_strict() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 2u32,
|
||||
set_at: 1u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn inequality_is_strict_wrapped() {
|
||||
let later = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 2u32,
|
||||
};
|
||||
let earlier = super::ActiveTimer {
|
||||
instant: 1u32,
|
||||
set_at: 2u32,
|
||||
};
|
||||
assert_eq!(super::left_is_later(later, earlier), false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user