Merge pull request #3593 from bugadani/refactor

Rework time-driver contract.
This commit is contained in:
Dario Nieuwenhuis
2024-12-16 12:30:30 +00:00
committed by GitHub
97 changed files with 1014 additions and 1630 deletions

View File

@@ -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"] }

View File

@@ -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
})
}
}