From 0c55a5269214853d9ce26a00bc0856563faa95fe Mon Sep 17 00:00:00 2001 From: Ivan Markov Date: Sat, 16 Apr 2022 19:04:09 +0300 Subject: [PATCH] Notion of MutexFamily (#63) * Signal generalized; efficient notifiable-from-ISR single-threaded executor * Notion of MutexFamily * New release * Fix compilation error on riscv * Fix fmt * Address clippy warnings * Address clippy warnings * Address clippy warnings --- Cargo.toml | 4 +- src/interrupt.rs | 201 +++++++++++++++++++++++++++++++++++++++++++---- src/mutex.rs | 37 ++++----- 3 files changed, 203 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c45c2834f..278ffe058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "esp-idf-hal" -version = "0.35.2" +version = "0.36.0" authors = ["sapir ", "Ivan Markov "] edition = "2018" resolver = "2" @@ -28,7 +28,7 @@ nb = "0.1.2" mutex-trait = { version = "0.2", optional = true, default-features = false } embedded-hal = "=1.0.0-alpha.7" embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } -embedded-svc = { version = "0.19", optional = true, default-features = false } +embedded-svc = { version = "0.20", optional = true, default-features = false } esp-idf-sys = { version = "0.31", optional = true, default-features = false, features = ["native"] } critical-section = { version = "0.2.5", optional = true, features = ["custom-impl"] } embassy = { version = "0", optional = true } diff --git a/src/interrupt.rs b/src/interrupt.rs index e7f8abfe7..cf547b1fd 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,5 +1,7 @@ use core::cell::{RefCell, RefMut}; use core::ops::{Deref, DerefMut}; +use core::ptr; +use core::sync::atomic::{AtomicPtr, Ordering}; use esp_idf_sys::*; @@ -10,24 +12,187 @@ pub fn active() -> bool { unsafe { xPortInIsrContext() != 0 } } -#[inline(always)] -#[link_section = ".iram1.interrupt_do_yield"] -pub fn do_yield() { - if active() { - #[cfg(esp32c3)] - unsafe { - vPortYieldFromISR(); - } +static ISR_YIELDER: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - #[cfg(not(esp32c3))] - unsafe { - vPortEvaluateYieldFromISR(0); - } +#[inline(always)] +#[link_section = ".iram1.interrupt_get_isr_yielder"] +unsafe fn get_isr_yielder() -> Option { + if active() { + ISR_YIELDER.load(Ordering::SeqCst).as_ref().copied() } else { - unsafe { - vPortYield(); + None + } +} + +/// # Safety +/// +/// This function should only be called from within an ISR handler, so as to set +/// a custom ISR yield function (e.g. when using the ESP-IDF timer service). +/// +/// Thus, if some function further down the ISR call chain invokes `do_yield`, +/// the custom yield function set here will be called. +/// +/// Users should not forget to call again `set_isr_yielder` at the end of the +/// ISR handler so as to reastore the yield function which was valid before the +/// ISR handler was invoked. +#[inline(always)] +#[link_section = ".iram1.interrupt_set_isr_yielder"] +pub unsafe fn set_isr_yielder(yielder: Option) -> Option { + if active() { + let yielder = if let Some(yielder) = yielder { + &yielder as *const _ as *mut _ + } else { + ptr::null_mut() + }; + + ISR_YIELDER + .swap(yielder, Ordering::SeqCst) + .as_ref() + .copied() + } else { + None + } +} + +pub mod task { + use core::ptr; + use core::time::Duration; + + use esp_idf_sys::*; + + use crate::delay::TickType; + + #[inline(always)] + #[link_section = ".iram1.interrupt_task_do_yield"] + pub fn do_yield() { + if super::active() { + #[cfg(esp32c3)] + unsafe { + if let Some(yielder) = super::get_isr_yielder() { + yielder(); + } else { + vPortYieldFromISR(); + } + } + + #[cfg(not(esp32c3))] + unsafe { + if let Some(yielder) = super::get_isr_yielder() { + yielder(); + } else { + vPortEvaluateYieldFromISR(0); + } + } + } else { + unsafe { + vPortYield(); + } } } + + #[inline(always)] + #[link_section = ".iram1.interrupt_task_current"] + pub fn current() -> Option { + if super::active() { + None + } else { + Some(unsafe { xTaskGetCurrentTaskHandle() }) + } + } + + pub fn wait_any_notification() { + loop { + if let Some(notification) = wait_notification(None) { + if notification != 0 { + break; + } + } + } + } + + pub fn wait_notification(duration: Option) -> Option { + let mut notification = 0_u32; + + #[cfg(esp_idf_version = "4.3")] + let notified = unsafe { + xTaskNotifyWait( + 0, + u32::MAX, + &mut notification as *mut _, + TickType::from(duration).0, + ) + } != 0; + + #[cfg(not(esp_idf_version = "4.3"))] + let notified = unsafe { + xTaskGenericNotifyWait( + 0, + 0, + u32::MAX, + &mut notification as *mut _, + TickType::from(duration).0, + ) + } != 0; + + if notified { + Some(notification) + } else { + None + } + } + + /// # Safety + /// + /// When calling this function care should be taken to pass a valid + /// FreeRTOS task handle. Moreover, the FreeRTOS task should be valid + /// when this function is being called. + pub unsafe fn notify(task: TaskHandle_t, notification: u32) -> bool { + let notified = if super::active() { + let mut higher_prio_task_woken: BaseType_t = Default::default(); + + #[cfg(esp_idf_version = "4.3")] + let notified = xTaskGenericNotifyFromISR( + task, + notification, + eNotifyAction_eSetBits, + ptr::null_mut(), + &mut higher_prio_task_woken as *mut _, + ); + + #[cfg(not(esp_idf_version = "4.3"))] + let notified = xTaskGenericNotifyFromISR( + task, + 0, + notification, + eNotifyAction_eSetBits, + ptr::null_mut(), + &mut higher_prio_task_woken as *mut _, + ); + + if higher_prio_task_woken != 0 { + do_yield(); + } + + notified + } else { + #[cfg(esp_idf_version = "4.3")] + let notified = + xTaskGenericNotify(task, notification, eNotifyAction_eSetBits, ptr::null_mut()); + + #[cfg(not(esp_idf_version = "4.3"))] + let notified = xTaskGenericNotify( + task, + 0, + notification, + eNotifyAction_eSetBits, + ptr::null_mut(), + ); + + notified + }; + + notified != 0 + } } /// A critical section allows the user to disable interrupts @@ -275,6 +440,14 @@ impl<'a, T> mutex_trait::Mutex for &'a Mutex { } } +#[cfg(feature = "embedded-svc")] +pub struct MutexFamily; + +#[cfg(feature = "embedded-svc")] +impl embedded_svc::mutex::MutexFamily for MutexFamily { + type Mutex = Mutex; +} + #[cfg(feature = "embedded-svc")] impl embedded_svc::mutex::Mutex for Mutex { type Data = T; diff --git a/src/mutex.rs b/src/mutex.rs index 2dc7ed8ef..76b30aff7 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -117,10 +117,7 @@ impl Condvar { Self(UnsafeCell::new(cond)) } - pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> - where - T: Send, - { + pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { let r = unsafe { pthread_cond_wait(self.0.get(), guard.0 .0.get()) }; debug_assert_eq!(r, 0); @@ -131,10 +128,7 @@ impl Condvar { &self, guard: MutexGuard<'a, T>, duration: Duration, - ) -> (MutexGuard<'a, T>, bool) - where - T: Send, - { + ) -> (MutexGuard<'a, T>, bool) { let abstime = timespec { tv_sec: duration.as_secs() as _, tv_nsec: duration.subsec_nanos() as _, @@ -175,12 +169,12 @@ impl Drop for Condvar { } #[cfg(feature = "embedded-svc")] -impl embedded_svc::mutex::Condvar for Condvar { - type Mutex - where - T: Send, - = Mutex; +impl embedded_svc::mutex::MutexFamily for Condvar { + type Mutex = Mutex; +} +#[cfg(feature = "embedded-svc")] +impl embedded_svc::mutex::Condvar for Condvar { #[inline(always)] fn new() -> Self { Condvar::new() @@ -188,25 +182,22 @@ impl embedded_svc::mutex::Condvar for Condvar { fn wait<'a, T>( &self, - guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, - ) -> <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a> - where - T: Send, + guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, + ) -> <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a> { Condvar::wait(self, guard) } fn wait_timeout<'a, T>( &self, - guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, + guard: <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, duration: Duration, ) -> ( - <::Mutex as embedded_svc::mutex::Mutex>::Guard<'a>, + <::Mutex as embedded_svc::mutex::Mutex>::Guard< + 'a, + >, bool, - ) - where - T: Send, - { + ) { Condvar::wait_timeout(self, guard, duration) }