diff --git a/esp-config/src/generate.rs b/esp-config/src/generate.rs index ede216f5d27..36c02da10f2 100644 --- a/esp-config/src/generate.rs +++ b/esp-config/src/generate.rs @@ -377,9 +377,11 @@ fn capture_from_env(prefix: &str, configs: &mut HashMap) { panic!("Invalid configuration options detected: {:?}", failed); } - if !unknown.is_empty() { - panic!("Unknown configuration options detected: {:?}", unknown); - } + // FIXME: disabled for testing purposes, esp-hal rejects esp-hal-embassy + // configs because of this + // if !unknown.is_empty() { + // panic!("Unknown configuration options detected: {:?}", unknown); + // } } fn emit_configuration( diff --git a/esp-hal-embassy/Cargo.toml b/esp-hal-embassy/Cargo.toml index 651c47f6222..97aef6bb381 100644 --- a/esp-hal-embassy/Cargo.toml +++ b/esp-hal-embassy/Cargo.toml @@ -12,16 +12,20 @@ default-target = "riscv32imac-unknown-none-elf" features = ["esp32c6"] [dependencies] -critical-section = "1.2.0" -defmt = { version = "0.3.8", optional = true } -document-features = "0.2.10" -embassy-executor = { version = "0.6.3", optional = true } -embassy-time-driver = { version = "0.1.0", features = [ "tick-hz-1_000_000" ] } -esp-hal = { version = "0.22.0", path = "../esp-hal" } -log = { version = "0.4.22", optional = true } -macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -portable-atomic = "1.9.0" -static_cell = "2.1.0" +critical-section = "1.2.0" +defmt = { version = "0.3.8", optional = true } +document-features = "0.2.10" +embassy-executor = { version = "0.6.3", features = ["timer-item-payload-size-4"], optional = true } +embassy-sync = { version = "0.6.1" } +embassy-time = { version = "0.3.0" } +embassy-time-driver = { version = "0.1.0", features = [ "tick-hz-1_000_000" ] } +embassy-time-queue-driver = { version = "0.1.0", features = ["_generic-queue"] } +esp-config = { version = "0.2.0", path = "../esp-config" } +esp-hal = { version = "0.22.0", path = "../esp-hal" } +log = { version = "0.4.22", optional = true } +macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } +portable-atomic = "1.9.0" +static_cell = "2.1.0" [build-dependencies] esp-build = { version = "0.1.0", path = "../esp-build" } @@ -45,8 +49,12 @@ defmt = ["dep:defmt", "embassy-executor?/defmt", "esp-hal/defmt"] log = ["dep:log"] ## Provide `Executor` and `InterruptExecutor` executors = ["dep:embassy-executor", "esp-hal/__esp_hal_embassy"] -## Use the executor-integrated `embassy-time` timer queue. -integrated-timers = ["embassy-executor?/integrated-timers"] [lints.rust] unexpected_cfgs = "allow" + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } diff --git a/esp-hal-embassy/build.rs b/esp-hal-embassy/build.rs index 5a4cf918864..2e9de630a0a 100644 --- a/esp-hal-embassy/build.rs +++ b/esp-hal-embassy/build.rs @@ -1,10 +1,10 @@ -use std::{error::Error, str::FromStr}; +use std::{error::Error as StdError, str::FromStr}; use esp_build::assert_unique_used_features; -use esp_config::{generate_config, Value}; +use esp_config::{generate_config, Error, Validator, Value}; use esp_metadata::{Chip, Config}; -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { // NOTE: update when adding new device support! // Ensure that exactly one chip has been specified: assert_unique_used_features!( @@ -39,16 +39,63 @@ fn main() -> Result<(), Box> { config.define_symbols(); // emit config - generate_config( + let crate_config = generate_config( "esp_hal_embassy", &[( "low-power-wait", "Enables the lower-power wait if no tasks are ready to run on the thread-mode executor. This allows the MCU to use less power if the workload allows. Recommended for battery-powered systems. May impact analog performance.", Value::Bool(true), None - )], + ), + ( + "timer-queue", + "

The flavour of the timer queue provided by this crate. Accepts one of 'single-integrated', 'multiple-integrated' or 'generic'. Integrated queues require the 'executors' feature to be enabled.

If you use embassy-executor, the 'single-integrated' queue is recommended for ease of use, while the 'multiple-integrated' queue is recommended for performance. The 'generic' queue allows using embassy-time without the embassy executors.

