Merge pull request #3593 from bugadani/refactor
Rework time-driver contract.
This commit is contained in:
@@ -119,7 +119,7 @@ _nrf52 = ["_ppi"]
|
||||
_nrf51 = ["_ppi"]
|
||||
_nrf91 = []
|
||||
|
||||
_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768"]
|
||||
_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-driver"]
|
||||
|
||||
# trustzone state.
|
||||
_s = []
|
||||
@@ -135,6 +135,7 @@ _nrf52832_anomaly_109 = []
|
||||
|
||||
[dependencies]
|
||||
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
|
||||
embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true }
|
||||
embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
|
||||
embassy-sync = { version = "0.6.1", path = "../embassy-sync" }
|
||||
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use core::cell::Cell;
|
||||
use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering};
|
||||
use core::{mem, ptr};
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
||||
|
||||
use critical_section::CriticalSection;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex;
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
use embassy_time_driver::Driver;
|
||||
use embassy_time_queue_driver::Queue;
|
||||
|
||||
use crate::interrupt::InterruptExt;
|
||||
use crate::{interrupt, pac};
|
||||
@@ -94,11 +94,6 @@ mod test {
|
||||
|
||||
struct AlarmState {
|
||||
timestamp: Cell<u64>,
|
||||
|
||||
// This is really a Option<(fn(*mut ()), *mut ())>
|
||||
// but fn pointers aren't allowed in const yet
|
||||
callback: Cell<*const ()>,
|
||||
ctx: Cell<*mut ()>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
@@ -107,26 +102,22 @@ impl AlarmState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
timestamp: Cell::new(u64::MAX),
|
||||
callback: Cell::new(ptr::null()),
|
||||
ctx: Cell::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ALARM_COUNT: usize = 3;
|
||||
|
||||
struct RtcDriver {
|
||||
/// Number of 2^23 periods elapsed since boot.
|
||||
period: AtomicU32,
|
||||
alarm_count: AtomicU8,
|
||||
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
|
||||
alarms: Mutex<[AlarmState; ALARM_COUNT]>,
|
||||
alarms: Mutex<AlarmState>,
|
||||
queue: Mutex<RefCell<Queue>>,
|
||||
}
|
||||
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
|
||||
period: AtomicU32::new(0),
|
||||
alarm_count: AtomicU8::new(0),
|
||||
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [const {AlarmState::new()}; ALARM_COUNT]),
|
||||
alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
|
||||
queue: Mutex::new(RefCell::new(Queue::new())),
|
||||
});
|
||||
|
||||
impl RtcDriver {
|
||||
@@ -169,13 +160,12 @@ impl RtcDriver {
|
||||
self.next_period();
|
||||
}
|
||||
|
||||
for n in 0..ALARM_COUNT {
|
||||
if r.events_compare(n).read() == 1 {
|
||||
r.events_compare(n).write_value(0);
|
||||
critical_section::with(|cs| {
|
||||
self.trigger_alarm(n, cs);
|
||||
})
|
||||
}
|
||||
let n = 0;
|
||||
if r.events_compare(n).read() == 1 {
|
||||
r.events_compare(n).write_value(0);
|
||||
critical_section::with(|cs| {
|
||||
self.trigger_alarm(cs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,38 +176,80 @@ impl RtcDriver {
|
||||
self.period.store(period, Ordering::Relaxed);
|
||||
let t = (period as u64) << 23;
|
||||
|
||||
for n in 0..ALARM_COUNT {
|
||||
let alarm = &self.alarms.borrow(cs)[n];
|
||||
let at = alarm.timestamp.get();
|
||||
let n = 0;
|
||||
let alarm = &self.alarms.borrow(cs);
|
||||
let at = alarm.timestamp.get();
|
||||
|
||||
if at < t + 0xc00000 {
|
||||
// just enable it. `set_alarm` has already set the correct CC val.
|
||||
r.intenset().write(|w| w.0 = compare_n(n));
|
||||
}
|
||||
if at < t + 0xc00000 {
|
||||
// just enable it. `set_alarm` has already set the correct CC val.
|
||||
r.intenset().write(|w| w.0 = compare_n(n));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState {
|
||||
// safety: we're allowed to assume the AlarmState is created by us, and
|
||||
// we never create one that's out of bounds.
|
||||
unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) }
|
||||
}
|
||||
|
||||
fn trigger_alarm(&self, n: usize, cs: CriticalSection) {
|
||||
fn trigger_alarm(&self, cs: CriticalSection) {
|
||||
let n = 0;
|
||||
let r = rtc();
|
||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||
|
||||
let alarm = &self.alarms.borrow(cs)[n];
|
||||
let alarm = &self.alarms.borrow(cs);
|
||||
alarm.timestamp.set(u64::MAX);
|
||||
|
||||
// Call after clearing alarm, so the callback can set another alarm.
|
||||
let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||
while !self.set_alarm(cs, next) {
|
||||
next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
|
||||
}
|
||||
}
|
||||
|
||||
// safety:
|
||||
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
|
||||
// - other than that we only store valid function pointers into alarm.callback
|
||||
let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) };
|
||||
f(alarm.ctx.get());
|
||||
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
|
||||
let n = 0;
|
||||
let alarm = &self.alarms.borrow(cs);
|
||||
alarm.timestamp.set(timestamp);
|
||||
|
||||
let r = rtc();
|
||||
|
||||
let t = self.now();
|
||||
if timestamp <= t {
|
||||
// If alarm timestamp has passed the alarm will not fire.
|
||||
// Disarm the alarm and return `false` to indicate that.
|
||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||
|
||||
alarm.timestamp.set(u64::MAX);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it hasn't triggered yet, setup it in the compare channel.
|
||||
|
||||
// Write the CC value regardless of whether we're going to enable it now or not.
|
||||
// This way, when we enable it later, the right value is already set.
|
||||
|
||||
// nrf52 docs say:
|
||||
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
|
||||
// To workaround this, we never write a timestamp smaller than N+3.
|
||||
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
|
||||
//
|
||||
// It is impossible for rtc to tick more than once because
|
||||
// - this code takes less time than 1 tick
|
||||
// - it runs with interrupts disabled so nothing else can preempt it.
|
||||
//
|
||||
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
||||
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
||||
// and we don't do that here.
|
||||
let safe_timestamp = timestamp.max(t + 3);
|
||||
r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF));
|
||||
|
||||
let diff = timestamp - t;
|
||||
if diff < 0xc00000 {
|
||||
r.intenset().write(|w| w.0 = compare_n(n));
|
||||
} else {
|
||||
// If it's too far in the future, don't setup the compare channel yet.
|
||||
// It will be setup later by `next_period`.
|
||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,76 +262,16 @@ impl Driver for RtcDriver {
|
||||
calc_now(period, counter)
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
critical_section::with(|_| {
|
||||
let id = self.alarm_count.load(Ordering::Relaxed);
|
||||
if id < ALARM_COUNT as u8 {
|
||||
self.alarm_count.store(id + 1, Ordering::Relaxed);
|
||||
Some(AlarmHandle::new(id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||
critical_section::with(|cs| {
|
||||
let alarm = self.get_alarm(cs, alarm);
|
||||
let mut queue = self.queue.borrow(cs).borrow_mut();
|
||||
|
||||
alarm.callback.set(callback as *const ());
|
||||
alarm.ctx.set(ctx);
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
critical_section::with(|cs| {
|
||||
let n = alarm.id() as _;
|
||||
let alarm = self.get_alarm(cs, alarm);
|
||||
alarm.timestamp.set(timestamp);
|
||||
|
||||
let r = rtc();
|
||||
|
||||
let t = self.now();
|
||||
if timestamp <= t {
|
||||
// If alarm timestamp has passed the alarm will not fire.
|
||||
// Disarm the alarm and return `false` to indicate that.
|
||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||
|
||||
alarm.timestamp.set(u64::MAX);
|
||||
|
||||
return false;
|
||||
if queue.schedule_wake(at, waker) {
|
||||
let mut next = queue.next_expiration(self.now());
|
||||
while !self.set_alarm(cs, next) {
|
||||
next = queue.next_expiration(self.now());
|
||||
}
|
||||
}
|
||||
|
||||
// If it hasn't triggered yet, setup it in the compare channel.
|
||||
|
||||
// Write the CC value regardless of whether we're going to enable it now or not.
|
||||
// This way, when we enable it later, the right value is already set.
|
||||
|
||||
// nrf52 docs say:
|
||||
// If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event.
|
||||
// To workaround this, we never write a timestamp smaller than N+3.
|
||||
// N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc.
|
||||
//
|
||||
// It is impossible for rtc to tick more than once because
|
||||
// - this code takes less time than 1 tick
|
||||
// - it runs with interrupts disabled so nothing else can preempt it.
|
||||
//
|
||||
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
||||
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
||||
// and we don't do that here.
|
||||
let safe_timestamp = timestamp.max(t + 3);
|
||||
r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF));
|
||||
|
||||
let diff = timestamp - t;
|
||||
if diff < 0xc00000 {
|
||||
r.intenset().write(|w| w.0 = compare_n(n));
|
||||
} else {
|
||||
// If it's too far in the future, don't setup the compare channel yet.
|
||||
// It will be setup later by `next_period`.
|
||||
r.intenclr().write(|w| w.0 = compare_n(n));
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user