Refactor integrated-timers

This commit is contained in:
Dániel Buga 2024-11-26 23:54:21 +01:00
parent 406d377b75
commit 5a5495aac4
No known key found for this signature in database
32 changed files with 611 additions and 1182 deletions

2
.github/ci/test.sh vendored
View File

@ -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 generic-queue,mock-driver cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver
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

View File

@ -24,7 +24,9 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread,integrated-timers \
--- build --release --manifest-path embassy-sync/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \ --- build --release --manifest-path embassy-sync/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \ --- build --release --manifest-path embassy-time/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,defmt-timestamp-uptime,mock-driver \
--- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target xtensa-esp32s2-none-elf --features integrated-timers \
--- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target xtensa-esp32s2-none-elf --features generic-queue-8 \
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \

4
ci.sh
View File

@ -45,7 +45,9 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \
--- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target thumbv6m-none-eabi --features integrated-timers \
--- build --release --manifest-path embassy-time-queue-driver/Cargo.toml --target thumbv6m-none-eabi --features generic-queue-8 \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \

View File

@ -68,7 +68,7 @@ nightly = ["embassy-executor-macros/nightly"]
turbowakers = [] turbowakers = []
## Use the executor-integrated `embassy-time` timer queue. ## Use the executor-integrated `embassy-time` timer queue.
integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"] integrated-timers = ["dep:embassy-time-driver"]
#! ### Architecture #! ### Architecture
_arch = [] # some arch was picked _arch = [] # some arch was picked

View File

@ -53,10 +53,6 @@ mod thread {
/// ///
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
unsafe {
self.inner.initialize();
}
init(self.inner.spawner()); init(self.inner.spawner());
loop { loop {

View File

@ -98,9 +98,6 @@ mod thread {
/// ///
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
unsafe {
self.inner.initialize();
}
init(self.inner.spawner()); init(self.inner.spawner());
loop { loop {
@ -210,9 +207,6 @@ mod interrupt {
} }
let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
unsafe {
executor.initialize();
}
unsafe { NVIC::unmask(irq) } unsafe { NVIC::unmask(irq) }

View File

@ -54,10 +54,6 @@ mod thread {
/// ///
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
unsafe {
self.inner.initialize();
}
init(self.inner.spawner()); init(self.inner.spawner());
loop { loop {

View File

@ -48,10 +48,6 @@ mod thread {
/// ///
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
unsafe {
self.inner.initialize();
}
init(self.inner.spawner()); init(self.inner.spawner());
loop { loop {

View File

@ -55,10 +55,6 @@ mod thread {
/// ///
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
unsafe {
self.inner.initialize();
}
init(self.inner.spawner()); init(self.inner.spawner());
loop { loop {

View File

@ -70,10 +70,6 @@ mod thread {
/// - a `static mut` (unsafe) /// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
unsafe {
self.inner.initialize();
}
unsafe { unsafe {
let executor = &self.inner; let executor = &self.inner;
let future = Closure::new(move |_| { let future = Closure::new(move |_| {

View File

@ -17,7 +17,7 @@ mod run_queue;
mod state; mod state;
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
mod timer_queue; pub mod timer_queue;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
mod trace; mod trace;
pub(crate) mod util; pub(crate) mod util;
@ -31,9 +31,6 @@ use core::pin::Pin;
use core::ptr::NonNull; use core::ptr::NonNull;
use core::task::{Context, Poll}; use core::task::{Context, Poll};
#[cfg(feature = "integrated-timers")]
use embassy_time_driver::AlarmHandle;
use self::run_queue::{RunQueue, RunQueueItem}; use self::run_queue::{RunQueue, RunQueueItem};
use self::state::State; use self::state::State;
use self::util::{SyncUnsafeCell, UninitCell}; use self::util::{SyncUnsafeCell, UninitCell};
@ -47,8 +44,7 @@ pub(crate) struct TaskHeader {
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>, pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
#[cfg(feature = "integrated-timers")] /// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
pub(crate) expires_at: SyncUnsafeCell<u64>,
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
pub(crate) timer_queue_item: timer_queue::TimerQueueItem, pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
} }
@ -80,6 +76,12 @@ impl TaskRef {
unsafe { self.ptr.as_ref() } unsafe { self.ptr.as_ref() }
} }
/// Returns a reference to the executor that the task is currently running on.
#[cfg(feature = "integrated-timers")]
pub unsafe fn executor(self) -> Option<&'static Executor> {
self.header().executor.get().map(|e| Executor::wrap(e))
}
/// The returned pointer is valid for the entire TaskStorage. /// The returned pointer is valid for the entire TaskStorage.
pub(crate) fn as_ptr(self) -> *const TaskHeader { pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr() self.ptr.as_ptr()
@ -120,8 +122,6 @@ impl<F: Future + 'static> TaskStorage<F> {
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: SyncUnsafeCell::new(None), poll_fn: SyncUnsafeCell::new(None),
#[cfg(feature = "integrated-timers")]
expires_at: SyncUnsafeCell::new(0),
#[cfg(feature = "integrated-timers")] #[cfg(feature = "integrated-timers")]
timer_queue_item: timer_queue::TimerQueueItem::new(), timer_queue_item: timer_queue::TimerQueueItem::new(),
}, },
@ -160,9 +160,6 @@ impl<F: Future + 'static> TaskStorage<F> {
Poll::Ready(_) => { Poll::Ready(_) => {
this.future.drop_in_place(); this.future.drop_in_place();
this.raw.state.despawn(); this.raw.state.despawn();
#[cfg(feature = "integrated-timers")]
this.raw.expires_at.set(u64::MAX);
} }
Poll::Pending => {} Poll::Pending => {}
} }
@ -316,34 +313,16 @@ impl Pender {
pub(crate) struct SyncExecutor { pub(crate) struct SyncExecutor {
run_queue: RunQueue, run_queue: RunQueue,
pender: Pender, pender: Pender,
#[cfg(feature = "integrated-timers")]
pub(crate) timer_queue: timer_queue::TimerQueue,
#[cfg(feature = "integrated-timers")]
alarm: AlarmHandle,
} }
impl SyncExecutor { impl SyncExecutor {
pub(crate) fn new(pender: Pender) -> Self { pub(crate) fn new(pender: Pender) -> Self {
#[cfg(feature = "integrated-timers")]
let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) };
Self { Self {
run_queue: RunQueue::new(), run_queue: RunQueue::new(),
pender, pender,
#[cfg(feature = "integrated-timers")]
timer_queue: timer_queue::TimerQueue::new(),
#[cfg(feature = "integrated-timers")]
alarm,
} }
} }
pub(crate) unsafe fn initialize(&'static self) {
#[cfg(feature = "integrated-timers")]
embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ());
}
/// Enqueue a task in the task queue /// Enqueue a task in the task queue
/// ///
/// # Safety /// # Safety
@ -360,12 +339,6 @@ impl SyncExecutor {
} }
} }
#[cfg(feature = "integrated-timers")]
fn alarm_callback(ctx: *mut ()) {
let this: &Self = unsafe { &*(ctx as *const Self) };
this.pender.pend();
}
pub(super) unsafe fn spawn(&'static self, task: TaskRef) { pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.header().executor.set(Some(self)); task.header().executor.set(Some(self));
@ -379,56 +352,27 @@ impl SyncExecutor {
/// ///
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
pub(crate) unsafe fn poll(&'static self) { pub(crate) unsafe fn poll(&'static self) {
#[allow(clippy::never_loop)] self.run_queue.dequeue_all(|p| {
loop { let task = p.header();
#[cfg(feature = "integrated-timers")]
self.timer_queue
.dequeue_expired(embassy_time_driver::now(), wake_task_no_pend);
self.run_queue.dequeue_all(|p| { if !task.state.run_dequeue() {
let task = p.header(); // If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts
#[cfg(feature = "integrated-timers")] // - While task is being polled, it gets woken. It gets placed in the queue.
task.expires_at.set(u64::MAX); // - Task poll finishes, returning done=true
// - RUNNING bit is cleared, but the task is already in the queue.
if !task.state.run_dequeue() { return;
// If task is not running, ignore it. This can happen in the following scenario:
// - Task gets dequeued, poll starts
// - While task is being polled, it gets woken. It gets placed in the queue.
// - Task poll finishes, returning done=true
// - RUNNING bit is cleared, but the task is already in the queue.
return;
}
#[cfg(feature = "trace")]
trace::task_exec_begin(self, &p);
// Run the task
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "trace")]
trace::task_exec_end(self, &p);
// Enqueue or update into timer_queue
#[cfg(feature = "integrated-timers")]
self.timer_queue.update(p);
});
#[cfg(feature = "integrated-timers")]
{
// If this is already in the past, set_alarm might return false
// In that case do another poll loop iteration.
let next_expiration = self.timer_queue.next_expiration();
if embassy_time_driver::set_alarm(self.alarm, next_expiration) {
break;
}
} }
#[cfg(not(feature = "integrated-timers"))] #[cfg(feature = "trace")]
{ trace::task_exec_begin(self, &p);
break;
} // Run the task
} task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "trace")]
trace::task_exec_end(self, &p);
});
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
trace::executor_idle(self) trace::executor_idle(self)
@ -494,15 +438,6 @@ impl Executor {
} }
} }
/// Initializes the executor.
///
/// # Safety
///
/// This function must be called once before any other method is called.
pub unsafe fn initialize(&'static self) {
self.inner.initialize();
}
/// Spawn a task in this executor. /// Spawn a task in this executor.
/// ///
/// # Safety /// # Safety
@ -575,21 +510,3 @@ pub fn wake_task_no_pend(task: TaskRef) {
} }
} }
} }
#[cfg(feature = "integrated-timers")]
struct TimerQueue;
#[cfg(feature = "integrated-timers")]
impl embassy_time_queue_driver::TimerQueue for TimerQueue {
fn schedule_wake(&'static self, at: u64, waker: &core::task::Waker) {
let task = waker::task_from_waker(waker);
let task = task.header();
unsafe {
let expires_at = task.expires_at.get();
task.expires_at.set(expires_at.min(at));
}
}
}
#[cfg(feature = "integrated-timers")]
embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue);

