time: allow storing state inside the driver struct.

This commit is contained in:
Dario Nieuwenhuis 2021-08-25 18:50:05 +02:00
parent 55b2d7b524
commit 7c0990ad1e
5 changed files with 218 additions and 197 deletions

View File

@ -82,7 +82,7 @@ impl AlarmState {
const ALARM_COUNT: usize = 3; const ALARM_COUNT: usize = 3;
struct State { struct RtcDriver {
/// Number of 2^23 periods elapsed since boot. /// Number of 2^23 periods elapsed since boot.
period: AtomicU32, period: AtomicU32,
alarm_count: AtomicU8, alarm_count: AtomicU8,
@ -91,13 +91,13 @@ struct State {
} }
const ALARM_STATE_NEW: AlarmState = AlarmState::new(); const ALARM_STATE_NEW: AlarmState = AlarmState::new();
static STATE: State = State { embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
period: AtomicU32::new(0), period: AtomicU32::new(0),
alarm_count: AtomicU8::new(0), alarm_count: AtomicU8::new(0),
alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]),
}; });
impl State { impl RtcDriver {
fn init(&'static self, irq_prio: crate::interrupt::Priority) { fn init(&'static self, irq_prio: crate::interrupt::Priority) {
let r = rtc(); let r = rtc();
r.cc[3].write(|w| unsafe { w.bits(0x800000) }); r.cc[3].write(|w| unsafe { w.bits(0x800000) });
@ -159,14 +159,6 @@ impl State {
}) })
} }
fn now(&self) -> u64 {
// `period` MUST be read before `counter`, see comment at the top for details.
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = rtc().counter.read().bits();
calc_now(period, counter)
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and // safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds. // we never create one that's out of bounds.
@ -188,8 +180,18 @@ impl State {
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get()); f(alarm.ctx.get());
} }
}
fn allocate_alarm(&self) -> Option<AlarmHandle> { impl Driver for RtcDriver {
fn now(&self) -> u64 {
// `period` MUST be read before `counter`, see comment at the top for details.
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = rtc().counter.read().bits();
calc_now(period, counter)
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self let id = self
.alarm_count .alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
@ -201,7 +203,7 @@ impl State {
}); });
match id { match id {
Ok(id) => Some(unsafe { AlarmHandle::new(id) }), Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None, Err(_) => None,
} }
} }
@ -263,32 +265,11 @@ impl State {
} }
} }
struct RtcDriver;
embassy::time_driver_impl!(RtcDriver);
impl Driver for RtcDriver {
fn now() -> u64 {
STATE.now()
}
unsafe fn allocate_alarm() -> Option<AlarmHandle> {
STATE.allocate_alarm()
}
fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
STATE.set_alarm_callback(alarm, callback, ctx)
}
fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
STATE.set_alarm(alarm, timestamp)
}
}
#[interrupt] #[interrupt]
fn RTC1() { fn RTC1() {
STATE.on_interrupt() DRIVER.on_interrupt()
} }
pub(crate) fn init(irq_prio: crate::interrupt::Priority) { pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
STATE.init(irq_prio) DRIVER.init(irq_prio)
} }

View File

