esp-hal/esp-hal-embassy/src/time_driver.rs
Dániel Buga 1e6820d1a7
Systimer improvements (#2451)
* Do not read to set update bit

* Deduplicate

* Try to bind interrupts to the correct core

* Inline poll_count into read_count

* Clean up

* Make sure only a single update is done at a time

* Changelog

* Fix docs

* Correct the channel count

* Assign enough timers for HIL test

* Use a lock to prevent re-update

* Remove locking, use esp-idf implementation

* Document timer count requirement
2024-11-04 11:36:34 +00:00

195 lines
6.1 KiB
Rust

use core::cell::{Cell, RefCell};
use critical_section::Mutex;
use embassy_time_driver::{AlarmHandle, Driver};
use esp_hal::{
interrupt::{InterruptHandler, Priority},
prelude::*,
time::now,
timer::{AnyTimer, OneShotTimer},
};
pub const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
pub type Timer = OneShotTimer<'static, AnyTimer>;
static TIMERS: Mutex<RefCell<Option<&'static mut [Timer]>>> = Mutex::new(RefCell::new(None));
#[allow(clippy::type_complexity)]
struct AlarmState {
pub callback: Cell<Option<(fn(*mut ()), *mut ())>>,
pub allocated: Cell<bool>,
}
unsafe impl Send for AlarmState {}
impl AlarmState {
pub const fn new() -> Self {
Self {
callback: Cell::new(None),
allocated: Cell::new(false),
}
}
}
pub(super) struct EmbassyTimer {
alarms: Mutex<[AlarmState; MAX_SUPPORTED_ALARM_COUNT]>,
}
embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
alarms: Mutex::new([const { AlarmState::new() }; MAX_SUPPORTED_ALARM_COUNT]),
});
impl EmbassyTimer {
pub(super) fn init(timers: &'static mut [Timer]) {
if timers.len() > MAX_SUPPORTED_ALARM_COUNT {
panic!(
"Maximum of {} timers can be used.",
MAX_SUPPORTED_ALARM_COUNT
);
}
critical_section::with(|cs| {
timers.iter_mut().enumerate().for_each(|(n, timer)| {
timer.enable_interrupt(false);
timer.stop();
if DRIVER.alarms.borrow(cs)[n].allocated.get() {
// FIXME: we should track which core allocated an alarm and bind the interrupt
// to that core.
timer.set_interrupt_handler(HANDLERS[n]);
}
});
TIMERS.replace(cs, Some(timers));
});
}
fn on_interrupt(&self, id: usize) {
let cb = critical_section::with(|cs| {
let mut timers = TIMERS.borrow_ref_mut(cs);
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
let timer = &mut timers[id];
timer.clear_interrupt();
let alarm = &self.alarms.borrow(cs)[id];
alarm.callback.get()
});
if let Some((f, ctx)) = cb {
f(ctx);
}
}
fn arm(timer: &mut Timer, timestamp: u64) {
let now = now().duration_since_epoch();
let ts = timestamp.micros();
// if the TS is already in the past make the timer fire immediately
let timeout = if ts > now { ts - now } else { 0.micros() };
unwrap!(timer.schedule(timeout));
timer.enable_interrupt(true);
}
}
impl Driver for EmbassyTimer {
fn now(&self) -> u64 {
now().ticks()
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
critical_section::with(|cs| {
for (i, alarm) in self.alarms.borrow(cs).iter().enumerate() {
if alarm.allocated.get() {
continue;
}
let mut timer = TIMERS.borrow_ref_mut(cs);
// `allocate_alarm` may be called before `esp_hal_embassy::init()`, so
// we need to check if we have timers.
if let Some(timer) = &mut *timer {
// If we do, bind the interrupt handler to the timer.
// This ensures that alarms allocated after init are correctly bound to the
// core that created the executor.
let timer = unwrap!(
timer.get_mut(i),
"There are not enough timers to allocate a new alarm. Call `esp_hal_embassy::init()` with the correct number of timers."
);
timer.set_interrupt_handler(HANDLERS[i]);
}
// set alarm so it is not overwritten
alarm.allocated.set(true);
return Some(AlarmHandle::new(i as u8));
}
None
})
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
let n = alarm.id() as usize;
critical_section::with(|cs| {
let alarm = &self.alarms.borrow(cs)[n];
alarm.callback.set(Some((callback, ctx)));
})
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
// 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;
}
// 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
// critical section.
//
// This is correct behavior. See https://docs.rs/embassy-time-driver/0.1.0/embassy_time_driver/trait.Driver.html#tymethod.set_alarm
// (... the driver should return true and arrange to call the alarm callback as
// soon as possible, but not synchronously.)
critical_section::with(|cs| {
let mut timers = TIMERS.borrow_ref_mut(cs);
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
let timer = &mut timers[alarm.id() as usize];
Self::arm(timer, timestamp);
});
true
}
}
static HANDLERS: [InterruptHandler; MAX_SUPPORTED_ALARM_COUNT] = [
handler0, handler1, handler2, handler3, handler4, handler5, handler6,
];
#[handler(priority = Priority::max())]
fn handler0() {
DRIVER.on_interrupt(0);
}
#[handler(priority = Priority::max())]
fn handler1() {
DRIVER.on_interrupt(1);
}
#[handler(priority = Priority::max())]
fn handler2() {
DRIVER.on_interrupt(2);
}
#[handler(priority = Priority::max())]
fn handler3() {
DRIVER.on_interrupt(3);
}
#[handler(priority = Priority::max())]
fn handler4() {
DRIVER.on_interrupt(4);
}
#[handler(priority = Priority::max())]
fn handler5() {
DRIVER.on_interrupt(5);
}
#[handler(priority = Priority::max())]
fn handler6() {
DRIVER.on_interrupt(6);
}