Dependency enablement trickery

This commit is contained in:
James Munns 2025-04-01 19:32:12 +02:00 committed by Dario Nieuwenhuis
parent ba0426f767
commit ed2e51bfa4
4 changed files with 37 additions and 13 deletions

View File

@ -46,7 +46,7 @@ flavors = [
[package.metadata.docs.rs]
default-target = "thumbv7em-none-eabi"
targets = ["thumbv7em-none-eabi"]
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "drs-scheduler"]
[dependencies]
defmt = { version = "1.0.1", optional = true }
@ -76,13 +76,17 @@ js-sys = { version = "0.3", optional = true }
# arch-avr dependencies
avr-device = { version = "0.7.0", optional = true }
[dependencies.cordyceps]
# note: targeting v0.3.3, to be released when
# Note: this is ONLY a dependency when the target has atomics, this is
# used for `run_queue_atomics`. We need to be conditional because
# cordyceps *requires* the use of atomics, so we pull it in when
# `run_queue_atomics` would be enabled, and NOT when `run_queue_critical_section`
# would be enabled.
[target.'cfg(target_has_atomic="ptr")'.dependencies.cordyceps]
# TODO: targeting v0.3.3, to be released when
# https://github.com/hawkw/mycelium/pull/520 is merged
version = "0.3"
git = "https://github.com/hawkw/mycelium"
rev = "9649db0525b9972b95937d83d52d3f51cc486281"
optional = true
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
@ -133,5 +137,7 @@ trace = ["_any_trace"]
rtos-trace = ["_any_trace", "metadata-name", "dep:rtos-trace", "dep:embassy-time-driver"]
_any_trace = []
## Enable "Deadline Rank Scheduler"
drs-scheduler = ["dep:cordyceps", "dep:embassy-time-driver"]
## Enable "Deadline Rank Sorted" Scheduler, using soft-realtime "deadlines" to prioritize
## tasks based on the remaining time before their deadline. Adds some overhead. Requires
## hardware atomic support
drs-scheduler = ["dep:embassy-time-driver"]

View File

@ -2,6 +2,8 @@ use core::future::{poll_fn, Future};
use core::task::Poll;
/// A type for interacting with the deadline of the current task
///
/// Requires the `drs-scheduler` feature
pub struct Deadline {
/// Deadline value in ticks, same time base and ticks as `embassy-time`
pub instant_ticks: u64,

View File

@ -101,14 +101,14 @@ extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static
/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`.
/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue`
pub(crate) struct TaskHeader {
pub(crate) state: State,
pub(crate) run_queue_item: RunQueueItem,
#[cfg(feature = "drs-scheduler")]
/// Deadline Rank Scheduler Deadline. This field should not be accessed outside the context of
/// the task itself as it being polled by the executor.
#[cfg(feature = "drs-scheduler")]
pub(crate) deadline: SyncUnsafeCell<u64>,
pub(crate) state: State,
pub(crate) executor: AtomicPtr<SyncExecutor>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
@ -211,10 +211,12 @@ impl<F: Future + 'static> TaskStorage<F> {
pub const fn new() -> Self {
Self {
raw: TaskHeader {
state: State::new(),
run_queue_item: RunQueueItem::new(),
// NOTE: The deadline is set to zero to allow the initializer to reside in `.bss`. This
// will be lazily initalized in `initialize_impl`
#[cfg(feature = "drs-scheduler")]
deadline: SyncUnsafeCell::new(0u64),
state: State::new(),
executor: AtomicPtr::new(core::ptr::null_mut()),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: SyncUnsafeCell::new(None),
@ -311,7 +313,8 @@ impl<F: Future + 'static> AvailableTask<F> {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write_in_place(future);
// TODO(AJM): Some other way of setting this? Just a placeholder
// By default, deadlines are set to the maximum value, so that any task WITH
// a set deadline will ALWAYS be scheduled BEFORE a task WITHOUT a set deadline
#[cfg(feature = "drs-scheduler")]
self.task.raw.deadline.set(u64::MAX);

View File

@ -66,6 +66,8 @@ impl RunQueue {
self.stack.push_was_empty(task)
}
/// # Standard atomic runqueue
///
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
@ -78,9 +80,20 @@ impl RunQueue {
}
}
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
/// # Deadline Ranked Sorted Scheduler
///
/// This algorithm will loop until all enqueued tasks are processed.
///
/// Before polling a task, all currently enqueued tasks will be popped from the
/// runqueue, and will be added to the working `sorted` list, a linked-list that
/// sorts tasks by their deadline, with nearest deadline items in the front, and
/// furthest deadline items in the back.
///
/// After popping and sorting all pending tasks, the SOONEST task will be popped
/// from the front of the queue, and polled by calling `on_task` on it.
///
/// This process will repeat until the local `sorted` queue AND the global
/// runqueue are both empty, at which point this function will return.
#[cfg(feature = "drs-scheduler")]
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
// SAFETY: `deadline` can only be set through the `Deadline` interface, which