@ -19,38 +19,40 @@ const DUMMY_ALARM: AlarmState = AlarmState {
callback: Cell::new(None), callback: Cell::new(None),
}; };
static ALARMS: Mutex<[AlarmState; ALARM_COUNT]> = Mutex::new([DUMMY_ALARM; ALARM_COUNT]); struct TimerDriver {
static NEXT_ALARM: AtomicU8 = AtomicU8::new(0); alarms: Mutex<[AlarmState; ALARM_COUNT]>,
next_alarm: AtomicU8,
}
fn now() -> u64 { embassy::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{
loop { alarms: Mutex::new([DUMMY_ALARM; ALARM_COUNT]),
unsafe { next_alarm: AtomicU8::new(0),
let hi = pac::TIMER.timerawh().read(); });
let lo = pac::TIMER.timerawl().read();
let hi2 = pac::TIMER.timerawh().read(); impl Driver for TimerDriver {
if hi == hi2 { fn now(&self) -> u64 {
return (hi as u64) << 32 | (lo as u64); loop {
unsafe {
let hi = pac::TIMER.timerawh().read();
let lo = pac::TIMER.timerawl().read();
let hi2 = pac::TIMER.timerawh().read();
if hi == hi2 {
return (hi as u64) << 32 | (lo as u64);
}
} }
} }
} }
}
struct TimerDriver; unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
embassy::time_driver_impl!(TimerDriver); let id = self
.next_alarm
impl Driver for TimerDriver { .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
fn now() -> u64 { if x < ALARM_COUNT as u8 {
now() Some(x + 1)
} } else {
None
unsafe fn allocate_alarm() -> Option<AlarmHandle> { }
let id = NEXT_ALARM.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { });
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id { match id {
Ok(id) => Some(AlarmHandle::new(id)), Ok(id) => Some(AlarmHandle::new(id)),
@ -58,18 +60,18 @@ impl Driver for TimerDriver {
} }
} }
fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
let n = alarm.id() as usize; let n = alarm.id() as usize;
critical_section::with(|cs| { critical_section::with(|cs| {
let alarm = &ALARMS.borrow(cs)[n]; let alarm = &self.alarms.borrow(cs)[n];
alarm.callback.set(Some((callback, ctx))); alarm.callback.set(Some((callback, ctx)));
}) })
} }
fn set_alarm(alarm: AlarmHandle, timestamp: u64) { fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
let n = alarm.id() as usize; let n = alarm.id() as usize;
critical_section::with(|cs| { critical_section::with(|cs| {
let alarm = &ALARMS.borrow(cs)[n]; let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(timestamp); alarm.timestamp.set(timestamp);
// Arm it. // Arm it.
@ -78,44 +80,46 @@ impl Driver for TimerDriver {
// it is checked if the alarm time has passed. // it is checked if the alarm time has passed.
unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) };
let now = now(); let now = self.now();
// If alarm timestamp has passed, trigger it instantly. // If alarm timestamp has passed, trigger it instantly.
// This disarms it. // This disarms it.
if timestamp <= now { if timestamp <= now {
trigger_alarm(n, cs); self.trigger_alarm(n, cs);
} }
}) })
} }
} }
fn check_alarm(n: usize) { impl TimerDriver {
critical_section::with(|cs| { fn check_alarm(&self, n: usize) {
let alarm = &ALARMS.borrow(cs)[n]; critical_section::with(|cs| {
let timestamp = alarm.timestamp.get(); let alarm = &self.alarms.borrow(cs)[n];
if timestamp <= now() { let timestamp = alarm.timestamp.get();
trigger_alarm(n, cs) if timestamp <= self.now() {
} else { self.trigger_alarm(n, cs)
// Not elapsed, arm it again. } else {
// This can happen if it was set more than 2^32 us in the future. // Not elapsed, arm it again.
unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; // This can happen if it was set more than 2^32 us in the future.
unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) };
}
});
// clear the irq
unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
// disarm
unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) }
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
if let Some((f, ctx)) = alarm.callback.get() {
f(ctx);
} }
});
// clear the irq
unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) }
}
fn trigger_alarm(n: usize, cs: CriticalSection) {
// disarm
unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) }
let alarm = &ALARMS.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
if let Some((f, ctx)) = alarm.callback.get() {
f(ctx);
} }
} }
@ -123,7 +127,7 @@ fn trigger_alarm(n: usize, cs: CriticalSection) {
pub unsafe fn init() { pub unsafe fn init() {
// init alarms // init alarms
critical_section::with(|cs| { critical_section::with(|cs| {
let alarms = ALARMS.borrow(cs); let alarms = DRIVER.alarms.borrow(cs);
for a in alarms { for a in alarms {
a.timestamp.set(u64::MAX); a.timestamp.set(u64::MAX);
} }
@ -144,20 +148,20 @@ pub unsafe fn init() {
#[interrupt] #[interrupt]
unsafe fn TIMER_IRQ_0() { unsafe fn TIMER_IRQ_0() {
check_alarm(0) DRIVER.check_alarm(0)
} }
#[interrupt] #[interrupt]
unsafe fn TIMER_IRQ_1() { unsafe fn TIMER_IRQ_1() {
check_alarm(1) DRIVER.check_alarm(1)
} }
#[interrupt] #[interrupt]
unsafe fn TIMER_IRQ_2() { unsafe fn TIMER_IRQ_2() {
check_alarm(2) DRIVER.check_alarm(2)
} }
#[interrupt] #[interrupt]
unsafe fn TIMER_IRQ_3() { unsafe fn TIMER_IRQ_3() {
check_alarm(3) DRIVER.check_alarm(3)
} }

View File

@ -26,12 +26,12 @@ type T = peripherals::TIM3;
#[cfg(feature = "time-driver-tim2")] #[cfg(feature = "time-driver-tim2")]
#[interrupt] #[interrupt]
fn TIM2() { fn TIM2() {
STATE.on_interrupt() DRIVER.on_interrupt()
} }
#[cfg(feature = "time-driver-tim3")] #[cfg(feature = "time-driver-tim3")]
#[interrupt] #[interrupt]
fn TIM3() { fn TIM3() {
STATE.on_interrupt() DRIVER.on_interrupt()
} }
// Clock timekeeping works with something we call "periods", which are time intervals // Clock timekeeping works with something we call "periods", which are time intervals
@ -76,7 +76,7 @@ impl AlarmState {
} }
} }
struct State { struct RtcDriver {
/// Number of 2^15 periods elapsed since boot. /// Number of 2^15 periods elapsed since boot.
period: AtomicU32, period: AtomicU32,
alarm_count: AtomicU8, alarm_count: AtomicU8,
@ -85,13 +85,14 @@ struct State {
} }
const ALARM_STATE_NEW: AlarmState = AlarmState::new(); const ALARM_STATE_NEW: AlarmState = AlarmState::new();
static STATE: State = State {
embassy::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
period: AtomicU32::new(0), period: AtomicU32::new(0),
alarm_count: AtomicU8::new(0), alarm_count: AtomicU8::new(0),
alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]), alarms: Mutex::new([ALARM_STATE_NEW; ALARM_COUNT]),
}; });
impl State { impl RtcDriver {
fn init(&'static self) { fn init(&'static self) {
let r = T::regs(); let r = T::regs();
@ -185,16 +186,6 @@ impl State {
}) })
} }
fn now(&self) -> u64 {
let r = T::regs();
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
// NOTE(unsafe) Atomic read with no side-effects
let counter = unsafe { r.cnt().read().cnt() };
calc_now(period, counter)
}
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
// safety: we're allowed to assume the AlarmState is created by us, and // safety: we're allowed to assume the AlarmState is created by us, and
// we never create one that's out of bounds. // we never create one that's out of bounds.
@ -213,8 +204,20 @@ impl State {
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get()); f(alarm.ctx.get());
} }
}
fn allocate_alarm(&self) -> Option<AlarmHandle> { impl Driver for RtcDriver {
fn now(&self) -> u64 {
let r = T::regs();
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
// NOTE(unsafe) Atomic read with no side-effects
let counter = unsafe { r.cnt().read().cnt() };
calc_now(period, counter)
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self let id = self
.alarm_count .alarm_count
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
@ -226,7 +229,7 @@ impl State {
}); });
match id { match id {
Ok(id) => Some(unsafe { AlarmHandle::new(id) }), Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None, Err(_) => None,
} }
} }
@ -269,29 +272,8 @@ impl State {
} }
} }
struct RtcDriver;
embassy::time_driver_impl!(RtcDriver);
impl Driver for RtcDriver {
fn now() -> u64 {
STATE.now()
}
unsafe fn allocate_alarm() -> Option<AlarmHandle> {
STATE.allocate_alarm()
}
fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
STATE.set_alarm_callback(alarm, callback, ctx)
}
fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
STATE.set_alarm(alarm, timestamp)
}
}
pub(crate) fn init() { pub(crate) fn init() {
STATE.init() DRIVER.init()
} }
// ------------------------------------------------------ // ------------------------------------------------------

