mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-10-01 06:10:41 +00:00
Merge time-driver and time-queue-driver traits, make HALs own and handle the queue.
This commit is contained in:
parent
ec96395d08
commit
b268b1795f
2
.github/ci/test.sh
vendored
2
.github/ci/test.sh
vendored
@ -17,7 +17,7 @@ cargo test --manifest-path ./embassy-futures/Cargo.toml
|
|||||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver
|
cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver,embassy-time-queue-driver/generic-queue-8
|
||||||
cargo test --manifest-path ./embassy-time-driver/Cargo.toml
|
cargo test --manifest-path ./embassy-time-driver/Cargo.toml
|
||||||
|
|
||||||
cargo test --manifest-path ./embassy-boot/Cargo.toml
|
cargo test --manifest-path ./embassy-boot/Cargo.toml
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use core::cell::Cell;
|
use core::cell::{Cell, RefCell};
|
||||||
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
||||||
|
|
||||||
use critical_section::CriticalSection;
|
use critical_section::CriticalSection;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex;
|
use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex;
|
||||||
use embassy_time_driver::Driver;
|
use embassy_time_driver::Driver;
|
||||||
use embassy_time_queue_driver::GlobalTimerQueue;
|
use embassy_time_queue_driver::Queue;
|
||||||
|
|
||||||
use crate::interrupt::InterruptExt;
|
use crate::interrupt::InterruptExt;
|
||||||
use crate::{interrupt, pac};
|
use crate::{interrupt, pac};
|
||||||
@ -111,11 +111,13 @@ struct RtcDriver {
|
|||||||
period: AtomicU32,
|
period: AtomicU32,
|
||||||
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
|
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
|
||||||
alarms: Mutex<AlarmState>,
|
alarms: Mutex<AlarmState>,
|
||||||
|
queue: Mutex<RefCell<Queue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
||||||
period: AtomicU32::new(0),
|
period: AtomicU32::new(0),
|
||||||
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
|
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
|
||||||
|
queue: Mutex::new(RefCell::new(Queue::new())),
|
||||||
});
|
});
|
||||||
|
|
||||||
impl RtcDriver {
|
impl RtcDriver {
|
||||||
@ -194,59 +196,60 @@ impl RtcDriver {
|
|||||||
alarm.timestamp.set(u64::MAX);
|
alarm.timestamp.set(u64::MAX);
|
||||||
|
|
||||||
// Call after clearing alarm, so the callback can set another alarm.
|
// Call after clearing alarm, so the callback can set another alarm.
|
||||||
TIMER_QUEUE_DRIVER.dispatch();
|
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_alarm(&self, timestamp: u64) -> bool {
|
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
|
||||||
critical_section::with(|cs| {
|
let n = 0;
|
||||||
let n = 0;
|
let alarm = &self.alarms.borrow(cs);
|
||||||
let alarm = &self.alarms.borrow(cs);
|
alarm.timestamp.set(timestamp);
|
||||||
alarm.timestamp.set(timestamp);
|
|
||||||
|
|
||||||
let r = rtc();
|
let r = rtc();
|
||||||
|
|
||||||
let t = self.now();
|
let t = self.now();
|
||||||
if timestamp <= t {
|
if timestamp <= t {
|
||||||
// If alarm timestamp has passed the alarm will not fire.
|
// If alarm timestamp has passed the alarm will not fire.
|
||||||
// Disarm the alarm and return `false` to indicate that.
|
// Disarm the alarm and return `false` to indicate that.
|
||||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||||
|
|
||||||
alarm.timestamp.set(u64::MAX);
|
alarm.timestamp.set(u64::MAX);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it hasn't triggered yet, setup it in the compare channel.
|
// If it hasn't triggered yet, setup it in the compare channel.
|
||||||
|
|
||||||
// Write the CC value regardless of whether we're going to enable it now or not.
|
// Write the CC value regardless of whether we're going to enable it now or not.
|
||||||
// This way, when we enable it later, the right value is already set.
|
// This way, when we enable it later, the right value is already set.
|
||||||
|
|
||||||
// nrf52 docs say:
|
// nrf52 docs say:
|
||||||
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
|
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
|
||||||
// To workaround this, we never write a timestamp smaller than N+3.
|
// To workaround this, we never write a timestamp smaller than N+3.
|
||||||
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
|
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
|
||||||
//
|
//
|
||||||
// It is impossible for rtc to tick more than once because
|
// It is impossible for rtc to tick more than once because
|
||||||
// - this code takes less time than 1 tick
|
// - this code takes less time than 1 tick
|
||||||
// - it runs with interrupts disabled so nothing else can preempt it.
|
// - it runs with interrupts disabled so nothing else can preempt it.
|
||||||
//
|
//
|
||||||
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
||||||
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
||||||
// and we don't do that here.
|
// and we don't do that here.
|
||||||
let safe_timestamp = timestamp.max(t + 3);
|
let safe_timestamp = timestamp.max(t + 3);
|
||||||
r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF));
|
r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF));
|
||||||
|
|
||||||
let diff = timestamp - t;
|
let diff = timestamp - t;
|
||||||
if diff < 0xc00000 {
|
if diff < 0xc00000 {
|
||||||
r.intenset().write(|w| w.0 = compare_n(n));
|
r.intenset().write(|w| w.0 = compare_n(n));
|
||||||
} else {
|
} else {
|
||||||
// If it's too far in the future, don't setup the compare channel yet.
|
// If it's too far in the future, don't setup the compare channel yet.
|
||||||
// It will be setup later by `next_period`.
|
// It will be setup later by `next_period`.
|
||||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +261,19 @@ impl Driver for RtcDriver {
|
|||||||
let counter = rtc().counter().read().0;
|
let counter = rtc().counter().read().0;
|
||||||
calc_now(period, counter)
|
calc_now(period, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut queue = self.queue.borrow(cs).borrow_mut();
|
||||||
|
|
||||||
|
if queue.schedule_wake(at, waker) {
|
||||||
|
let mut next = queue.next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = queue.next_expiration(self.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "_nrf54l")]
|
#[cfg(feature = "_nrf54l")]
|
||||||
@ -277,8 +293,3 @@ fn RTC1() {
|
|||||||
pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
|
pub(crate) fn init(irq_prio: crate::interrupt::Priority) {
|
||||||
DRIVER.init(irq_prio)
|
DRIVER.init(irq_prio)
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_queue_driver::timer_queue_impl!(
|
|
||||||
static TIMER_QUEUE_DRIVER: GlobalTimerQueue
|
|
||||||
= GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
|
|
||||||
);
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
//! Timer driver.
|
//! Timer driver.
|
||||||
use core::cell::Cell;
|
use core::cell::{Cell, RefCell};
|
||||||
|
|
||||||
|
use critical_section::CriticalSection;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::blocking_mutex::Mutex;
|
use embassy_sync::blocking_mutex::Mutex;
|
||||||
use embassy_time_driver::Driver;
|
use embassy_time_driver::Driver;
|
||||||
use embassy_time_queue_driver::GlobalTimerQueue;
|
use embassy_time_queue_driver::Queue;
|
||||||
#[cfg(feature = "rp2040")]
|
#[cfg(feature = "rp2040")]
|
||||||
use pac::TIMER;
|
use pac::TIMER;
|
||||||
#[cfg(feature = "_rp235x")]
|
#[cfg(feature = "_rp235x")]
|
||||||
@ -20,12 +21,14 @@ unsafe impl Send for AlarmState {}
|
|||||||
|
|
||||||
struct TimerDriver {
|
struct TimerDriver {
|
||||||
alarms: Mutex<CriticalSectionRawMutex, AlarmState>,
|
alarms: Mutex<CriticalSectionRawMutex, AlarmState>,
|
||||||
|
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{
|
embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{
|
||||||
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState {
|
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState {
|
||||||
timestamp: Cell::new(0),
|
timestamp: Cell::new(0),
|
||||||
}),
|
}),
|
||||||
|
queue: Mutex::new(RefCell::new(Queue::new()))
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Driver for TimerDriver {
|
impl Driver for TimerDriver {
|
||||||
@ -39,34 +42,45 @@ impl Driver for TimerDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut queue = self.queue.borrow(cs).borrow_mut();
|
||||||
|
|
||||||
|
if queue.schedule_wake(at, waker) {
|
||||||
|
let mut next = queue.next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = queue.next_expiration(self.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimerDriver {
|
impl TimerDriver {
|
||||||
fn set_alarm(&self, timestamp: u64) -> bool {
|
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
|
||||||
let n = 0;
|
let n = 0;
|
||||||
critical_section::with(|cs| {
|
let alarm = &self.alarms.borrow(cs);
|
||||||
let alarm = &self.alarms.borrow(cs);
|
alarm.timestamp.set(timestamp);
|
||||||
alarm.timestamp.set(timestamp);
|
|
||||||
|
|
||||||
// Arm it.
|
// Arm it.
|
||||||
// Note that we're not checking the high bits at all. This means the irq may fire early
|
// Note that we're not checking the high bits at all. This means the irq may fire early
|
||||||
// if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire
|
// if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire
|
||||||
// it is checked if the alarm time has passed.
|
// it is checked if the alarm time has passed.
|
||||||
TIMER.alarm(n).write_value(timestamp as u32);
|
TIMER.alarm(n).write_value(timestamp as u32);
|
||||||
|
|
||||||
let now = self.now();
|
let now = self.now();
|
||||||
if timestamp <= now {
|
if timestamp <= now {
|
||||||
// If alarm timestamp has passed the alarm will not fire.
|
// If alarm timestamp has passed the alarm will not fire.
|
||||||
// Disarm the alarm and return `false` to indicate that.
|
// Disarm the alarm and return `false` to indicate that.
|
||||||
TIMER.armed().write(|w| w.set_armed(1 << n));
|
TIMER.armed().write(|w| w.set_armed(1 << n));
|
||||||
|
|
||||||
alarm.timestamp.set(u64::MAX);
|
alarm.timestamp.set(u64::MAX);
|
||||||
|
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_alarm(&self) {
|
fn check_alarm(&self) {
|
||||||
@ -75,7 +89,7 @@ impl TimerDriver {
|
|||||||
let alarm = &self.alarms.borrow(cs);
|
let alarm = &self.alarms.borrow(cs);
|
||||||
let timestamp = alarm.timestamp.get();
|
let timestamp = alarm.timestamp.get();
|
||||||
if timestamp <= self.now() {
|
if timestamp <= self.now() {
|
||||||
self.trigger_alarm()
|
self.trigger_alarm(cs)
|
||||||
} else {
|
} else {
|
||||||
// Not elapsed, arm it again.
|
// Not elapsed, arm it again.
|
||||||
// This can happen if it was set more than 2^32 us in the future.
|
// This can happen if it was set more than 2^32 us in the future.
|
||||||
@ -87,8 +101,11 @@ impl TimerDriver {
|
|||||||
TIMER.intr().write(|w| w.set_alarm(n, true));
|
TIMER.intr().write(|w| w.set_alarm(n, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_alarm(&self) {
|
fn trigger_alarm(&self, cs: CriticalSection) {
|
||||||
TIMER_QUEUE_DRIVER.dispatch();
|
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,8 +142,3 @@ fn TIMER_IRQ_0() {
|
|||||||
fn TIMER0_IRQ_0() {
|
fn TIMER0_IRQ_0() {
|
||||||
DRIVER.check_alarm()
|
DRIVER.check_alarm()
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_queue_driver::timer_queue_impl!(
|
|
||||||
static TIMER_QUEUE_DRIVER: GlobalTimerQueue
|
|
||||||
= GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
|
|
||||||
);
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::{Cell, RefCell};
|
||||||
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
||||||
|
|
||||||
use critical_section::CriticalSection;
|
use critical_section::CriticalSection;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::blocking_mutex::Mutex;
|
use embassy_sync::blocking_mutex::Mutex;
|
||||||
use embassy_time_driver::{Driver, TICK_HZ};
|
use embassy_time_driver::{Driver, TICK_HZ};
|
||||||
use embassy_time_queue_driver::GlobalTimerQueue;
|
use embassy_time_queue_driver::Queue;
|
||||||
use stm32_metapac::timer::{regs, TimGp16};
|
use stm32_metapac::timer::{regs, TimGp16};
|
||||||
|
|
||||||
use crate::interrupt::typelevel::Interrupt;
|
use crate::interrupt::typelevel::Interrupt;
|
||||||
@ -214,6 +214,7 @@ pub(crate) struct RtcDriver {
|
|||||||
alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
|
alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
|
||||||
#[cfg(feature = "low-power")]
|
#[cfg(feature = "low-power")]
|
||||||
rtc: Mutex<CriticalSectionRawMutex, Cell<Option<&'static Rtc>>>,
|
rtc: Mutex<CriticalSectionRawMutex, Cell<Option<&'static Rtc>>>,
|
||||||
|
queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
||||||
@ -221,6 +222,7 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
|||||||
alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
|
alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
|
||||||
#[cfg(feature = "low-power")]
|
#[cfg(feature = "low-power")]
|
||||||
rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)),
|
rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)),
|
||||||
|
queue: Mutex::new(RefCell::new(Queue::new()))
|
||||||
});
|
});
|
||||||
|
|
||||||
impl RtcDriver {
|
impl RtcDriver {
|
||||||
@ -266,8 +268,7 @@ impl RtcDriver {
|
|||||||
fn on_interrupt(&self) {
|
fn on_interrupt(&self) {
|
||||||
let r = regs_gp16();
|
let r = regs_gp16();
|
||||||
|
|
||||||
// XXX: reduce the size of this critical section ?
|
critical_section::with(|cs| {
|
||||||
critical_section::with(|_cs| {
|
|
||||||
let sr = r.sr().read();
|
let sr = r.sr().read();
|
||||||
let dier = r.dier().read();
|
let dier = r.dier().read();
|
||||||
|
|
||||||
@ -288,7 +289,7 @@ impl RtcDriver {
|
|||||||
|
|
||||||
let n = 0;
|
let n = 0;
|
||||||
if sr.ccif(n + 1) && dier.ccie(n + 1) {
|
if sr.ccif(n + 1) && dier.ccie(n + 1) {
|
||||||
self.trigger_alarm();
|
self.trigger_alarm(cs);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -315,8 +316,11 @@ impl RtcDriver {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_alarm(&self) {
|
fn trigger_alarm(&self, cs: CriticalSection) {
|
||||||
TIMER_QUEUE_DRIVER.dispatch();
|
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -366,9 +370,9 @@ impl RtcDriver {
|
|||||||
// Now, recompute alarm
|
// Now, recompute alarm
|
||||||
let alarm = self.alarm.borrow(cs);
|
let alarm = self.alarm.borrow(cs);
|
||||||
|
|
||||||
if !self.set_alarm(alarm.timestamp.get()) {
|
if !self.set_alarm(cs, alarm.timestamp.get()) {
|
||||||
// If the alarm timestamp has passed, we need to trigger it
|
// If the alarm timestamp has passed, we need to trigger it
|
||||||
self.trigger_alarm();
|
self.trigger_alarm(cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,49 +445,47 @@ impl RtcDriver {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_alarm(&self, timestamp: u64) -> bool {
|
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
|
||||||
critical_section::with(|cs| {
|
let r = regs_gp16();
|
||||||
let r = regs_gp16();
|
|
||||||
|
|
||||||
let n = 0;
|
let n = 0;
|
||||||
self.alarm.borrow(cs).timestamp.set(timestamp);
|
self.alarm.borrow(cs).timestamp.set(timestamp);
|
||||||
|
|
||||||
let t = self.now();
|
let t = self.now();
|
||||||
if timestamp <= t {
|
if timestamp <= t {
|
||||||
// If alarm timestamp has passed the alarm will not fire.
|
// If alarm timestamp has passed the alarm will not fire.
|
||||||
// Disarm the alarm and return `false` to indicate that.
|
// Disarm the alarm and return `false` to indicate that.
|
||||||
r.dier().modify(|w| w.set_ccie(n + 1, false));
|
r.dier().modify(|w| w.set_ccie(n + 1, false));
|
||||||
|
|
||||||
self.alarm.borrow(cs).timestamp.set(u64::MAX);
|
self.alarm.borrow(cs).timestamp.set(u64::MAX);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the CCR value regardless of whether we're going to enable it now or not.
|
// Write the CCR value regardless of whether we're going to enable it now or not.
|
||||||
// This way, when we enable it later, the right value is already set.
|
// This way, when we enable it later, the right value is already set.
|
||||||
r.ccr(n + 1).write(|w| w.set_ccr(timestamp as u16));
|
r.ccr(n + 1).write(|w| w.set_ccr(timestamp as u16));
|
||||||
|
|
||||||
// Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
|
// Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
|
||||||
let diff = timestamp - t;
|
let diff = timestamp - t;
|
||||||
r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000));
|
r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000));
|
||||||
|
|
||||||
// Reevaluate if the alarm timestamp is still in the future
|
// Reevaluate if the alarm timestamp is still in the future
|
||||||
let t = self.now();
|
let t = self.now();
|
||||||
if timestamp <= t {
|
if timestamp <= t {
|
||||||
// If alarm timestamp has passed since we set it, we have a race condition and
|
// If alarm timestamp has passed since we set it, we have a race condition and
|
||||||
// the alarm may or may not have fired.
|
// the alarm may or may not have fired.
|
||||||
// Disarm the alarm and return `false` to indicate that.
|
// Disarm the alarm and return `false` to indicate that.
|
||||||
// It is the caller's responsibility to handle this ambiguity.
|
// It is the caller's responsibility to handle this ambiguity.
|
||||||
r.dier().modify(|w| w.set_ccie(n + 1, false));
|
r.dier().modify(|w| w.set_ccie(n + 1, false));
|
||||||
|
|
||||||
self.alarm.borrow(cs).timestamp.set(u64::MAX);
|
self.alarm.borrow(cs).timestamp.set(u64::MAX);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're confident the alarm will ring in the future.
|
// We're confident the alarm will ring in the future.
|
||||||
true
|
true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,6 +498,19 @@ impl Driver for RtcDriver {
|
|||||||
let counter = r.cnt().read().cnt();
|
let counter = r.cnt().read().cnt();
|
||||||
calc_now(period, counter)
|
calc_now(period, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut queue = self.queue.borrow(cs).borrow_mut();
|
||||||
|
|
||||||
|
if queue.schedule_wake(at, waker) {
|
||||||
|
let mut next = queue.next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = queue.next_expiration(self.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "low-power")]
|
#[cfg(feature = "low-power")]
|
||||||
@ -506,8 +521,3 @@ pub(crate) fn get_driver() -> &'static RtcDriver {
|
|||||||
pub(crate) fn init(cs: CriticalSection) {
|
pub(crate) fn init(cs: CriticalSection) {
|
||||||
DRIVER.init(cs)
|
DRIVER.init(cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_queue_driver::timer_queue_impl!(
|
|
||||||
static TIMER_QUEUE_DRIVER: GlobalTimerQueue
|
|
||||||
= GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
|
|
||||||
);
|
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! use core::task::Waker;
|
||||||
|
//!
|
||||||
//! use embassy_time_driver::Driver;
|
//! use embassy_time_driver::Driver;
|
||||||
//!
|
//!
|
||||||
//! struct MyDriver{} // not public!
|
//! struct MyDriver{} // not public!
|
||||||
@ -46,6 +48,10 @@
|
|||||||
//! fn now(&self) -> u64 {
|
//! fn now(&self) -> u64 {
|
||||||
//! todo!()
|
//! todo!()
|
||||||
//! }
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn schedule_wake(&self, at: u64, waker: &Waker) {
|
||||||
|
//! todo!()
|
||||||
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
|
//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
|
||||||
@ -54,6 +60,8 @@
|
|||||||
//! ## Feature flags
|
//! ## Feature flags
|
||||||
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
||||||
|
|
||||||
|
use core::task::Waker;
|
||||||
|
|
||||||
mod tick;
|
mod tick;
|
||||||
|
|
||||||
/// Ticks per second of the global timebase.
|
/// Ticks per second of the global timebase.
|
||||||
@ -74,6 +82,10 @@ pub trait Driver: Send + Sync + 'static {
|
|||||||
/// you MUST extend them to 64-bit, for example by counting overflows in software,
|
/// you MUST extend them to 64-bit, for example by counting overflows in software,
|
||||||
/// or chaining multiple timers together.
|
/// or chaining multiple timers together.
|
||||||
fn now(&self) -> u64;
|
fn now(&self) -> u64;
|
||||||
|
|
||||||
|
/// Schedules a waker to be awoken at moment `at`.
|
||||||
|
/// If this moment is in the past, the waker might be awoken immediately.
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &Waker);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
@ -97,5 +109,10 @@ macro_rules! time_driver_impl {
|
|||||||
fn _embassy_time_now() -> u64 {
|
fn _embassy_time_now() -> u64 {
|
||||||
<$t as $crate::Driver>::now(&$name)
|
<$t as $crate::Driver>::now(&$name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) {
|
||||||
|
<$t as $crate::Driver>::schedule_wake(&$name, at, waker);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -49,23 +49,18 @@
|
|||||||
//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
|
//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use core::task::Waker;
|
||||||
|
|
||||||
#[cfg(not(feature = "integrated-timers"))]
|
#[cfg(not(feature = "integrated-timers"))]
|
||||||
pub mod queue_generic;
|
pub mod queue_generic;
|
||||||
#[cfg(feature = "integrated-timers")]
|
#[cfg(feature = "integrated-timers")]
|
||||||
pub mod queue_integrated;
|
pub mod queue_integrated;
|
||||||
|
|
||||||
use core::cell::RefCell;
|
#[cfg(feature = "integrated-timers")]
|
||||||
use core::task::Waker;
|
pub use queue_integrated::Queue;
|
||||||
|
|
||||||
use critical_section::Mutex;
|
#[cfg(not(feature = "integrated-timers"))]
|
||||||
|
pub use queue_generic::Queue;
|
||||||
/// Timer queue
|
|
||||||
pub trait TimerQueue {
|
|
||||||
/// Schedules a waker in the queue to be awoken at moment `at`.
|
|
||||||
///
|
|
||||||
/// If this moment is in the past, the waker might be awoken immediately.
|
|
||||||
fn schedule_wake(&'static self, at: u64, waker: &Waker);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
fn _embassy_time_schedule_wake(at: u64, waker: &Waker);
|
fn _embassy_time_schedule_wake(at: u64, waker: &Waker);
|
||||||
@ -73,7 +68,10 @@ extern "Rust" {
|
|||||||
|
|
||||||
/// Schedule the given waker to be woken at `at`.
|
/// Schedule the given waker to be woken at `at`.
|
||||||
pub fn schedule_wake(at: u64, waker: &Waker) {
|
pub fn schedule_wake(at: u64, waker: &Waker) {
|
||||||
#[cfg(feature = "integrated-timers")]
|
// This function is not implemented in embassy-time-driver because it needs access to executor
|
||||||
|
// internals. The function updates task state, then delegates to the implementation provided
|
||||||
|
// by the time driver.
|
||||||
|
#[cfg(not(feature = "_generic-queue"))]
|
||||||
{
|
{
|
||||||
use embassy_executor::raw::task_from_waker;
|
use embassy_executor::raw::task_from_waker;
|
||||||
use embassy_executor::raw::timer_queue::TimerEnqueueOperation;
|
use embassy_executor::raw::timer_queue::TimerEnqueueOperation;
|
||||||
@ -89,121 +87,3 @@ pub fn schedule_wake(at: u64, waker: &Waker) {
|
|||||||
}
|
}
|
||||||
unsafe { _embassy_time_schedule_wake(at, waker) }
|
unsafe { _embassy_time_schedule_wake(at, waker) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the TimerQueue implementation.
|
|
||||||
///
|
|
||||||
/// See the module documentation for an example.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! timer_queue_impl {
|
|
||||||
(static $name:ident: $t: ty = $val:expr) => {
|
|
||||||
static $name: $t = $val;
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) {
|
|
||||||
<$t as $crate::TimerQueue>::schedule_wake(&$name, at, waker);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
type InnerQueue = queue_integrated::TimerQueue;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "integrated-timers"))]
|
|
||||||
type InnerQueue = queue_generic::Queue;
|
|
||||||
|
|
||||||
/// A timer queue implementation that can be used as a global timer queue.
|
|
||||||
///
|
|
||||||
/// This implementation is not thread-safe, and should be protected by a mutex of some sort.
|
|
||||||
pub struct GenericTimerQueue<F: Fn(u64) -> bool> {
|
|
||||||
queue: InnerQueue,
|
|
||||||
set_alarm: F,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: Fn(u64) -> bool> GenericTimerQueue<F> {
|
|
||||||
/// Creates a new timer queue.
|
|
||||||
///
|
|
||||||
/// `set_alarm` is a function that should set the next alarm time. The function should
|
|
||||||
/// return `true` if the alarm was set, and `false` if the alarm was in the past.
|
|
||||||
pub const fn new(set_alarm: F) -> Self {
|
|
||||||
Self {
|
|
||||||
queue: InnerQueue::new(),
|
|
||||||
set_alarm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedules a task to run at a specific time, and returns whether any changes were made.
|
|
||||||
pub fn schedule_wake(&mut self, at: u64, waker: &core::task::Waker) {
|
|
||||||
#[cfg(feature = "integrated-timers")]
|
|
||||||
let waker = embassy_executor::raw::task_from_waker(waker);
|
|
||||||
|
|
||||||
if self.queue.schedule_wake(at, waker) {
|
|
||||||
self.dispatch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dequeues expired timers and returns the next alarm time.
|
|
||||||
pub fn next_expiration(&mut self, now: u64) -> u64 {
|
|
||||||
self.queue.next_expiration(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle the alarm.
|
|
||||||
///
|
|
||||||
/// Call this function when the next alarm is due.
|
|
||||||
pub fn dispatch(&mut self) {
|
|
||||||
let mut next_expiration = self.next_expiration(embassy_time_driver::now());
|
|
||||||
|
|
||||||
while !(self.set_alarm)(next_expiration) {
|
|
||||||
// next_expiration is in the past, dequeue and find a new expiration
|
|
||||||
next_expiration = self.next_expiration(next_expiration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`GenericTimerQueue`] protected by a critical section. Directly useable as a [`TimerQueue`].
|
|
||||||
pub struct GlobalTimerQueue {
|
|
||||||
inner: Mutex<RefCell<GenericTimerQueue<fn(u64) -> bool>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalTimerQueue {
|
|
||||||
/// Creates a new timer queue.
|
|
||||||
///
|
|
||||||
/// `set_alarm` is a function that should set the next alarm time. The function should
|
|
||||||
/// return `true` if the alarm was set, and `false` if the alarm was in the past.
|
|
||||||
pub const fn new(set_alarm: fn(u64) -> bool) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Mutex::new(RefCell::new(GenericTimerQueue::new(set_alarm))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedules a task to run at a specific time, and returns whether any changes were made.
|
|
||||||
pub fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut inner = self.inner.borrow_ref_mut(cs);
|
|
||||||
inner.schedule_wake(at, waker);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dequeues expired timers and returns the next alarm time.
|
|
||||||
pub fn next_expiration(&self, now: u64) -> u64 {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut inner = self.inner.borrow_ref_mut(cs);
|
|
||||||
inner.next_expiration(now)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle the alarm.
|
|
||||||
///
|
|
||||||
/// Call this function when the next alarm is due.
|
|
||||||
pub fn dispatch(&self) {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut inner = self.inner.borrow_ref_mut(cs);
|
|
||||||
inner.dispatch()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimerQueue for GlobalTimerQueue {
|
|
||||||
fn schedule_wake(&'static self, at: u64, waker: &Waker) {
|
|
||||||
GlobalTimerQueue::schedule_wake(self, at, waker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
//! Timer queue operations.
|
//! Timer queue operations.
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
use core::cmp::min;
|
use core::cmp::min;
|
||||||
|
use core::task::Waker;
|
||||||
|
|
||||||
use embassy_executor::raw::TaskRef;
|
use embassy_executor::raw::TaskRef;
|
||||||
|
|
||||||
/// A timer queue, with items integrated into tasks.
|
/// A timer queue, with items integrated into tasks.
|
||||||
pub struct TimerQueue {
|
pub struct Queue {
|
||||||
head: Cell<Option<TaskRef>>,
|
head: Cell<Option<TaskRef>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimerQueue {
|
impl Queue {
|
||||||
/// Creates a new timer queue.
|
/// Creates a new timer queue.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self { head: Cell::new(None) }
|
Self { head: Cell::new(None) }
|
||||||
@ -19,11 +20,12 @@ impl TimerQueue {
|
|||||||
///
|
///
|
||||||
/// If this function returns `true`, the called should find the next expiration time and set
|
/// If this function returns `true`, the called should find the next expiration time and set
|
||||||
/// a new alarm for that time.
|
/// a new alarm for that time.
|
||||||
pub fn schedule_wake(&mut self, at: u64, p: TaskRef) -> bool {
|
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
|
||||||
let item = p.timer_queue_item();
|
let task = embassy_executor::raw::task_from_waker(waker);
|
||||||
|
let item = task.timer_queue_item();
|
||||||
if item.next.get().is_none() {
|
if item.next.get().is_none() {
|
||||||
// If not in the queue, add it and update.
|
// If not in the queue, add it and update.
|
||||||
let prev = self.head.replace(Some(p));
|
let prev = self.head.replace(Some(task));
|
||||||
item.next.set(if prev.is_none() {
|
item.next.set(if prev.is_none() {
|
||||||
Some(unsafe { TaskRef::dangling() })
|
Some(unsafe { TaskRef::dangling() })
|
||||||
} else {
|
} else {
|
||||||
|
@ -384,7 +384,6 @@ tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" }
|
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" }
|
||||||
embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver" }
|
|
||||||
|
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
|
use core::task::Waker;
|
||||||
|
|
||||||
use critical_section::Mutex as CsMutex;
|
use critical_section::Mutex as CsMutex;
|
||||||
use embassy_time_driver::Driver;
|
use embassy_time_driver::Driver;
|
||||||
|
use embassy_time_queue_driver::Queue;
|
||||||
|
|
||||||
use crate::{Duration, Instant};
|
use crate::{Duration, Instant};
|
||||||
|
|
||||||
@ -52,50 +54,12 @@ impl MockDriver {
|
|||||||
/// Advances the time by the specified [`Duration`].
|
/// Advances the time by the specified [`Duration`].
|
||||||
/// Calling any alarm callbacks that are due.
|
/// Calling any alarm callbacks that are due.
|
||||||
pub fn advance(&self, duration: Duration) {
|
pub fn advance(&self, duration: Duration) {
|
||||||
let notify = {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut inner = self.0.borrow_ref_mut(cs);
|
|
||||||
|
|
||||||
inner.now += duration;
|
|
||||||
|
|
||||||
let now = inner.now.as_ticks();
|
|
||||||
|
|
||||||
if inner.alarm.timestamp <= now {
|
|
||||||
inner.alarm.timestamp = u64::MAX;
|
|
||||||
|
|
||||||
Some((inner.alarm.callback, inner.alarm.ctx))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((callback, ctx)) = notify {
|
|
||||||
(callback)(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures a callback to be called when the alarm fires.
|
|
||||||
pub fn set_alarm_callback(&self, callback: fn(*mut ()), ctx: *mut ()) {
|
|
||||||
critical_section::with(|cs| {
|
critical_section::with(|cs| {
|
||||||
let mut inner = self.0.borrow_ref_mut(cs);
|
let inner = &mut *self.0.borrow_ref_mut(cs);
|
||||||
|
|
||||||
inner.alarm.callback = callback;
|
inner.now += duration;
|
||||||
inner.alarm.ctx = ctx;
|
// wake expired tasks.
|
||||||
});
|
inner.queue.next_expiration(inner.now.as_ticks());
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the alarm to fire at the specified timestamp.
|
|
||||||
pub fn set_alarm(&self, timestamp: u64) -> bool {
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut inner = self.0.borrow_ref_mut(cs);
|
|
||||||
|
|
||||||
if timestamp <= inner.now.as_ticks() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
inner.alarm.timestamp = timestamp;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,44 +68,38 @@ impl Driver for MockDriver {
|
|||||||
fn now(&self) -> u64 {
|
fn now(&self) -> u64 {
|
||||||
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
|
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &Waker) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let inner = &mut *self.0.borrow_ref_mut(cs);
|
||||||
|
// enqueue it
|
||||||
|
inner.queue.schedule_wake(at, waker);
|
||||||
|
// wake it if it's in the past.
|
||||||
|
inner.queue.next_expiration(inner.now.as_ticks());
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerMockDriver {
|
struct InnerMockDriver {
|
||||||
now: Instant,
|
now: Instant,
|
||||||
alarm: AlarmState,
|
queue: Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InnerMockDriver {
|
impl InnerMockDriver {
|
||||||
const fn new() -> Self {
|
const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
now: Instant::from_ticks(0),
|
now: Instant::from_ticks(0),
|
||||||
alarm: AlarmState::new(),
|
queue: Queue::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AlarmState {
|
|
||||||
timestamp: u64,
|
|
||||||
callback: fn(*mut ()),
|
|
||||||
ctx: *mut (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlarmState {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp: u64::MAX,
|
|
||||||
callback: Self::noop,
|
|
||||||
ctx: core::ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn noop(_ctx: *mut ()) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for AlarmState {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::Wake;
|
||||||
|
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -163,24 +121,25 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_set_alarm_not_in_future() {
|
fn test_schedule_wake() {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
let driver = MockDriver::get();
|
static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false);
|
||||||
assert_eq!(false, driver.set_alarm(driver.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
struct MockWaker;
|
||||||
#[serial]
|
|
||||||
fn test_alarm() {
|
impl Wake for MockWaker {
|
||||||
setup();
|
fn wake(self: Arc<Self>) {
|
||||||
|
CALLBACK_CALLED.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let waker = Arc::new(MockWaker).into();
|
||||||
|
|
||||||
let driver = MockDriver::get();
|
let driver = MockDriver::get();
|
||||||
static mut CALLBACK_CALLED: bool = false;
|
|
||||||
driver.set_alarm_callback(|_| unsafe { CALLBACK_CALLED = true }, core::ptr::null_mut());
|
driver.schedule_wake(driver.now() + 1, &waker);
|
||||||
driver.set_alarm(driver.now() + 1);
|
assert_eq!(false, CALLBACK_CALLED.load(Ordering::Relaxed));
|
||||||
assert_eq!(false, unsafe { CALLBACK_CALLED });
|
|
||||||
driver.advance(Duration::from_secs(1));
|
driver.advance(Duration::from_secs(1));
|
||||||
assert_eq!(true, unsafe { CALLBACK_CALLED });
|
assert_eq!(true, CALLBACK_CALLED.load(Ordering::Relaxed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,97 +1,67 @@
|
|||||||
use std::cell::{RefCell, UnsafeCell};
|
use std::sync::{Condvar, Mutex};
|
||||||
use std::mem::MaybeUninit;
|
use std::thread;
|
||||||
use std::sync::{Condvar, Mutex, Once};
|
|
||||||
use std::time::{Duration as StdDuration, Instant as StdInstant};
|
use std::time::{Duration as StdDuration, Instant as StdInstant};
|
||||||
use std::{ptr, thread};
|
|
||||||
|
|
||||||
use critical_section::Mutex as CsMutex;
|
|
||||||
use embassy_time_driver::Driver;
|
use embassy_time_driver::Driver;
|
||||||
use embassy_time_queue_driver::GlobalTimerQueue;
|
use embassy_time_queue_driver::Queue;
|
||||||
|
|
||||||
struct AlarmState {
|
|
||||||
timestamp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for AlarmState {}
|
|
||||||
|
|
||||||
impl AlarmState {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self { timestamp: u64::MAX }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimeDriver {
|
struct TimeDriver {
|
||||||
once: Once,
|
signaler: Signaler,
|
||||||
// The STD Driver implementation requires the alarm's mutex to be reentrant, which the STD Mutex isn't
|
inner: Mutex<Inner>,
|
||||||
// Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections
|
}
|
||||||
// themselves are reentrant
|
|
||||||
alarm: UninitCell<CsMutex<RefCell<AlarmState>>>,
|
struct Inner {
|
||||||
zero_instant: UninitCell<StdInstant>,
|
zero_instant: Option<StdInstant>,
|
||||||
signaler: UninitCell<Signaler>,
|
queue: Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
||||||
once: Once::new(),
|
inner: Mutex::new(Inner{
|
||||||
alarm: UninitCell::uninit(),
|
zero_instant: None,
|
||||||
zero_instant: UninitCell::uninit(),
|
queue: Queue::new(),
|
||||||
signaler: UninitCell::uninit(),
|
}),
|
||||||
|
signaler: Signaler::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
impl TimeDriver {
|
impl Inner {
|
||||||
fn init(&self) {
|
fn init(&mut self) -> StdInstant {
|
||||||
self.once.call_once(|| unsafe {
|
*self.zero_instant.get_or_insert_with(|| {
|
||||||
self.alarm
|
thread::spawn(alarm_thread);
|
||||||
.write(CsMutex::new(RefCell::new(const { AlarmState::new() })));
|
StdInstant::now()
|
||||||
self.zero_instant.write(StdInstant::now());
|
})
|
||||||
self.signaler.write(Signaler::new());
|
|
||||||
|
|
||||||
thread::spawn(Self::alarm_thread);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alarm_thread() {
|
|
||||||
let zero = unsafe { DRIVER.zero_instant.read() };
|
|
||||||
loop {
|
|
||||||
let now = DRIVER.now();
|
|
||||||
|
|
||||||
let next_alarm = critical_section::with(|cs| {
|
|
||||||
let mut alarm = unsafe { DRIVER.alarm.as_ref() }.borrow_ref_mut(cs);
|
|
||||||
if alarm.timestamp <= now {
|
|
||||||
alarm.timestamp = u64::MAX;
|
|
||||||
|
|
||||||
TIMER_QUEUE_DRIVER.dispatch();
|
|
||||||
}
|
|
||||||
alarm.timestamp
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure we don't overflow
|
|
||||||
let until = zero
|
|
||||||
.checked_add(StdDuration::from_micros(next_alarm))
|
|
||||||
.unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1));
|
|
||||||
|
|
||||||
unsafe { DRIVER.signaler.as_ref() }.wait_until(until);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_alarm(&self, timestamp: u64) -> bool {
|
|
||||||
self.init();
|
|
||||||
critical_section::with(|cs| {
|
|
||||||
let mut alarm = unsafe { self.alarm.as_ref() }.borrow_ref_mut(cs);
|
|
||||||
alarm.timestamp = timestamp;
|
|
||||||
unsafe { self.signaler.as_ref() }.signal();
|
|
||||||
});
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Driver for TimeDriver {
|
impl Driver for TimeDriver {
|
||||||
fn now(&self) -> u64 {
|
fn now(&self) -> u64 {
|
||||||
self.init();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let zero = inner.init();
|
||||||
let zero = unsafe { self.zero_instant.read() };
|
|
||||||
StdInstant::now().duration_since(zero).as_micros() as u64
|
StdInstant::now().duration_since(zero).as_micros() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.init();
|
||||||
|
if inner.queue.schedule_wake(at, waker) {
|
||||||
|
self.signaler.signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alarm_thread() {
|
||||||
|
let zero = DRIVER.inner.lock().unwrap().zero_instant.unwrap();
|
||||||
|
loop {
|
||||||
|
let now = DRIVER.now();
|
||||||
|
|
||||||
|
let next_alarm = DRIVER.inner.lock().unwrap().queue.next_expiration(now);
|
||||||
|
|
||||||
|
// Ensure we don't overflow
|
||||||
|
let until = zero
|
||||||
|
.checked_add(StdDuration::from_micros(next_alarm))
|
||||||
|
.unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1));
|
||||||
|
|
||||||
|
DRIVER.signaler.wait_until(until);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Signaler {
|
struct Signaler {
|
||||||
@ -100,7 +70,7 @@ struct Signaler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signaler {
|
impl Signaler {
|
||||||
fn new() -> Self {
|
const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mutex: Mutex::new(false),
|
mutex: Mutex::new(false),
|
||||||
condvar: Condvar::new(),
|
condvar: Condvar::new(),
|
||||||
@ -132,40 +102,3 @@ impl Signaler {
|
|||||||
self.condvar.notify_one();
|
self.condvar.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
|
||||||
unsafe impl<T> Send for UninitCell<T> {}
|
|
||||||
unsafe impl<T> Sync for UninitCell<T> {}
|
|
||||||
|
|
||||||
impl<T> UninitCell<T> {
|
|
||||||
pub const fn uninit() -> Self {
|
|
||||||
Self(MaybeUninit::uninit())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn as_ptr(&self) -> *const T {
|
|
||||||
(*self.0.as_ptr()).get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
|
||||||
(*self.0.as_ptr()).get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn as_ref(&self) -> &T {
|
|
||||||
&*self.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn write(&self, val: T) {
|
|
||||||
ptr::write(self.as_mut_ptr(), val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy> UninitCell<T> {
|
|
||||||
pub unsafe fn read(&self) -> T {
|
|
||||||
ptr::read(self.as_mut_ptr())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
embassy_time_queue_driver::timer_queue_impl!(
|
|
||||||
static TIMER_QUEUE_DRIVER: GlobalTimerQueue
|
|
||||||
= GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
|
|
||||||
);
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use std::cell::UnsafeCell;
|
use std::sync::Mutex;
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
use std::ptr;
|
|
||||||
use std::sync::{Mutex, Once};
|
|
||||||
|
|
||||||
use embassy_time_driver::Driver;
|
use embassy_time_driver::Driver;
|
||||||
use embassy_time_queue_driver::GlobalTimerQueue;
|
use embassy_time_queue_driver::Queue;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_timer::Instant as StdInstant;
|
use wasm_timer::Instant as StdInstant;
|
||||||
|
|
||||||
@ -12,8 +9,6 @@ struct AlarmState {
|
|||||||
token: Option<f64>,
|
token: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for AlarmState {}
|
|
||||||
|
|
||||||
impl AlarmState {
|
impl AlarmState {
|
||||||
const fn new() -> Self {
|
const fn new() -> Self {
|
||||||
Self { token: None }
|
Self { token: None }
|
||||||
@ -27,33 +22,38 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct TimeDriver {
|
struct TimeDriver {
|
||||||
once: Once,
|
inner: Mutex<Inner>,
|
||||||
alarm: UninitCell<Mutex<AlarmState>>,
|
|
||||||
zero_instant: UninitCell<StdInstant>,
|
|
||||||
closure: UninitCell<Closure<dyn FnMut()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Inner {
|
||||||
|
alarm: AlarmState,
|
||||||
|
zero_instant: Option<StdInstant>,
|
||||||
|
queue: Queue,
|
||||||
|
closure: Option<Closure<dyn FnMut()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Inner {}
|
||||||
|
|
||||||
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
||||||
once: Once::new(),
|
inner: Mutex::new(Inner{
|
||||||
alarm: UninitCell::uninit(),
|
zero_instant: None,
|
||||||
zero_instant: UninitCell::uninit(),
|
queue: Queue::new(),
|
||||||
closure: UninitCell::uninit()
|
alarm: AlarmState::new(),
|
||||||
|
closure: None,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
impl TimeDriver {
|
impl Inner {
|
||||||
fn init(&self) {
|
fn init(&mut self) -> StdInstant {
|
||||||
self.once.call_once(|| unsafe {
|
*self.zero_instant.get_or_insert_with(StdInstant::now)
|
||||||
self.alarm.write(Mutex::new(const { AlarmState::new() }));
|
|
||||||
self.zero_instant.write(StdInstant::now());
|
|
||||||
self.closure
|
|
||||||
.write(Closure::new(Box::new(|| TIMER_QUEUE_DRIVER.dispatch())));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_alarm(&self, timestamp: u64) -> bool {
|
fn now(&mut self) -> u64 {
|
||||||
self.init();
|
StdInstant::now().duration_since(self.zero_instant.unwrap()).as_micros() as u64
|
||||||
let mut alarm = unsafe { self.alarm.as_ref() }.lock().unwrap();
|
}
|
||||||
if let Some(token) = alarm.token {
|
|
||||||
|
fn set_alarm(&mut self, timestamp: u64) -> bool {
|
||||||
|
if let Some(token) = self.alarm.token {
|
||||||
clearTimeout(token);
|
clearTimeout(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,8 @@ impl TimeDriver {
|
|||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
let timeout = (timestamp - now) as u32;
|
let timeout = (timestamp - now) as u32;
|
||||||
alarm.token = Some(setTimeout(unsafe { self.closure.as_ref() }, timeout / 1000));
|
let closure = self.closure.get_or_insert_with(|| Closure::new(dispatch));
|
||||||
|
self.alarm.token = Some(setTimeout(closure, timeout / 1000));
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -71,45 +72,32 @@ impl TimeDriver {
|
|||||||
|
|
||||||
impl Driver for TimeDriver {
|
impl Driver for TimeDriver {
|
||||||
fn now(&self) -> u64 {
|
fn now(&self) -> u64 {
|
||||||
self.init();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let zero = inner.init();
|
||||||
let zero = unsafe { self.zero_instant.read() };
|
|
||||||
StdInstant::now().duration_since(zero).as_micros() as u64
|
StdInstant::now().duration_since(zero).as_micros() as u64
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
unsafe impl<T> Send for UninitCell<T> {}
|
let mut inner = self.inner.lock().unwrap();
|
||||||
unsafe impl<T> Sync for UninitCell<T> {}
|
inner.init();
|
||||||
|
if inner.queue.schedule_wake(at, waker) {
|
||||||
impl<T> UninitCell<T> {
|
let now = inner.now();
|
||||||
pub const fn uninit() -> Self {
|
let mut next = inner.queue.next_expiration(now);
|
||||||
Self(MaybeUninit::uninit())
|
while !inner.set_alarm(next) {
|
||||||
}
|
let now = inner.now();
|
||||||
unsafe fn as_ptr(&self) -> *const T {
|
next = inner.queue.next_expiration(now);
|
||||||
(*self.0.as_ptr()).get()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
|
||||||
(*self.0.as_ptr()).get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn as_ref(&self) -> &T {
|
|
||||||
&*self.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn write(&self, val: T) {
|
|
||||||
ptr::write(self.as_mut_ptr(), val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Copy> UninitCell<T> {
|
fn dispatch() {
|
||||||
pub unsafe fn read(&self) -> T {
|
let inner = &mut *DRIVER.inner.lock().unwrap();
|
||||||
ptr::read(self.as_mut_ptr())
|
|
||||||
|
let now = inner.now();
|
||||||
|
let mut next = inner.queue.next_expiration(now);
|
||||||
|
while !inner.set_alarm(next) {
|
||||||
|
let now = inner.now();
|
||||||
|
next = inner.queue.next_expiration(now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
embassy_time_queue_driver::timer_queue_impl!(
|
|
||||||
static TIMER_QUEUE_DRIVER: GlobalTimerQueue
|
|
||||||
= GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
|
|
||||||
);
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user