View File

@ -1,75 +1,100 @@
//! Timer queue operations.
use core::cmp::min; use core::cmp::min;
use super::util::SyncUnsafeCell;
use super::TaskRef; use super::TaskRef;
use crate::raw::util::SyncUnsafeCell;
pub(crate) struct TimerQueueItem { pub(crate) struct TimerQueueItem {
next: SyncUnsafeCell<Option<TaskRef>>, next: SyncUnsafeCell<Option<TaskRef>>,
expires_at: SyncUnsafeCell<u64>,
} }
impl TimerQueueItem { impl TimerQueueItem {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
next: SyncUnsafeCell::new(None), next: SyncUnsafeCell::new(None),
expires_at: SyncUnsafeCell::new(0),
} }
} }
} }
pub(crate) struct TimerQueue { /// A timer queue, with items integrated into tasks.
pub struct TimerQueue {
head: SyncUnsafeCell<Option<TaskRef>>, head: SyncUnsafeCell<Option<TaskRef>>,
} }
impl TimerQueue { impl TimerQueue {
/// Creates a new timer queue.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
head: SyncUnsafeCell::new(None), head: SyncUnsafeCell::new(None),
} }
} }
pub(crate) unsafe fn update(&self, p: TaskRef) { /// Schedules a task to run at a specific time.
let task = p.header(); ///
if task.expires_at.get() != u64::MAX { /// If this function returns `true`, the called should find the next expiration time and set
/// a new alarm for that time.
pub fn schedule_wake(&mut self, at: u64, p: TaskRef) -> bool {
unsafe {
let task = p.header();
let item = &task.timer_queue_item;
if task.state.timer_enqueue() { if task.state.timer_enqueue() {
task.timer_queue_item.next.set(self.head.get()); // If not in the queue, add it and update.
self.head.set(Some(p)); let prev = self.head.replace(Some(p));
item.next.set(prev);
} else if at <= item.expires_at.get() {
// If expiration is sooner than previously set, update.
} else {
// Task does not need to be updated.
return false;
} }
item.expires_at.set(at);
true
} }
} }
pub(crate) unsafe fn next_expiration(&self) -> u64 { /// Dequeues expired timers and returns the next alarm time.
let mut res = u64::MAX; ///
self.retain(|p| { /// The provided callback will be called for each expired task. Tasks that never expire
let task = p.header(); /// will be removed, but the callback will not be called.
let expires = task.expires_at.get(); pub fn next_expiration(&mut self, now: u64) -> u64 {
res = min(res, expires); let mut next_expiration = u64::MAX;
expires != u64::MAX
});
res
}
pub(crate) unsafe fn dequeue_expired(&self, now: u64, on_task: impl Fn(TaskRef)) {
self.retain(|p| { self.retain(|p| {
let task = p.header(); let task = p.header();
if task.expires_at.get() <= now { let item = &task.timer_queue_item;
on_task(p); let expires = unsafe { item.expires_at.get() };
if expires <= now {
// Timer expired, process task.
super::wake_task(p);
false false
} else { } else {
true // Timer didn't yet expire, or never expires.
next_expiration = min(next_expiration, expires);
expires != u64::MAX
} }
}); });
next_expiration
} }
pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head; unsafe {
while let Some(p) = prev.get() { let mut prev = &self.head;
let task = p.header(); while let Some(p) = prev.get() {
if f(p) { let task = p.header();
// Skip to next let item = &task.timer_queue_item;
prev = &task.timer_queue_item.next; if f(p) {
} else { // Skip to next
// Remove it prev = &item.next;
prev.set(task.timer_queue_item.next.get()); } else {
task.state.timer_dequeue(); // Remove it
prev.set(item.next.get());
task.state.timer_dequeue();
}
} }
} }
} }

View File

@ -54,4 +54,9 @@ impl<T> SyncUnsafeCell<T> {
{ {
*self.value.get() *self.value.get()
} }
#[cfg(feature = "integrated-timers")]
pub unsafe fn replace(&self, value: T) -> T {
core::mem::replace(&mut *self.value.get(), value)
}
} }

View File

@ -40,9 +40,6 @@ fn setup() -> (&'static Executor, Trace) {
let trace = Trace::new(); let trace = Trace::new();
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
let executor = &*Box::leak(Box::new(Executor::new(context))); let executor = &*Box::leak(Box::new(Executor::new(context)));
unsafe {
executor.initialize();
}
(executor, trace) (executor, trace)
} }

View File

@ -119,7 +119,7 @@ _nrf52 = ["_ppi"]
_nrf51 = ["_ppi"] _nrf51 = ["_ppi"]
_nrf91 = [] _nrf91 = []
_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768"] _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-driver"]
# trustzone state. # trustzone state.
_s = [] _s = []
@ -135,6 +135,7 @@ _nrf52832_anomaly_109 = []
[dependencies] [dependencies]
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true }
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
embassy-sync = { version = "0.6.1", path = "../embassy-sync" } embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }

View File

@ -1,11 +1,11 @@
use core::cell::Cell; use core::cell::Cell;
use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
use core::{mem, ptr};
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::{AlarmHandle, Driver}; use embassy_time_driver::Driver;
use embassy_time_queue_driver::GlobalTimerQueue;
use crate::interrupt::InterruptExt; use crate::interrupt::InterruptExt;
use crate::{interrupt, pac}; use crate::{interrupt, pac};
@ -94,11 +94,6 @@ mod test {
struct AlarmState { struct AlarmState {
timestamp: Cell<u64>, timestamp: Cell<u64>,
// This is really a Option<(fn(*mut ()), *mut ())>
// but fn pointers aren't allowed in const yet
callback: Cell<*const ()>,
ctx: Cell<*mut ()>,
} }
unsafe impl Send for AlarmState {} unsafe impl Send for AlarmState {}
@ -107,26 +102,20 @@ impl AlarmState {
const fn new() -> Self { const fn new() -> Self {
Self { Self {
timestamp: Cell::new(u64::MAX), timestamp: Cell::new(u64::MAX),
callback: Cell::new(ptr::null()),
ctx: Cell::new(ptr::null_mut()),
} }
} }
} }
const ALARM_COUNT: usize = 3;
struct RtcDriver { 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,
/// 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; ALARM_COUNT]>, alarms: Mutex<AlarmState>,
} }
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),
alarm_count: AtomicU8::new(0), alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [const {AlarmState::new()}; ALARM_COUNT]),
}); });
impl RtcDriver { impl RtcDriver {
@ -169,13 +158,12 @@ impl RtcDriver {
self.next_period(); self.next_period();
} }
for n in 0..ALARM_COUNT { let n = 0;
if r.events_compare(n).read() == 1 { if r.events_compare(n).read() == 1 {
r.events_compare(n).write_value(0); r.events_compare(n).write_value(0);
critical_section::with(|cs| { critical_section::with(|cs| {
self.trigger_alarm(n, cs); self.trigger_alarm(cs);
}) });
}
} }
} }
@ -186,75 +174,33 @@ impl RtcDriver {
self.period.store(period, Ordering::Relaxed); self.period.store(period, Ordering::Relaxed);
let t = (period as u64) << 23; let t = (period as u64) << 23;
for n in 0..ALARM_COUNT { let n = 0;
let alarm = &self.alarms.borrow(cs)[n]; let alarm = &self.alarms.borrow(cs);
let at = alarm.timestamp.get(); let at = alarm.timestamp.get();
if at < t + 0xc00000 { if at < t + 0xc00000 {
// just enable it. `set_alarm` has already set the correct CC val. // just enable it. `set_alarm` has already set the correct CC val.
r.intenset().write(|w| w.0 = compare_n(n)); r.intenset().write(|w| w.0 = compare_n(n));
}
} }
}) })
} }
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { fn trigger_alarm(&self, cs: CriticalSection) {
// safety: we're allowed to assume the AlarmState is created by us, and let n = 0;
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let r = rtc(); let r = rtc();
r.intenclr().write(|w| w.0 = compare_n(n)); r.intenclr().write(|w| w.0 = compare_n(n));
let alarm = &self.alarms.borrow(cs)[n]; let alarm = &self.alarms.borrow(cs);
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();
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
}
}
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().0;
calc_now(period, counter)
} }
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { fn set_alarm(&self, timestamp: u64) -> bool {
critical_section::with(|_| {
let id = self.alarm_count.load(Ordering::Relaxed);
if id < ALARM_COUNT as u8 {
self.alarm_count.store(id + 1, Ordering::Relaxed);
Some(AlarmHandle::new(id))
} else {
None
}
})
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| { critical_section::with(|cs| {
let alarm = self.get_alarm(cs, alarm); let n = 0;
let alarm = &self.alarms.borrow(cs);
alarm.callback.set(callback as *const ());
alarm.ctx.set(ctx);
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
critical_section::with(|cs| {
let n = alarm.id() as _;
let alarm = self.get_alarm(cs, alarm);
alarm.timestamp.set(timestamp); alarm.timestamp.set(timestamp);
let r = rtc(); let r = rtc();
@ -304,6 +250,16 @@ impl Driver for RtcDriver {
} }
} }
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().0;
calc_now(period, counter)
}
}
#[cfg(feature = "_nrf54l")] #[cfg(feature = "_nrf54l")]
#[cfg(feature = "rt")] #[cfg(feature = "rt")]
#[interrupt] #[interrupt]
@ -321,3 +277,8 @@ 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))
);