View File

@ -0,0 +1,67 @@
use std::marker::PhantomData;
use std::sync::{Condvar, Mutex};
use super::{raw, Spawner};
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
signaler: &'static Signaler,
}
impl Executor {
pub fn new() -> Self {
let signaler = &*Box::leak(Box::new(Signaler::new()));
Self {
inner: raw::Executor::new(
|p| unsafe {
let s = &*(p as *const () as *const Signaler);
s.signal()
},
signaler as *const _ as _,
),
not_send: PhantomData,
signaler,
}
}
/// Runs the executor.
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(unsafe { self.inner.spawner() });
loop {
unsafe { self.inner.run_queued() };
self.signaler.wait()
}
}
}
struct Signaler {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Signaler {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
let mut signaled = self.mutex.lock().unwrap();
while !*signaled {
signaled = self.condvar.wait(signaled).unwrap();
}
*signaled = false;
}
fn signal(&self) {
let mut signaled = self.mutex.lock().unwrap();
*signaled = true;
self.condvar.notify_one();
}
}

View File

@ -26,32 +26,34 @@
//! This method has a few key advantages for something as foundational as timekeeping: //! This method has a few key advantages for something as foundational as timekeeping:
//! //!
//! - The time driver is available everywhere easily, without having to thread the implementation //! - The time driver is available everywhere easily, without having to thread the implementation
//~ through generic parameters. This is especially helpful for libraries. //! through generic parameters. This is especially helpful for libraries.
//! - It means comparing `Instant`s will always make sense: if there were multiple drivers //! - It means comparing `Instant`s will always make sense: if there were multiple drivers
//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which //! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
//! would yield incorrect results. //! would yield incorrect results.
//! //!
/// # Example //! # Example
/// //!
/// ``` //! ```
/// struct MyDriver; // not public! //! use embassy::time::driver::{Driver, AlarmHandle};
/// embassy::time_driver_impl!(MyDriver); //!
/// //! struct MyDriver{}; // not public!
/// unsafe impl embassy::time::driver::Driver for MyDriver { //! embassy::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
/// fn now() -> u64 { //!
/// todo!() //! impl Driver for MyDriver {
/// } //! fn now(&self) -> u64 {
/// unsafe fn allocate_alarm() -> Option<AlarmHandle> { //! todo!()
/// todo!() //! }
/// } //! unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { //! todo!()
/// todo!() //! }
/// } //! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) { //! todo!()
/// todo!() //! }
/// } //! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) {
/// } //! todo!()
/// ``` //! }
//! }
//! ```
/// Alarm handle, assigned by the driver. /// Alarm handle, assigned by the driver.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -76,22 +78,22 @@ impl AlarmHandle {
} }
/// Time driver /// Time driver
pub trait Driver { pub trait Driver: Send + Sync + 'static {
/// Return the current timestamp in ticks. /// Return the current timestamp in ticks.
/// This is guaranteed to be monotonic, i.e. a call to now() will always return /// This is guaranteed to be monotonic, i.e. a call to now() will always return
/// a greater or equal value than earler calls. /// a greater or equal value than earler calls.
fn now() -> u64; fn now(&self) -> u64;
/// Try allocating an alarm handle. Returns None if no alarms left. /// Try allocating an alarm handle. Returns None if no alarms left.
/// Initially the alarm has no callback set, and a null `ctx` pointer. /// Initially the alarm has no callback set, and a null `ctx` pointer.
/// ///
/// # Safety /// # Safety
/// It is UB to make the alarm fire before setting a callback. /// It is UB to make the alarm fire before setting a callback.
unsafe fn allocate_alarm() -> Option<AlarmHandle>; unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
/// Sets the callback function to be called when the alarm triggers. /// Sets the callback function to be called when the alarm triggers.
/// The callback may be called from any context (interrupt or thread mode). /// The callback may be called from any context (interrupt or thread mode).
fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
/// Sets an alarm at the given timestamp. When the current timestamp reaches that /// Sets an alarm at the given timestamp. When the current timestamp reaches that
/// timestamp, the provided callback funcion will be called. /// timestamp, the provided callback funcion will be called.
@ -99,7 +101,7 @@ pub trait Driver {
/// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
/// ///
/// Only one alarm can be active at a time. This overwrites any previously-set alarm if any. /// Only one alarm can be active at a time. This overwrites any previously-set alarm if any.
fn set_alarm(alarm: AlarmHandle, timestamp: u64); fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64);
} }
extern "Rust" { extern "Rust" {
@ -125,49 +127,34 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
/// Set the time Driver implementation. /// Set the time Driver implementation.
/// ///
/// # Example /// See the module documentation for an example.
///
/// ```
/// struct MyDriver; // not public!
/// embassy::time_driver_impl!(MyDriver);
///
/// unsafe impl embassy::time::driver::Driver for MyDriver {
/// fn now() -> u64 {
/// todo!()
/// }
/// unsafe fn allocate_alarm() -> Option<AlarmHandle> {
/// todo!()
/// }
/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
/// todo!()
/// }
/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
/// todo!()
/// }
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! time_driver_impl { macro_rules! time_driver_impl {
($t: ty) => { (static $name:ident: $t: ty = $val:expr) => {
static $name: $t = $val;
#[no_mangle] #[no_mangle]
fn _embassy_time_now() -> u64 { fn _embassy_time_now() -> u64 {
<$t as $crate::time::driver::Driver>::now() <$t as $crate::time::driver::Driver>::now(&$name)
} }
#[no_mangle] #[no_mangle]
unsafe fn _embassy_time_allocate_alarm() -> Option<AlarmHandle> { unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::time::driver::AlarmHandle> {
<$t as $crate::time::driver::Driver>::allocate_alarm() <$t as $crate::time::driver::Driver>::allocate_alarm(&$name)
} }
#[no_mangle] #[no_mangle]
fn _embassy_time_set_alarm_callback( fn _embassy_time_set_alarm_callback(
alarm: AlarmHandle, alarm: $crate::time::driver::AlarmHandle,
callback: fn(*mut ()), callback: fn(*mut ()),
ctx: *mut (), ctx: *mut (),
) { ) {
<$t as $crate::time::driver::Driver>::set_alarm_callback(alarm, callback, ctx) <$t as $crate::time::driver::Driver>::set_alarm_callback(&$name, alarm, callback, ctx)
} }
#[no_mangle] #[no_mangle]
fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) { fn _embassy_time_set_alarm(alarm: $crate::time::driver::AlarmHandle, timestamp: u64) {
<$t as $crate::time::driver::Driver>::set_alarm(alarm, timestamp) <$t as $crate::time::driver::Driver>::set_alarm(&$name, alarm, timestamp)
} }
}; };
} }