aboutsummaryrefslogtreecommitdiff
path: root/embassy-time-queue-driver
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-time-queue-driver')
-rw-r--r--embassy-time-queue-driver/Cargo.toml33
-rw-r--r--embassy-time-queue-driver/src/lib.rs136
-rw-r--r--embassy-time-queue-driver/src/queue_generic.rs146
3 files changed, 313 insertions, 2 deletions
diff --git a/embassy-time-queue-driver/Cargo.toml b/embassy-time-queue-driver/Cargo.toml
index 9ce9d79bb..599041a3f 100644
--- a/embassy-time-queue-driver/Cargo.toml
+++ b/embassy-time-queue-driver/Cargo.toml
@@ -20,6 +20,39 @@ categories = [
20# This is especially common when mixing crates from crates.io and git. 20# This is especially common when mixing crates from crates.io and git.
21links = "embassy-time-queue" 21links = "embassy-time-queue"
22 22
23[dependencies]
24critical-section = "1.2.0"
25heapless = "0.8"
26embassy-executor = { version = "0.6.3", path = "../embassy-executor", optional = true }
27embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" }
28
29[features]
30#! ### Generic Queue
31
32## Use the executor-integrated `embassy-time` timer queue. The timer items are stored inside
33## the task headers, so you do not need to set a capacity for the queue.
34## To use this you must have a time driver provided.
35##
36## If this feature is not enabled, a generic queue is available with a configurable capacity.
37integrated-timers = ["embassy-executor/integrated-timers"]
38
39#! The following features set how many timers are used for the generic queue. At most one
40#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used.
41#!
42#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the
43#! end user to pick.
44
45## Generic Queue with 8 timers
46generic-queue-8 = []
47## Generic Queue with 16 timers
48generic-queue-16 = []
49## Generic Queue with 32 timers
50generic-queue-32 = []
51## Generic Queue with 64 timers
52generic-queue-64 = []
53## Generic Queue with 128 timers
54generic-queue-128 = []
55
23[package.metadata.embassy_docs] 56[package.metadata.embassy_docs]
24src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/" 57src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/"
25src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/" 58src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/"
diff --git a/embassy-time-queue-driver/src/lib.rs b/embassy-time-queue-driver/src/lib.rs
index 50736e8c7..c5e989854 100644
--- a/embassy-time-queue-driver/src/lib.rs
+++ b/embassy-time-queue-driver/src/lib.rs
@@ -6,7 +6,29 @@
6//! 6//!
7//! - Define a struct `MyTimerQueue` 7//! - Define a struct `MyTimerQueue`
8//! - Implement [`TimerQueue`] for it 8//! - Implement [`TimerQueue`] for it
9//! - Register it as the global timer queue with [`timer_queue_impl`](crate::timer_queue_impl). 9//! - Register it as the global timer queue with [`timer_queue_impl`].
10//! - Ensure that you process the timer queue when `schedule_wake` is due. This usually involves
11//! waking expired tasks, finding the next expiration time and setting an alarm.
12//!
13//! If a single global timer queue is sufficient for you, you can use the
14//! [`GlobalTimerQueue`] type, which is a wrapper around a global timer queue
15//! protected by a critical section.
16//!
17//! ```
18//! use embassy_time_queue_driver::GlobalTimerQueue;
19//! embassy_time_queue_driver::timer_queue_impl!(
20//! static TIMER_QUEUE_DRIVER: GlobalTimerQueue
21//! = GlobalTimerQueue::new(|next_expiration| todo!("Set an alarm"))
22//! );
23//! ```
24//!
25//! You can also use the `queue_generic` or the `embassy_executor::raw::timer_queue` modules to
26//! implement your own timer queue. These modules contain queue implementations which you can wrap
27//! and tailor to your needs.
28//!
29//! If you are providing an embassy-executor implementation besides a timer queue, you can choose to
30//! expose the `integrated-timers` feature in your implementation. This feature stores timer items
31//! in the tasks themselves, so you don't need a fixed-size queue or dynamic memory allocation.
10//! 32//!
11//! ## Example 33//! ## Example
12//! 34//!
@@ -14,7 +36,7 @@
14//! use core::task::Waker; 36//! use core::task::Waker;
15//! 37//!
16//! use embassy_time::Instant; 38//! use embassy_time::Instant;
17//! use embassy_time::queue::{TimerQueue}; 39//! use embassy_time::queue::TimerQueue;
18//! 40//!
19//! struct MyTimerQueue{}; // not public! 41//! struct MyTimerQueue{}; // not public!
20//! 42//!
@@ -26,11 +48,18 @@
26//! 48//!
27//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{}); 49//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
28//! ``` 50//! ```
51
52pub mod queue_generic;
53
54use core::cell::RefCell;
29use core::task::Waker; 55use core::task::Waker;
30 56
57use critical_section::Mutex;
58
31/// Timer queue 59/// Timer queue
32pub trait TimerQueue { 60pub trait TimerQueue {
33 /// Schedules a waker in the queue to be awoken at moment `at`. 61 /// Schedules a waker in the queue to be awoken at moment `at`.
62 ///
34 /// If this moment is in the past, the waker might be awoken immediately. 63 /// If this moment is in the past, the waker might be awoken immediately.
35 fn schedule_wake(&'static self, at: u64, waker: &Waker); 64 fn schedule_wake(&'static self, at: u64, waker: &Waker);
36} 65}
@@ -58,3 +87,106 @@ macro_rules! timer_queue_impl {
58 } 87 }
59 }; 88 };
60} 89}
90
91#[cfg(feature = "integrated-timers")]
92type InnerQueue = embassy_executor::raw::timer_queue::TimerQueue;
93
94#[cfg(not(feature = "integrated-timers"))]
95type InnerQueue = queue_generic::Queue;
96
97/// A timer queue implementation that can be used as a global timer queue.
98///
99/// This implementation is not thread-safe, and should be protected by a mutex of some sort.
100pub struct GenericTimerQueue<F: Fn(u64) -> bool> {
101 queue: InnerQueue,
102 set_alarm: F,
103}
104
105impl<F: Fn(u64) -> bool> GenericTimerQueue<F> {
106 /// Creates a new timer queue.
107 ///
108 /// `set_alarm` is a function that should set the next alarm time. The function should
109 /// return `true` if the alarm was set, and `false` if the alarm was in the past.
110 pub const fn new(set_alarm: F) -> Self {
111 Self {
112 queue: InnerQueue::new(),
113 set_alarm,
114 }
115 }
116
117 /// Schedules a task to run at a specific time, and returns whether any changes were made.
118 pub fn schedule_wake(&mut self, at: u64, waker: &core::task::Waker) {
119 #[cfg(feature = "integrated-timers")]
120 let waker = embassy_executor::raw::task_from_waker(waker);
121
122 if self.queue.schedule_wake(at, waker) {
123 self.dispatch()
124 }
125 }
126
127 /// Dequeues expired timers and returns the next alarm time.
128 pub fn next_expiration(&mut self, now: u64) -> u64 {
129 self.queue.next_expiration(now)
130 }
131
132 /// Handle the alarm.
133 ///
134 /// Call this function when the next alarm is due.
135 pub fn dispatch(&mut self) {
136 let mut next_expiration = self.next_expiration(embassy_time_driver::now());
137
138 while !(self.set_alarm)(next_expiration) {
139 // next_expiration is in the past, dequeue and find a new expiration
140 next_expiration = self.next_expiration(next_expiration);
141 }
142 }
143}
144
145/// A [`GenericTimerQueue`] protected by a critical section. Directly useable as a [`TimerQueue`].
146pub struct GlobalTimerQueue {
147 inner: Mutex<RefCell<GenericTimerQueue<fn(u64) -> bool>>>,
148}
149
150impl GlobalTimerQueue {
151 /// Creates a new timer queue.
152 ///
153 /// `set_alarm` is a function that should set the next alarm time. The function should
154 /// return `true` if the alarm was set, and `false` if the alarm was in the past.
155 pub const fn new(set_alarm: fn(u64) -> bool) -> Self {
156 Self {
157 inner: Mutex::new(RefCell::new(GenericTimerQueue::new(set_alarm))),
158 }
159 }
160
161 /// Schedules a task to run at a specific time, and returns whether any changes were made.
162 pub fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
163 critical_section::with(|cs| {
164 let mut inner = self.inner.borrow_ref_mut(cs);
165 inner.schedule_wake(at, waker);
166 });
167 }
168
169 /// Dequeues expired timers and returns the next alarm time.
170 pub fn next_expiration(&self, now: u64) -> u64 {
171 critical_section::with(|cs| {
172 let mut inner = self.inner.borrow_ref_mut(cs);
173 inner.next_expiration(now)
174 })
175 }
176
177 /// Handle the alarm.
178 ///
179 /// Call this function when the next alarm is due.
180 pub fn dispatch(&self) {
181 critical_section::with(|cs| {
182 let mut inner = self.inner.borrow_ref_mut(cs);
183 inner.dispatch()
184 })
185 }
186}
187
188impl TimerQueue for GlobalTimerQueue {
189 fn schedule_wake(&'static self, at: u64, waker: &Waker) {
190 GlobalTimerQueue::schedule_wake(self, at, waker)
191 }
192}
diff --git a/embassy-time-queue-driver/src/queue_generic.rs b/embassy-time-queue-driver/src/queue_generic.rs
new file mode 100644
index 000000000..232035bc6
--- /dev/null
+++ b/embassy-time-queue-driver/src/queue_generic.rs
@@ -0,0 +1,146 @@
1//! Generic timer queue implementations.
2//!
3//! Time queue drivers may use this to simplify their implementation.
4
5use core::cmp::{min, Ordering};
6use core::task::Waker;
7
8use heapless::Vec;
9
10#[derive(Debug)]
11struct Timer {
12 at: u64,
13 waker: Waker,
14}
15
16impl PartialEq for Timer {
17 fn eq(&self, other: &Self) -> bool {
18 self.at == other.at
19 }
20}
21
22impl Eq for Timer {}
23
24impl PartialOrd for Timer {
25 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
26 self.at.partial_cmp(&other.at)
27 }
28}
29
30impl Ord for Timer {
31 fn cmp(&self, other: &Self) -> Ordering {
32 self.at.cmp(&other.at)
33 }
34}
35
36/// A timer queue with a pre-determined capacity.
37pub struct ConstGenericQueue<const QUEUE_SIZE: usize> {
38 queue: Vec<Timer, QUEUE_SIZE>,
39}
40
41impl<const QUEUE_SIZE: usize> ConstGenericQueue<QUEUE_SIZE> {
42 /// Creates a new timer queue.
43 pub const fn new() -> Self {
44 Self { queue: Vec::new() }
45 }
46
47 /// Schedules a task to run at a specific time, and returns whether any changes were made.
48 ///
49 /// If this function returns `true`, the called should find the next expiration time and set
50 /// a new alarm for that time.
51 pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
52 self.queue
53 .iter_mut()
54 .find(|timer| timer.waker.will_wake(waker))
55 .map(|timer| {
56 if timer.at > at {
57 timer.at = at;
58 true
59 } else {
60 false
61 }
62 })
63 .unwrap_or_else(|| {
64 let mut timer = Timer {
65 waker: waker.clone(),
66 at,
67 };
68
69 loop {
70 match self.queue.push(timer) {
71 Ok(()) => break,
72 Err(e) => timer = e,
73 }
74
75 self.queue.pop().unwrap().waker.wake();
76 }
77
78 true
79 })
80 }
81
82 /// Dequeues expired timers and returns the next alarm time.
83 pub fn next_expiration(&mut self, now: u64) -> u64 {
84 let mut next_alarm = u64::MAX;
85
86 let mut i = 0;
87 while i < self.queue.len() {
88 let timer = &self.queue[i];
89 if timer.at <= now {
90 let timer = self.queue.swap_remove(i);
91 timer.waker.wake();
92 } else {
93 next_alarm = min(next_alarm, timer.at);
94 i += 1;
95 }
96 }
97
98 next_alarm
99 }
100}
101
102#[cfg(feature = "generic-queue-8")]
103const QUEUE_SIZE: usize = 8;
104#[cfg(feature = "generic-queue-16")]
105const QUEUE_SIZE: usize = 16;
106#[cfg(feature = "generic-queue-32")]
107const QUEUE_SIZE: usize = 32;
108#[cfg(feature = "generic-queue-64")]
109const QUEUE_SIZE: usize = 64;
110#[cfg(feature = "generic-queue-128")]
111const QUEUE_SIZE: usize = 128;
112#[cfg(not(any(
113 feature = "generic-queue-8",
114 feature = "generic-queue-16",
115 feature = "generic-queue-32",
116 feature = "generic-queue-64",
117 feature = "generic-queue-128"
118)))]
119const QUEUE_SIZE: usize = 64;
120
121/// A timer queue with a pre-determined capacity.
122pub struct Queue {
123 queue: ConstGenericQueue<QUEUE_SIZE>,
124}
125
126impl Queue {
127 /// Creates a new timer queue.
128 pub const fn new() -> Self {
129 Self {
130 queue: ConstGenericQueue::new(),
131 }
132 }
133
134 /// Schedules a task to run at a specific time, and returns whether any changes were made.
135 ///
136 /// If this function returns `true`, the called should find the next expiration time and set
137 /// a new alarm for that time.
138 pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
139 self.queue.schedule_wake(at, waker)
140 }
141
142 /// Dequeues expired timers and returns the next alarm time.
143 pub fn next_expiration(&mut self, now: u64) -> u64 {
144 self.queue.next_expiration(now)
145 }
146}