#[cfg(not(single_queue))]
use core::cell::Cell;
use embassy_time_driver::Driver;
use esp_hal::{
Blocking,
interrupt::{InterruptHandler, Priority},
sync::Locked,
time::{Duration, Instant},
timer::{Error, OneShotTimer},
};
pub type Timer = OneShotTimer<'static, Blocking>;
#[derive(Clone, Copy)]
pub(crate) struct AlarmHandle {
id: usize,
}
impl AlarmHandle {
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()),
Initialized(&'static mut Timer),
}
impl AlarmState {
fn initialize(timer: &'static mut Timer, interrupt_handler: InterruptHandler) -> AlarmState {
timer.set_interrupt_handler(interrupt_handler);
timer.enable_interrupt(true);
AlarmState::Initialized(timer)
}
}
struct AlarmInner {
#[cfg(not(single_queue))]
pub context: Cell<*const ()>,
pub state: AlarmState,
}
struct Alarm {
pub inner: Locked<AlarmInner>,
}
unsafe impl Send for Alarm {}
impl Alarm {
pub const fn new(handler: extern "C" fn()) -> Self {
Self {
inner: Locked::new(AlarmInner {
#[cfg(not(single_queue))]
context: Cell::new(core::ptr::null_mut()),
state: AlarmState::Created(handler),
}),
}
}
}
pub(super) struct EmbassyTimer {
#[cfg(single_queue)]
pub(crate) inner: crate::timer_queue::TimerQueue,
alarms: [Alarm; MAX_SUPPORTED_ALARM_COUNT],
available_timers: Locked<Option<&'static mut [Timer]>>,
}
macro_rules! alarms {
($($idx:literal),*) => {
[$(
Alarm::new({
extern "C" fn handler() {
DRIVER.on_interrupt($idx);
}
handler
})
),*]
};
}
const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
#[cfg(single_queue)]
inner: crate::timer_queue::TimerQueue::new(Priority::max()),
alarms: alarms!(0, 1, 2, 3, 4, 5, 6),
available_timers: Locked::new(None),
});
impl EmbassyTimer {
pub(super) fn init(timers: &'static mut [Timer]) {
assert!(
timers.len() <= MAX_SUPPORTED_ALARM_COUNT,
"Maximum {} timers can be used.",
MAX_SUPPORTED_ALARM_COUNT
);
timers.iter_mut().for_each(|timer| {
timer.enable_interrupt(false);
timer.stop();
});
DRIVER.available_timers.with(|available_timers| {
assert!(
available_timers.is_none(),
"The timers have already been initialized."
);
*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.context.set(ctx.cast_mut());
})
}
fn on_interrupt(&self, id: usize) {
#[cfg_attr(single_queue, allow(clippy::let_unit_value))]
let _ctx = self.alarms[id].inner.with(|alarm| {
if let AlarmState::Initialized(timer) = &mut alarm.state {
timer.clear_interrupt();
#[cfg(not(single_queue))]
alarm.context.get()
} else {
unsafe {
core::hint::unreachable_unchecked()
}
}
});
#[cfg(all(integrated_timers, not(single_queue)))]
{
let executor = unsafe { &*_ctx.cast::<crate::executor::InnerExecutor>() };
executor.timer_queue.dispatch();
}
#[cfg(single_queue)]
self.inner.dispatch();
}
fn arm(timer: &mut Timer, timestamp: u64) -> bool {
let now = Instant::now().duration_since_epoch().as_micros();
if timestamp > now {
let mut timeout = Duration::from_micros(timestamp - now);
loop {
match timer.schedule(timeout) {
Ok(()) => break,
Err(Error::InvalidTimeout) => {
timeout = timeout / 2;
assert_ne!(timeout, Duration::ZERO);
}
other => unwrap!(other),
}
}
true
} else {
timer.stop();
false
}
}
pub(crate) unsafe fn allocate_alarm(&self, priority: Priority) -> Option<AlarmHandle> {
unsafe {
for (i, alarm) in self.alarms.iter().enumerate() {
let handle = alarm.inner.with(|alarm| {
let AlarmState::Created(interrupt_handler) = alarm.state else {
return None;
};
let timer = self.available_timers.with(|available_timers| {
if let Some(timers) = available_timers.take() {
let Some((timer, rest)) = timers.split_first_mut() else {
not_enough_timers();
};
*available_timers = Some(rest);
timer
} else {
panic!("schedule_wake called before esp_hal_embassy::init()")
}
});
alarm.state = AlarmState::initialize(
timer,
InterruptHandler::new(interrupt_handler, priority),
);
Some(AlarmHandle::new(i))
});
if handle.is_some() {
return handle;
}
}
None
}
}
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
let alarm = &self.alarms[alarm.id];
alarm.inner.with(|alarm| {
if let AlarmState::Initialized(timer) = &mut alarm.state {
Self::arm(timer, timestamp)
} else {
unsafe {
core::hint::unreachable_unchecked()
}
}
})
}
}
impl Driver for EmbassyTimer {
fn now(&self) -> u64 {
Instant::now().duration_since_epoch().as_micros()
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
#[cfg(not(single_queue))]
unsafe {
use embassy_executor::raw::Executor as RawExecutor;
use portable_atomic::{AtomicPtr, Ordering};
let task = embassy_executor::raw::task_from_waker(waker);
let mut executor = task.executor().unwrap_unchecked() as *const RawExecutor;
let owner = task
.timer_queue_item()
.payload
.as_ref::<AtomicPtr<RawExecutor>>();
let owner = owner.compare_exchange(
core::ptr::null_mut(),
executor.cast_mut(),
Ordering::AcqRel,
Ordering::Acquire,
);
if let Err(owner) = owner {
executor = owner;
};
let executor_addr = executor as usize;
let executor = core::ptr::with_exposed_provenance_mut::<crate::executor::InnerExecutor>(
executor_addr,
);
(*executor).timer_queue.schedule_wake(at, waker);
}
#[cfg(single_queue)]
self.inner.schedule_wake(at, waker);
}
}
#[cold]
#[track_caller]
fn not_enough_timers() -> ! {
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-integrated` or the `generic` timer queue flavors."
);
}
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
}