diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-12-16 12:30:30 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-12-16 12:30:30 +0000 |
| commit | 2c3bc75da6008afa7cacc1045954cef7e3d8740f (patch) | |
| tree | 47661322d49d3e38717e2fc3f38e920c222138f7 /embassy-nrf | |
| parent | 99ad61cecf4fe098feeced5524d3e60625137457 (diff) | |
| parent | e1c00613288024623f7fde61f65c4c40c9a5381a (diff) | |
Merge pull request #3593 from bugadani/refactor
Rework time-driver contract.
Diffstat (limited to 'embassy-nrf')
| -rw-r--r-- | embassy-nrf/Cargo.toml | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/time_driver.rs | 194 |
2 files changed, 85 insertions, 112 deletions
diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 9da050a22..48f80bb5e 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml | |||
| @@ -119,7 +119,7 @@ _nrf52 = ["_ppi"] | |||
| 119 | _nrf51 = ["_ppi"] | 119 | _nrf51 = ["_ppi"] |
| 120 | _nrf91 = [] | 120 | _nrf91 = [] |
| 121 | 121 | ||
| 122 | _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768"] | 122 | _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-driver"] |
| 123 | 123 | ||
| 124 | # trustzone state. | 124 | # trustzone state. |
| 125 | _s = [] | 125 | _s = [] |
| @@ -135,6 +135,7 @@ _nrf52832_anomaly_109 = [] | |||
| 135 | 135 | ||
| 136 | [dependencies] | 136 | [dependencies] |
| 137 | embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } | 137 | embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } |
| 138 | embassy-time-queue-driver = { version = "0.1", path = "../embassy-time-queue-driver", optional = true } | ||
| 138 | embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } | 139 | embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } |
| 139 | embassy-sync = { version = "0.6.1", path = "../embassy-sync" } | 140 | embassy-sync = { version = "0.6.1", path = "../embassy-sync" } |
| 140 | embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } | 141 | embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } |
diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 9ba38ec1b..a27fae9a8 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs | |||
| @@ -1,11 +1,11 @@ | |||
| 1 | use core::cell::Cell; | 1 | use core::cell::{Cell, RefCell}; |
| 2 | use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; | 2 | use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; |
| 3 | use core::{mem, ptr}; | ||
| 4 | 3 | ||
| 5 | use critical_section::CriticalSection; | 4 | use critical_section::CriticalSection; |
| 6 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | 5 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; |
| 7 | use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; | 6 | use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; |
| 8 | use embassy_time_driver::{AlarmHandle, Driver}; | 7 | use embassy_time_driver::Driver; |
| 8 | use embassy_time_queue_driver::Queue; | ||
| 9 | 9 | ||
| 10 | use crate::interrupt::InterruptExt; | 10 | use crate::interrupt::InterruptExt; |
| 11 | use crate::{interrupt, pac}; | 11 | use crate::{interrupt, pac}; |
| @@ -94,11 +94,6 @@ mod test { | |||
| 94 | 94 | ||
| 95 | struct AlarmState { | 95 | struct AlarmState { |
| 96 | timestamp: Cell<u64>, | 96 | timestamp: Cell<u64>, |
| 97 | |||
| 98 | // This is really a Option<(fn(*mut ()), *mut ())> | ||
| 99 | // but fn pointers aren't allowed in const yet | ||
| 100 | callback: Cell<*const ()>, | ||
| 101 | ctx: Cell<*mut ()>, | ||
| 102 | } | 97 | } |
| 103 | 98 | ||
| 104 | unsafe impl Send for AlarmState {} | 99 | unsafe impl Send for AlarmState {} |
| @@ -107,26 +102,22 @@ impl AlarmState { | |||
| 107 | const fn new() -> Self { | 102 | const fn new() -> Self { |
| 108 | Self { | 103 | Self { |
| 109 | timestamp: Cell::new(u64::MAX), | 104 | timestamp: Cell::new(u64::MAX), |
| 110 | callback: Cell::new(ptr::null()), | ||
| 111 | ctx: Cell::new(ptr::null_mut()), | ||
| 112 | } | 105 | } |
| 113 | } | 106 | } |
| 114 | } | 107 | } |
| 115 | 108 | ||
| 116 | const ALARM_COUNT: usize = 3; | ||
| 117 | |||
| 118 | struct RtcDriver { | 109 | struct RtcDriver { |
| 119 | /// Number of 2^23 periods elapsed since boot. | 110 | /// Number of 2^23 periods elapsed since boot. |
| 120 | period: AtomicU32, | 111 | period: AtomicU32, |
| 121 | alarm_count: AtomicU8, | ||
| 122 | /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. | 112 | /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. |
| 123 | alarms: Mutex<[AlarmState; ALARM_COUNT]>, | 113 | alarms: Mutex<AlarmState>, |
| 114 | queue: Mutex<RefCell<Queue>>, | ||
| 124 | } | 115 | } |
| 125 | 116 | ||
| 126 | embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { | 117 | embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { |
| 127 | period: AtomicU32::new(0), | 118 | period: AtomicU32::new(0), |
| 128 | alarm_count: AtomicU8::new(0), | 119 | alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), |
| 129 | alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [const {AlarmState::new()}; ALARM_COUNT]), | 120 | queue: Mutex::new(RefCell::new(Queue::new())), |
| 130 | }); | 121 | }); |
| 131 | 122 | ||
| 132 | impl RtcDriver { | 123 | impl RtcDriver { |
| @@ -169,13 +160,12 @@ impl RtcDriver { | |||
| 169 | self.next_period(); | 160 | self.next_period(); |
| 170 | } | 161 | } |
| 171 | 162 | ||
| 172 | for n in 0..ALARM_COUNT { | 163 | let n = 0; |
| 173 | if r.events_compare(n).read() == 1 { | 164 | if r.events_compare(n).read() == 1 { |
| 174 | r.events_compare(n).write_value(0); | 165 | r.events_compare(n).write_value(0); |
| 175 | critical_section::with(|cs| { | 166 | critical_section::with(|cs| { |
| 176 | self.trigger_alarm(n, cs); | 167 | self.trigger_alarm(cs); |
| 177 | }) | 168 | }); |
| 178 | } | ||
| 179 | } | 169 | } |
| 180 | } | 170 | } |
| 181 | 171 | ||
| @@ -186,38 +176,80 @@ impl RtcDriver { | |||
| 186 | self.period.store(period, Ordering::Relaxed); | 176 | self.period.store(period, Ordering::Relaxed); |
| 187 | let t = (period as u64) << 23; | 177 | let t = (period as u64) << 23; |
| 188 | 178 | ||
| 189 | for n in 0..ALARM_COUNT { | 179 | let n = 0; |
| 190 | let alarm = &self.alarms.borrow(cs)[n]; | 180 | let alarm = &self.alarms.borrow(cs); |
| 191 | let at = alarm.timestamp.get(); | 181 | let at = alarm.timestamp.get(); |
| 192 | 182 | ||
| 193 | if at < t + 0xc00000 { | 183 | if at < t + 0xc00000 { |
| 194 | // just enable it. `set_alarm` has already set the correct CC val. | 184 | // just enable it. `set_alarm` has already set the correct CC val. |
| 195 | r.intenset().write(|w| w.0 = compare_n(n)); | 185 | r.intenset().write(|w| w.0 = compare_n(n)); |
| 196 | } | ||
| 197 | } | 186 | } |
| 198 | }) | 187 | }) |
| 199 | } | 188 | } |
| 200 | 189 | ||
| 201 | fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { | 190 | fn trigger_alarm(&self, cs: CriticalSection) { |
| 202 | // safety: we're allowed to assume the AlarmState is created by us, and | 191 | let n = 0; |
| 203 | // we never create one that's out of bounds. | ||
| 204 | unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } | ||
| 205 | } | ||
| 206 | |||
| 207 | fn trigger_alarm(&self, n: usize, cs: CriticalSection) { | ||
| 208 | let r = rtc(); | 192 | let r = rtc(); |
| 209 | r.intenclr().write(|w| w.0 = compare_n(n)); | 193 | r.intenclr().write(|w| w.0 = compare_n(n)); |
| 210 | 194 | ||
| 211 | let alarm = &self.alarms.borrow(cs)[n]; | 195 | let alarm = &self.alarms.borrow(cs); |
| 212 | alarm.timestamp.set(u64::MAX); | 196 | alarm.timestamp.set(u64::MAX); |
| 213 | 197 | ||
| 214 | // Call after clearing alarm, so the callback can set another alarm. | 198 | // Call after clearing alarm, so the callback can set another alarm. |
| 199 | let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); | ||
| 200 | while !self.set_alarm(cs, next) { | ||
| 201 | next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { | ||
| 206 | let n = 0; | ||
| 207 | let alarm = &self.alarms.borrow(cs); | ||
| 208 | alarm.timestamp.set(timestamp); | ||
| 209 | |||
| 210 | let r = rtc(); | ||
| 211 | |||
| 212 | let t = self.now(); | ||
| 213 | if timestamp <= t { | ||
| 214 | // If alarm timestamp has passed the alarm will not fire. | ||
| 215 | // Disarm the alarm and return `false` to indicate that. | ||
| 216 | r.intenclr().write(|w| w.0 = compare_n(n)); | ||
| 217 | |||
| 218 | alarm.timestamp.set(u64::MAX); | ||
| 219 | |||
| 220 | return false; | ||
| 221 | } | ||
| 222 | |||
| 223 | // If it hasn't triggered yet, setup it in the compare channel. | ||
| 224 | |||
| 225 | // Write the CC value regardless of whether we're going to enable it now or not. | ||
| 226 | // This way, when we enable it later, the right value is already set. | ||
| 227 | |||
| 228 | // nrf52 docs say: | ||
| 229 | // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event. | ||
| 230 | // To workaround this, we never write a timestamp smaller than N+3. | ||
| 231 | // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. | ||
| 232 | // | ||
| 233 | // It is impossible for rtc to tick more than once because | ||
| 234 | // - this code takes less time than 1 tick | ||
| 235 | // - it runs with interrupts disabled so nothing else can preempt it. | ||
| 236 | // | ||
| 237 | // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed | ||
| 238 | // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, | ||
| 239 | // and we don't do that here. | ||
| 240 | let safe_timestamp = timestamp.max(t + 3); | ||
| 241 | r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); | ||
| 242 | |||
| 243 | let diff = timestamp - t; | ||
| 244 | if diff < 0xc00000 { | ||
| 245 | r.intenset().write(|w| w.0 = compare_n(n)); | ||
| 246 | } else { | ||
| 247 | // If it's too far in the future, don't setup the compare channel yet. | ||
| 248 | // It will be setup later by `next_period`. | ||
| 249 | r.intenclr().write(|w| w.0 = compare_n(n)); | ||
| 250 | } | ||
| 215 | 251 | ||
| 216 | // safety: | 252 | true |
| 217 | // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. | ||
| 218 | // - other than that we only store valid function pointers into alarm.callback | ||
| 219 | let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; | ||
| 220 | f(alarm.ctx.get()); | ||
| 221 | } | 253 | } |
| 222 | } | 254 | } |
| 223 | 255 | ||
| @@ -230,76 +262,16 @@ impl Driver for RtcDriver { | |||
| 230 | calc_now(period, counter) | 262 | calc_now(period, counter) |
| 231 | } | 263 | } |
| 232 | 264 | ||
| 233 | unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { | 265 | fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { |
| 234 | critical_section::with(|_| { | ||
| 235 | let id = self.alarm_count.load(Ordering::Relaxed); | ||
| 236 | if id < ALARM_COUNT as u8 { | ||
| 237 | self.alarm_count.store(id + 1, Ordering::Relaxed); | ||
| 238 | Some(AlarmHandle::new(id)) | ||
| 239 | } else { | ||
| 240 | None | ||
| 241 | } | ||
| 242 | }) | ||
| 243 | } | ||
| 244 | |||
| 245 | fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { | ||
| 246 | critical_section::with(|cs| { | ||
| 247 | let alarm = self.get_alarm(cs, alarm); | ||
| 248 | |||
| 249 | alarm.callback.set(callback as *const ()); | ||
| 250 | alarm.ctx.set(ctx); | ||
| 251 | }) | ||
| 252 | } | ||
| 253 | |||
| 254 | fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { | ||
| 255 | critical_section::with(|cs| { | 266 | critical_section::with(|cs| { |
| 256 | let n = alarm.id() as _; | 267 | let mut queue = self.queue.borrow(cs).borrow_mut(); |
| 257 | let alarm = self.get_alarm(cs, alarm); | ||
| 258 | alarm.timestamp.set(timestamp); | ||
| 259 | 268 | ||
| 260 | let r = rtc(); | 269 | if queue.schedule_wake(at, waker) { |
| 261 | 270 | let mut next = queue.next_expiration(self.now()); | |
| 262 | let t = self.now(); | 271 | while !self.set_alarm(cs, next) { |
| 263 | if timestamp <= t { | 272 | next = queue.next_expiration(self.now()); |
| 264 | // If alarm timestamp has passed the alarm will not fire. | 273 | } |
| 265 | // Disarm the alarm and return `false` to indicate that. | ||
| 266 | r.intenclr().write(|w| w.0 = compare_n(n)); | ||
| 267 | |||
| 268 | alarm.timestamp.set(u64::MAX); | ||
| 269 | |||
| 270 | return false; | ||
| 271 | } | ||
| 272 | |||
| 273 | // If it hasn't triggered yet, setup it in the compare channel. | ||
| 274 | |||
| 275 | // Write the CC value regardless of whether we're going to enable it now or not. | ||
| 276 | // This way, when we enable it later, the right value is already set. | ||
| 277 | |||
| 278 | // nrf52 docs say: | ||
| 279 | // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event. | ||
| 280 | // To workaround this, we never write a timestamp smaller than N+3. | ||
| 281 | // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. | ||
| 282 | // | ||
| 283 | // It is impossible for rtc to tick more than once because | ||
| 284 | // - this code takes less time than 1 tick | ||
| 285 | // - it runs with interrupts disabled so nothing else can preempt it. | ||
| 286 | // | ||
| 287 | // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed | ||
| 288 | // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, | ||
| 289 | // and we don't do that here. | ||
| 290 | let safe_timestamp = timestamp.max(t + 3); | ||
| 291 | r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); | ||
| 292 | |||
| 293 | let diff = timestamp - t; | ||
| 294 | if diff < 0xc00000 { | ||
| 295 | r.intenset().write(|w| w.0 = compare_n(n)); | ||
| 296 | } else { | ||
| 297 | // If it's too far in the future, don't setup the compare channel yet. | ||
| 298 | // It will be setup later by `next_period`. | ||
| 299 | r.intenclr().write(|w| w.0 = compare_n(n)); | ||
| 300 | } | 274 | } |
| 301 | |||
| 302 | true | ||
| 303 | }) | 275 | }) |
| 304 | } | 276 | } |
| 305 | } | 277 | } |