View File

@ -40,7 +40,7 @@ critical-section-impl = ["critical-section/restore-state-u8"]
unstable-pac = [] unstable-pac = []
## Enable the timer for use with `embassy-time` with a 1MHz tick rate. ## Enable the timer for use with `embassy-time` with a 1MHz tick rate.
time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000"] time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000", "dep:embassy-time-queue-driver"]
## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls. ## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls.
rom-func-cache = [] rom-func-cache = []
@ -110,6 +110,7 @@ binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"]
[dependencies] [dependencies]
embassy-sync = { version = "0.6.1", path = "../embassy-sync" } embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true }
embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-time = { version = "0.3.2", path = "../embassy-time" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }

View File

@ -1,11 +1,10 @@
//! Timer driver. //! Timer driver.
use core::cell::Cell; use core::cell::Cell;
use atomic_polyfill::{AtomicU8, Ordering};
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::{AlarmHandle, Driver}; use embassy_time_driver::Driver;
use embassy_time_queue_driver::GlobalTimerQueue;
#[cfg(feature = "rp2040")] #[cfg(feature = "rp2040")]
use pac::TIMER; use pac::TIMER;
#[cfg(feature = "_rp235x")] #[cfg(feature = "_rp235x")]
@ -16,23 +15,17 @@ use crate::{interrupt, pac};
struct AlarmState { struct AlarmState {
timestamp: Cell<u64>, timestamp: Cell<u64>,
callback: Cell<Option<(fn(*mut ()), *mut ())>>,
} }
unsafe impl Send for AlarmState {} unsafe impl Send for AlarmState {}
const ALARM_COUNT: usize = 4;
struct TimerDriver { struct TimerDriver {
alarms: Mutex<CriticalSectionRawMutex, [AlarmState; ALARM_COUNT]>, alarms: Mutex<CriticalSectionRawMutex, AlarmState>,
next_alarm: AtomicU8,
} }
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(), [const{AlarmState { alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState {
timestamp: Cell::new(0), timestamp: Cell::new(0),
callback: Cell::new(None), }),
}}; ALARM_COUNT]),
next_alarm: AtomicU8::new(0),
}); });
impl Driver for TimerDriver { impl Driver for TimerDriver {
@ -46,34 +39,13 @@ impl Driver for TimerDriver {
} }
} }
} }
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { impl TimerDriver {
let id = self.next_alarm.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { fn set_alarm(&self, timestamp: u64) -> bool {
if x < ALARM_COUNT as u8 { let n = 0;
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
let n = alarm.id() as usize;
critical_section::with(|cs| { critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs)[n]; let alarm = &self.alarms.borrow(cs);
alarm.callback.set(Some((callback, ctx)));
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
let n = alarm.id() as usize;
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(timestamp); alarm.timestamp.set(timestamp);
// Arm it. // Arm it.
@ -96,15 +68,14 @@ impl Driver for TimerDriver {
} }
}) })
} }
}
impl TimerDriver { fn check_alarm(&self) {
fn check_alarm(&self, n: usize) { let n = 0;
critical_section::with(|cs| { critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs)[n]; 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(n, cs) self.trigger_alarm()
} 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.
@ -116,17 +87,8 @@ impl TimerDriver {
TIMER.intr().write(|w| w.set_alarm(n, true)); TIMER.intr().write(|w| w.set_alarm(n, true));
} }
fn trigger_alarm(&self, n: usize, cs: CriticalSection) { fn trigger_alarm(&self) {
// disarm TIMER_QUEUE_DRIVER.dispatch();
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);
}
} }
} }
@ -134,79 +96,37 @@ impl TimerDriver {
pub unsafe fn init() { pub unsafe fn init() {
// init alarms // init alarms
critical_section::with(|cs| { critical_section::with(|cs| {
let alarms = DRIVER.alarms.borrow(cs); let alarm = DRIVER.alarms.borrow(cs);
for a in alarms { alarm.timestamp.set(u64::MAX);
a.timestamp.set(u64::MAX);
}
}); });
// enable all irqs // enable irq
TIMER.inte().write(|w| { TIMER.inte().write(|w| {
w.set_alarm(0, true); w.set_alarm(0, true);
w.set_alarm(1, true);
w.set_alarm(2, true);
w.set_alarm(3, true);
}); });
#[cfg(feature = "rp2040")] #[cfg(feature = "rp2040")]
{ {
interrupt::TIMER_IRQ_0.enable(); interrupt::TIMER_IRQ_0.enable();
interrupt::TIMER_IRQ_1.enable();
interrupt::TIMER_IRQ_2.enable();
interrupt::TIMER_IRQ_3.enable();
} }
#[cfg(feature = "_rp235x")] #[cfg(feature = "_rp235x")]
{ {
interrupt::TIMER0_IRQ_0.enable(); interrupt::TIMER0_IRQ_0.enable();
interrupt::TIMER0_IRQ_1.enable();
interrupt::TIMER0_IRQ_2.enable();
interrupt::TIMER0_IRQ_3.enable();
} }
} }
#[cfg(all(feature = "rt", feature = "rp2040"))] #[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt] #[interrupt]
fn TIMER_IRQ_0() { fn TIMER_IRQ_0() {
DRIVER.check_alarm(0) DRIVER.check_alarm()
}
#[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt]
fn TIMER_IRQ_1() {
DRIVER.check_alarm(1)
}
#[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt]
fn TIMER_IRQ_2() {
DRIVER.check_alarm(2)
}
#[cfg(all(feature = "rt", feature = "rp2040"))]
#[interrupt]
fn TIMER_IRQ_3() {
DRIVER.check_alarm(3)
} }
#[cfg(all(feature = "rt", feature = "_rp235x"))] #[cfg(all(feature = "rt", feature = "_rp235x"))]
#[interrupt] #[interrupt]
fn TIMER0_IRQ_0() { fn TIMER0_IRQ_0() {
DRIVER.check_alarm(0) DRIVER.check_alarm()
} }
#[cfg(all(feature = "rt", feature = "_rp235x"))] embassy_time_queue_driver::timer_queue_impl!(
#[interrupt] static TIMER_QUEUE_DRIVER: GlobalTimerQueue
fn TIMER0_IRQ_1() { = GlobalTimerQueue::new(|next_expiration| DRIVER.set_alarm(next_expiration))
DRIVER.check_alarm(1) );
}
#[cfg(all(feature = "rt", feature = "_rp235x"))]
#[interrupt]
fn TIMER0_IRQ_2() {
DRIVER.check_alarm(2)
}
#[cfg(all(feature = "rt", feature = "_rp235x"))]
#[interrupt]
fn TIMER0_IRQ_3() {
DRIVER.check_alarm(3)
}

View File