", + Value::String(if cfg!(feature = "executors") { + String::from("single-integrated") + } else { + String::from("generic") + }), + Some(Validator::Custom(Box::new(|value| { + let Value::String(string) = value else { + return Err(Error::Validation(String::from("Expected a string"))); + }; + + match string.as_str() { + "single-integrated" => Ok(()), // preferred for ease of use + "multiple-integrated" => Ok(()), // preferred for performance + "generic" => Ok(()), // allows using embassy-time without the embassy executors + _ => Err(Error::Validation(format!("Expected 'single-integrated', 'multiple-integrated' or 'generic', found {string}"))) + } + }))) + ), + ( + "generic-queue-size", + "The size of the generic queue. Only used if the `generic` timer-queue flavour is selected.", + Value::Integer(64), + Some(Validator::PositiveInteger), + ), + ], true, ); + println!("cargo:rustc-check-cfg=cfg(integrated_timers)"); + println!("cargo:rustc-check-cfg=cfg(single_queue)"); + println!("cargo:rustc-check-cfg=cfg(generic_timers)"); + + match &crate_config["ESP_HAL_EMBASSY_TIMER_QUEUE"] { + Value::String(s) if s.as_str() == "single-integrated" => { + println!("cargo:rustc-cfg=integrated_timers"); + println!("cargo:rustc-cfg=single_queue"); + } + Value::String(s) if s.as_str() == "multiple-integrated" => { + println!("cargo:rustc-cfg=integrated_timers"); + } + Value::String(s) if s.as_str() == "generic" => { + println!("cargo:rustc-cfg=generic_timers"); + println!("cargo:rustc-cfg=single_queue"); + } + _ => unreachable!(), + } + Ok(()) } diff --git a/esp-hal-embassy/src/executor/interrupt.rs b/esp-hal-embassy/src/executor/interrupt.rs index 7f3599127c2..14eb64fb816 100644 --- a/esp-hal-embassy/src/executor/interrupt.rs +++ b/esp-hal-embassy/src/executor/interrupt.rs @@ -2,13 +2,15 @@ use core::{cell::UnsafeCell, mem::MaybeUninit}; -use embassy_executor::{raw, SendSpawner}; +use embassy_executor::SendSpawner; use esp_hal::{ interrupt::{self, software::SoftwareInterrupt, InterruptHandler}, Cpu, }; use portable_atomic::{AtomicUsize, Ordering}; +use super::InnerExecutor; + const COUNT: usize = 3 + cfg!(not(multi_core)) as usize; static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() }; COUNT]; @@ -19,7 +21,7 @@ static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() /// software. pub struct InterruptExecutor { core: AtomicUsize, - executor: UnsafeCell>, + executor: UnsafeCell>, interrupt: SoftwareInterrupt, } @@ -27,7 +29,7 @@ unsafe impl Send for InterruptExecutor {} unsafe impl Sync for InterruptExecutor {} struct CallbackContext { - raw_executor: UnsafeCell<*mut raw::Executor>, + raw_executor: UnsafeCell<*mut InnerExecutor>, } impl CallbackContext { @@ -37,11 +39,11 @@ impl CallbackContext { } } - fn get(&self) -> *mut raw::Executor { + fn get(&self) -> *mut InnerExecutor { unsafe { *self.raw_executor.get() } } - fn set(&self, executor: *mut raw::Executor) { + fn set(&self, executor: *mut InnerExecutor) { unsafe { self.raw_executor.get().write(executor) }; } } @@ -52,7 +54,7 @@ extern "C" fn handle_interrupt() { unsafe { let executor = unwrap!(EXECUTORS[NUM as usize].get().as_mut()); - executor.poll(); + executor.inner.poll(); } } @@ -99,7 +101,7 @@ impl InterruptExecutor { unsafe { (*self.executor.get()) .as_mut_ptr() - .write(raw::Executor::new((SWI as usize) as *mut ())); + .write(InnerExecutor::new(priority, (SWI as usize) as *mut ())); EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr()); } @@ -117,7 +119,8 @@ impl InterruptExecutor { .set_interrupt_handler(InterruptHandler::new(swi_handler, priority)); let executor = unsafe { (*self.executor.get()).assume_init_ref() }; - executor.spawner().make_send() + executor.init(); + executor.inner.spawner().make_send() } /// Get a SendSpawner for this executor @@ -132,6 +135,6 @@ impl InterruptExecutor { panic!("InterruptExecutor::spawner() called on uninitialized executor."); } let executor = unsafe { (*self.executor.get()).assume_init_ref() }; - executor.spawner().make_send() + executor.inner.spawner().make_send() } } diff --git a/esp-hal-embassy/src/executor/mod.rs b/esp-hal-embassy/src/executor/mod.rs index 5ed53770421..fd77ff30698 100644 --- a/esp-hal-embassy/src/executor/mod.rs +++ b/esp-hal-embassy/src/executor/mod.rs @@ -1,4 +1,9 @@ +use embassy_executor::raw; +use esp_hal::interrupt::Priority; + pub use self::{interrupt::*, thread::*}; +#[cfg(not(single_queue))] +use crate::timer_queue::TimerQueue; mod interrupt; mod thread; @@ -22,3 +27,31 @@ fn __pender(context: *mut ()) { _ => unreachable!(), } } + +#[repr(C)] +pub(crate) struct InnerExecutor { + inner: raw::Executor, + #[cfg(not(single_queue))] + pub(crate) timer_queue: TimerQueue, +} + +impl InnerExecutor { + /// Create a new executor. + /// + /// When the executor has work to do, it will call the pender function and + /// pass `context` to it. + /// + /// See [`Executor`] docs for details on the pender. + pub(crate) fn new(_prio: Priority, context: *mut ()) -> Self { + Self { + inner: raw::Executor::new(context), + #[cfg(not(single_queue))] + timer_queue: TimerQueue::new(_prio), + } + } + + pub(crate) fn init(&self) { + #[cfg(not(single_queue))] + self.timer_queue.set_context(self as *const _ as *mut ()); + } +} diff --git a/esp-hal-embassy/src/executor/thread.rs b/esp-hal-embassy/src/executor/thread.rs index 47d826c6f90..ce1427f46e3 100644 --- a/esp-hal-embassy/src/executor/thread.rs +++ b/esp-hal-embassy/src/executor/thread.rs @@ -2,13 +2,15 @@ use core::marker::PhantomData; -use embassy_executor::{raw, Spawner}; -use esp_hal::Cpu; +use embassy_executor::Spawner; #[cfg(multi_core)] use esp_hal::{interrupt::software::SoftwareInterrupt, macros::handler}; +use esp_hal::{interrupt::Priority, Cpu}; #[cfg(low_power_wait)] use portable_atomic::{AtomicBool, Ordering}; +use super::InnerExecutor; + pub(crate) const THREAD_MODE_CONTEXT: usize = 16; /// global atomic used to keep track of whether there is work to do since sev() @@ -55,7 +57,7 @@ pub(crate) fn pend_thread_mode(_core: usize) { create one instance per core. The executors don't steal tasks from each other." )] pub struct Executor { - inner: raw::Executor, + inner: InnerExecutor, not_send: PhantomData<*mut ()>, } @@ -74,7 +76,10 @@ This will use software-interrupt 3 which isn't available for anything else to wa } Self { - inner: raw::Executor::new((THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut ()), + inner: InnerExecutor::new( + Priority::Priority1, + (THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut (), + ), not_send: PhantomData, } } @@ -100,13 +105,15 @@ This will use software-interrupt 3 which isn't available for anything else to wa /// /// This function never returns. pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); + self.inner.init(); + + init(self.inner.inner.spawner()); #[cfg(low_power_wait)] let cpu = Cpu::current() as usize; loop { - unsafe { self.inner.poll() }; + unsafe { self.inner.inner.poll() }; #[cfg(low_power_wait)] Self::wait_impl(cpu); diff --git a/esp-hal-embassy/src/lib.rs b/esp-hal-embassy/src/lib.rs index 72b733c32e5..4ded7d6c6a9 100644 --- a/esp-hal-embassy/src/lib.rs +++ b/esp-hal-embassy/src/lib.rs @@ -29,6 +29,19 @@ //! Embassy **must** be initialized by calling the [init] function. This //! initialization must be performed *prior* to spawning any tasks. //! +//! Initialization requires a number of timers to be passed in. The number of +//! timers required depends on the timer queue flavour used, as well as the +//! number of executors started. If you use the `multiple-integrated` timer +//! queue flavour, then you need to pass as many timers as you start executors. +//! In other cases, you can pass a single timer. +//! +//! ## Configuration +//! +//! You can configure the behaviour of the embassy runtime by using the +//! following environment variables: +#![doc = ""] +#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_embassy_config_table.md"))] +#![doc = ""] //! ## Feature Flags #![doc = document_features::document_features!()] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] @@ -49,8 +62,9 @@ pub use self::executor::{Executor, InterruptExecutor}; use self::time_driver::{EmbassyTimer, Timer}; #[cfg(feature = "executors")] -mod executor; +pub(crate) mod executor; mod time_driver; +mod timer_queue; macro_rules! mk_static { ($t:ty,$val:expr) => {{ @@ -142,8 +156,9 @@ impl_array!(4); /// - A mutable static array of `OneShotTimer` instances /// - A 2, 3, 4 element array of `AnyTimer` instances /// -/// Note that if you use the `integrated-timers` feature, -/// you need to pass as many timers as you start executors. +/// Note that if you use the `multiple-integrated` timer-queue flavour, then +/// you need to pass as many timers as you start executors. In other cases, +/// you can pass a single timer. /// /// # Examples /// diff --git a/esp-hal-embassy/src/time_driver.rs b/esp-hal-embassy/src/time_driver.rs index fa785cdeecd..e130a69e761 100644 --- a/esp-hal-embassy/src/time_driver.rs +++ b/esp-hal-embassy/src/time_driver.rs @@ -1,6 +1,12 @@ +//! Embassy time driver implementation +//! +//! The time driver is responsible for keeping time, as well as to manage the +//! wait queue for embassy-time. + +#[cfg(not(single_queue))] use core::cell::Cell; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; use esp_hal::{ interrupt::{InterruptHandler, Priority}, prelude::*, @@ -12,28 +18,60 @@ use esp_hal::{ pub type Timer = OneShotTimer<'static, Blocking, AnyTimer>; +/// Alarm handle, assigned by the driver. +#[derive(Clone, Copy)] +pub(crate) struct AlarmHandle { + id: usize, +} + +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: usize) -> Self { + Self { id } + } + + pub fn update(&self, expiration: u64) -> bool { + if expiration == u64::MAX { + true + } else { + DRIVER.set_alarm(*self, expiration) + } + } +} + enum AlarmState { Created(extern "C" fn()), - Allocated(extern "C" fn()), Initialized(&'static mut Timer), } impl AlarmState { - fn initialize(timer: &'static mut Timer, interrupt_handler: extern "C" fn()) -> AlarmState { + fn initialize(timer: &'static mut Timer, interrupt_handler: InterruptHandler) -> AlarmState { // If the driver is initialized, bind the interrupt handler to the // timer. This ensures that alarms allocated after init are correctly // bound to the core that created the executor. - timer.set_interrupt_handler(InterruptHandler::new(interrupt_handler, Priority::max())); + timer.set_interrupt_handler(interrupt_handler); timer.enable_interrupt(true); AlarmState::Initialized(timer) } } struct AlarmInner { - pub callback: Cell<(*const (), *mut ())>, + /// If multiple queues are used, we store the appropriate timer queue here. + // FIXME: we currently store the executor, but we could probably avoid an addition by actually + // storing a reference to the timer queue. + #[cfg(not(single_queue))] + pub context: Cell<*const ()>, + pub state: AlarmState, } struct Alarm { + // FIXME: we should be able to use priority-limited locks here, but we can initialize alarms + // while running at an arbitrary priority level. We need to rework alarm allocation to only use + // a critical section to allocate an alarm, but not when using it. pub inner: Locked, } @@ -43,14 +81,37 @@ impl Alarm { pub const fn new(handler: extern "C" fn()) -> Self { Self { inner: Locked::new(AlarmInner { - callback: Cell::new((core::ptr::null(), core::ptr::null_mut())), + #[cfg(not(single_queue))] + context: Cell::new(core::ptr::null_mut()), state: AlarmState::Created(handler), }), } } } +/// embassy requires us to implement the [embassy_time_driver::Driver] trait, +/// which we do here. This trait needs us to be able to tell the current time, +/// as well as to schedule a wake-up at a certain time. +/// +/// We are free to choose how we implement these features, and we provide three +/// options: +/// +/// - If the `generic` feature is enabled, we implement a single timer queue, +/// using the implementation provided by embassy-time-queue-driver. +/// - If the `single-integrated` feature is enabled, we implement a single timer +/// queue, using our own integrated timer implementation. Our implementation +/// is a copy of the embassy integrated timer queue, with the addition of +/// clearing the "owner" information upon dequeueing. +/// - If the `multiple-integrated` feature is enabled, we provide a separate +/// timer queue for each executor. We store a separate timer queue for each +/// executor, and we use the scheduled task's owner to determine which queue +/// to use. This mode allows us to use less disruptive locks around the timer +/// queue, but requires more timers - one per timer queue. pub(super) struct EmbassyTimer { + /// The timer queue, if we use a single one (single-integrated, or generic). + #[cfg(single_queue)] + pub(crate) inner: crate::timer_queue::TimerQueue, + alarms: [Alarm; MAX_SUPPORTED_ALARM_COUNT], available_timers: Locked>, } @@ -71,14 +132,21 @@ macro_rules! alarms { }; } +// TODO: we can reduce this to 1 for single_queue, but that would break current +// tests. Resolve when tests can use separate configuration sets, or update +// tests to always pass a single timer. const MAX_SUPPORTED_ALARM_COUNT: usize = 7; + embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer { + // Single queue, needs maximum priority. + #[cfg(single_queue)] + inner: crate::timer_queue::TimerQueue::new(Priority::max()), alarms: alarms!(0, 1, 2, 3, 4, 5, 6), available_timers: Locked::new(None), }); impl EmbassyTimer { - pub(super) fn init(mut timers: &'static mut [Timer]) { + pub(super) fn init(timers: &'static mut [Timer]) { assert!( timers.len() <= MAX_SUPPORTED_ALARM_COUNT, "Maximum {} timers can be used.", @@ -91,36 +159,27 @@ impl EmbassyTimer { timer.stop(); }); - // Initialize already allocated timers - for alarm in DRIVER.alarms.iter() { - timers = alarm.inner.with(move |alarm| { - if let AlarmState::Allocated(interrupt_handler) = alarm.state { - // Pluck off a timer - - let Some((timer, remaining_timers)) = timers.split_first_mut() else { - not_enough_timers(); - }; - - alarm.state = AlarmState::initialize(timer, interrupt_handler); - - remaining_timers - } else { - timers - } - }); - } - // Store the available timers DRIVER .available_timers .with(|available_timers| *available_timers = Some(timers)); } + #[cfg(not(single_queue))] + pub(crate) fn set_callback_ctx(&self, alarm: AlarmHandle, ctx: *const ()) { + self.alarms[alarm.id].inner.with(|alarm| { + alarm.context.set(ctx.cast_mut()); + }) + } + fn on_interrupt(&self, id: usize) { - let (cb, ctx) = self.alarms[id].inner.with(|alarm| { + // On interrupt, we clear the alarm that was triggered... + #[cfg_attr(single_queue, allow(clippy::let_unit_value))] + let _ctx = self.alarms[id].inner.with(|alarm| { if let AlarmState::Initialized(timer) = &mut alarm.state { timer.clear_interrupt(); - alarm.callback.get() + #[cfg(not(single_queue))] + alarm.context.get() } else { unsafe { // SAFETY: `on_interrupt` is registered right when the alarm is initialized. @@ -129,15 +188,17 @@ impl EmbassyTimer { } }); - let cb: fn(*mut ()) = unsafe { - // 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 - core::mem::transmute(cb) - }; + // ... and process the timer queue if we have one. For multiple queues, the + // timer queue is stored in the alarm's context. + #[cfg(all(integrated_timers, not(single_queue)))] + { + let executor = unsafe { &*_ctx.cast::() }; + executor.timer_queue.dispatch(); + } - cb(ctx); + // If we have a single queue, it lives in this struct. + #[cfg(single_queue)] + self.inner.dispatch(); } /// Returns `true` if the timer was armed, `false` if the timestamp is in @@ -157,14 +218,18 @@ impl EmbassyTimer { false } } -} -impl Driver for EmbassyTimer { - fn now(&self) -> u64 { - now().ticks() - } - - unsafe fn allocate_alarm(&self) -> Option { + /// Allocate an alarm, if possible. + /// + /// Returns `None` if there are no available alarms. + /// + /// When using multiple timer queues, the `priority` parameter indicates the + /// priority of the interrupt handler. It is 1 for thread-mode + /// executors, or equals to the priority of an interrupt executor. + /// + /// When using a single timer queue, the `priority` parameter is always the + /// highest value possible. + pub(crate) unsafe fn allocate_alarm(&self, priority: Priority) -> Option { for (i, alarm) in self.alarms.iter().enumerate() { let handle = alarm.inner.with(|alarm| { let AlarmState::Created(interrupt_handler) = alarm.state else { @@ -172,9 +237,6 @@ impl Driver for EmbassyTimer { }; let timer = self.available_timers.with(|available_timers| { - // `allocate_alarm` may be called before `esp_hal_embassy::init()`. If - // `timers` is `None`, we return `None` to signal that the alarm cannot be - // initialized yet. These alarms will be initialized when `init` is called. if let Some(timers) = available_timers.take() { // If the driver is initialized, we can allocate a timer. // If this fails, we can't do anything about it. @@ -182,22 +244,18 @@ impl Driver for EmbassyTimer { not_enough_timers(); }; *available_timers = Some(rest); - Some(timer) + timer } else { - None + panic!("schedule_wake called before esp_hal_embassy::init()") } }); - alarm.state = match timer { - Some(timer) => AlarmState::initialize(timer, interrupt_handler), + alarm.state = AlarmState::initialize( + timer, + InterruptHandler::new(interrupt_handler, priority), + ); - None => { - // No timers are available yet, mark the alarm as allocated. - AlarmState::Allocated(interrupt_handler) - } - }; - - Some(AlarmHandle::new(i as u8)) + Some(AlarmHandle::new(i)) }); if handle.is_some() { @@ -208,23 +266,11 @@ impl Driver for EmbassyTimer { None } - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - let n = alarm.id() as usize; - - self.alarms[n].inner.with(|alarm| { - alarm.callback.set((callback as *const (), ctx)); - }) - } - + /// Set an alarm to fire at a certain timestamp. + /// + /// Returns `false` if the timestamp is in the past. fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - let alarm = &self.alarms[alarm.id() as usize]; - - // If `embassy-executor/integrated-timers` is enabled and there are no pending - // timers, embassy still calls `set_alarm` with `u64::MAX`. By returning - // `true` we signal that no re-polling is necessary. - if timestamp == u64::MAX { - return true; - } + let alarm = &self.alarms[alarm.id]; // The hardware fires the alarm even if timestamp is lower than the current // time. In this case the interrupt handler will pend a wake-up when we exit the @@ -238,17 +284,88 @@ impl Driver for EmbassyTimer { if let AlarmState::Initialized(timer) = &mut alarm.state { Self::arm(timer, timestamp) } else { - panic!("set_alarm called before esp_hal_embassy::init()") + unsafe { + // SAFETY: We only create `AlarmHandle` instances after the alarm is + // initialized. + core::hint::unreachable_unchecked() + } } }) } } +impl Driver for EmbassyTimer { + fn now(&self) -> u64 { + now().ticks() + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + #[cfg(not(single_queue))] + unsafe { + // If we have multiple queues, we have integrated timers and our own timer queue + // implementation. + use embassy_executor::raw::Executor as RawExecutor; + use portable_atomic::{AtomicPtr, Ordering}; + + let task = embassy_executor::raw::task_from_waker(waker); + + // SAFETY: it is impossible to schedule a task that has not yet been spawned, + // so the executor is guaranteed to be set to a non-null value. + let mut executor = task.executor().unwrap_unchecked() as *const RawExecutor; + + let owner = task + .timer_queue_item() + .payload + .as_ref::>(); + + // Try to take ownership over the timer item. + let owner = owner.compare_exchange( + core::ptr::null_mut(), + executor.cast_mut(), + Ordering::AcqRel, + Ordering::Acquire, + ); + + // We can't take ownership, but we may still be able to enqueue the task. Point + // at the current owner. + if let Err(owner) = owner { + executor = owner; + }; + + // It is possible that the task's owner changes in the mean time. It doesn't + // matter, at this point the only interesting question is: can we enqueue in the + // currently loaded owner's timer queue? + + // Try to enqueue in the current owner's timer queue. This will fail if the + // owner has a lower priority ceiling than the current context. + + // FIXME: this is UB, use Exposed Provenance API (or something better) when + // available. Expose provenance in `InnerExecutor::init`, and use it here. + let executor = &*(executor.cast::()); + executor.timer_queue.schedule_wake(at, waker); + } + + #[cfg(single_queue)] + self.inner.schedule_wake(at, waker); + } +} + #[cold] #[track_caller] fn not_enough_timers() -> ! { // This is wrapped in a separate function because rustfmt does not like // extremely long strings. Also, if log is used, this avoids storing the string // twice. - panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider using one of the embassy-timer/generic-queue-X features."); + panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider either using the `single-queue` or the `generic-queue` feature."); +} + +pub(crate) fn set_up_alarm(priority: Priority, _ctx: *mut ()) -> AlarmHandle { + let alarm = unsafe { + DRIVER + .allocate_alarm(priority) + .unwrap_or_else(|| not_enough_timers()) + }; + #[cfg(not(single_queue))] + DRIVER.set_callback_ctx(alarm, _ctx); + alarm } diff --git a/esp-hal-embassy/src/timer_queue.rs b/esp-hal-embassy/src/timer_queue.rs new file mode 100644 index 00000000000..a48ca309406 --- /dev/null +++ b/esp-hal-embassy/src/timer_queue.rs @@ -0,0 +1,194 @@ +//! Timer waiter queue. +//! +//! This module implements the timer queue, which is managed by the time driver. +//! The timer queue contains wakers and their expiration times, and is used to +//! wake tasks at the correct time. + +#[cfg(not(single_queue))] +use core::cell::Cell; +use core::cell::{RefCell, UnsafeCell}; + +use embassy_sync::blocking_mutex::Mutex; +use esp_hal::{interrupt::Priority, sync::RawPriorityLimitedMutex}; +use queue_impl::RawQueue; + +use crate::time_driver::{set_up_alarm, AlarmHandle}; + +pub(crate) struct TimerQueue { + inner: Mutex>, + priority: Priority, + #[cfg(not(single_queue))] + context: Cell<*mut ()>, + alarm: UnsafeCell>, +} + +unsafe impl Sync for TimerQueue {} + +impl TimerQueue { + pub(crate) const fn new(prio: Priority) -> Self { + Self { + inner: Mutex::const_new( + RawPriorityLimitedMutex::new(prio), + RefCell::new(RawQueue::new()), + ), + priority: prio, + #[cfg(not(single_queue))] + context: Cell::new(core::ptr::null_mut()), + alarm: UnsafeCell::new(None), + } + } + + #[cfg(not(single_queue))] + pub(crate) fn set_context(&self, context: *mut ()) { + self.context.set(context); + } + + #[cfg(not(single_queue))] + fn context(&self) -> *mut () { + self.context.get() + } + + #[cfg(single_queue)] + fn context(&self) -> *mut () { + core::ptr::null_mut() + } + + fn alarm(&self) -> AlarmHandle { + unsafe { + let alarm = &mut *self.alarm.get(); + *alarm.get_or_insert_with(|| set_up_alarm(self.priority, self.context())) + } + } + + pub fn dispatch(&self) { + let now = esp_hal::time::now().ticks(); + let next_expiration = self.inner.lock(|q| q.borrow_mut().next_expiration(now)); + self.arm_alarm(next_expiration); + } + + fn arm_alarm(&self, mut next_expiration: u64) { + let alarm = self.alarm(); + + while !alarm.update(next_expiration) { + // next_expiration is in the past, dequeue and find a new expiration + next_expiration = self + .inner + .lock(|q| q.borrow_mut().next_expiration(next_expiration)); + } + } + + pub fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + if self.inner.lock(|q| q.borrow_mut().schedule_wake(at, waker)) { + self.dispatch(); + } + } +} + +#[cfg(integrated_timers)] +mod queue_impl { + use core::{cell::Cell, cmp::min, ptr, task::Waker}; + + use embassy_executor::raw::TaskRef; + use portable_atomic::{AtomicPtr, Ordering}; + + /// Copy of the embassy integrated timer queue, that clears the owner upon + /// dequeueing. + pub(super) struct RawQueue { + head: Cell>, + } + + impl RawQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + head: Cell::new(None), + } + } + + /// Schedules a task to run at a specific time. + /// + /// 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 { + let task = embassy_executor::raw::task_from_waker(waker); + let item = task.timer_queue_item(); + if item.next.get().is_none() { + // If not in the queue, add it and update. + let prev = self.head.replace(Some(task)); + item.next.set(if prev.is_none() { + Some(unsafe { TaskRef::dangling() }) + } else { + prev + }); + item.expires_at.set(at); + true + } else if at <= item.expires_at.get() { + // If expiration is sooner than previously set, update. + item.expires_at.set(at); + true + } else { + // Task does not need to be updated. + false + } + } + + /// Dequeues expired timers and returns the next alarm time. + /// + /// The provided callback will be called for each expired task. Tasks + /// that never expire will be removed, but the callback will not + /// be called. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_expiration = u64::MAX; + + self.retain(|p| { + let item = p.timer_queue_item(); + let expires = item.expires_at.get(); + + if expires <= now { + // Timer expired, process task. + embassy_executor::raw::wake_task(p); + false + } else { + // Timer didn't yet expire, or never expires. + next_expiration = min(next_expiration, expires); + expires != u64::MAX + } + }); + + next_expiration + } + + fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { + let mut prev = &self.head; + while let Some(p) = prev.get() { + if unsafe { p == TaskRef::dangling() } { + // prev was the last item, stop + break; + } + let item = p.timer_queue_item(); + if f(p) { + // Skip to next + prev = &item.next; + } else { + // Remove it + prev.set(item.next.get()); + // Clear owner + unsafe { + // SAFETY: our payload is an AtomicPtr. + item.payload + .as_ref::>() + .store(ptr::null_mut(), Ordering::Relaxed); + } + item.next.set(None); + } + } + } + } +} + +#[cfg(generic_timers)] +mod queue_impl { + pub(super) type RawQueue = embassy_time_queue_driver::queue_generic::ConstGenericQueue< + { esp_config::esp_config_int!(usize, "ESP_HAL_EMBASSY_GENERIC_QUEUE_SIZE") }, + >; +} diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index 195238f179a..df54224506b 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -1628,7 +1628,7 @@ pub(crate) fn wifi_start() -> Result<(), WifiError> { cntry_code[2] = crate::CONFIG.country_code_operating_class; let country = wifi_country_t { - cc: core::mem::transmute::<[u8; 3], [i8; 3]>(cntry_code), // [u8] -> [i8] conversion + cc: core::mem::transmute::<[u8; 3], [core::ffi::c_char; 3]>(cntry_code), schan: 1, nchan: 13, max_tx_power: 20, diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0e521d25db8..802e38d2767 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -64,7 +64,6 @@ esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-hal-embassy?/esp32s3 esp-wifi = ["dep:esp-wifi"] embassy = ["dep:esp-hal-embassy"] -embassy-generic-timers = ["embassy-time/generic-queue-8"] [profile.release] codegen-units = 1 @@ -74,3 +73,9 @@ incremental = false opt-level = 3 lto = 'fat' overflow-checks = false + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } diff --git a/examples/src/bin/embassy_hello_world.rs b/examples/src/bin/embassy_hello_world.rs index c8c54354b50..b11e31e752c 100644 --- a/examples/src/bin/embassy_hello_world.rs +++ b/examples/src/bin/embassy_hello_world.rs @@ -4,7 +4,7 @@ //! concurrently. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy esp-hal-embassy/integrated-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_multicore.rs b/examples/src/bin/embassy_multicore.rs index 6ac3966aaa1..eb3a6695735 100644 --- a/examples/src/bin/embassy_multicore.rs +++ b/examples/src/bin/embassy_multicore.rs @@ -7,7 +7,7 @@ //! - LED => GPIO0 //% CHIPS: esp32 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_multicore_interrupt.rs b/examples/src/bin/embassy_multicore_interrupt.rs index 905befee0f3..808430e2812 100644 --- a/examples/src/bin/embassy_multicore_interrupt.rs +++ b/examples/src/bin/embassy_multicore_interrupt.rs @@ -7,7 +7,7 @@ //! - LED => GPIO0 //% CHIPS: esp32 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_multiprio.rs b/examples/src/bin/embassy_multiprio.rs index ae94ed0a0c5..f72f1f6feb1 100644 --- a/examples/src/bin/embassy_multiprio.rs +++ b/examples/src/bin/embassy_multiprio.rs @@ -15,7 +15,7 @@ // The interrupt-executor is created in `main` and is used to spawn `high_prio`. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy esp-hal-embassy/log esp-hal-embassy/integrated-timers esp-hal/unstable +//% FEATURES: embassy esp-hal-embassy/log esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_rmt_rx.rs b/examples/src/bin/embassy_rmt_rx.rs index eb55708cecd..5d0a01f26f3 100644 --- a/examples/src/bin/embassy_rmt_rx.rs +++ b/examples/src/bin/embassy_rmt_rx.rs @@ -4,7 +4,7 @@ //! - Connect GPIO4 and GPIO5 //% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_rmt_tx.rs b/examples/src/bin/embassy_rmt_tx.rs index 749e4af1ecb..2ae6ed312a6 100644 --- a/examples/src/bin/embassy_rmt_tx.rs +++ b/examples/src/bin/embassy_rmt_tx.rs @@ -6,7 +6,7 @@ //! - generated pulses => GPIO4 //% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_serial.rs b/examples/src/bin/embassy_serial.rs index 2df7dcc9968..63c87c6973d 100644 --- a/examples/src/bin/embassy_serial.rs +++ b/examples/src/bin/embassy_serial.rs @@ -4,7 +4,7 @@ //! writing to and reading from UART. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_spi.rs b/examples/src/bin/embassy_spi.rs index c6838f7bd8c..dc7942fb19c 100644 --- a/examples/src/bin/embassy_spi.rs +++ b/examples/src/bin/embassy_spi.rs @@ -13,7 +13,7 @@ //! CS => GPIO5 //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_usb_serial.rs b/examples/src/bin/embassy_usb_serial.rs index 614765e19c4..fbcf4961f39 100644 --- a/examples/src/bin/embassy_usb_serial.rs +++ b/examples/src/bin/embassy_usb_serial.rs @@ -7,7 +7,7 @@ //! - DM => GPIO19 //% CHIPS: esp32s2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/embassy_usb_serial_jtag.rs b/examples/src/bin/embassy_usb_serial_jtag.rs index ea7b5991e5d..c7b5a717934 100644 --- a/examples/src/bin/embassy_usb_serial_jtag.rs +++ b/examples/src/bin/embassy_usb_serial_jtag.rs @@ -3,7 +3,7 @@ //! Most dev-kits use a USB-UART-bridge - in that case you won't see any output. //% CHIPS: esp32c3 esp32c6 esp32h2 esp32s3 -//% FEATURES: embassy embassy-generic-timers esp-hal/unstable +//% FEATURES: embassy esp-hal/unstable #![no_std] #![no_main] diff --git a/examples/src/bin/wifi_embassy_access_point.rs b/examples/src/bin/wifi_embassy_access_point.rs index 762f9bb02e0..82c25e3d14b 100644 --- a/examples/src/bin/wifi_embassy_access_point.rs +++ b/examples/src/bin/wifi_embassy_access_point.rs @@ -9,7 +9,7 @@ //! Because of the huge task-arena size configured this won't work on ESP32-S2 //! -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] diff --git a/examples/src/bin/wifi_embassy_access_point_with_sta.rs b/examples/src/bin/wifi_embassy_access_point_with_sta.rs index d25ce440077..90629eba8c0 100644 --- a/examples/src/bin/wifi_embassy_access_point_with_sta.rs +++ b/examples/src/bin/wifi_embassy_access_point_with_sta.rs @@ -12,7 +12,7 @@ //! Because of the huge task-arena size configured this won't work on ESP32-S2 //! -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] diff --git a/examples/src/bin/wifi_embassy_bench.rs b/examples/src/bin/wifi_embassy_bench.rs index f85b0c7a863..b546576c509 100644 --- a/examples/src/bin/wifi_embassy_bench.rs +++ b/examples/src/bin/wifi_embassy_bench.rs @@ -10,7 +10,7 @@ //! Because of the huge task-arena size configured this won't work on ESP32-S2 and ESP32-C2 //! -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 #![allow(static_mut_refs)] diff --git a/examples/src/bin/wifi_embassy_ble.rs b/examples/src/bin/wifi_embassy_ble.rs index d687937797e..883e655b5a4 100644 --- a/examples/src/bin/wifi_embassy_ble.rs +++ b/examples/src/bin/wifi_embassy_ble.rs @@ -4,7 +4,7 @@ //! - offers one service with three characteristics (one is read/write, one is write only, one is read/write/notify) //! - pressing the boot-button on a dev-board will send a notification if it is subscribed -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/ble esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/ble esp-hal/unstable //% CHIPS: esp32 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 #![no_std] diff --git a/examples/src/bin/wifi_embassy_dhcp.rs b/examples/src/bin/wifi_embassy_dhcp.rs index 5f53e5d70a5..45c3d4ecb1e 100644 --- a/examples/src/bin/wifi_embassy_dhcp.rs +++ b/examples/src/bin/wifi_embassy_dhcp.rs @@ -7,7 +7,7 @@ //! //! Because of the huge task-arena size configured this won't work on ESP32-S2 -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] diff --git a/examples/src/bin/wifi_embassy_esp_now.rs b/examples/src/bin/wifi_embassy_esp_now.rs index 13c76a4285f..502057c6639 100644 --- a/examples/src/bin/wifi_embassy_esp_now.rs +++ b/examples/src/bin/wifi_embassy_esp_now.rs @@ -4,7 +4,7 @@ //! //! Because of the huge task-arena size configured this won't work on ESP32-S2 -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] diff --git a/examples/src/bin/wifi_embassy_esp_now_duplex.rs b/examples/src/bin/wifi_embassy_esp_now_duplex.rs index 6073dbd0e1a..67567ef2b6b 100644 --- a/examples/src/bin/wifi_embassy_esp_now_duplex.rs +++ b/examples/src/bin/wifi_embassy_esp_now_duplex.rs @@ -4,7 +4,7 @@ //! //! Because of the huge task-arena size configured this won't work on ESP32-S2 -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] diff --git a/examples/src/bin/wifi_embassy_trouble.rs b/examples/src/bin/wifi_embassy_trouble.rs index 46d012d1661..772f7b6a941 100644 --- a/examples/src/bin/wifi_embassy_trouble.rs +++ b/examples/src/bin/wifi_embassy_trouble.rs @@ -5,7 +5,7 @@ //! - automatically notifies subscribers every second //! -//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/ble esp-hal/unstable +//% FEATURES: embassy esp-wifi esp-wifi/ble esp-hal/unstable //% CHIPS: esp32 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 #![no_std] diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 97204555d4e..10e0ae8c5e0 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -290,12 +290,6 @@ embassy = [ "embedded-test/external-executor", "dep:esp-hal-embassy", ] -generic-queue = [ - "embassy-time/generic-queue-64" -] -integrated-timers = [ - "esp-hal-embassy/integrated-timers", -] octal-psram = ["esp-hal/octal-psram", "esp-alloc"] # https://doc.rust-lang.org/cargo/reference/profiles.html#test @@ -316,3 +310,9 @@ incremental = false opt-level = 3 lto = false # LTO (thin or fat) miscompiles some tests on RISC-V overflow-checks = false + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } diff --git a/hil-test/tests/embassy_interrupt_executor.rs b/hil-test/tests/embassy_interrupt_executor.rs index 7731aa9fa0e..2a5126c3f76 100644 --- a/hil-test/tests/embassy_interrupt_executor.rs +++ b/hil-test/tests/embassy_interrupt_executor.rs @@ -2,8 +2,6 @@ //! code. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: integrated-timers -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/embassy_interrupt_spi_dma.rs b/hil-test/tests/embassy_interrupt_spi_dma.rs index cf1d7e3f1fb..4d8c6dd9d39 100644 --- a/hil-test/tests/embassy_interrupt_spi_dma.rs +++ b/hil-test/tests/embassy_interrupt_spi_dma.rs @@ -1,8 +1,6 @@ //! Reproduction and regression test for a sneaky issue. //% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2 -//% FEATURES: integrated-timers -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/embassy_timers_executors.rs b/hil-test/tests/embassy_timers_executors.rs index 88f33023596..add52a1b37a 100644 --- a/hil-test/tests/embassy_timers_executors.rs +++ b/hil-test/tests/embassy_timers_executors.rs @@ -1,8 +1,6 @@ //! Embassy timer and executor Test //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: integrated-timers -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/gpio.rs b/hil-test/tests/gpio.rs index 7ff28cb4c84..51c8f2af66e 100644 --- a/hil-test/tests/gpio.rs +++ b/hil-test/tests/gpio.rs @@ -1,7 +1,6 @@ //! GPIO Test //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/gpio_custom_handler.rs b/hil-test/tests/gpio_custom_handler.rs index c7033206740..9dddab0a7e3 100644 --- a/hil-test/tests/gpio_custom_handler.rs +++ b/hil-test/tests/gpio_custom_handler.rs @@ -6,7 +6,6 @@ //! async API works for user handlers automatically. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: integrated-timers #![no_std] #![no_main] diff --git a/hil-test/tests/i2s.rs b/hil-test/tests/i2s.rs index 26fcd78911f..acdb9af940f 100644 --- a/hil-test/tests/i2s.rs +++ b/hil-test/tests/i2s.rs @@ -4,7 +4,6 @@ //! with loopback mode enabled). //% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: generic-queue // FIXME: re-enable on ESP32 when it no longer fails spuriously #![no_std] diff --git a/hil-test/tests/lcd_cam_i8080_async.rs b/hil-test/tests/lcd_cam_i8080_async.rs index 482f75667f7..e908b00a7aa 100644 --- a/hil-test/tests/lcd_cam_i8080_async.rs +++ b/hil-test/tests/lcd_cam_i8080_async.rs @@ -1,7 +1,6 @@ //! lcd_cam i8080 tests //% CHIPS: esp32s3 -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/parl_io_tx_async.rs b/hil-test/tests/parl_io_tx_async.rs index f3f82bcf1dd..a285a6f7f7e 100644 --- a/hil-test/tests/parl_io_tx_async.rs +++ b/hil-test/tests/parl_io_tx_async.rs @@ -1,7 +1,6 @@ //! PARL_IO TX async test //% CHIPS: esp32c6 esp32h2 -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/spi_full_duplex.rs b/hil-test/tests/spi_full_duplex.rs index cc8a4b5ffa4..15090f9bdf0 100644 --- a/hil-test/tests/spi_full_duplex.rs +++ b/hil-test/tests/spi_full_duplex.rs @@ -1,7 +1,6 @@ //! SPI Full Duplex test suite. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: generic-queue // FIXME: add async test cases that don't rely on PCNT diff --git a/hil-test/tests/uart_async.rs b/hil-test/tests/uart_async.rs index 7c60ab9647b..740b8291239 100644 --- a/hil-test/tests/uart_async.rs +++ b/hil-test/tests/uart_async.rs @@ -1,7 +1,6 @@ //! UART Test //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/hil-test/tests/uart_tx_rx_async.rs b/hil-test/tests/uart_tx_rx_async.rs index 30b916e7584..678c756a0d9 100644 --- a/hil-test/tests/uart_tx_rx_async.rs +++ b/hil-test/tests/uart_tx_rx_async.rs @@ -1,7 +1,6 @@ //! UART TX/RX Async Test //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: generic-queue #![no_std] #![no_main] diff --git a/qa-test/Cargo.toml b/qa-test/Cargo.toml index 2a1ded08a39..8884e4f178b 100644 --- a/qa-test/Cargo.toml +++ b/qa-test/Cargo.toml @@ -28,10 +28,14 @@ esp32h2 = ["esp-backtrace/esp32h2", "esp-hal/esp32h2", "esp-hal-embassy/esp32h2" esp32s2 = ["esp-backtrace/esp32s2", "esp-hal/esp32s2", "esp-hal-embassy/esp32s2", "esp-println/esp32s2"] esp32s3 = ["esp-backtrace/esp32s3", "esp-hal/esp32s3", "esp-hal-embassy/esp32s3", "esp-println/esp32s3"] -embassy-generic-timers = ["embassy-time/generic-queue-8"] - [profile.release] debug = 2 debug-assertions = true lto = "fat" codegen-units = 1 + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy", rev = "0c245892c6812538f4f51b784ed8afa1ce47f25d" } diff --git a/qa-test/src/bin/embassy_executor_benchmark.rs b/qa-test/src/bin/embassy_executor_benchmark.rs index e1945bfca9a..e2f358b5b54 100644 --- a/qa-test/src/bin/embassy_executor_benchmark.rs +++ b/qa-test/src/bin/embassy_executor_benchmark.rs @@ -1,7 +1,6 @@ //! Embassy executor benchmark, used to try out optimization ideas. //% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: esp-hal-embassy/integrated-timers #![no_std] #![no_main] @@ -22,9 +21,11 @@ use esp_hal::{ use esp_println::println; static mut COUNTER: u32 = 0; +static mut T2_COUNTER: u32 = 0; +static mut T3_COUNTER: u32 = 0; const CLOCK: CpuClock = CpuClock::max(); -const TEST_MILLIS: u64 = 50; +const TEST_MILLIS: u64 = 500; #[handler] fn timer_handler() { @@ -32,6 +33,8 @@ fn timer_handler() { let cpu_clock = CLOCK.hz() as u64; let timer_ticks_per_second = SystemTimer::ticks_per_second(); let cpu_cycles_per_timer_ticks = cpu_clock / timer_ticks_per_second; + println!("task2 count={}", unsafe { T2_COUNTER }); + println!("task3 count={}", unsafe { T3_COUNTER }); println!( "Test OK, count={}, cycles={}/100", c, @@ -53,15 +56,33 @@ impl Future for Task1 { static TASK1: TaskStorage = TaskStorage::new(); +#[embassy_executor::task] +async fn task2() { + loop { + unsafe { T2_COUNTER += 1 }; + embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await; + } +} + +#[embassy_executor::task] +async fn task3() { + loop { + unsafe { T3_COUNTER += 1 }; + embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; + } +} + #[esp_hal_embassy::main] async fn main(spawner: Spawner) { - let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); + let config = esp_hal::Config::default().with_cpu_clock(CLOCK); let peripherals = esp_hal::init(config); let systimer = SystemTimer::new(peripherals.SYSTIMER); esp_hal_embassy::init(systimer.alarm0); println!("Embassy initialized!"); spawner.spawn(TASK1.spawn(|| Task1 {})).unwrap(); + spawner.spawn(task2()).unwrap(); + spawner.spawn(task3()).unwrap(); println!("Starting test"); diff --git a/qa-test/src/bin/embassy_i2c.rs b/qa-test/src/bin/embassy_i2c.rs index 45a58efcec7..d974cd88936 100644 --- a/qa-test/src/bin/embassy_i2c.rs +++ b/qa-test/src/bin/embassy_i2c.rs @@ -11,7 +11,6 @@ //! - SCL => GPIO5 //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy-generic-timers #![no_std] #![no_main] diff --git a/qa-test/src/bin/embassy_i2c_bmp180_calibration_data.rs b/qa-test/src/bin/embassy_i2c_bmp180_calibration_data.rs index 77af5f4c76d..42fa5cb5e76 100644 --- a/qa-test/src/bin/embassy_i2c_bmp180_calibration_data.rs +++ b/qa-test/src/bin/embassy_i2c_bmp180_calibration_data.rs @@ -11,7 +11,6 @@ //! pins. //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy-generic-timers #![no_std] #![no_main] diff --git a/qa-test/src/bin/embassy_i2s_read.rs b/qa-test/src/bin/embassy_i2s_read.rs index ad12db40d2d..c9716a58479 100644 --- a/qa-test/src/bin/embassy_i2s_read.rs +++ b/qa-test/src/bin/embassy_i2s_read.rs @@ -12,7 +12,6 @@ //! - DIN => GPIO5 //% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy-generic-timers #![no_std] #![no_main] diff --git a/qa-test/src/bin/embassy_i2s_sound.rs b/qa-test/src/bin/embassy_i2s_sound.rs index 9678d976496..a780a195d27 100644 --- a/qa-test/src/bin/embassy_i2s_sound.rs +++ b/qa-test/src/bin/embassy_i2s_sound.rs @@ -26,7 +26,6 @@ //! | XSMT | +3V3 | //% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy-generic-timers #![no_std] #![no_main] diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index d2563bd0d40..a458e59b4aa 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -185,6 +185,9 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec { features.push("coex".to_owned()); } } + Package::EspHalEmbassy => { + features.push("esp-hal/unstable".to_owned()); + } _ => {} } features diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 49dac201dfe..e972cc5cca6 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -644,7 +644,7 @@ fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> { &[ "-Zbuild-std=core", &format!("--target={}", chip.target()), - &format!("--features={chip},executors,defmt,integrated-timers,esp-hal/unstable"), + &format!("--features={chip},executors,defmt,esp-hal/unstable"), ], args.fix, )?;