Refactor integrated-timers
This commit is contained in:
parent
406d377b75
commit
5a5495aac4
2
.github/ci/test.sh
vendored
2
.github/ci/test.sh
vendored
@ -17,7 +17,7 @@ cargo test --manifest-path ./embassy-futures/Cargo.toml
|
|||||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-time/Cargo.toml --features 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
|
||||||
|
|||||||
@ -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
4
ci.sh
@ -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 \
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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) }
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 |_| {
|
||||||
|
|||||||
@ -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);
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"] }
|
||||||
|
|||||||
@ -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))
|
||||||
|
);
|
||||||
|
|||||||
@ -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"] }
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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"]
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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))
|
||||||
|
);
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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/"
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
146
embassy-time-queue-driver/src/queue_generic.rs
Normal file
146
embassy-time-queue-driver/src/queue_generic.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
|
);
|
||||||
|
|||||||
@ -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))
|
||||||
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user