diff --git a/src/lib.rs b/src/lib.rs index 235f66d9f67..16ed0cd4e62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub mod httpd; #[cfg(feature = "alloc")] // TODO: Ideally should not need "alloc" (also for performance reasons) pub mod log; +pub mod misc; #[cfg(esp_idf_config_lwip_ipv4_napt)] pub mod napt; pub mod netif; @@ -43,6 +44,9 @@ pub mod nvs_storage; pub mod ota; pub mod ping; pub mod sysloop; +#[cfg(feature = "alloc")] +pub mod task; +pub mod time; #[cfg(feature = "alloc")] // TODO: Expose a subset which does not require "alloc" pub mod wifi; diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 00000000000..2ac0fb0396f --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,11 @@ +use esp_idf_sys::*; + +pub fn get_default_efuse_mac() -> Result<[u8; 6], EspError> { + let mut mac = [0; 6]; + unsafe { esp!(esp_efuse_mac_get_default(mac.as_mut_ptr()))? } + Ok(mac) +} + +pub fn restart() { + unsafe { esp_restart() }; +} diff --git a/src/private/cstr.rs b/src/private/cstr.rs index 50e2509442b..ddeda1d4c70 100644 --- a/src/private/cstr.rs +++ b/src/private/cstr.rs @@ -12,6 +12,7 @@ extern crate alloc; #[cfg(feature = "alloc")] pub fn set_str(buf: &mut [u8], s: &str) { + assert!(s.len() < buf.len()); let cs = CString::new(s).unwrap(); let ss: &[u8] = cs.as_bytes_with_nul(); buf[..ss.len()].copy_from_slice(ss); diff --git a/src/private/mod.rs b/src/private/mod.rs index 26ff2fafc46..cdd5f4eaa3a 100644 --- a/src/private/mod.rs +++ b/src/private/mod.rs @@ -1,6 +1,7 @@ pub mod common; pub mod cstr; pub mod net; +pub mod wait; mod stubs; diff --git a/src/private/wait.rs b/src/private/wait.rs new file mode 100644 index 00000000000..6608842f4ce --- /dev/null +++ b/src/private/wait.rs @@ -0,0 +1,97 @@ +use core::time::Duration; +#[cfg(feature = "std")] +use std::sync::{Condvar, Mutex}; + +use embedded_svc::mutex::Mutex as _; +#[cfg(not(feature = "std"))] +use esp_idf_sys::*; + +#[cfg(not(feature = "std"))] +use super::time::micros_since_boot; + +pub struct Waiter { + #[cfg(feature = "std")] + cvar: Condvar, + #[cfg(feature = "std")] + running: Mutex, + #[cfg(not(feature = "std"))] + running: EspMutex, +} + +impl Waiter { + pub fn new() -> Self { + Waiter { + #[cfg(feature = "std")] + cvar: Condvar::new(), + #[cfg(feature = "std")] + running: Mutex::new(false), + #[cfg(not(feature = "std"))] + running: EspMutex::new(false), + } + } + + pub fn start(&self) { + self.running.with_lock(|running| *running = true); + } + + #[cfg(feature = "std")] + pub fn wait(&self) { + if !self.running.with_lock(|running| *running) { + return; + } + + let _running = self + .cvar + .wait_while(self.running.lock().unwrap(), |running| *running) + .unwrap(); + } + + #[cfg(not(feature = "std"))] + pub fn wait(&self) { + while self.running.with_lock(|running| *running) { + unsafe { vTaskDelay(500) }; + } + } + + /// return = !timeout (= success) + #[cfg(feature = "std")] + pub fn wait_timeout(&self, dur: Duration) -> bool { + if !self.running.with_lock(|running| *running) { + return true; + } + + let (_running, res) = self + .cvar + .wait_timeout_while(self.running.lock().unwrap(), dur, |running| *running) + .unwrap(); + + return !res.timed_out(); + } + + /// return = !timeout (= success) + #[cfg(not(feature = "std"))] + pub fn wait_timeout(&self, dur: Duration) { + let now = micros_since_boot(); + let end = now + dur.as_micros(); + + while self.running.with_lock(|running| *running) { + if micros_since_boot() > end { + return false; + } + unsafe { vTaskDelay(500) }; + } + + return true; + } + + #[cfg(feature = "std")] + pub fn notify(&self) { + *self.running.lock().unwrap() = false; + self.cvar.notify_all(); + } + + #[cfg(not(feature = "std"))] + pub fn notify(&self) { + self.running.with_lock(|running| *running = false); + } +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 00000000000..6fd23a535e3 --- /dev/null +++ b/src/task.rs @@ -0,0 +1,130 @@ +use core::time::Duration; + +use log::*; + +use esp_idf_sys::c_types::*; +use esp_idf_sys::*; + +use crate::private::cstr::CString; + +#[allow(non_upper_case_globals)] +const pdPASS: c_int = 1; + +pub struct TaskHandle(TaskHandle_t); + +impl TaskHandle { + pub fn stop(&self) { + unsafe { vTaskDelete(self.0) }; + } +} + +struct TaskInternal { + name: String, + f: Box anyhow::Result<()>>, +} + +pub struct TaskConfig { + stack_size: u32, + priority: u32, +} + +impl Default for TaskConfig { + fn default() -> Self { + TaskConfig { + stack_size: DEFAULT_THREAD_STACKSIZE, + priority: DEFAULT_THREAD_PRIO, + } + } +} + +impl TaskConfig { + pub fn new(stack_size: u32, priority: u32) -> Self { + TaskConfig { + stack_size, + priority, + } + } + + pub fn stack_size(self, stack_size: u32) -> Self { + TaskConfig { stack_size, ..self } + } + + pub fn priority(self, priority: u32) -> Self { + TaskConfig { priority, ..self } + } + + pub fn spawn( + self, + name: impl AsRef, + f: F, + ) -> Result + where + F: FnOnce() -> anyhow::Result<()>, + F: Send + 'static { + let parameters = TaskInternal { + name: name.as_ref().to_string(), + f: Box::new(f), + }; + let parameters = Box::into_raw(Box::new(parameters)) as *mut _; + + info!("starting task {:?}", name.as_ref()); + + let name = CString::new(name.as_ref()).unwrap(); + let mut handle: TaskHandle_t = core::ptr::null_mut(); + let res = unsafe { + xTaskCreatePinnedToCore( + Some(esp_idf_svc_task), + name.as_ptr(), + self.stack_size, + parameters, + self.priority, + &mut handle, + tskNO_AFFINITY as i32, + ) + }; + if res != pdPASS { + return Err(EspError::from(ESP_ERR_NO_MEM as i32).unwrap().into()); + } + + Ok(TaskHandle(handle)) + } +} + +pub fn spawn( + name: impl AsRef, + f: F, +) -> Result +where + F: FnOnce() -> anyhow::Result<()>, + F: Send + 'static, +{ + TaskConfig::default().spawn(name, f) +} + +extern "C" fn esp_idf_svc_task(args: *mut c_void) { + let internal = unsafe { Box::from_raw(args as *mut TaskInternal) }; + + info!("started task {:?}", internal.name); + + match (internal.f)() { + Err(e) => { + panic!("unexpected error in task {:?}: {:?}", internal.name, e); + } + Ok(_) => {} + } + + info!("destroying task {:?}", internal.name); + + unsafe { vTaskDelete(core::ptr::null_mut() as _) }; +} + +#[allow(non_upper_case_globals)] +pub const TICK_PERIOD_MS: u32 = 1000 / configTICK_RATE_HZ; + +/// sleep tells FreeRTOS to put the current thread to sleep for at least the specified duration, +/// this is not an exact duration and can't be shorter than the rtos tick period. +pub fn sleep(duration: Duration) { + unsafe { + vTaskDelay(duration.as_millis() as u32 / TICK_PERIOD_MS); + } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 00000000000..8b0ba70bbe2 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,7 @@ +use core::convert::TryInto; + +use esp_idf_sys::*; + +pub fn micros_since_boot() -> u64 { + unsafe { esp_timer_get_time() }.try_into().unwrap() +}