aboutsummaryrefslogtreecommitdiff
path: root/embassy-time-queue-utils
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-time-queue-utils')
-rw-r--r--embassy-time-queue-utils/CHANGELOG.md23
-rw-r--r--embassy-time-queue-utils/Cargo.toml67
-rw-r--r--embassy-time-queue-utils/README.md8
-rw-r--r--embassy-time-queue-utils/build.rs1
-rw-r--r--embassy-time-queue-utils/src/lib.rs13
-rw-r--r--embassy-time-queue-utils/src/queue_generic.rs148
-rw-r--r--embassy-time-queue-utils/src/queue_integrated.rs136
7 files changed, 396 insertions, 0 deletions
diff --git a/embassy-time-queue-utils/CHANGELOG.md b/embassy-time-queue-utils/CHANGELOG.md
new file mode 100644
index 000000000..03d89f9a7
--- /dev/null
+++ b/embassy-time-queue-utils/CHANGELOG.md
@@ -0,0 +1,23 @@
1# Changelog for embassy-time-queue-utils
2
3All notable changes to this project will be documented in this file.
4
5The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
8<!-- next-header -->
9## Unreleased - ReleaseDate
10
11## 0.3.0 - 2025-08-26
12
13## 0.2.1 - 2025-08-26
14
15- Removed the embassy-executor dependency
16
17## 0.2.0 - 2025-08-04
18
19Bumped embassy-executor
20
21## 0.1.0 - 2024-01-11
22
23Initial release
diff --git a/embassy-time-queue-utils/Cargo.toml b/embassy-time-queue-utils/Cargo.toml
new file mode 100644
index 000000000..8da30c544
--- /dev/null
+++ b/embassy-time-queue-utils/Cargo.toml
@@ -0,0 +1,67 @@
1[package]
2name = "embassy-time-queue-utils"
3version = "0.3.0"
4edition = "2024"
5description = "Timer queue driver trait for embassy-time"
6repository = "https://github.com/embassy-rs/embassy"
7documentation = "https://docs.embassy.dev/embassy-time-queue-utils"
8readme = "README.md"
9license = "MIT OR Apache-2.0"
10categories = [
11 "embedded",
12 "no-std",
13 "concurrency",
14 "asynchronous",
15]
16
17# Prevent multiple copies of this crate in the same binary.
18# Needed because different copies might get different tick rates, causing
19# wrong delays if the time driver is using one copy and user code is using another.
20# This is especially common when mixing crates from crates.io and git.
21links = "embassy-time-queue"
22
23[dependencies]
24heapless = "0.8"
25embassy-executor-timer-queue = { version = "0.1", path = "../embassy-executor-timer-queue", features = ["timer-item-size-6-words"] }
26
27[features]
28#! ### Generic Queue
29
30#! By default this crate uses a timer queue implementation that is faster but depends on `embassy-executor`.
31#! It will panic if you try to await any timer when using another executor.
32#!
33#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor.
34#! To enable it, enable any of the features below.
35#!
36#! The features also set how many timers are used for the generic queue. At most one
37#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used.
38#!
39#! When using embassy-time-queue-driver from libraries, you should *not* enable any `generic-queue-*` feature, to allow the
40#! end user to pick.
41
42## Generic Queue with 8 timers
43generic-queue-8 = ["_generic-queue"]
44## Generic Queue with 16 timers
45generic-queue-16 = ["_generic-queue"]
46## Generic Queue with 32 timers
47generic-queue-32 = ["_generic-queue"]
48## Generic Queue with 64 timers
49generic-queue-64 = ["_generic-queue"]
50## Generic Queue with 128 timers
51generic-queue-128 = ["_generic-queue"]
52
53_generic-queue = []
54
55[package.metadata.embassy]
56build = [
57 {target = "thumbv6m-none-eabi", features = []},
58 {target = "thumbv6m-none-eabi", features = ["generic-queue-8"]},
59 # Xtensa builds
60 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = []},
61 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = ["generic-queue-8"]},
62]
63
64[package.metadata.embassy_docs]
65src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-utils-v$VERSION/embassy-time-queue-utils/src/"
66src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-utils/src/"
67target = "x86_64-unknown-linux-gnu"
diff --git a/embassy-time-queue-utils/README.md b/embassy-time-queue-utils/README.md
new file mode 100644
index 000000000..36461f1cb
--- /dev/null
+++ b/embassy-time-queue-utils/README.md
@@ -0,0 +1,8 @@
1# embassy-time-queue-utils
2
3This crate contains timer queues to help implementing an [`embassy-time-driver`](https://crates.io/crates/embassy-time-driver).
4
5As a HAL user, you should not need to depend on this crate.
6
7As a HAL implementer, you need to depend on this crate if you want to implement a time driver,
8but how you should do so is documented in `embassy-time-driver`.
diff --git a/embassy-time-queue-utils/build.rs b/embassy-time-queue-utils/build.rs
new file mode 100644
index 000000000..f328e4d9d
--- /dev/null
+++ b/embassy-time-queue-utils/build.rs
@@ -0,0 +1 @@
fn main() {}
diff --git a/embassy-time-queue-utils/src/lib.rs b/embassy-time-queue-utils/src/lib.rs
new file mode 100644
index 000000000..a6f66913f
--- /dev/null
+++ b/embassy-time-queue-utils/src/lib.rs
@@ -0,0 +1,13 @@
1#![no_std]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5#[cfg(feature = "_generic-queue")]
6pub mod queue_generic;
7#[cfg(not(feature = "_generic-queue"))]
8pub mod queue_integrated;
9
10#[cfg(feature = "_generic-queue")]
11pub use queue_generic::Queue;
12#[cfg(not(feature = "_generic-queue"))]
13pub use queue_integrated::Queue;
diff --git a/embassy-time-queue-utils/src/queue_generic.rs b/embassy-time-queue-utils/src/queue_generic.rs
new file mode 100644
index 000000000..88986953d
--- /dev/null
+++ b/embassy-time-queue-utils/src/queue_generic.rs
@@ -0,0 +1,148 @@
1//! Generic timer queue implementations.
2//!
3//! Time queue drivers may use this to simplify their implementation.
4
5use core::cmp::{Ordering, min};
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.
37#[derive(Debug)]
38pub struct ConstGenericQueue<const QUEUE_SIZE: usize> {
39 queue: Vec<Timer, QUEUE_SIZE>,
40}
41
42impl<const QUEUE_SIZE: usize> ConstGenericQueue<QUEUE_SIZE> {
43 /// Creates a new timer queue.
44 pub const fn new() -> Self {
45 Self { queue: Vec::new() }
46 }
47
48 /// Schedules a task to run at a specific time, and returns whether any changes were made.
49 ///
50 /// If this function returns `true`, the called should find the next expiration time and set
51 /// a new alarm for that time.
52 pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
53 self.queue
54 .iter_mut()
55 .find(|timer| timer.waker.will_wake(waker))
56 .map(|timer| {
57 if timer.at > at {
58 timer.at = at;
59 true
60 } else {
61 false
62 }
63 })
64 .unwrap_or_else(|| {
65 let mut timer = Timer {
66 waker: waker.clone(),
67 at,
68 };
69
70 loop {
71 match self.queue.push(timer) {
72 Ok(()) => break,
73 Err(e) => timer = e,
74 }
75
76 self.queue.pop().unwrap().waker.wake();
77 }
78
79 true
80 })
81 }
82
83 /// Dequeues expired timers and returns the next alarm time.
84 pub fn next_expiration(&mut self, now: u64) -> u64 {
85 let mut next_alarm = u64::MAX;
86
87 let mut i = 0;
88 while i < self.queue.len() {
89 let timer = &self.queue[i];
90 if timer.at <= now {
91 let timer = self.queue.swap_remove(i);
92 timer.waker.wake();
93 } else {
94 next_alarm = min(next_alarm, timer.at);
95 i += 1;
96 }
97 }
98
99 next_alarm
100 }
101}
102
103#[cfg(feature = "generic-queue-8")]
104const QUEUE_SIZE: usize = 8;
105#[cfg(feature = "generic-queue-16")]
106const QUEUE_SIZE: usize = 16;
107#[cfg(feature = "generic-queue-32")]
108const QUEUE_SIZE: usize = 32;
109#[cfg(feature = "generic-queue-64")]
110const QUEUE_SIZE: usize = 64;
111#[cfg(feature = "generic-queue-128")]
112const QUEUE_SIZE: usize = 128;
113#[cfg(not(any(
114 feature = "generic-queue-8",
115 feature = "generic-queue-16",
116 feature = "generic-queue-32",
117 feature = "generic-queue-64",
118 feature = "generic-queue-128"
119)))]
120const QUEUE_SIZE: usize = 64;
121
122/// A timer queue with a pre-determined capacity.
123#[derive(Debug)]
124pub struct Queue {
125 queue: ConstGenericQueue<QUEUE_SIZE>,
126}
127
128impl Queue {
129 /// Creates a new timer queue.
130 pub const fn new() -> Self {
131 Self {
132 queue: ConstGenericQueue::new(),
133 }
134 }
135
136 /// Schedules a task to run at a specific time, and returns whether any changes were made.
137 ///
138 /// If this function returns `true`, the called should find the next expiration time and set
139 /// a new alarm for that time.
140 pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
141 self.queue.schedule_wake(at, waker)
142 }
143
144 /// Dequeues expired timers and returns the next alarm time.
145 pub fn next_expiration(&mut self, now: u64) -> u64 {
146 self.queue.next_expiration(now)
147 }
148}
diff --git a/embassy-time-queue-utils/src/queue_integrated.rs b/embassy-time-queue-utils/src/queue_integrated.rs
new file mode 100644
index 000000000..2731d1ac6
--- /dev/null
+++ b/embassy-time-queue-utils/src/queue_integrated.rs
@@ -0,0 +1,136 @@
1//! Timer queue operations.
2use core::cell::Cell;
3use core::cmp::min;
4use core::ptr::NonNull;
5use core::task::Waker;
6
7use embassy_executor_timer_queue::TimerQueueItem;
8
9/// An item in the timer queue.
10#[derive(Default)]
11struct QueueItem {
12 /// The next item in the queue.
13 ///
14 /// If this field contains `Some`, the item is in the queue. The last item in the queue has a
15 /// value of `Some(dangling_pointer)`
16 pub next: Cell<Option<NonNull<QueueItem>>>,
17
18 /// The time at which this item expires.
19 pub expires_at: u64,
20
21 /// The registered waker. If Some, the item is enqueued in the timer queue.
22 pub waker: Option<Waker>,
23}
24
25unsafe impl Sync for QueueItem {}
26
27/// A timer queue, with items integrated into tasks.
28///
29/// # Safety
30///
31/// **This Queue is only safe when there is a single integrated queue in the system.**
32///
33/// If there are multiple integrated queues, additional checks are necessary to ensure that a Waker
34/// is not attempted to be enqueued in multiple queues.
35pub struct Queue {
36 head: Cell<Option<NonNull<QueueItem>>>,
37}
38
39impl core::fmt::Debug for Queue {
40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 f.debug_struct("Queue").finish()
42 }
43}
44
45unsafe impl Send for Queue {}
46unsafe impl Sync for Queue {}
47
48impl Queue {
49 /// Creates a new timer queue.
50 pub const fn new() -> Self {
51 Self { head: Cell::new(None) }
52 }
53
54 /// Schedules a task to run at a specific time.
55 ///
56 /// If this function returns `true`, the called should find the next expiration time and set
57 /// a new alarm for that time.
58 pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
59 let item = unsafe {
60 // Safety: the `&mut self`, along with the Safety note of the Queue, are sufficient to
61 // ensure that this function creates the only mutable reference to the queue item.
62 TimerQueueItem::from_embassy_waker(waker)
63 };
64 let item = unsafe { item.as_mut::<QueueItem>() };
65 match item.waker.as_ref() {
66 Some(_) if at <= item.expires_at => {
67 // If expiration is sooner than previously set, update.
68 item.expires_at = at;
69 // The waker is always stored in its own queue item, so we don't need to update it.
70
71 // Trigger a queue update in case this item can be immediately dequeued.
72 true
73 }
74 Some(_) => {
75 // Queue item does not need to be updated, the task will be scheduled to be woken
76 // before the new expiration.
77 false
78 }
79 None => {
80 // If not in the queue, add it and update.
81 let mut item_ptr = NonNull::from(item);
82 let prev = self.head.replace(Some(item_ptr));
83
84 let item = unsafe { item_ptr.as_mut() };
85
86 item.expires_at = at;
87 item.waker = Some(waker.clone());
88 item.next.set(prev);
89 // The default implementation doesn't care about the
90 // opaque payload, leave it unchanged.
91
92 true
93 }
94 }
95 }
96
97 /// Dequeues expired timers and returns the next alarm time.
98 ///
99 /// The provided callback will be called for each expired task. Tasks that never expire
100 /// will be removed, but the callback will not be called.
101 pub fn next_expiration(&mut self, now: u64) -> u64 {
102 let mut next_expiration = u64::MAX;
103
104 self.retain(|item| {
105 if item.expires_at <= now {
106 // Timer expired, process task.
107 if let Some(waker) = item.waker.take() {
108 waker.wake();
109 }
110 false
111 } else {
112 // Timer didn't yet expire, or never expires.
113 next_expiration = min(next_expiration, item.expires_at);
114 item.expires_at != u64::MAX
115 }
116 });
117
118 next_expiration
119 }
120
121 fn retain(&mut self, mut f: impl FnMut(&mut QueueItem) -> bool) {
122 let mut prev = &self.head;
123 while let Some(mut p) = prev.get() {
124 let mut item = unsafe { p.as_mut() };
125
126 if f(&mut item) {
127 // Skip to next
128 prev = &item.next;
129 } else {
130 // Remove it
131 prev.set(item.next.get());
132 item.next.set(None);
133 }
134 }
135 }
136}