Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: double reset detector #16

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,7 +43,12 @@ pub mod nvs_storage;
))]
pub mod ota;
pub mod ping;
#[cfg(feature = "alloc")]
pub mod reset;
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;

Expand Down
11 changes: 11 additions & 0 deletions src/misc.rs
Original file line number Diff line number Diff line change
@@ -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() };
}
1 change: 1 addition & 0 deletions src/private/cstr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/private/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod common;
pub mod cstr;
pub mod net;
pub mod wait;

mod stubs;

Expand Down
97 changes: 97 additions & 0 deletions src/private/wait.rs
Original file line number Diff line number Diff line change
@@ -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<bool>,
#[cfg(not(feature = "std"))]
running: EspMutex<bool>,
}

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);
}
}
104 changes: 104 additions & 0 deletions src/reset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use alloc::sync::Arc;
use core::time::Duration;

use log::*;
use mutex_trait::Mutex;

use esp_idf_sys::*;

use crate::nvs::EspDefaultNvs;
use crate::task;
use crate::time::micros_since_boot;

const FLAG_SET: u16 = 0x1234;
const FLAG_CLEAR: u16 = 0x4321;
const FLAG_KEY: &str = "drd_flag";

static mut DOUBLE_RESET: EspMutex<Option<bool>> = EspMutex::new(None);

pub fn detect_double_reset(nvs: Arc<EspDefaultNvs>, timeout: Duration) -> Result<bool, IdfError> {
unsafe {
DOUBLE_RESET.lock(move |v| {
if let Some(double_reset) = v {
Ok(*double_reset)
} else {
let mut drd = DoubleResetDetector::new(nvs.clone(), timeout);

let double_reset = drd.detect()?;
if double_reset {
info!("detected double reset");
drd.stop()?;
} else {
task::spawn("drd", move || {
task::sleep(timeout - Duration::from_micros(micros_since_boot()));
drd.stop()?;
Ok(())
})?;
}

*v = Some(double_reset);
Ok(double_reset)
}
})
}
}

pub struct DoubleResetDetector {
// task_handle: Option<TaskHandle_t>,
nvs: Arc<EspDefaultNvs>,
timeout: Duration,
waiting_for_double_reset: bool,
}

impl DoubleResetDetector {
pub fn new(nvs: Arc<EspDefaultNvs>, timeout: Duration) -> Self {
DoubleResetDetector {
// task_handle: None,
nvs,
timeout,
waiting_for_double_reset: false,
}
}

fn start(&mut self) -> Result<(), EspError> {
self.waiting_for_double_reset = true;
self.set_flag()?;
Ok(())
}

pub fn stop(&mut self) -> Result<(), EspError> {
info!("stopping double reset detector");
self.waiting_for_double_reset = false;
self.clear_flag()?;
Ok(())
}

fn detect(&mut self) -> Result<bool, EspError> {
let flag = self.has_flag()?;
self.start()?;
Ok(flag)
}

fn clear_flag(&mut self) -> Result<(), EspError> {
let mut handle = self.nvs.open("esp_idf_svc", true)?;
handle.set_u16(FLAG_KEY, FLAG_CLEAR)
}

fn set_flag(&mut self) -> Result<(), EspError> {
let mut handle = self.nvs.open("esp_idf_svc", true)?;
handle.set_u16(FLAG_KEY, FLAG_SET)
}

fn has_flag(&self) -> Result<bool, EspError> {
let handle = self.nvs.open("esp_idf_svc", true)?;
let res = handle.get_u16(FLAG_KEY)?.map_or(false, |f| f == FLAG_SET);
Ok(res)
}

pub fn run(&mut self) -> Result<(), EspError> {
if self.waiting_for_double_reset && micros_since_boot() > self.timeout.as_micros() as u64 {
self.stop()?;
}
Ok(())
}
}
130 changes: 130 additions & 0 deletions src/task.rs
Original file line number Diff line number Diff line change
@@ -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<dyn FnOnce() -> 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<F>(
self,
name: impl AsRef<str>,
f: F,
) -> Result<TaskHandle, IdfError>
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<F>(
name: impl AsRef<str>,
f: F,
) -> Result<TaskHandle, IdfError>
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);
}
}
7 changes: 7 additions & 0 deletions src/time.rs
Original file line number Diff line number Diff line change
@@ -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()
}