diff options
| -rw-r--r-- | embassy-executor-timer-queue/CHANGELOG.md | 10 | ||||
| -rw-r--r-- | embassy-executor-timer-queue/Cargo.toml | 35 | ||||
| -rw-r--r-- | embassy-executor-timer-queue/README.md | 10 | ||||
| -rw-r--r-- | embassy-executor-timer-queue/src/lib.rs | 97 | ||||
| -rw-r--r-- | embassy-executor/CHANGELOG.md | 5 | ||||
| -rw-r--r-- | embassy-executor/Cargo.toml | 17 | ||||
| -rw-r--r-- | embassy-executor/src/raw/mod.rs | 33 | ||||
| -rw-r--r-- | embassy-executor/src/raw/timer_queue.rs | 73 | ||||
| -rw-r--r-- | embassy-time-queue-utils/CHANGELOG.md | 8 | ||||
| -rw-r--r-- | embassy-time-queue-utils/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy-time-queue-utils/src/lib.rs | 1 | ||||
| -rw-r--r-- | embassy-time-queue-utils/src/queue_integrated.rs | 122 |
12 files changed, 267 insertions, 146 deletions
diff --git a/embassy-executor-timer-queue/CHANGELOG.md b/embassy-executor-timer-queue/CHANGELOG.md new file mode 100644 index 000000000..d43e0060d --- /dev/null +++ b/embassy-executor-timer-queue/CHANGELOG.md | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | # Changelog for embassy-time-queue-utils | ||
| 2 | |||
| 3 | All notable changes to this project will be documented in this file. | ||
| 4 | |||
| 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
| 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
| 7 | |||
| 8 | ## Unreeased | ||
| 9 | |||
| 10 | - Initial implementation | ||
diff --git a/embassy-executor-timer-queue/Cargo.toml b/embassy-executor-timer-queue/Cargo.toml new file mode 100644 index 000000000..0db327ba9 --- /dev/null +++ b/embassy-executor-timer-queue/Cargo.toml | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | [package] | ||
| 2 | name = "embassy-executor-timer-queue" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | description = "Timer queue item and interface between embassy-executor and timer queues" | ||
| 6 | repository = "https://github.com/embassy-rs/embassy" | ||
| 7 | documentation = "https://docs.embassy.dev/embassy-executor-timer-queue" | ||
| 8 | readme = "README.md" | ||
| 9 | license = "MIT OR Apache-2.0" | ||
| 10 | categories = [ | ||
| 11 | "embedded", | ||
| 12 | "no-std", | ||
| 13 | "concurrency", | ||
| 14 | "asynchronous", | ||
| 15 | ] | ||
| 16 | |||
| 17 | [dependencies] | ||
| 18 | |||
| 19 | [features] | ||
| 20 | #! ### Timer Queue Item Size | ||
| 21 | #! Sets the size of the timer items. | ||
| 22 | |||
| 23 | ## 4 words | ||
| 24 | timer-item-size-4-words = [] | ||
| 25 | |||
| 26 | ## 6 words | ||
| 27 | timer-item-size-6-words = [] | ||
| 28 | |||
| 29 | ## 8 words | ||
| 30 | timer-item-size-8-words = [] | ||
| 31 | |||
| 32 | [package.metadata.embassy_docs] | ||
| 33 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-timer-queue-v$VERSION/embassy-executor-timer-queue/src/" | ||
| 34 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor-timer-queue/src/" | ||
| 35 | target = "x86_64-unknown-linux-gnu" | ||
diff --git a/embassy-executor-timer-queue/README.md b/embassy-executor-timer-queue/README.md new file mode 100644 index 000000000..602aca7b1 --- /dev/null +++ b/embassy-executor-timer-queue/README.md | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | # embassy-executor-time-queue | ||
| 2 | |||
| 3 | This crate defines the timer queue item that embassy-executor provides, and a way to access it, for | ||
| 4 | executor-integrated timer queues. The crate decouples the release cycle of embassy-executor from | ||
| 5 | that of the queue implementations'. | ||
| 6 | |||
| 7 | As a HAL implementer, you only need to depend on this crate if you want to implement executor-integrated | ||
| 8 | timer queues yourself, without using [`embassy-time-queue-utils`](https://crates.io/crates/embassy-time-queue-utils). | ||
| 9 | |||
| 10 | As a HAL user, you should not need to depend on this crate. | ||
diff --git a/embassy-executor-timer-queue/src/lib.rs b/embassy-executor-timer-queue/src/lib.rs new file mode 100644 index 000000000..456ccaec3 --- /dev/null +++ b/embassy-executor-timer-queue/src/lib.rs | |||
| @@ -0,0 +1,97 @@ | |||
| 1 | //! Timer queue item for embassy-executor integrated timer queues | ||
| 2 | //! | ||
| 3 | //! `embassy-executor` provides the memory needed to implement integrated timer queues. This crate | ||
| 4 | //! exists to separate that memory from `embassy-executor` itself, to decouple the timer queue's | ||
| 5 | //! release cycle from `embassy-executor`. | ||
| 6 | //! | ||
| 7 | //! This crate contains two things: | ||
| 8 | //! - [`TimerQueueItem`]: The item type that can be requested from the executor. The size of this | ||
| 9 | //! type can be configured using the `timer-item-size-N-words` Cargo features. | ||
| 10 | //! - The expectation that `extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &mut TimerQueueItem` | ||
| 11 | //! is implemented (by `embassy-executor`, most likely). This function must return a mutable | ||
| 12 | //! reference to the `TimerQueueItem` associated with the given waker. | ||
| 13 | //! | ||
| 14 | //! As a queue implementor, you will need to choose one of the `timer-item-size-N-words` features to | ||
| 15 | //! select a queue item size. You can then define your own item type, which must be | ||
| 16 | //! `#[repr(align(8))]` (or less) and must fit into the size you selected. | ||
| 17 | //! | ||
| 18 | //! You can access the `TimerQueueItem` from a `Waker` using the [`from_embassy_waker`](TimerQueueItem::from_embassy_waker) | ||
| 19 | //! method. You can then use the [`as_ref`](TimerQueueItem::as_ref) and [`as_mut`](TimerQueueItem::as_mut) | ||
| 20 | //! methods to reinterpret the data stored in the item as your custom item type. | ||
| 21 | #![no_std] | ||
| 22 | |||
| 23 | use core::task::Waker; | ||
| 24 | |||
| 25 | const ITEM_SIZE: usize = if cfg!(feature = "timer-item-size-8-words") { | ||
| 26 | 8 | ||
| 27 | } else if cfg!(feature = "timer-item-size-6-words") { | ||
| 28 | 6 | ||
| 29 | } else if cfg!(feature = "timer-item-size-4-words") { | ||
| 30 | 4 | ||
| 31 | } else { | ||
| 32 | 0 | ||
| 33 | }; | ||
| 34 | |||
| 35 | /// The timer queue item provided by the executor. | ||
| 36 | /// | ||
| 37 | /// This type is opaque, it only provides the raw storage for a queue item. The queue implementation | ||
| 38 | /// is responsible for reinterpreting the contents of the item using [`TimerQueueItem::as_ref`] and | ||
| 39 | /// [`TimerQueueItem::as_mut`]. | ||
| 40 | #[repr(align(8))] | ||
| 41 | pub struct TimerQueueItem { | ||
| 42 | data: [usize; ITEM_SIZE], | ||
| 43 | } | ||
| 44 | |||
| 45 | impl TimerQueueItem { | ||
| 46 | /// Creates a new, zero-initialized `TimerQueueItem`. | ||
| 47 | pub const fn new() -> Self { | ||
| 48 | Self { data: [0; ITEM_SIZE] } | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Retrieves the `TimerQueueItem` reference that belongs to the task of the waker. | ||
| 52 | /// | ||
| 53 | /// Panics if called with a non-embassy waker. | ||
| 54 | /// | ||
| 55 | /// # Safety | ||
| 56 | /// | ||
| 57 | /// The caller must ensure they are not violating Rust's aliasing rules - it is not allowed | ||
| 58 | /// to use this method to create multiple mutable references to the same `TimerQueueItem` at | ||
| 59 | /// the same time. | ||
| 60 | /// | ||
| 61 | /// This function must only be called in the context of a timer queue implementation. | ||
| 62 | pub unsafe fn from_embassy_waker(waker: &Waker) -> &'static mut Self { | ||
| 63 | unsafe extern "Rust" { | ||
| 64 | // Waker -> TimerQueueItem, validates that Waker is an embassy Waker. | ||
| 65 | fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem; | ||
| 66 | } | ||
| 67 | unsafe { __embassy_time_queue_item_from_waker(waker) } | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Access the data as a reference to a type `T`. | ||
| 71 | /// | ||
| 72 | /// Safety: | ||
| 73 | /// | ||
| 74 | /// - The type must be valid when zero-initialized. | ||
| 75 | /// - The timer queue should only be interpreted as a single type `T` during its lifetime. | ||
| 76 | pub unsafe fn as_ref<T>(&self) -> &T { | ||
| 77 | const { | ||
| 78 | assert!(core::mem::size_of::<Self>() >= core::mem::size_of::<T>()); | ||
| 79 | assert!(core::mem::align_of::<Self>() >= core::mem::align_of::<T>()); | ||
| 80 | } | ||
| 81 | unsafe { &*(self.data.as_ptr() as *const T) } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Access the data as a reference to a type `T`. | ||
| 85 | /// | ||
| 86 | /// Safety: | ||
| 87 | /// | ||
| 88 | /// - The type must be valid when zero-initialized. | ||
| 89 | /// - The timer queue should only be interpreted as a single type `T` during its lifetime. | ||
| 90 | pub unsafe fn as_mut<T>(&self) -> &mut T { | ||
| 91 | const { | ||
| 92 | assert!(core::mem::size_of::<Self>() >= core::mem::size_of::<T>()); | ||
| 93 | assert!(core::mem::align_of::<Self>() >= core::mem::align_of::<T>()); | ||
| 94 | } | ||
| 95 | unsafe { &mut *(self.data.as_ptr() as *mut T) } | ||
| 96 | } | ||
| 97 | } | ||
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index c35ad10f3..e2214b8ef 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md | |||
| @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 8 | <!-- next-header --> | 8 | <!-- next-header --> |
| 9 | ## Unreleased - ReleaseDate | 9 | ## Unreleased - ReleaseDate |
| 10 | 10 | ||
| 11 | - Added `extern "Rust" fn __embassy_time_queue_item_from_waker` | ||
| 12 | - Removed `TaskRef::dangling` and `TaskRef::timer_queue_item` | ||
| 13 | - Added `embassy_time_queue_utils` as a dependency | ||
| 14 | - Moved the `TimeQueueItem` struct and `timer-item-payload-size-*` features into embassy-time-queue-utils | ||
| 15 | |||
| 11 | ## 0.8.0 - 2025-07-31 | 16 | ## 0.8.0 - 2025-07-31 |
| 12 | 17 | ||
| 13 | - Added `SpawnToken::id` | 18 | - Added `SpawnToken::id` |
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 5e950bf45..bff13de56 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml | |||
| @@ -35,6 +35,7 @@ rtos-trace = { version = "0.1.3", optional = true } | |||
| 35 | 35 | ||
| 36 | embassy-executor-macros = { version = "0.7.0", path = "../embassy-executor-macros" } | 36 | embassy-executor-macros = { version = "0.7.0", path = "../embassy-executor-macros" } |
| 37 | embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } | 37 | embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } |
| 38 | embassy-executor-timer-queue = { version = "0.1", path = "../embassy-executor-timer-queue" } | ||
| 38 | critical-section = "1.1" | 39 | critical-section = "1.1" |
| 39 | 40 | ||
| 40 | document-features = "0.2.7" | 41 | document-features = "0.2.7" |
| @@ -98,19 +99,3 @@ executor-interrupt = [] | |||
| 98 | trace = [] | 99 | trace = [] |
| 99 | ## Enable support for rtos-trace framework | 100 | ## Enable support for rtos-trace framework |
| 100 | rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] | 101 | rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] |
| 101 | |||
| 102 | #! ### Timer Item Payload Size | ||
| 103 | #! Sets the size of the payload for timer items, allowing integrated timer implementors to store | ||
| 104 | #! additional data in the timer item. The payload field will be aligned to this value as well. | ||
| 105 | #! If these features are not defined, the timer item will contain no payload field. | ||
| 106 | |||
| 107 | _timer-item-payload = [] # A size was picked | ||
| 108 | |||
| 109 | ## 1 bytes | ||
| 110 | timer-item-payload-size-1 = ["_timer-item-payload"] | ||
| 111 | ## 2 bytes | ||
| 112 | timer-item-payload-size-2 = ["_timer-item-payload"] | ||
| 113 | ## 4 bytes | ||
| 114 | timer-item-payload-size-4 = ["_timer-item-payload"] | ||
| 115 | ## 8 bytes | ||
| 116 | timer-item-payload-size-8 = ["_timer-item-payload"] | ||
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index c8f1f46c2..8e783b2af 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs | |||
| @@ -16,7 +16,6 @@ mod run_queue; | |||
| 16 | #[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] | 16 | #[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] |
| 17 | mod state; | 17 | mod state; |
| 18 | 18 | ||
| 19 | pub mod timer_queue; | ||
| 20 | #[cfg(feature = "trace")] | 19 | #[cfg(feature = "trace")] |
| 21 | pub mod trace; | 20 | pub mod trace; |
| 22 | pub(crate) mod util; | 21 | pub(crate) mod util; |
| @@ -31,8 +30,9 @@ use core::ptr::NonNull; | |||
| 31 | #[cfg(not(feature = "arch-avr"))] | 30 | #[cfg(not(feature = "arch-avr"))] |
| 32 | use core::sync::atomic::AtomicPtr; | 31 | use core::sync::atomic::AtomicPtr; |
| 33 | use core::sync::atomic::Ordering; | 32 | use core::sync::atomic::Ordering; |
| 34 | use core::task::{Context, Poll}; | 33 | use core::task::{Context, Poll, Waker}; |
| 35 | 34 | ||
| 35 | use embassy_executor_timer_queue::TimerQueueItem; | ||
| 36 | #[cfg(feature = "arch-avr")] | 36 | #[cfg(feature = "arch-avr")] |
| 37 | use portable_atomic::AtomicPtr; | 37 | use portable_atomic::AtomicPtr; |
| 38 | 38 | ||
| @@ -42,6 +42,11 @@ use self::util::{SyncUnsafeCell, UninitCell}; | |||
| 42 | pub use self::waker::task_from_waker; | 42 | pub use self::waker::task_from_waker; |
| 43 | use super::SpawnToken; | 43 | use super::SpawnToken; |
| 44 | 44 | ||
| 45 | #[no_mangle] | ||
| 46 | extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem { | ||
| 47 | unsafe { task_from_waker(waker).timer_queue_item() } | ||
| 48 | } | ||
| 49 | |||
| 45 | /// Raw task header for use in task pointers. | 50 | /// Raw task header for use in task pointers. |
| 46 | /// | 51 | /// |
| 47 | /// A task can be in one of the following states: | 52 | /// A task can be in one of the following states: |
| @@ -88,7 +93,7 @@ pub(crate) struct TaskHeader { | |||
| 88 | poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, | 93 | poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, |
| 89 | 94 | ||
| 90 | /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. | 95 | /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. |
| 91 | pub(crate) timer_queue_item: timer_queue::TimerQueueItem, | 96 | pub(crate) timer_queue_item: TimerQueueItem, |
| 92 | #[cfg(feature = "trace")] | 97 | #[cfg(feature = "trace")] |
| 93 | pub(crate) name: Option<&'static str>, | 98 | pub(crate) name: Option<&'static str>, |
| 94 | #[cfg(feature = "trace")] | 99 | #[cfg(feature = "trace")] |
| @@ -120,16 +125,6 @@ impl TaskRef { | |||
| 120 | } | 125 | } |
| 121 | } | 126 | } |
| 122 | 127 | ||
| 123 | /// # Safety | ||
| 124 | /// | ||
| 125 | /// The result of this function must only be compared | ||
| 126 | /// for equality, or stored, but not used. | ||
| 127 | pub const unsafe fn dangling() -> Self { | ||
| 128 | Self { | ||
| 129 | ptr: NonNull::dangling(), | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | pub(crate) fn header(self) -> &'static TaskHeader { | 128 | pub(crate) fn header(self) -> &'static TaskHeader { |
| 134 | unsafe { self.ptr.as_ref() } | 129 | unsafe { self.ptr.as_ref() } |
| 135 | } | 130 | } |
| @@ -140,9 +135,13 @@ impl TaskRef { | |||
| 140 | executor.as_ref().map(|e| Executor::wrap(e)) | 135 | executor.as_ref().map(|e| Executor::wrap(e)) |
| 141 | } | 136 | } |
| 142 | 137 | ||
| 143 | /// Returns a reference to the timer queue item. | 138 | /// Returns a mutable reference to the timer queue item. |
| 144 | pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { | 139 | /// |
| 145 | &self.header().timer_queue_item | 140 | /// Safety |
| 141 | /// | ||
| 142 | /// This function must only be called in the context of the integrated timer queue. | ||
| 143 | unsafe fn timer_queue_item(mut self) -> &'static mut TimerQueueItem { | ||
| 144 | unsafe { &mut self.ptr.as_mut().timer_queue_item } | ||
| 146 | } | 145 | } |
| 147 | 146 | ||
| 148 | /// The returned pointer is valid for the entire TaskStorage. | 147 | /// The returned pointer is valid for the entire TaskStorage. |
| @@ -189,7 +188,7 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 189 | // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` | 188 | // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` |
| 190 | poll_fn: SyncUnsafeCell::new(None), | 189 | poll_fn: SyncUnsafeCell::new(None), |
| 191 | 190 | ||
| 192 | timer_queue_item: timer_queue::TimerQueueItem::new(), | 191 | timer_queue_item: TimerQueueItem::new(), |
| 193 | #[cfg(feature = "trace")] | 192 | #[cfg(feature = "trace")] |
| 194 | name: None, | 193 | name: None, |
| 195 | #[cfg(feature = "trace")] | 194 | #[cfg(feature = "trace")] |
diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs deleted file mode 100644 index e52453be4..000000000 --- a/embassy-executor/src/raw/timer_queue.rs +++ /dev/null | |||
| @@ -1,73 +0,0 @@ | |||
| 1 | //! Timer queue operations. | ||
| 2 | |||
| 3 | use core::cell::Cell; | ||
| 4 | |||
| 5 | use super::TaskRef; | ||
| 6 | |||
| 7 | #[cfg(feature = "_timer-item-payload")] | ||
| 8 | macro_rules! define_opaque { | ||
| 9 | ($size:tt) => { | ||
| 10 | /// An opaque data type. | ||
| 11 | #[repr(align($size))] | ||
| 12 | pub struct OpaqueData { | ||
| 13 | data: [u8; $size], | ||
| 14 | } | ||
| 15 | |||
| 16 | impl OpaqueData { | ||
| 17 | const fn new() -> Self { | ||
| 18 | Self { data: [0; $size] } | ||
| 19 | } | ||
| 20 | |||
| 21 | /// Access the data as a reference to a type `T`. | ||
| 22 | /// | ||
| 23 | /// Safety: | ||
| 24 | /// | ||
| 25 | /// The caller must ensure that the size of the type `T` is less than, or equal to | ||
| 26 | /// the size of the payload, and must ensure that the alignment of the type `T` is | ||
| 27 | /// less than, or equal to the alignment of the payload. | ||
| 28 | /// | ||
| 29 | /// The type must be valid when zero-initialized. | ||
| 30 | pub unsafe fn as_ref<T>(&self) -> &T { | ||
| 31 | &*(self.data.as_ptr() as *const T) | ||
| 32 | } | ||
| 33 | } | ||
| 34 | }; | ||
| 35 | } | ||
| 36 | |||
| 37 | #[cfg(feature = "timer-item-payload-size-1")] | ||
| 38 | define_opaque!(1); | ||
| 39 | #[cfg(feature = "timer-item-payload-size-2")] | ||
| 40 | define_opaque!(2); | ||
| 41 | #[cfg(feature = "timer-item-payload-size-4")] | ||
| 42 | define_opaque!(4); | ||
| 43 | #[cfg(feature = "timer-item-payload-size-8")] | ||
| 44 | define_opaque!(8); | ||
| 45 | |||
| 46 | /// An item in the timer queue. | ||
| 47 | pub struct TimerQueueItem { | ||
| 48 | /// The next item in the queue. | ||
| 49 | /// | ||
| 50 | /// If this field contains `Some`, the item is in the queue. The last item in the queue has a | ||
| 51 | /// value of `Some(dangling_pointer)` | ||
| 52 | pub next: Cell<Option<TaskRef>>, | ||
| 53 | |||
| 54 | /// The time at which this item expires. | ||
| 55 | pub expires_at: Cell<u64>, | ||
| 56 | |||
| 57 | /// Some implementation-defined, zero-initialized piece of data. | ||
| 58 | #[cfg(feature = "_timer-item-payload")] | ||
| 59 | pub payload: OpaqueData, | ||
| 60 | } | ||
| 61 | |||
| 62 | unsafe impl Sync for TimerQueueItem {} | ||
| 63 | |||
| 64 | impl TimerQueueItem { | ||
| 65 | pub(crate) const fn new() -> Self { | ||
| 66 | Self { | ||
| 67 | next: Cell::new(None), | ||
| 68 | expires_at: Cell::new(0), | ||
| 69 | #[cfg(feature = "_timer-item-payload")] | ||
| 70 | payload: OpaqueData::new(), | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
diff --git a/embassy-time-queue-utils/CHANGELOG.md b/embassy-time-queue-utils/CHANGELOG.md index ae4714f62..ebd6565d1 100644 --- a/embassy-time-queue-utils/CHANGELOG.md +++ b/embassy-time-queue-utils/CHANGELOG.md | |||
| @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. | |||
| 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | 7 | ||
| 8 | ## Unreleased | ||
| 9 | |||
| 10 | - Removed the embassy-executor dependency | ||
| 11 | |||
| 12 | ## 0.2.0 - 2025-08-04 | ||
| 13 | |||
| 14 | Bumpep embassy-executor | ||
| 15 | |||
| 8 | ## 0.1.0 - 2024-01-11 | 16 | ## 0.1.0 - 2024-01-11 |
| 9 | 17 | ||
| 10 | Initial release | 18 | Initial release |
diff --git a/embassy-time-queue-utils/Cargo.toml b/embassy-time-queue-utils/Cargo.toml index 93fa0ce3c..8991da66c 100644 --- a/embassy-time-queue-utils/Cargo.toml +++ b/embassy-time-queue-utils/Cargo.toml | |||
| @@ -22,7 +22,7 @@ links = "embassy-time-queue" | |||
| 22 | 22 | ||
| 23 | [dependencies] | 23 | [dependencies] |
| 24 | heapless = "0.8" | 24 | heapless = "0.8" |
| 25 | embassy-executor = { version = "0.8", path = "../embassy-executor" } | 25 | embassy-executor-timer-queue = { version = "0.1", path = "../embassy-executor-timer-queue", features = ["timer-item-size-6-words"] } |
| 26 | 26 | ||
| 27 | [features] | 27 | [features] |
| 28 | #! ### Generic Queue | 28 | #! ### Generic Queue |
diff --git a/embassy-time-queue-utils/src/lib.rs b/embassy-time-queue-utils/src/lib.rs index 08e186432..a6f66913f 100644 --- a/embassy-time-queue-utils/src/lib.rs +++ b/embassy-time-queue-utils/src/lib.rs | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | #![no_std] | 1 | #![no_std] |
| 2 | #![doc = include_str!("../README.md")] | 2 | #![doc = include_str!("../README.md")] |
| 3 | #![warn(missing_docs)] | 3 | #![warn(missing_docs)] |
| 4 | #![deny(missing_debug_implementations)] | ||
| 5 | 4 | ||
| 6 | #[cfg(feature = "_generic-queue")] | 5 | #[cfg(feature = "_generic-queue")] |
| 7 | pub mod queue_generic; | 6 | pub mod queue_generic; |
diff --git a/embassy-time-queue-utils/src/queue_integrated.rs b/embassy-time-queue-utils/src/queue_integrated.rs index 748cd7843..2731d1ac6 100644 --- a/embassy-time-queue-utils/src/queue_integrated.rs +++ b/embassy-time-queue-utils/src/queue_integrated.rs | |||
| @@ -1,16 +1,50 @@ | |||
| 1 | //! Timer queue operations. | 1 | //! Timer queue operations. |
| 2 | use core::cell::Cell; | 2 | use core::cell::Cell; |
| 3 | use core::cmp::min; | 3 | use core::cmp::min; |
| 4 | use core::ptr::NonNull; | ||
| 4 | use core::task::Waker; | 5 | use core::task::Waker; |
| 5 | 6 | ||
| 6 | use embassy_executor::raw::TaskRef; | 7 | use embassy_executor_timer_queue::TimerQueueItem; |
| 8 | |||
| 9 | /// An item in the timer queue. | ||
| 10 | #[derive(Default)] | ||
| 11 | struct 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 | |||
| 25 | unsafe impl Sync for QueueItem {} | ||
| 7 | 26 | ||
| 8 | /// A timer queue, with items integrated into tasks. | 27 | /// A timer queue, with items integrated into tasks. |
| 9 | #[derive(Debug)] | 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. | ||
| 10 | pub struct Queue { | 35 | pub struct Queue { |
| 11 | head: Cell<Option<TaskRef>>, | 36 | head: Cell<Option<NonNull<QueueItem>>>, |
| 12 | } | 37 | } |
| 13 | 38 | ||
| 39 | impl 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 | |||
| 45 | unsafe impl Send for Queue {} | ||
| 46 | unsafe impl Sync for Queue {} | ||
| 47 | |||
| 14 | impl Queue { | 48 | impl Queue { |
| 15 | /// Creates a new timer queue. | 49 | /// Creates a new timer queue. |
| 16 | pub const fn new() -> Self { | 50 | pub const fn new() -> Self { |
| @@ -22,25 +56,41 @@ impl Queue { | |||
| 22 | /// If this function returns `true`, the called should find the next expiration time and set | 56 | /// If this function returns `true`, the called should find the next expiration time and set |
| 23 | /// a new alarm for that time. | 57 | /// a new alarm for that time. |
| 24 | pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { | 58 | pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { |
| 25 | let task = embassy_executor::raw::task_from_waker(waker); | 59 | let item = unsafe { |
| 26 | let item = task.timer_queue_item(); | 60 | // Safety: the `&mut self`, along with the Safety note of the Queue, are sufficient to |
| 27 | if item.next.get().is_none() { | 61 | // ensure that this function creates the only mutable reference to the queue item. |
| 28 | // If not in the queue, add it and update. | 62 | TimerQueueItem::from_embassy_waker(waker) |
| 29 | let prev = self.head.replace(Some(task)); | 63 | }; |
| 30 | item.next.set(if prev.is_none() { | 64 | let item = unsafe { item.as_mut::<QueueItem>() }; |
| 31 | Some(unsafe { TaskRef::dangling() }) | 65 | match item.waker.as_ref() { |
| 32 | } else { | 66 | Some(_) if at <= item.expires_at => { |
| 33 | prev | 67 | // If expiration is sooner than previously set, update. |
| 34 | }); | 68 | item.expires_at = at; |
| 35 | item.expires_at.set(at); | 69 | // The waker is always stored in its own queue item, so we don't need to update it. |
| 36 | true | 70 | |
| 37 | } else if at <= item.expires_at.get() { | 71 | // Trigger a queue update in case this item can be immediately dequeued. |
| 38 | // If expiration is sooner than previously set, update. | 72 | true |
| 39 | item.expires_at.set(at); | 73 | } |
| 40 | true | 74 | Some(_) => { |
| 41 | } else { | 75 | // Queue item does not need to be updated, the task will be scheduled to be woken |
| 42 | // Task does not need to be updated. | 76 | // before the new expiration. |
| 43 | false | 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 | } | ||
| 44 | } | 94 | } |
| 45 | } | 95 | } |
| 46 | 96 | ||
| @@ -51,33 +101,29 @@ impl Queue { | |||
| 51 | pub fn next_expiration(&mut self, now: u64) -> u64 { | 101 | pub fn next_expiration(&mut self, now: u64) -> u64 { |
| 52 | let mut next_expiration = u64::MAX; | 102 | let mut next_expiration = u64::MAX; |
| 53 | 103 | ||
| 54 | self.retain(|p| { | 104 | self.retain(|item| { |
| 55 | let item = p.timer_queue_item(); | 105 | if item.expires_at <= now { |
| 56 | let expires = item.expires_at.get(); | ||
| 57 | |||
| 58 | if expires <= now { | ||
| 59 | // Timer expired, process task. | 106 | // Timer expired, process task. |
| 60 | embassy_executor::raw::wake_task(p); | 107 | if let Some(waker) = item.waker.take() { |
| 108 | waker.wake(); | ||
| 109 | } | ||
| 61 | false | 110 | false |
| 62 | } else { | 111 | } else { |
| 63 | // Timer didn't yet expire, or never expires. | 112 | // Timer didn't yet expire, or never expires. |
| 64 | next_expiration = min(next_expiration, expires); | 113 | next_expiration = min(next_expiration, item.expires_at); |
| 65 | expires != u64::MAX | 114 | item.expires_at != u64::MAX |
| 66 | } | 115 | } |
| 67 | }); | 116 | }); |
| 68 | 117 | ||
| 69 | next_expiration | 118 | next_expiration |
| 70 | } | 119 | } |
| 71 | 120 | ||
| 72 | fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { | 121 | fn retain(&mut self, mut f: impl FnMut(&mut QueueItem) -> bool) { |
| 73 | let mut prev = &self.head; | 122 | let mut prev = &self.head; |
| 74 | while let Some(p) = prev.get() { | 123 | while let Some(mut p) = prev.get() { |
| 75 | if unsafe { p == TaskRef::dangling() } { | 124 | let mut item = unsafe { p.as_mut() }; |
| 76 | // prev was the last item, stop | 125 | |
| 77 | break; | 126 | if f(&mut item) { |
| 78 | } | ||
| 79 | let item = p.timer_queue_item(); | ||
| 80 | if f(p) { | ||
| 81 | // Skip to next | 127 | // Skip to next |
| 82 | prev = &item.next; | 128 | prev = &item.next; |
| 83 | } else { | 129 | } else { |