@ -45,6 +45,7 @@ rustdoc-args = ["--cfg", "docsrs"]
embassy-sync = { version = "0.6.1", path = "../embassy-sync" } embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] }
embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false } embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false }
@ -125,6 +126,7 @@ defmt = [
exti = [] exti = []
low-power = [ "dep:embassy-executor", "embassy-executor?/arch-cortex-m", "time" ] low-power = [ "dep:embassy-executor", "embassy-executor?/arch-cortex-m", "time" ]
low-power-debug-with-sleep = [] low-power-debug-with-sleep = []
integrated-timers = ["dep:embassy-executor", "_time-driver"]
## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) ## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/)
memory-x = ["stm32-metapac/memory-x"] memory-x = ["stm32-metapac/memory-x"]
@ -149,7 +151,7 @@ time = ["dep:embassy-time", "embassy-embedded-hal/time"]
# Features starting with `_` are for internal use only. They're not intended # Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees. # to be enabled by other crates, and are not covered by semver guarantees.
_time-driver = ["dep:embassy-time-driver", "time"] _time-driver = ["dep:embassy-time-driver", "time", "dep:embassy-time-queue-driver"]
## Use any time driver ## Use any time driver
time-driver-any = ["_time-driver"] time-driver-any = ["_time-driver"]

View File

@ -256,9 +256,6 @@ impl Executor {
/// This function never returns. /// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
let executor = unsafe { EXECUTOR.as_mut().unwrap() }; let executor = unsafe { EXECUTOR.as_mut().unwrap() };
unsafe {
executor.inner.initialize();
}
init(executor.inner.spawner()); init(executor.inner.spawner());
loop { loop {

View File

@ -1,13 +1,13 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use core::cell::Cell; use core::cell::Cell;
use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
use core::{mem, ptr};
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::{AlarmHandle, Driver, TICK_HZ}; use embassy_time_driver::{Driver, TICK_HZ};
use embassy_time_queue_driver::GlobalTimerQueue;
use stm32_metapac::timer::{regs, TimGp16}; use stm32_metapac::timer::{regs, TimGp16};
use crate::interrupt::typelevel::Interrupt; use crate::interrupt::typelevel::Interrupt;
@ -24,18 +24,6 @@ use crate::{interrupt, peripherals};
// additional CC capabilities to provide timer alarms to embassy-time. embassy-time requires AT LEAST // additional CC capabilities to provide timer alarms to embassy-time. embassy-time requires AT LEAST
// one alarm to be allocatable, which means timers that only have CC1, such as TIM16/TIM17, are not // one alarm to be allocatable, which means timers that only have CC1, such as TIM16/TIM17, are not
// candidates for use as an embassy-time driver provider. (a.k.a 1CH and 1CH_CMP are not, others are good.) // candidates for use as an embassy-time driver provider. (a.k.a 1CH and 1CH_CMP are not, others are good.)
//
// The values of ALARM_COUNT below are not the TOTAL CC registers available, but rather the number
// available after reserving CC1 for regular time keeping. For example, TIM2 has four CC registers:
// CC1, CC2, CC3, and CC4, so it can provide ALARM_COUNT = 3.
cfg_if::cfg_if! {
if #[cfg(any(time_driver_tim9, time_driver_tim12, time_driver_tim15, time_driver_tim21, time_driver_tim22))] {
const ALARM_COUNT: usize = 1;
} else {
const ALARM_COUNT: usize = 3;
}
}
#[cfg(time_driver_tim1)] #[cfg(time_driver_tim1)]
type T = peripherals::TIM1; type T = peripherals::TIM1;
@ -208,11 +196,6 @@ fn calc_now(period: u32, counter: u16) -> u64 {
struct AlarmState { struct AlarmState {
timestamp: Cell<u64>, timestamp: Cell<u64>,
// This is really a Option<(fn(*mut ()), *mut ())>
// but fn pointers aren't allowed in const yet
callback: Cell<*const ()>,
ctx: Cell<*mut ()>,
} }
unsafe impl Send for AlarmState {} unsafe impl Send for AlarmState {}
@ -221,8 +204,6 @@ impl AlarmState {
const fn new() -> Self { const fn new() -> Self {
Self { Self {
timestamp: Cell::new(u64::MAX), timestamp: Cell::new(u64::MAX),
callback: Cell::new(ptr::null()),
ctx: Cell::new(ptr::null_mut()),
} }
} }
} }
@ -230,17 +211,14 @@ impl AlarmState {
pub(crate) struct RtcDriver { pub(crate) 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: Mutex<CriticalSectionRawMutex, AlarmState>,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<CriticalSectionRawMutex, [AlarmState; ALARM_COUNT]>,
#[cfg(feature = "low-power")] #[cfg(feature = "low-power")]
rtc: Mutex<CriticalSectionRawMutex, Cell<Option<&'static Rtc>>>, rtc: Mutex<CriticalSectionRawMutex, Cell<Option<&'static Rtc>>>,
} }
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),
alarm_count: AtomicU8::new(0), alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [const{AlarmState::new()}; ALARM_COUNT]),
#[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)),
}); });
@ -289,7 +267,7 @@ impl RtcDriver {
let r = regs_gp16(); let r = regs_gp16();
// XXX: reduce the size of this critical section ? // 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();
@ -308,10 +286,9 @@ impl RtcDriver {
self.next_period(); self.next_period();
} }
for n in 0..ALARM_COUNT { let n = 0;
if sr.ccif(n + 1) && dier.ccie(n + 1) { if sr.ccif(n + 1) && dier.ccie(n + 1) {
self.trigger_alarm(n, cs); self.trigger_alarm();
}
} }
}) })
} }
@ -326,36 +303,20 @@ impl RtcDriver {
critical_section::with(move |cs| { critical_section::with(move |cs| {
r.dier().modify(move |w| { r.dier().modify(move |w| {
for n in 0..ALARM_COUNT { let n = 0;
let alarm = &self.alarms.borrow(cs)[n]; let alarm = self.alarm.borrow(cs);
let at = alarm.timestamp.get(); let at = alarm.timestamp.get();
if at < t + 0xc000 { if at < t + 0xc000 {
// just enable it. `set_alarm` has already set the correct CCR val. // just enable it. `set_alarm` has already set the correct CCR val.
w.set_ccie(n + 1, true); w.set_ccie(n + 1, true);
}
} }
}) })
}) })
} }
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { fn trigger_alarm(&self) {
// safety: we're allowed to assume the AlarmState is created by us, and TIMER_QUEUE_DRIVER.dispatch();
// we never create one that's out of bounds.
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
}
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
let alarm = &self.alarms.borrow(cs)[n];
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
// safety:
// - we can ignore the possibility of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
f(alarm.ctx.get());
} }
/* /*
@ -367,14 +328,7 @@ impl RtcDriver {
fn time_until_next_alarm(&self, cs: CriticalSection) -> embassy_time::Duration { fn time_until_next_alarm(&self, cs: CriticalSection) -> embassy_time::Duration {
let now = self.now() + 32; let now = self.now() + 32;
embassy_time::Duration::from_ticks( embassy_time::Duration::from_ticks(self.alarm.borrow(cs).timestamp.get().saturating_sub(now))
self.alarms
.borrow(cs)
.iter()
.map(|alarm: &AlarmState| alarm.timestamp.get().saturating_sub(now))
.min()
.unwrap_or(u64::MAX),
)
} }
#[cfg(feature = "low-power")] #[cfg(feature = "low-power")]
@ -409,15 +363,12 @@ impl RtcDriver {
self.period.store(period, Ordering::SeqCst); self.period.store(period, Ordering::SeqCst);
regs_gp16().cnt().write(|w| w.set_cnt(cnt as u16)); regs_gp16().cnt().write(|w| w.set_cnt(cnt as u16));
// Now, recompute all alarms // Now, recompute alarm
for i in 0..self.alarm_count.load(Ordering::Relaxed) as usize { let alarm = self.alarm.borrow(cs);
let alarm_handle = unsafe { AlarmHandle::new(i as u8) };
let alarm = self.get_alarm(cs, alarm_handle);
if !self.set_alarm(alarm_handle, alarm.timestamp.get()) { if !self.set_alarm(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(i, cs); self.trigger_alarm();
}
} }
} }
@ -489,46 +440,13 @@ impl RtcDriver {
regs_gp16().cr1().modify(|w| w.set_cen(true)); regs_gp16().cr1().modify(|w| w.set_cen(true));
}) })
} }
}
impl Driver for RtcDriver { fn set_alarm(&self, timestamp: u64) -> bool {
fn now(&self) -> u64 {
let r = regs_gp16();
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = r.cnt().read().cnt();
calc_now(period, counter)
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
critical_section::with(|_| {
let id = self.alarm_count.load(Ordering::Relaxed);
if id < ALARM_COUNT as u8 {
self.alarm_count.store(id + 1, Ordering::Relaxed);
Some(AlarmHandle::new(id))
} else {
None
}
})
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| {
let alarm = self.get_alarm(cs, alarm);
alarm.callback.set(callback as *const ());
alarm.ctx.set(ctx);
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
critical_section::with(|cs| { critical_section::with(|cs| {
let r = regs_gp16(); let r = regs_gp16();
let n = alarm.id() as usize; let n = 0;
let alarm = self.get_alarm(cs, alarm); self.alarm.borrow(cs).timestamp.set(timestamp);
alarm.timestamp.set(timestamp);
let t = self.now(); let t = self.now();
if timestamp <= t { if timestamp <= t {
@ -536,7 +454,7 @@ impl Driver for RtcDriver {
// 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));
alarm.timestamp.set(u64::MAX); self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false; return false;
} }
@ -558,7 +476,7 @@ impl Driver for RtcDriver {
// 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));
alarm.timestamp.set(u64::MAX); self.alarm.borrow(cs).timestamp.set(u64::MAX);
return false; return false;
} }
@ -569,6 +487,17 @@ impl Driver for RtcDriver {
} }
} }
impl Driver for RtcDriver {
fn now(&self) -> u64 {
let r = regs_gp16();
let period = self.period.load(Ordering::Relaxed);
compiler_fence(Ordering::Acquire);
let counter = r.cnt().read().cnt();
calc_now(period, counter)
}
}
#[cfg(feature = "low-power")] #[cfg(feature = "low-power")]
pub(crate) fn get_driver() -> &'static RtcDriver { pub(crate) fn get_driver() -> &'static RtcDriver {
&DRIVER &DRIVER
@ -577,3 +506,8 @@ 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))
);

View File

@ -21,8 +21,8 @@
//! //!
//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions. //! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
//! //!
//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them. //! `embassy` internally defines the driver function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it.
//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the //! The driver crate defines the function as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
//! calls from the `embassy` crate to call into the driver crate. //! calls from the `embassy` crate to call into the driver crate.
//! //!
//! If there is none or multiple drivers in the crate tree, linking will fail. //! If there is none or multiple drivers in the crate tree, linking will fail.
@ -38,7 +38,7 @@
//! # Example //! # Example
//! //!
//! ``` //! ```
//! use embassy_time_driver::{Driver, AlarmHandle}; //! use embassy_time_driver::Driver;
//! //!
//! struct MyDriver{} // not public! //! struct MyDriver{} // not public!
//! //!
@ -46,15 +46,6 @@
//! fn now(&self) -> u64 { //! fn now(&self) -> u64 {
//! todo!() //! todo!()
//! } //! }
//! unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
//! todo!()
//! }
//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
//! todo!()
//! }
//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
//! todo!()
//! }
//! } //! }
//! //!
//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); //! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
@ -70,28 +61,6 @@ mod tick;
/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate) /// This value is specified by the [`tick-*` Cargo features](crate#tick-rate)
pub const TICK_HZ: u64 = tick::TICK_HZ; pub const TICK_HZ: u64 = tick::TICK_HZ;
/// Alarm handle, assigned by the driver.
#[derive(Clone, Copy)]
pub struct AlarmHandle {
id: u8,
}
impl AlarmHandle {
/// Create an AlarmHandle
///
/// Safety: May only be called by the current global Driver impl.
/// The impl is allowed to rely on the fact that all `AlarmHandle` instances
/// are created by itself in unsafe code (e.g. indexing operations)
pub unsafe fn new(id: u8) -> Self {
Self { id }
}
/// Get the ID of the AlarmHandle.
pub fn id(&self) -> u8 {
self.id
}
}
/// Time driver /// Time driver
pub trait Driver: Send + Sync + 'static { pub trait Driver: Send + Sync + 'static {
/// Return the current timestamp in ticks. /// Return the current timestamp in ticks.
@ -105,76 +74,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;
/// Try allocating an alarm handle. Returns None if no alarms left.
/// Initially the alarm has no callback set, and a null `ctx` pointer.
///
/// The allocated alarm is a reusable resource and can be used multiple times.
/// Once the alarm has fired, it remains allocated and can be set again without needing
/// to be reallocated.
///
/// # Safety
/// It is UB to make the alarm fire before setting a callback.
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
/// Set the callback function to be called when the alarm triggers.
/// The callback may be called from any context (interrupt or thread mode).
///
/// The callback is maintained after the alarm has fired. Callers do not need
/// to set a callback again before setting another alarm, unless they want to
/// change the callback function or context.
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
/// Set an alarm at the given timestamp.
///
/// ## Behavior
///
/// If `timestamp` is in the future, `set_alarm` schedules calling the callback function
/// at that time, and returns `true`.
///
/// If `timestamp` is in the past, `set_alarm` has two allowed behaviors. Implementations can pick whether to:
///
/// - Schedule calling the callback function "immediately", as if the requested timestamp was "now+epsilon" and return `true`, or
/// - Not schedule the callback, and return `false`.
///
/// Callers must ensure to behave correctly with either behavior.
///
/// When callback is called, it is guaranteed that `now()` will return a value greater than or equal to `timestamp`.
///
/// ## Reentrancy
///
/// Calling the callback from `set_alarm` synchronously is not allowed. If the implementation chooses the first option above,
/// it must still call the callback from another context (i.e. an interrupt handler or background thread), it's not allowed
/// to call it synchronously in the context `set_alarm` is running.
///
/// The reason for the above is callers are explicitly permitted to do both of:
/// - Lock a mutex in the alarm callback.
/// - Call `set_alarm` while having that mutex locked.
///
/// If `set_alarm` called the callback synchronously, it'd cause a deadlock or panic because it'd cause the
/// mutex to be locked twice reentrantly in the same context.
///
/// ## Overwriting alarms
///
/// Only one alarm can be active at a time for each `AlarmHandle`. This overwrites any previously-set alarm if any.
///
/// ## Unsetting the alarm
///
/// There is no `unset_alarm` API. Instead, callers can call `set_alarm` with `timestamp` set to `u64::MAX`.
///
/// This allows for more efficient implementations, since they don't need to distinguish between the "alarm set" and
/// "alarm not set" cases, thanks to the fact "Alarm set for u64::MAX" is effectively equivalent for "alarm not set".
///
/// This means implementations need to be careful to avoid timestamp overflows. The recommendation is to make `timestamp`
/// be in the same units as hardware ticks to avoid any conversions, which makes avoiding overflow easier.
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool;
} }
extern "Rust" { extern "Rust" {
fn _embassy_time_now() -> u64; fn _embassy_time_now() -> u64;
fn _embassy_time_allocate_alarm() -> Option<AlarmHandle>;
fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool;
} }
/// See [`Driver::now`] /// See [`Driver::now`]
@ -182,23 +85,6 @@ pub fn now() -> u64 {
unsafe { _embassy_time_now() } unsafe { _embassy_time_now() }
} }
/// See [`Driver::allocate_alarm`]
///
/// Safety: it is UB to make the alarm fire before setting a callback.
pub unsafe fn allocate_alarm() -> Option<AlarmHandle> {
_embassy_time_allocate_alarm()
}
/// See [`Driver::set_alarm_callback`]
pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) }
}
/// See [`Driver::set_alarm`]
pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool {
unsafe { _embassy_time_set_alarm(alarm, timestamp) }
}
/// Set the time Driver implementation. /// Set the time Driver implementation.
/// ///
/// See the module documentation for an example. /// See the module documentation for an example.
@ -211,20 +97,5 @@ 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]
unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::AlarmHandle> {
<$t as $crate::Driver>::allocate_alarm(&$name)
}
#[no_mangle]
fn _embassy_time_set_alarm_callback(alarm: $crate::AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
<$t as $crate::Driver>::set_alarm_callback(&$name, alarm, callback, ctx)
}
#[no_mangle]
fn _embassy_time_set_alarm(alarm: $crate::AlarmHandle, timestamp: u64) -> bool {
<$t as $crate::Driver>::set_alarm(&$name, alarm, timestamp)
}
}; };
} }

View File

@ -20,6 +20,39 @@ categories = [
# This is especially common when mixing crates from crates.io and git. # This is especially common when mixing crates from crates.io and git.
links = "embassy-time-queue" links = "embassy-time-queue"
[dependencies]
critical-section = "1.2.0"
heapless = "0.8"
embassy-executor = { version = "0.6.3", path = "../embassy-executor", optional = true }
embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" }
[features]
#! ### Generic Queue
## Use the executor-integrated `embassy-time` timer queue. The timer items are stored inside
## the task headers, so you do not need to set a capacity for the queue.
## To use this you must have a time driver provided.
##
## If this feature is not enabled, a generic queue is available with a configurable capacity.
integrated-timers = ["embassy-executor/integrated-timers"]
#! The following features set how many timers are used for the generic queue. At most one
#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used.
#!
#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the
#! end user to pick.
## Generic Queue with 8 timers
generic-queue-8 = []
## Generic Queue with 16 timers
generic-queue-16 = []
## Generic Queue with 32 timers
generic-queue-32 = []
## Generic Queue with 64 timers
generic-queue-64 = []
## Generic Queue with 128 timers
generic-queue-128 = []
[package.metadata.embassy_docs] [package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/"

View File

@ -6,7 +6,29 @@
//! //!
//! - Define a struct `MyTimerQueue` //! - Define a struct `MyTimerQueue`
//! - Implement [`TimerQueue`] for it //! - Implement [`TimerQueue`] for it
//! - Register it as the global timer queue with [`timer_queue_impl`](crate::timer_queue_impl). //! - Register it as the global timer queue with [`timer_queue_impl`].
//! - Ensure that you process the timer queue when `schedule_wake` is due. This usually involves
//! waking expired tasks, finding the next expiration time and setting an alarm.
//!
//! If a single global timer queue is sufficient for you, you can use the
//! [`GlobalTimerQueue`] type, which is a wrapper around a global timer queue
//! protected by a critical section.
//!
//! ```
//! use embassy_time_queue_driver::GlobalTimerQueue;
//! embassy_time_queue_driver::timer_queue_impl!(
//! static TIMER_QUEUE_DRIVER: GlobalTimerQueue
//! = GlobalTimerQueue::new(|next_expiration| todo!("Set an alarm"))
//! );
//! ```
//!
//! You can also use the `queue_generic` or the `embassy_executor::raw::timer_queue` modules to
//! implement your own timer queue. These modules contain queue implementations which you can wrap
//! and tailor to your needs.
//!
//! If you are providing an embassy-executor implementation besides a timer queue, you can choose to
//! expose the `integrated-timers` feature in your implementation. This feature stores timer items
//! in the tasks themselves, so you don't need a fixed-size queue or dynamic memory allocation.
//! //!
//! ## Example //! ## Example
//! //!
@ -14,7 +36,7 @@
//! use core::task::Waker; //! use core::task::Waker;
//! //!
//! use embassy_time::Instant; //! use embassy_time::Instant;
//! use embassy_time::queue::{TimerQueue}; //! use embassy_time::queue::TimerQueue;
//! //!
//! struct MyTimerQueue{}; // not public! //! struct MyTimerQueue{}; // not public!
//! //!
@ -26,11 +48,18 @@
//! //!
//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{}); //! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
//! ``` //! ```
pub mod queue_generic;
use core::cell::RefCell;
use core::task::Waker; use core::task::Waker;
use critical_section::Mutex;
/// Timer queue /// Timer queue
pub trait TimerQueue { pub trait TimerQueue {
/// Schedules a waker in the queue to be awoken at moment `at`. /// 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. /// If this moment is in the past, the waker might be awoken immediately.
fn schedule_wake(&'static self, at: u64, waker: &Waker); fn schedule_wake(&'static self, at: u64, waker: &Waker);
} }
@ -58,3 +87,106 @@ macro_rules! timer_queue_impl {
} }
}; };
} }
#[cfg(feature = "integrated-timers")]
type InnerQueue = embassy_executor::raw::timer_queue::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)
}
}

View File

@ -0,0 +1,146 @@
//! Generic timer queue implementations.
//!
//! Time queue drivers may use this to simplify their implementation.
use core::cmp::{min, Ordering};
use core::task::Waker;
use heapless::Vec;
#[derive(Debug)]
struct Timer {
at: u64,
waker: Waker,
}
impl PartialEq for Timer {
fn eq(&self, other: &Self) -> bool {
self.at == other.at
}
}
impl Eq for Timer {}
impl PartialOrd for Timer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.at.partial_cmp(&other.at)
}
}
impl Ord for Timer {
fn cmp(&self, other: &Self) -> Ordering {
self.at.cmp(&other.at)
}
}
/// A timer queue with a pre-determined capacity.
pub struct ConstGenericQueue<const QUEUE_SIZE: usize> {
queue: Vec<Timer, QUEUE_SIZE>,
}
impl<const QUEUE_SIZE: usize> ConstGenericQueue<QUEUE_SIZE> {
/// Creates a new timer queue.
pub const fn new() -> Self {
Self { queue: Vec::new() }
}
/// Schedules a task to run at a specific time, and returns whether any changes were made.
///
/// If this function returns `true`, the called should find the next expiration time and set
/// a new alarm for that time.
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
self.queue
.iter_mut()
.find(|timer| timer.waker.will_wake(waker))
.map(|timer| {
if timer.at > at {
timer.at = at;
true
} else {
false
}
})
.unwrap_or_else(|| {
let mut timer = Timer {
waker: waker.clone(),
at,
};
loop {
match self.queue.push(timer) {
Ok(()) => break,
Err(e) => timer = e,
}
self.queue.pop().unwrap().waker.wake();
}
true
})
}
/// Dequeues expired timers and returns the next alarm time.
pub fn next_expiration(&mut self, now: u64) -> u64 {
let mut next_alarm = u64::MAX;
let mut i = 0;
while i < self.queue.len() {
let timer = &self.queue[i];
if timer.at <= now {
let timer = self.queue.swap_remove(i);
timer.waker.wake();
} else {
next_alarm = min(next_alarm, timer.at);
i += 1;
}
}
next_alarm
}
}
#[cfg(feature = "generic-queue-8")]
const QUEUE_SIZE: usize = 8;
#[cfg(feature = "generic-queue-16")]
const QUEUE_SIZE: usize = 16;
#[cfg(feature = "generic-queue-32")]
const QUEUE_SIZE: usize = 32;
#[cfg(feature = "generic-queue-64")]
const QUEUE_SIZE: usize = 64;
#[cfg(feature = "generic-queue-128")]
const QUEUE_SIZE: usize = 128;
#[cfg(not(any(
feature = "generic-queue-8",
feature = "generic-queue-16",
feature = "generic-queue-32",
feature = "generic-queue-64",
feature = "generic-queue-128"
)))]
const QUEUE_SIZE: usize = 64;
/// A timer queue with a pre-determined capacity.
pub struct Queue {
queue: ConstGenericQueue<QUEUE_SIZE>,
}
impl Queue {
/// Creates a new timer queue.
pub const fn new() -> Self {
Self {
queue: ConstGenericQueue::new(),
}
}
/// Schedules a task to run at a specific time, and returns whether any changes were made.
///
/// If this function returns `true`, the called should find the next expiration time and set
/// a new alarm for that time.
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
self.queue.schedule_wake(at, waker)
}
/// Dequeues expired timers and returns the next alarm time.
pub fn next_expiration(&mut self, now: u64) -> u64 {
self.queue.next_expiration(now)
}
}

View File

@ -42,29 +42,6 @@ defmt-timestamp-uptime-tus = ["defmt"]
## Create a `MockDriver` that can be manually advanced for testing purposes. ## Create a `MockDriver` that can be manually advanced for testing purposes.
mock-driver = ["tick-hz-1_000_000"] mock-driver = ["tick-hz-1_000_000"]
#! ### Generic Queue
## Create a global, generic queue that can be used with any executor.
## To use this you must have a time driver provided.
generic-queue = []
#! The following features set how many timers are used for the generic queue. At most one
#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used.
#!
#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the
#! end user to pick.
## Generic Queue with 8 timers
generic-queue-8 = ["generic-queue"]
## Generic Queue with 16 timers
generic-queue-16 = ["generic-queue"]
## Generic Queue with 32 timers
generic-queue-32 = ["generic-queue"]
## Generic Queue with 64 timers
generic-queue-64 = ["generic-queue"]
## Generic Queue with 128 timers
generic-queue-128 = ["generic-queue"]
#! ### Tick Rate #! ### Tick Rate
#! #!
#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. #! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used.
@ -419,7 +396,6 @@ embedded-hal-async = { version = "1.0" }
futures-util = { version = "0.3.17", default-features = false } futures-util = { version = "0.3.17", default-features = false }
critical-section = "1.1" critical-section = "1.1"
cfg-if = "1.0.0" cfg-if = "1.0.0"
heapless = "0.8"
document-features = "0.2.7" document-features = "0.2.7"

View File

@ -1,7 +1,7 @@
use core::cell::RefCell; use core::cell::RefCell;
use critical_section::Mutex as CsMutex; use critical_section::Mutex as CsMutex;
use embassy_time_driver::{AlarmHandle, Driver}; use embassy_time_driver::Driver;
use crate::{Duration, Instant}; use crate::{Duration, Instant};
@ -60,15 +60,13 @@ impl MockDriver {
let now = inner.now.as_ticks(); let now = inner.now.as_ticks();
inner if inner.alarm.timestamp <= now {
.alarm inner.alarm.timestamp = u64::MAX;
.as_mut()
.filter(|alarm| alarm.timestamp <= now)
.map(|alarm| {
alarm.timestamp = u64::MAX;
(alarm.callback, alarm.ctx) Some((inner.alarm.callback, inner.alarm.ctx))
}) } else {
None
}
}) })
}; };
@ -76,68 +74,48 @@ impl MockDriver {
(callback)(ctx); (callback)(ctx);
} }
} }
}
impl Driver for MockDriver { /// Configures a callback to be called when the alarm fires.
fn now(&self) -> u64 { pub fn set_alarm_callback(&self, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
critical_section::with(|cs| { critical_section::with(|cs| {
let mut inner = self.0.borrow_ref_mut(cs); let mut inner = self.0.borrow_ref_mut(cs);
if inner.alarm.is_some() { inner.alarm.callback = callback;
None inner.alarm.ctx = ctx;
} else {
inner.alarm.replace(AlarmState::new());
Some(AlarmHandle::new(0))
}
})
}
fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
critical_section::with(|cs| {
let mut inner = self.0.borrow_ref_mut(cs);
let Some(alarm) = inner.alarm.as_mut() else {
panic!("Alarm not allocated");
};
alarm.callback = callback;
alarm.ctx = ctx;
}); });
} }
fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool { /// Sets the alarm to fire at the specified timestamp.
pub fn set_alarm(&self, timestamp: u64) -> bool {
critical_section::with(|cs| { critical_section::with(|cs| {
let mut inner = self.0.borrow_ref_mut(cs); let mut inner = self.0.borrow_ref_mut(cs);
if timestamp <= inner.now.as_ticks() { if timestamp <= inner.now.as_ticks() {
false false
} else { } else {
let Some(alarm) = inner.alarm.as_mut() else { inner.alarm.timestamp = timestamp;
panic!("Alarm not allocated");
};
alarm.timestamp = timestamp;
true true
} }
}) })
} }
} }
impl Driver for MockDriver {
fn now(&self) -> u64 {
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
}
}
struct InnerMockDriver { struct InnerMockDriver {
now: Instant, now: Instant,
alarm: Option<AlarmState>, alarm: AlarmState,
} }
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: None, alarm: AlarmState::new(),
} }
} }
} }
@ -189,8 +167,7 @@ mod tests {
setup(); setup();
let driver = MockDriver::get(); let driver = MockDriver::get();
let alarm = unsafe { AlarmHandle::new(0) }; assert_eq!(false, driver.set_alarm(driver.now()));
assert_eq!(false, driver.set_alarm(alarm, driver.now()));
} }
#[test] #[test]
@ -199,23 +176,11 @@ mod tests {
setup(); setup();
let driver = MockDriver::get(); let driver = MockDriver::get();
let alarm = unsafe { driver.allocate_alarm() }.expect("No alarms available");
static mut CALLBACK_CALLED: bool = false; static mut CALLBACK_CALLED: bool = false;
let ctx = &mut () as *mut (); driver.set_alarm_callback(|_| unsafe { CALLBACK_CALLED = true }, core::ptr::null_mut());
driver.set_alarm_callback(alarm, |_| unsafe { CALLBACK_CALLED = true }, ctx); driver.set_alarm(driver.now() + 1);
driver.set_alarm(alarm, driver.now() + 1);
assert_eq!(false, unsafe { CALLBACK_CALLED }); 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, unsafe { CALLBACK_CALLED });
} }
#[test]
#[serial]
fn test_allocate_alarm() {
setup();
let driver = MockDriver::get();
assert!(unsafe { driver.allocate_alarm() }.is_some());
assert!(unsafe { driver.allocate_alarm() }.is_none());
}
} }

View File

@ -1,53 +1,38 @@
use core::sync::atomic::{AtomicU8, Ordering};
use std::cell::{RefCell, UnsafeCell}; use std::cell::{RefCell, UnsafeCell};
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::sync::{Condvar, Mutex, Once}; 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::{mem, ptr, thread}; use std::{ptr, thread};
use critical_section::Mutex as CsMutex; use critical_section::Mutex as CsMutex;
use embassy_time_driver::{AlarmHandle, Driver}; use embassy_time_driver::Driver;
use embassy_time_queue_driver::GlobalTimerQueue;
const ALARM_COUNT: usize = 4;
struct AlarmState { struct AlarmState {
timestamp: u64, timestamp: u64,
// This is really a Option<(fn(*mut ()), *mut ())>
// but fn pointers aren't allowed in const yet
callback: *const (),
ctx: *mut (),
} }
unsafe impl Send for AlarmState {} unsafe impl Send for AlarmState {}
impl AlarmState { impl AlarmState {
const fn new() -> Self { const fn new() -> Self {
Self { Self { timestamp: u64::MAX }
timestamp: u64::MAX,
callback: ptr::null(),
ctx: ptr::null_mut(),
}
} }
} }
struct TimeDriver { struct TimeDriver {
alarm_count: AtomicU8,
once: Once, once: Once,
// The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't // The STD Driver implementation requires the alarm's mutex to be reentrant, which the STD Mutex isn't
// Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections // Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections
// themselves are reentrant // themselves are reentrant
alarms: UninitCell<CsMutex<RefCell<[AlarmState; ALARM_COUNT]>>>, alarm: UninitCell<CsMutex<RefCell<AlarmState>>>,
zero_instant: UninitCell<StdInstant>, zero_instant: UninitCell<StdInstant>,
signaler: UninitCell<Signaler>, signaler: UninitCell<Signaler>,
} }
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
alarm_count: AtomicU8::new(0),
once: Once::new(), once: Once::new(),
alarms: UninitCell::uninit(), alarm: UninitCell::uninit(),
zero_instant: UninitCell::uninit(), zero_instant: UninitCell::uninit(),
signaler: UninitCell::uninit(), signaler: UninitCell::uninit(),
}); });
@ -55,8 +40,8 @@ embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
impl TimeDriver { impl TimeDriver {
fn init(&self) { fn init(&self) {
self.once.call_once(|| unsafe { self.once.call_once(|| unsafe {
self.alarms self.alarm
.write(CsMutex::new(RefCell::new([const { AlarmState::new() }; ALARM_COUNT]))); .write(CsMutex::new(RefCell::new(const { AlarmState::new() })));
self.zero_instant.write(StdInstant::now()); self.zero_instant.write(StdInstant::now());
self.signaler.write(Signaler::new()); self.signaler.write(Signaler::new());
@ -70,36 +55,13 @@ impl TimeDriver {
let now = DRIVER.now(); let now = DRIVER.now();
let next_alarm = critical_section::with(|cs| { let next_alarm = critical_section::with(|cs| {
let alarms = unsafe { DRIVER.alarms.as_ref() }.borrow(cs); let mut alarm = unsafe { DRIVER.alarm.as_ref() }.borrow_ref_mut(cs);
loop { if alarm.timestamp <= now {
let pending = alarms alarm.timestamp = u64::MAX;
.borrow_mut()
.iter_mut()
.find(|alarm| alarm.timestamp <= now)
.map(|alarm| {
alarm.timestamp = u64::MAX;
(alarm.callback, alarm.ctx) TIMER_QUEUE_DRIVER.dispatch();
});
if let Some((callback, ctx)) = pending {
// safety:
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
let f: fn(*mut ()) = unsafe { mem::transmute(callback) };
f(ctx);
} else {
// No alarm due
break;
}
} }
alarm.timestamp
alarms
.borrow()
.iter()
.map(|alarm| alarm.timestamp)
.min()
.unwrap_or(u64::MAX)
}); });
// Ensure we don't overflow // Ensure we don't overflow
@ -110,6 +72,17 @@ impl TimeDriver {
unsafe { DRIVER.signaler.as_ref() }.wait_until(until); 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 {
@ -119,43 +92,6 @@ impl Driver for TimeDriver {
let zero = unsafe { self.zero_instant.read() }; 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
} }
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
self.init();
critical_section::with(|cs| {
let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs);
let alarm = &mut alarms[alarm.id() as usize];
alarm.callback = callback as *const ();
alarm.ctx = ctx;
});
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
self.init();
critical_section::with(|cs| {
let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs);
let alarm = &mut alarms[alarm.id() as usize];
alarm.timestamp = timestamp;
unsafe { self.signaler.as_ref() }.signal();
});
true
}
} }
struct Signaler { struct Signaler {
@ -228,3 +164,8 @@ impl<T: Copy> UninitCell<T> {
ptr::read(self.as_mut_ptr()) 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))
);

View File

@ -1,28 +1,22 @@
use core::sync::atomic::{AtomicU8, Ordering};
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::ptr; use std::ptr;
use std::sync::{Mutex, Once}; use std::sync::{Mutex, Once};
use embassy_time_driver::{AlarmHandle, Driver}; use embassy_time_driver::Driver;
use embassy_time_queue_driver::GlobalTimerQueue;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_timer::Instant as StdInstant; use wasm_timer::Instant as StdInstant;
const ALARM_COUNT: usize = 4;
struct AlarmState { struct AlarmState {
token: Option<f64>, token: Option<f64>,
closure: Option<Closure<dyn FnMut() + 'static>>,
} }
unsafe impl Send for AlarmState {} unsafe impl Send for AlarmState {}
impl AlarmState { impl AlarmState {
const fn new() -> Self { const fn new() -> Self {
Self { Self { token: None }
token: None,
closure: None,
}
} }
} }
@ -33,66 +27,32 @@ extern "C" {
} }
struct TimeDriver { struct TimeDriver {
alarm_count: AtomicU8,
once: Once, once: Once,
alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>, alarm: UninitCell<Mutex<AlarmState>>,
zero_instant: UninitCell<StdInstant>, zero_instant: UninitCell<StdInstant>,
closure: UninitCell<Closure<dyn FnMut()>>,
} }
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
alarm_count: AtomicU8::new(0),
once: Once::new(), once: Once::new(),
alarms: UninitCell::uninit(), alarm: UninitCell::uninit(),
zero_instant: UninitCell::uninit(), zero_instant: UninitCell::uninit(),
closure: UninitCell::uninit()
}); });
impl TimeDriver { impl TimeDriver {
fn init(&self) { fn init(&self) {
self.once.call_once(|| unsafe { self.once.call_once(|| unsafe {
self.alarms self.alarm.write(Mutex::new(const { AlarmState::new() }));
.write(Mutex::new([const { AlarmState::new() }; ALARM_COUNT]));
self.zero_instant.write(StdInstant::now()); self.zero_instant.write(StdInstant::now());
self.closure
.write(Closure::new(Box::new(|| TIMER_QUEUE_DRIVER.dispatch())));
}); });
} }
}
impl Driver for TimeDriver { fn set_alarm(&self, timestamp: u64) -> bool {
fn now(&self) -> u64 {
self.init(); self.init();
let mut alarm = unsafe { self.alarm.as_ref() }.lock().unwrap();
let zero = unsafe { self.zero_instant.read() };
StdInstant::now().duration_since(zero).as_micros() as u64
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
if x < ALARM_COUNT as u8 {
Some(x + 1)
} else {
None
}
});
match id {
Ok(id) => Some(AlarmHandle::new(id)),
Err(_) => None,
}
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
self.init();
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
let alarm = &mut alarms[alarm.id() as usize];
alarm.closure.replace(Closure::new(move || {
callback(ctx);
}));
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
self.init();
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
let alarm = &mut alarms[alarm.id() as usize];
if let Some(token) = alarm.token { if let Some(token) = alarm.token {
clearTimeout(token); clearTimeout(token);
} }
@ -102,13 +62,22 @@ impl Driver for TimeDriver {
false false
} else { } else {
let timeout = (timestamp - now) as u32; let timeout = (timestamp - now) as u32;
alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); alarm.token = Some(setTimeout(unsafe { self.closure.as_ref() }, timeout / 1000));
true true
} }
} }
} }
impl Driver for TimeDriver {
fn now(&self) -> u64 {
self.init();
let zero = unsafe { self.zero_instant.read() };
StdInstant::now().duration_since(zero).as_micros() as u64
}
}
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>); pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
unsafe impl<T> Send for UninitCell<T> {} unsafe impl<T> Send for UninitCell<T> {}
unsafe impl<T> Sync for UninitCell<T> {} unsafe impl<T> Sync for UninitCell<T> {}
@ -139,3 +108,8 @@ impl<T: Copy> UninitCell<T> {
ptr::read(self.as_mut_ptr()) 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))
);

View File

@ -25,8 +25,6 @@ pub use driver_mock::MockDriver;
mod driver_std; mod driver_std;
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
mod driver_wasm; mod driver_wasm;
#[cfg(feature = "generic-queue")]
mod queue_generic;
pub use delay::{block_for, Delay}; pub use delay::{block_for, Delay};
pub use duration::Duration; pub use duration::Duration;

View File

@ -1,346 +0,0 @@
use core::cell::RefCell;
use core::cmp::{min, Ordering};
use core::task::Waker;
use critical_section::Mutex;
use embassy_time_driver::{allocate_alarm, set_alarm, set_alarm_callback, AlarmHandle};
use embassy_time_queue_driver::TimerQueue;
use heapless::Vec;
use crate::Instant;
#[cfg(feature = "generic-queue-8")]
const QUEUE_SIZE: usize = 8;
#[cfg(feature = "generic-queue-16")]
const QUEUE_SIZE: usize = 16;
#[cfg(feature = "generic-queue-32")]
const QUEUE_SIZE: usize = 32;
#[cfg(feature = "generic-queue-64")]
const QUEUE_SIZE: usize = 64;
#[cfg(feature = "generic-queue-128")]
const QUEUE_SIZE: usize = 128;
#[cfg(not(any(
feature = "generic-queue-8",
feature = "generic-queue-16",
feature = "generic-queue-32",
feature = "generic-queue-64",
feature = "generic-queue-128"
)))]
const QUEUE_SIZE: usize = 64;
#[derive(Debug)]
struct Timer {
at: Instant,
waker: Waker,
}
impl PartialEq for Timer {
fn eq(&self, other: &Self) -> bool {
self.at == other.at
}
}
impl Eq for Timer {}
impl PartialOrd for Timer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.at.partial_cmp(&other.at)
}
}
impl Ord for Timer {
fn cmp(&self, other: &Self) -> Ordering {
self.at.cmp(&other.at)
}
}
struct InnerQueue {
queue: Vec<Timer, QUEUE_SIZE>,
alarm: AlarmHandle,
}
impl InnerQueue {
fn schedule_wake(&mut self, at: Instant, waker: &Waker) {
self.queue
.iter_mut()
.find(|timer| timer.waker.will_wake(waker))
.map(|timer| {
timer.at = min(timer.at, at);
})
.unwrap_or_else(|| {
let mut timer = Timer {
waker: waker.clone(),
at,
};
loop {
match self.queue.push(timer) {
Ok(()) => break,
Err(e) => timer = e,
}
self.queue.pop().unwrap().waker.wake();
}
});
// Don't wait for the alarm callback to trigger and directly
// dispatch all timers that are already due
//
// Then update the alarm if necessary
self.dispatch();
}
fn dispatch(&mut self) {
loop {
let now = Instant::now();
let mut next_alarm = Instant::MAX;
let mut i = 0;
while i < self.queue.len() {
let timer = &self.queue[i];
if timer.at <= now {
let timer = self.queue.swap_remove(i);
timer.waker.wake();
} else {
next_alarm = min(next_alarm, timer.at);
i += 1;
}
}
if self.update_alarm(next_alarm) {
break;
}
}
}
fn update_alarm(&mut self, next_alarm: Instant) -> bool {
if next_alarm == Instant::MAX {
true
} else {
set_alarm(self.alarm, next_alarm.as_ticks())
}
}
fn handle_alarm(&mut self) {
self.dispatch();
}
}
struct Queue {
inner: Mutex<RefCell<Option<InnerQueue>>>,
}
impl Queue {
const fn new() -> Self {
Self {
inner: Mutex::new(RefCell::new(None)),
}
}
fn schedule_wake(&'static self, at: Instant, waker: &Waker) {
critical_section::with(|cs| {
let mut inner = self.inner.borrow_ref_mut(cs);
inner
.get_or_insert_with(|| {
let handle = unsafe { allocate_alarm() }.unwrap();
set_alarm_callback(handle, Self::handle_alarm_callback, self as *const _ as _);
InnerQueue {
queue: Vec::new(),
alarm: handle,
}
})
.schedule_wake(at, waker)
});
}
fn handle_alarm(&self) {
critical_section::with(|cs| self.inner.borrow_ref_mut(cs).as_mut().unwrap().handle_alarm())
}
fn handle_alarm_callback(ctx: *mut ()) {
unsafe { (ctx as *const Self).as_ref().unwrap() }.handle_alarm();
}
}
impl TimerQueue for Queue {
fn schedule_wake(&'static self, at: u64, waker: &Waker) {
Queue::schedule_wake(self, Instant::from_ticks(at), waker);
}
}
embassy_time_queue_driver::timer_queue_impl!(static QUEUE: Queue = Queue::new());
#[cfg(test)]
#[cfg(feature = "mock-driver")]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::Waker;
use std::sync::Arc;
use std::task::Wake;
use serial_test::serial;
use crate::driver_mock::MockDriver;
use crate::queue_generic::QUEUE;
use crate::{Duration, Instant};
struct TestWaker {
pub awoken: AtomicBool,
}
impl Wake for TestWaker {
fn wake(self: Arc<Self>) {
self.awoken.store(true, Ordering::Relaxed);
}
fn wake_by_ref(self: &Arc<Self>) {
self.awoken.store(true, Ordering::Relaxed);
}
}
fn test_waker() -> (Arc<TestWaker>, Waker) {
let arc = Arc::new(TestWaker {
awoken: AtomicBool::new(false),
});
let waker = Waker::from(arc.clone());
(arc, waker)
}
fn setup() {
MockDriver::get().reset();
critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None);
}
fn queue_len() -> usize {
critical_section::with(|cs| {
QUEUE
.inner
.borrow_ref(cs)
.as_ref()
.map(|inner| inner.queue.iter().count())
.unwrap_or(0)
})
}
#[test]
#[serial]
fn test_schedule() {
setup();
assert_eq!(queue_len(), 0);
let (flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
assert!(!flag.awoken.load(Ordering::Relaxed));
assert_eq!(queue_len(), 1);
}
#[test]
#[serial]
fn test_schedule_same() {
setup();
let (_flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
assert_eq!(queue_len(), 1);
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
assert_eq!(queue_len(), 1);
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
assert_eq!(queue_len(), 1);
let (_flag2, waker2) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(100), &waker2);
assert_eq!(queue_len(), 2);
}
#[test]
#[serial]
fn test_trigger() {
setup();
let (flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
assert!(!flag.awoken.load(Ordering::Relaxed));
MockDriver::get().advance(Duration::from_secs(99));
assert!(!flag.awoken.load(Ordering::Relaxed));
assert_eq!(queue_len(), 1);
MockDriver::get().advance(Duration::from_secs(1));
assert!(flag.awoken.load(Ordering::Relaxed));
assert_eq!(queue_len(), 0);
}
#[test]
#[serial]
fn test_immediate_trigger() {
setup();
let (flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
MockDriver::get().advance(Duration::from_secs(50));
let (flag2, waker2) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(40), &waker2);
assert!(!flag.awoken.load(Ordering::Relaxed));
assert!(flag2.awoken.load(Ordering::Relaxed));
assert_eq!(queue_len(), 1);
}
#[test]
#[serial]
fn test_queue_overflow() {
setup();
for i in 1..super::QUEUE_SIZE {
let (flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(310), &waker);
assert_eq!(queue_len(), i);
assert!(!flag.awoken.load(Ordering::Relaxed));
}
let (flag, waker) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(300), &waker);
assert_eq!(queue_len(), super::QUEUE_SIZE);
assert!(!flag.awoken.load(Ordering::Relaxed));
let (flag2, waker2) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(305), &waker2);
assert_eq!(queue_len(), super::QUEUE_SIZE);
assert!(flag.awoken.load(Ordering::Relaxed));
let (_flag3, waker3) = test_waker();
QUEUE.schedule_wake(Instant::from_secs(320), &waker3);
assert_eq!(queue_len(), super::QUEUE_SIZE);
assert!(flag2.awoken.load(Ordering::Relaxed));
}
}

View File

@ -9,7 +9,8 @@ rtic = { version = "2", features = ["thumbv7-backend"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] } embassy-sync = { version = "0.6.1", path = "../../embassy-sync", features = ["defmt"] }
embassy-time = { version = "0.3.2", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", "generic-queue"] } embassy-time = { version = "0.3.2", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime"] }
embassy-time-queue-driver = { version = "0.1.0", path = "../../embassy-time-queue-driver" }
embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
defmt = "0.3" defmt = "0.3"