diff --git a/esp-hal-embassy/Cargo.toml b/esp-hal-embassy/Cargo.toml index 9d6885b2b6f..834dae8c659 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", 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" } +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,18 @@ 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"] +## Use the executor-integrated `embassy-time` timer queue. If not set, the crate provides a generic +## timer queue that can be used with any executor. +integrated-timers = ["embassy-executor?/integrated-timers", "executors"] +## Use a single, global timer queue. This option only needs a single alarm, no matter how many +## executors are used. Ignored if `integrated-timers` is not set. +single-queue = [] [lints.rust] unexpected_cfgs = "allow" + +[patch.crates-io] +embassy-executor = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-queue-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } diff --git a/esp-hal-embassy/build.rs b/esp-hal-embassy/build.rs index 5a4cf918864..dd4e2a777dd 100644 --- a/esp-hal-embassy/build.rs +++ b/esp-hal-embassy/build.rs @@ -1,7 +1,7 @@ use std::{error::Error, str::FromStr}; use esp_build::assert_unique_used_features; -use esp_config::{generate_config, Value}; +use esp_config::{generate_config, Validator, Value}; use esp_metadata::{Chip, Config}; fn main() -> Result<(), Box> { @@ -46,9 +46,29 @@ fn main() -> Result<(), Box> { "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 + ), + ( + "generic-queue-size", + "The size of the generic queue. Only used if `generic-queue` is enabled.", + 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)"); + + if cfg!(feature = "integrated-timers") { + println!("cargo:rustc-cfg=integrated_timers"); + if cfg!(feature = "single-queue") { + println!("cargo:rustc-cfg=single_queue"); + } + } else { + println!("cargo:rustc-cfg=generic_timers"); + println!("cargo:rustc-cfg=single_queue"); + } + 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 eccd214f389..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); @@ -138,7 +145,15 @@ This will use software-interrupt 3 which isn't available for anything else to wa // sections in Xtensa are implemented via increasing `PS.INTLEVEL`. // The critical section ends here. Take care not add code after // `waiti` if it needs to be inside the CS. - unsafe { core::arch::asm!("waiti 0") }; + // Do not lower INTLEVEL below the current value. + match token & 0x0F { + 0 => unsafe { core::arch::asm!("waiti 0") }, + 1 => unsafe { core::arch::asm!("waiti 1") }, + 2 => unsafe { core::arch::asm!("waiti 2") }, + 3 => unsafe { core::arch::asm!("waiti 3") }, + 4 => unsafe { core::arch::asm!("waiti 4") }, + _ => unsafe { core::arch::asm!("waiti 5") }, + } } // If this races and some waker sets the signal, we'll reset it, but still poll. SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::Relaxed); diff --git a/esp-hal-embassy/src/lib.rs b/esp-hal-embassy/src/lib.rs index 72b733c32e5..eb89c27ffce 100644 --- a/esp-hal-embassy/src/lib.rs +++ b/esp-hal-embassy/src/lib.rs @@ -49,8 +49,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) => {{ diff --git a/esp-hal-embassy/src/time_driver.rs b/esp-hal-embassy/src/time_driver.rs index fa785cdeecd..b96aba07643 100644 --- a/esp-hal-embassy/src/time_driver.rs +++ b/esp-hal-embassy/src/time_driver.rs @@ -1,6 +1,6 @@ use core::cell::Cell; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; use esp_hal::{ interrupt::{InterruptHandler, Priority}, prelude::*, @@ -12,24 +12,49 @@ 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 ())>, + pub callback: Cell<*const ()>, + pub state: AlarmState, } @@ -43,7 +68,7 @@ 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())), + callback: Cell::new(core::ptr::null_mut()), state: AlarmState::Created(handler), }), } @@ -78,7 +103,7 @@ embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTime }); 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,33 +116,21 @@ 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.callback.set(ctx.cast_mut()); + }) + } + fn on_interrupt(&self, id: usize) { - let (cb, ctx) = self.alarms[id].inner.with(|alarm| { + let ctx = self.alarms[id].inner.with(|alarm| { if let AlarmState::Initialized(timer) = &mut alarm.state { timer.clear_interrupt(); alarm.callback.get() @@ -129,15 +142,7 @@ 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) - }; - - cb(ctx); + TIMER_QUEUE_DRIVER.handle_alarm(ctx); } /// Returns `true` if the timer was armed, `false` if the timestamp is in @@ -157,14 +162,8 @@ impl EmbassyTimer { false } } -} - -impl Driver for EmbassyTimer { - fn now(&self) -> u64 { - now().ticks() - } - unsafe fn allocate_alarm(&self) -> Option { + 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 { @@ -189,15 +188,15 @@ impl Driver for EmbassyTimer { }); alarm.state = match timer { - Some(timer) => AlarmState::initialize(timer, interrupt_handler), + Some(timer) => AlarmState::initialize( + timer, + InterruptHandler::new(interrupt_handler, priority), + ), - None => { - // No timers are available yet, mark the alarm as allocated. - AlarmState::Allocated(interrupt_handler) - } + None => panic!(), }; - Some(AlarmHandle::new(i as u8)) + Some(AlarmHandle::new(i)) }); if handle.is_some() { @@ -208,16 +207,8 @@ 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)); - }) - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - let alarm = &self.alarms[alarm.id() as usize]; + pub(crate) fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { + let alarm = &self.alarms[alarm.id]; // If `embassy-executor/integrated-timers` is enabled and there are no pending // timers, embassy still calls `set_alarm` with `u64::MAX`. By returning @@ -244,11 +235,55 @@ impl Driver for EmbassyTimer { } } +impl Driver for EmbassyTimer { + fn now(&self) -> u64 { + now().ticks() + } +} + #[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 feature or disabling integrated-timers."); } + +pub(crate) struct TimerQueueDriver { + #[cfg(single_queue)] + pub(crate) inner: crate::timer_queue::TimerQueue, +} + +impl TimerQueueDriver { + const fn new() -> Self { + Self { + #[cfg(single_queue)] + inner: crate::timer_queue::TimerQueue::new(Priority::max()), + } + } + + fn handle_alarm(&self, _ctx: *const ()) { + #[cfg(all(integrated_timers, not(single_queue)))] + { + let executor = unsafe { &*_ctx.cast::() }; + executor.timer_queue.dispatch(); + } + + #[cfg(single_queue)] + self.inner.dispatch(); + } +} + +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 +} + +embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE_DRIVER: TimerQueueDriver = TimerQueueDriver::new()); diff --git a/esp-hal-embassy/src/timer_queue.rs b/esp-hal-embassy/src/timer_queue.rs new file mode 100644 index 00000000000..93369be3217 --- /dev/null +++ b/esp-hal-embassy/src/timer_queue.rs @@ -0,0 +1,183 @@ +#[cfg(not(single_queue))] +use core::cell::Cell; +use core::cell::UnsafeCell; + +use embassy_sync::blocking_mutex::Mutex; +use esp_hal::{interrupt::Priority, sync::RawPriorityLimitedMutex}; + +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), adapter::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() + } + + pub 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| adapter::dequeue(q, 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| adapter::dequeue(q, next_expiration)); + } + } +} + +impl embassy_time_queue_driver::TimerQueue for crate::time_driver::TimerQueueDriver { + fn schedule_wake(&'static self, at: u64, waker: &core::task::Waker) { + #[cfg(integrated_timers)] + let waker = embassy_executor::raw::task_from_waker(waker); + + #[cfg(not(single_queue))] + unsafe { + // FIXME: this is UB, use Exposed Provenance API (or something better) when + // available. Expose provenance in `InnerExecutor::init`, and use it here. + let executor = &*(waker.executor().unwrap_unchecked() + as *const embassy_executor::raw::Executor) + .cast::(); + executor.timer_queue.schedule_wake(at, waker); + } + + #[cfg(single_queue)] + self.inner.schedule_wake(at, waker); + } +} + +#[cfg(integrated_timers)] +mod adapter { + use core::cell::RefCell; + + use embassy_executor::raw; + + type Q = raw::timer_queue::TimerQueue; + + /// A simple wrapper around a `Queue` to provide interior mutability. + pub struct RefCellQueue { + inner: RefCell, + } + + impl RefCellQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + inner: RefCell::new(Q::new()), + } + } + + /// Schedules a task to run at a specific time, and returns whether any + /// changes were made. + pub fn schedule_wake(&self, at: u64, waker: raw::TaskRef) -> bool { + self.inner.borrow_mut().schedule_wake(at, waker) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&self, now: u64) -> u64 { + self.inner.borrow_mut().next_expiration(now) + } + } + + pub(super) type RawQueue = RefCellQueue; + + pub(super) fn dequeue(q: &RawQueue, now: u64) -> u64 { + q.next_expiration(now) + } + + impl super::TimerQueue { + pub fn schedule_wake(&self, at: u64, task: raw::TaskRef) { + if self.inner.lock(|q| q.schedule_wake(at, task)) { + self.dispatch(); + } + } + } +} + +#[cfg(generic_timers)] +mod adapter { + use core::{cell::RefCell, task::Waker}; + + type Q = embassy_time_queue_driver::queue_generic::ConstGenericQueue< + { esp_config::esp_config_int!(usize, "ESP_HAL_EMBASSY_GENERIC_QUEUE_SIZE") }, + >; + + /// A simple wrapper around a `Queue` to provide interior mutability. + pub struct RefCellQueue { + inner: RefCell, + } + + impl RefCellQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + inner: RefCell::new(Q::new()), + } + } + + /// Schedules a task to run at a specific time, and returns whether any + /// changes were made. + pub fn schedule_wake(&self, at: u64, waker: &core::task::Waker) -> bool { + self.inner.borrow_mut().schedule_wake(at, waker) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&self, now: u64) -> u64 { + self.inner.borrow_mut().next_expiration(now) + } + } + + pub(super) type RawQueue = RefCellQueue; + + pub(super) fn dequeue(q: &RawQueue, now: u64) -> u64 { + q.next_expiration(now) + } + + impl super::TimerQueue { + pub fn schedule_wake(&self, at: u64, waker: &Waker) { + if self.inner.lock(|q| q.schedule_wake(at, waker)) { + self.dispatch(); + } + } + } +} diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index c54adfb77df..b2cac3c08e2 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -59,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dropped GPIO futures stop listening for interrupts (#2625) - UART driver's `StopBits` enum variants now correctly use UpperCamelCase (#2669) - The `PeripheralInput` and `PeripheralOutput` traits are now sealed (#2690) +- `esp_hal::sync::Lock` has been renamed to RawMutex (#2684) ### Fixed diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index 212b99379ff..95680b8940d 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -117,7 +117,7 @@ pub enum CpuInterrupt { } /// Interrupt priority levels. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Priority { @@ -167,6 +167,32 @@ impl Priority { } } +impl TryFrom for Priority { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Priority::None), + 1 => Ok(Priority::Priority1), + 2 => Ok(Priority::Priority2), + 3 => Ok(Priority::Priority3), + 4 => Ok(Priority::Priority4), + 5 => Ok(Priority::Priority5), + 6 => Ok(Priority::Priority6), + 7 => Ok(Priority::Priority7), + 8 => Ok(Priority::Priority8), + 9 => Ok(Priority::Priority9), + 10 => Ok(Priority::Priority10), + 11 => Ok(Priority::Priority11), + 12 => Ok(Priority::Priority12), + 13 => Ok(Priority::Priority13), + 14 => Ok(Priority::Priority14), + 15 => Ok(Priority::Priority15), + _ => Err(Error::InvalidInterruptPriority), + } + } +} + /// The interrupts reserved by the HAL #[cfg_attr(place_switch_tables_in_ram, link_section = ".rwtext")] pub static RESERVED_INTERRUPTS: &[usize] = PRIORITY_TO_INTERRUPT; diff --git a/esp-hal/src/interrupt/xtensa.rs b/esp-hal/src/interrupt/xtensa.rs index f0aaa54e6f6..bb5ca7dd52a 100644 --- a/esp-hal/src/interrupt/xtensa.rs +++ b/esp-hal/src/interrupt/xtensa.rs @@ -340,7 +340,7 @@ mod vectored { use super::*; /// Interrupt priority levels. - #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Priority { @@ -366,6 +366,20 @@ mod vectored { } } + impl TryFrom for Priority { + type Error = Error; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Priority::None), + 1 => Ok(Priority::Priority1), + 2 => Ok(Priority::Priority2), + 3 => Ok(Priority::Priority3), + _ => Err(Error::InvalidInterrupt), + } + } + } + impl CpuInterrupt { #[inline] fn level(&self) -> Priority { diff --git a/esp-hal/src/sync.rs b/esp-hal/src/sync.rs index 2f93590ce5b..6a8b8b7a5b6 100644 --- a/esp-hal/src/sync.rs +++ b/esp-hal/src/sync.rs @@ -1,52 +1,150 @@ -//! Under construction: This is public only for tests, please avoid using it. +//! Under construction: This is public only for tests, please avoid using it +//! directly. #[cfg(single_core)] use core::cell::Cell; use core::cell::UnsafeCell; +use crate::interrupt::Priority; + mod single_core { use core::sync::atomic::{compiler_fence, Ordering}; - pub unsafe fn disable_interrupts() -> critical_section::RawRestoreState { - cfg_if::cfg_if! { - if #[cfg(riscv)] { - let mut mstatus = 0u32; - core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus); - let token = ((mstatus & 0b1000) != 0) as critical_section::RawRestoreState; - } else if #[cfg(xtensa)] { - let token: critical_section::RawRestoreState; - core::arch::asm!("rsil {0}, 5", out(reg) token); - } else { - compile_error!("Unsupported architecture") - } - }; + use crate::interrupt::Priority; + + /// Trait for single-core locks. + pub trait RawLock { + unsafe fn enter(&self) -> critical_section::RawRestoreState; + unsafe fn exit(&self, token: critical_section::RawRestoreState); + } + + /// A lock that disables interrupts below a certain priority. + pub struct PriorityLock(pub Priority); + + impl PriorityLock { + fn current_priority() -> Priority { + cfg_if::cfg_if! { + if #[cfg(plic)] { + let plic = unsafe { &*crate::peripherals::PLIC_MX::PTR }; + let prev_interrupt_priority = plic.mxint_thresh().read().cpu_mxint_thresh().bits().saturating_sub(1); + } else if #[cfg(all(riscv, not(plic)))] { + let intr = unsafe { &*crate::peripherals::INTERRUPT_CORE0::PTR }; + let prev_interrupt_priority = intr.cpu_int_thresh().read().bits().saturating_sub(1) as u8; + } else if #[cfg(xtensa)] { + let ps: u32; + unsafe { core::arch::asm!("rsr.ps {0}", out(reg) ps) }; + + let prev_interrupt_priority = ps as u8 & 0x0F; + } else { + compile_error!("Unsupported architecture") + } + }; - // Ensure no subsequent memory accesses are reordered to before interrupts are - // disabled. - compiler_fence(Ordering::SeqCst); + unwrap!(Priority::try_from(prev_interrupt_priority)) + } + + /// Prevents interrupts above `level` from firing and returns the + /// current run level. + unsafe fn change_current_level(level: Priority) -> Priority { + cfg_if::cfg_if! { + if #[cfg(plic)] { + let plic = &*crate::peripherals::PLIC_MX::PTR; + let prev_interrupt_priority = plic.mxint_thresh().read().cpu_mxint_thresh().bits().saturating_sub(1); + plic.mxint_thresh().write(|w| w.cpu_mxint_thresh().bits(level as u8 + 1)); + } else if #[cfg(all(riscv, not(plic)))] { + let intr = &*crate::peripherals::INTERRUPT_CORE0::PTR; + let prev_interrupt_priority = intr.cpu_int_thresh().read().bits().saturating_sub(1) as u8; + intr.cpu_int_thresh().write(|w| w.bits(level as u32 + 1)); + } else if #[cfg(xtensa)] { + let token: u32; + match level { + Priority::None => core::arch::asm!("rsil {0}, 0", out(reg) token), + Priority::Priority1 => core::arch::asm!("rsil {0}, 1", out(reg) token), + Priority::Priority2 => core::arch::asm!("rsil {0}, 2", out(reg) token), + Priority::Priority3 => core::arch::asm!("rsil {0}, 3", out(reg) token), + }; + + let prev_interrupt_priority = token as u8 & 0x0F; + } else { + compile_error!("Unsupported architecture") + } + }; - token + unwrap!(Priority::try_from(prev_interrupt_priority)) + } } - pub unsafe fn reenable_interrupts(token: critical_section::RawRestoreState) { - // Ensure no preceeding memory accesses are reordered to after interrupts are - // enabled. - compiler_fence(Ordering::SeqCst); + impl RawLock for PriorityLock { + unsafe fn enter(&self) -> critical_section::RawRestoreState { + let prev_interrupt_priority = unsafe { Self::change_current_level(self.0) }; + assert!(prev_interrupt_priority <= self.0); - cfg_if::cfg_if! { - if #[cfg(riscv)] { - if token != 0 { - esp_riscv_rt::riscv::interrupt::enable(); + // Ensure no subsequent memory accesses are reordered to before interrupts are + // disabled. + compiler_fence(Ordering::SeqCst); + + prev_interrupt_priority as _ + } + + unsafe fn exit(&self, token: critical_section::RawRestoreState) { + assert!(Self::current_priority() <= self.0); + // Ensure no preceeding memory accesses are reordered to after interrupts are + // enabled. + compiler_fence(Ordering::SeqCst); + + #[cfg(xtensa)] + let token = token as u8; + + let priority = unwrap!(Priority::try_from(token)); + unsafe { Self::change_current_level(priority) }; + } + } + + /// A lock that disables interrupts. + pub struct InterruptLock; + + impl RawLock for InterruptLock { + unsafe fn enter(&self) -> critical_section::RawRestoreState { + cfg_if::cfg_if! { + if #[cfg(riscv)] { + let mut mstatus = 0u32; + core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus); + let token = ((mstatus & 0b1000) != 0) as critical_section::RawRestoreState; + } else if #[cfg(xtensa)] { + let token: critical_section::RawRestoreState; + core::arch::asm!("rsil {0}, 5", out(reg) token); + } else { + compile_error!("Unsupported architecture") + } + }; + + // Ensure no subsequent memory accesses are reordered to before interrupts are + // disabled. + compiler_fence(Ordering::SeqCst); + + token + } + + unsafe fn exit(&self, token: critical_section::RawRestoreState) { + // Ensure no preceeding memory accesses are reordered to after interrupts are + // enabled. + compiler_fence(Ordering::SeqCst); + + cfg_if::cfg_if! { + if #[cfg(riscv)] { + if token != 0 { + esp_riscv_rt::riscv::interrupt::enable(); + } + } else if #[cfg(xtensa)] { + // Reserved bits in the PS register, these must be written as 0. + const RESERVED_MASK: u32 = 0b1111_1111_1111_1000_1111_0000_0000_0000; + debug_assert!(token & RESERVED_MASK == 0); + core::arch::asm!( + "wsr.ps {0}", + "rsync", in(reg) token) + } else { + compile_error!("Unsupported architecture") } - } else if #[cfg(xtensa)] { - // Reserved bits in the PS register, these must be written as 0. - const RESERVED_MASK: u32 = 0b1111_1111_1111_1000_1111_0000_0000_0000; - debug_assert!(token & RESERVED_MASK == 0); - core::arch::asm!( - "wsr.ps {0}", - "rsync", in(reg) token) - } else { - compile_error!("Unsupported architecture") } } } @@ -121,26 +219,24 @@ cfg_if::cfg_if! { } } -/// A lock that can be used to protect shared resources. -pub struct Lock { +/// A generic lock that wraps [`single_core::RawLock`] and +/// [`multicore::AtomicLock`] and tracks whether the caller has locked +/// recursively. +struct GenericRawMutex { + lock: L, #[cfg(multi_core)] inner: multicore::AtomicLock, #[cfg(single_core)] is_locked: Cell, } -unsafe impl Sync for Lock {} +unsafe impl Sync for GenericRawMutex {} -impl Default for Lock { - fn default() -> Self { - Self::new() - } -} - -impl Lock { +impl GenericRawMutex { /// Create a new lock. - pub const fn new() -> Self { + pub const fn new(lock: L) -> Self { Self { + lock, #[cfg(multi_core)] inner: multicore::AtomicLock::new(), #[cfg(single_core)] @@ -156,10 +252,10 @@ impl Lock { /// - The returned token must be passed to the corresponding `release` call. /// - The caller must ensure to release the locks in the reverse order they /// were acquired. - pub unsafe fn acquire(&self) -> critical_section::RawRestoreState { + unsafe fn acquire(&self) -> critical_section::RawRestoreState { cfg_if::cfg_if! { if #[cfg(single_core)] { - let mut tkn = unsafe { single_core::disable_interrupts() }; + let mut tkn = unsafe { self.lock.enter() }; let was_locked = self.is_locked.replace(true); if was_locked { tkn |= REENTRY_FLAG; @@ -175,7 +271,7 @@ impl Lock { // context with the same `current_thread_id`, so it would be allowed to lock the // resource in a theoretically incorrect way. let try_lock = |current_thread_id| { - let mut tkn = unsafe { single_core::disable_interrupts() }; + let mut tkn = unsafe { self.lock.enter() }; match self.inner.try_lock(current_thread_id) { Ok(()) => Some(tkn), @@ -184,7 +280,7 @@ impl Lock { Some(tkn) } Err(_) => { - unsafe { single_core::reenable_interrupts(tkn) }; + unsafe { self.lock.exit(tkn) }; None } } @@ -209,7 +305,7 @@ impl Lock { /// - The caller must ensure to release the locks in the reverse order they /// were acquired. /// - Each release call must be paired with an acquire call. - pub unsafe fn release(&self, token: critical_section::RawRestoreState) { + unsafe fn release(&self, token: critical_section::RawRestoreState) { if token & REENTRY_FLAG == 0 { #[cfg(multi_core)] self.inner.unlock(); @@ -217,25 +313,140 @@ impl Lock { #[cfg(single_core)] self.is_locked.set(false); - single_core::reenable_interrupts(token); + self.lock.exit(token) + } + } + + /// Runs the callback with this lock locked. + /// + /// Note that this function is not reentrant, calling it reentrantly will + /// panic. + pub fn lock(&self, f: impl FnOnce() -> R) -> R { + let _token = LockGuard::new(self); + f() + } +} + +/// A mutual exclusion primitive. +/// +/// This lock disables interrupts on the current core while locked. +#[cfg_attr( + multi_core, + doc = r#"It needs a bit of memory, but it does not take a global critical + section, making it preferrable for use in multi-core systems."# +)] +pub struct RawMutex { + inner: GenericRawMutex, +} + +impl Default for RawMutex { + fn default() -> Self { + Self::new() + } +} + +impl RawMutex { + /// Create a new lock. + pub const fn new() -> Self { + Self { + inner: GenericRawMutex::new(single_core::InterruptLock), + } + } + + /// Acquires the lock. + /// + /// # Safety + /// + /// - Each release call must be paired with an acquire call. + /// - The returned token must be passed to the corresponding `release` call. + /// - The caller must ensure to release the locks in the reverse order they + /// were acquired. + pub unsafe fn acquire(&self) -> critical_section::RawRestoreState { + self.inner.acquire() + } + + /// Releases the lock. + /// + /// # Safety + /// + /// - This function must only be called if the lock was acquired by the + /// current thread. + /// - The caller must ensure to release the locks in the reverse order they + /// were acquired. + /// - Each release call must be paired with an acquire call. + pub unsafe fn release(&self, token: critical_section::RawRestoreState) { + self.inner.release(token); + } + + /// Runs the callback with this lock locked. + /// + /// Note that this function is not reentrant, calling it reentrantly will + /// panic. + pub fn lock(&self, f: impl FnOnce() -> R) -> R { + self.inner.lock(f) + } +} + +unsafe impl embassy_sync::blocking_mutex::raw::RawMutex for RawMutex { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + // embassy_sync semantics allow reentrancy. + let _token = LockGuard::new_reentrant(&self.inner); + f() + } +} + +/// A mutual exclusion primitive that only disables a limited range of +/// interrupts. +/// +/// Trying to acquire or release the lock at a higher priority level will panic. +pub struct RawPriorityLimitedMutex { + inner: GenericRawMutex, +} + +impl RawPriorityLimitedMutex { + /// Create a new lock that is accessible at or below the given `priority`. + pub const fn new(priority: Priority) -> Self { + Self { + inner: GenericRawMutex::new(single_core::PriorityLock(priority)), } } + + /// Runs the callback with this lock locked. + /// + /// Note that this function is not reentrant, calling it reentrantly will + /// panic. + pub fn lock(&self, f: impl FnOnce() -> R) -> R { + self.inner.lock(f) + } +} + +unsafe impl embassy_sync::blocking_mutex::raw::RawMutex for RawPriorityLimitedMutex { + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Self = Self::new(Priority::max()); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + // embassy_sync semantics allow reentrancy. + let _token = LockGuard::new_reentrant(&self.inner); + f() + } } // Prefer this over a critical-section as this allows you to have multiple // locks active at the same time rather than using the global mutex that is // critical-section. -pub(crate) fn lock(lock: &Lock, f: impl FnOnce() -> T) -> T { - let _token = LockGuard::new(lock); - f() +pub(crate) fn lock(lock: &RawMutex, f: impl FnOnce() -> T) -> T { + lock.lock(f) } -/// Data protected by a [Lock]. +/// Data protected by a [RawMutex]. /// /// This is largely equivalent to a `Mutex>`, but accessing the inner /// data doesn't hold a critical section on multi-core systems. pub struct Locked { - lock_state: Lock, + lock_state: RawMutex, data: UnsafeCell, } @@ -243,7 +454,7 @@ impl Locked { /// Create a new instance pub const fn new(data: T) -> Self { Self { - lock_state: Lock::new(), + lock_state: RawMutex::new(), data: UnsafeCell::new(data), } } @@ -262,7 +473,7 @@ struct CriticalSection; critical_section::set_impl!(CriticalSection); -static CRITICAL_SECTION: Lock = Lock::new(); +static CRITICAL_SECTION: RawMutex = RawMutex::new(); unsafe impl critical_section::Impl for CriticalSection { unsafe fn acquire() -> critical_section::RawRestoreState { @@ -274,46 +485,19 @@ unsafe impl critical_section::Impl for CriticalSection { } } -/// A mutual exclusion primitive. -/// -/// This is an implementation of `embassy_sync::blocking_mutex::raw::RawMutex`. -/// It needs a bit of memory, but it does not take a global critical section, -/// making it preferrable for use in multi-core systems. -/// -/// On single core systems, this is equivalent to `CriticalSectionRawMutex`. -pub struct RawMutex(Lock); - -impl RawMutex { - /// Create a new mutex. - #[allow(clippy::new_without_default)] - pub const fn new() -> Self { - Self(Lock::new()) - } -} - -unsafe impl embassy_sync::blocking_mutex::raw::RawMutex for RawMutex { - #[allow(clippy::declare_interior_mutable_const)] - const INIT: Self = Self(Lock::new()); - - fn lock(&self, f: impl FnOnce() -> R) -> R { - let _token = LockGuard::new_reentrant(&self.0); - f() - } -} - -struct LockGuard<'a> { - lock: &'a Lock, +struct LockGuard<'a, L: single_core::RawLock> { + lock: &'a GenericRawMutex, token: critical_section::RawRestoreState, } -impl<'a> LockGuard<'a> { - fn new(lock: &'a Lock) -> Self { +impl<'a, L: single_core::RawLock> LockGuard<'a, L> { + fn new(lock: &'a GenericRawMutex) -> Self { let this = Self::new_reentrant(lock); assert!(this.token & REENTRY_FLAG == 0, "lock is not reentrant"); this } - fn new_reentrant(lock: &'a Lock) -> Self { + fn new_reentrant(lock: &'a GenericRawMutex) -> Self { let token = unsafe { // SAFETY: the same lock will be released when dropping the guard. // This ensures that the lock is released on the same thread, in the reverse @@ -325,7 +509,7 @@ impl<'a> LockGuard<'a> { } } -impl Drop for LockGuard<'_> { +impl Drop for LockGuard<'_, L> { fn drop(&mut self) { unsafe { self.lock.release(self.token) }; } diff --git a/esp-hal/src/timer/systimer.rs b/esp-hal/src/timer/systimer.rs index 2cdbf9ea5c8..c14d00ef8c8 100644 --- a/esp-hal/src/timer/systimer.rs +++ b/esp-hal/src/timer/systimer.rs @@ -26,7 +26,7 @@ use crate::{ interrupt::{self, InterruptConfigurable, InterruptHandler}, peripheral::Peripheral, peripherals::{Interrupt, SYSTIMER}, - sync::{lock, Lock}, + sync::{lock, RawMutex}, system::{Peripheral as PeripheralEnable, PeripheralClockControl}, Cpu, }; @@ -628,8 +628,8 @@ impl Peripheral for Alarm { impl crate::private::Sealed for Alarm {} -static CONF_LOCK: Lock = Lock::new(); -static INT_ENA_LOCK: Lock = Lock::new(); +static CONF_LOCK: RawMutex = RawMutex::new(); +static INT_ENA_LOCK: RawMutex = RawMutex::new(); // Async functionality of the system timer. mod asynch { diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs index 239207516eb..223facbb3cb 100644 --- a/esp-hal/src/timer/timg.rs +++ b/esp-hal/src/timer/timg.rs @@ -76,13 +76,13 @@ use crate::{ peripheral::Peripheral, peripherals::{timg0::RegisterBlock, Interrupt, TIMG0}, private::Sealed, - sync::{lock, Lock}, + sync::{lock, RawMutex}, system::PeripheralClockControl, }; const NUM_TIMG: usize = 1 + cfg!(timg1) as usize; -static INT_ENA_LOCK: [Lock; NUM_TIMG] = [const { Lock::new() }; NUM_TIMG]; +static INT_ENA_LOCK: [RawMutex; NUM_TIMG] = [const { RawMutex::new() }; NUM_TIMG]; /// A timer group consisting of #[cfg_attr(not(timg_timer1), doc = "a general purpose timer")] diff --git a/esp-wifi/src/wifi/os_adapter.rs b/esp-wifi/src/wifi/os_adapter.rs index 10e77a9298d..ad9dfd6248e 100644 --- a/esp-wifi/src/wifi/os_adapter.rs +++ b/esp-wifi/src/wifi/os_adapter.rs @@ -10,7 +10,7 @@ pub(crate) mod os_adapter_chip_specific; use core::{cell::RefCell, ptr::addr_of_mut}; use enumset::EnumSet; -use esp_hal::sync::{Lock, Locked}; +use esp_hal::sync::{Locked, RawMutex}; use super::WifiEvent; use crate::{ @@ -35,7 +35,7 @@ use crate::{ timer::yield_task, }; -static WIFI_LOCK: Lock = Lock::new(); +static WIFI_LOCK: RawMutex = RawMutex::new(); static mut QUEUE_HANDLE: *mut ConcurrentQueue = core::ptr::null_mut(); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0e521d25db8..d99a569f249 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/bugadani/embassy", branch = "refactor" } +embassy-time = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-queue-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } 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_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 799d44b274c..ff6411923c2 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 93dd7811cd6..6b46fea1484 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 8061689e097..0d4ed571a6c 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 d0cdd475e32..4e80313344b 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..420abd41f5e 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -290,12 +290,7 @@ embassy = [ "embedded-test/external-executor", "dep:esp-hal-embassy", ] -generic-queue = [ - "embassy-time/generic-queue-64" -] -integrated-timers = [ - "esp-hal-embassy/integrated-timers", -] +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 +311,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/bugadani/embassy", branch = "refactor" } +embassy-time = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-queue-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } diff --git a/hil-test/tests/critical_section.rs b/hil-test/tests/critical_section.rs index f49ffe7fb15..e0d8f69d6c3 100644 --- a/hil-test/tests/critical_section.rs +++ b/hil-test/tests/critical_section.rs @@ -7,16 +7,45 @@ #![no_std] #![no_main] +use esp_hal::{ + delay::Delay, + interrupt::{ + software::{SoftwareInterrupt, SoftwareInterruptControl}, + InterruptHandler, + Priority, + }, + peripherals::Peripherals, + sync::{Locked, RawPriorityLimitedMutex}, +}; use hil_test as _; +fn test_access_at_priority(peripherals: Peripherals, priority: Priority) { + static LOCK: RawPriorityLimitedMutex = RawPriorityLimitedMutex::new(Priority::Priority1); + + extern "C" fn access() { + unsafe { SoftwareInterrupt::::steal().reset() }; + LOCK.lock(|| {}); + embedded_test::export::check_outcome(()); + } + + let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + + let mut prio_2_interrupt = sw_ints.software_interrupt1; + + prio_2_interrupt.set_interrupt_handler(InterruptHandler::new(access::<1>, priority)); + + prio_2_interrupt.raise(); + loop {} +} + #[cfg(test)] #[embedded_test::tests(default_timeout = 3)] mod tests { - use esp_hal::sync::Locked; + use super::*; #[init] - fn init() { - esp_hal::init(esp_hal::Config::default()); + fn init() -> Peripherals { + esp_hal::init(esp_hal::Config::default()) } #[test] @@ -55,4 +84,66 @@ mod tests { }); }); } + + #[test] + fn priority_lock_tests(peripherals: Peripherals) { + use portable_atomic::{AtomicU32, Ordering}; + + static COUNTER: AtomicU32 = AtomicU32::new(0); + + extern "C" fn increment() { + unsafe { SoftwareInterrupt::::steal().reset() }; + COUNTER.fetch_add(1, Ordering::AcqRel); + } + + let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + + let mut prio_1_interrupt = sw_ints.software_interrupt0; + let mut prio_2_interrupt = sw_ints.software_interrupt1; + + prio_1_interrupt + .set_interrupt_handler(InterruptHandler::new(increment::<0>, Priority::Priority1)); + prio_2_interrupt + .set_interrupt_handler(InterruptHandler::new(increment::<1>, Priority::Priority2)); + + let lock = RawPriorityLimitedMutex::new(Priority::Priority1); + + let delay = Delay::new(); + + // Lock does nothing unless taken + + prio_1_interrupt.raise(); + // Software interrupts may not trigger immediately and there may be some + // instructions executed after `raise`. We need to wait a short while + // to ensure that the interrupt has been serviced before reading the counter. + delay.delay_millis(1); + assert_eq!(COUNTER.load(Ordering::Acquire), 1); + + // Taking the lock masks the lower priority interrupt + lock.lock(|| { + prio_1_interrupt.raise(); + delay.delay_millis(1); + assert_eq!(COUNTER.load(Ordering::Acquire), 1); // not incremented + + // Taken lock does not mask higher priority interrupts + prio_2_interrupt.raise(); + delay.delay_millis(1); + assert_eq!(COUNTER.load(Ordering::Acquire), 2); + }); + + // Releasing the lock unmasks the lower priority interrupt + delay.delay_millis(1); + assert_eq!(COUNTER.load(Ordering::Acquire), 3); + } + + #[test] + fn priority_lock_allows_access_from_equal_priority(peripherals: Peripherals) { + test_access_at_priority(peripherals, Priority::Priority1); + } + + #[test] + #[should_panic] + fn priority_lock_panics_on_higher_priority_access(peripherals: Peripherals) { + test_access_at_priority(peripherals, Priority::Priority2); + } } diff --git a/hil-test/tests/embassy_interrupt_executor.rs b/hil-test/tests/embassy_interrupt_executor.rs index 7731aa9fa0e..a3503ee1529 100644 --- a/hil-test/tests/embassy_interrupt_executor.rs +++ b/hil-test/tests/embassy_interrupt_executor.rs @@ -3,7 +3,7 @@ //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: integrated-timers -//% FEATURES: generic-queue +//% FEATURES: #![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..8229365f06d 100644 --- a/hil-test/tests/embassy_interrupt_spi_dma.rs +++ b/hil-test/tests/embassy_interrupt_spi_dma.rs @@ -2,7 +2,7 @@ //% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2 //% FEATURES: integrated-timers -//% FEATURES: generic-queue +//% FEATURES: #![no_std] #![no_main] diff --git a/hil-test/tests/embassy_timers_executors.rs b/hil-test/tests/embassy_timers_executors.rs index 88f33023596..7d832e877f7 100644 --- a/hil-test/tests/embassy_timers_executors.rs +++ b/hil-test/tests/embassy_timers_executors.rs @@ -2,7 +2,7 @@ //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: integrated-timers -//% FEATURES: generic-queue +//% FEATURES: #![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/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 9c587ef6fa4..1f93d6e29bd 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..4f7b959e062 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/bugadani/embassy", branch = "refactor" } +embassy-time = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } +embassy-time-queue-driver = { git = "https://github.com/bugadani/embassy", branch = "refactor" } diff --git a/qa-test/src/bin/embassy_executor_benchmark.rs b/qa-test/src/bin/embassy_executor_benchmark.rs index e1945bfca9a..b7933f2087b 100644 --- a/qa-test/src/bin/embassy_executor_benchmark.rs +++ b/qa-test/src/bin/embassy_executor_benchmark.rs @@ -2,6 +2,8 @@ //% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: esp-hal-embassy/integrated-timers +// FEATURES: esp-hal-embassy/integrated-timers esp-hal-embassy/single-queue +// FEATURES: #![no_std] #![no_main] @@ -22,9 +24,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 +36,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 +59,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]