diff options
| -rw-r--r-- | embassy-nrf/src/time_driver.rs | 63 | ||||
| -rw-r--r-- | embassy/src/time/driver.rs | 11 |
2 files changed, 54 insertions, 20 deletions
diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 4240b9ac4..a32a7bc7c 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs | |||
| @@ -14,24 +14,51 @@ fn rtc() -> &'static pac::rtc0::RegisterBlock { | |||
| 14 | unsafe { &*pac::RTC1::ptr() } | 14 | unsafe { &*pac::RTC1::ptr() } |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | // RTC timekeeping works with something we call "periods", which are time intervals | 17 | /// Calculate the timestamp from the period count and the tick count. |
| 18 | // of 2^23 ticks. The RTC counter value is 24 bits, so one "overflow cycle" is 2 periods. | 18 | /// |
| 19 | // | 19 | /// The RTC counter is 24 bit. Ticking at 32768hz, it overflows every ~8 minutes. This is |
| 20 | // A `period` count is maintained in parallel to the RTC hardware `counter`, like this: | 20 | /// too short. We must make it "never" overflow. |
| 21 | // - `period` and `counter` start at 0 | 21 | /// |
| 22 | // - `period` is incremented on overflow (at counter value 0) | 22 | /// The obvious way would be to count overflow periods. Every time the counter overflows, |
| 23 | // - `period` is incremented "midway" between overflows (at counter value 0x800000) | 23 | /// increase a `periods` variable. `now()` simply does `periods << 24 + counter`. So, the logic |
| 24 | // | 24 | /// around an overflow would look like this: |
| 25 | // Therefore, when `period` is even, counter is in 0..0x7fffff. When odd, counter is in 0x800000..0xFFFFFF | 25 | /// |
| 26 | // This allows for now() to return the correct value even if it races an overflow. | 26 | /// ```not_rust |
| 27 | // | 27 | /// periods = 1, counter = 0xFF_FFFE --> now = 0x1FF_FFFE |
| 28 | // To get `now()`, `period` is read first, then `counter` is read. If the counter value matches | 28 | /// periods = 1, counter = 0xFF_FFFF --> now = 0x1FF_FFFF |
| 29 | // the expected range for the `period` parity, we're done. If it doesn't, this means that | 29 | /// **OVERFLOW** |
| 30 | // a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value | 30 | /// periods = 2, counter = 0x00_0000 --> now = 0x200_0000 |
| 31 | // corresponds to the next period. | 31 | /// periods = 2, counter = 0x00_0001 --> now = 0x200_0001 |
| 32 | // | 32 | /// ``` |
| 33 | // `period` is a 32bit integer, so It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 years. | 33 | /// |
| 34 | 34 | /// The problem is this is vulnerable to race conditions if `now()` runs at the exact time an | |
| 35 | /// overflow happens. | ||
| 36 | /// | ||
| 37 | /// If `now()` reads `periods` first and `counter` later, and overflow happens between the reads, | ||
| 38 | /// it would return a wrong value: | ||
| 39 | /// | ||
| 40 | /// ```not_rust | ||
| 41 | /// periods = 1 (OLD), counter = 0x00_0000 (NEW) --> now = 0x100_0000 -> WRONG | ||
| 42 | /// ``` | ||
| 43 | /// | ||
| 44 | /// It fails similarly if it reads `counter` first and `periods` second. | ||
| 45 | /// | ||
| 46 | /// To fix this, we define a "period" to be 2^23 ticks (instead of 2^24). One "overflow cycle" is 2 periods. | ||
| 47 | /// | ||
| 48 | /// - `period` is incremented on overflow (at counter value 0) | ||
| 49 | /// - `period` is incremented "midway" between overflows (at counter value 0x80_0000) | ||
| 50 | /// | ||
| 51 | /// Therefore, when `period` is even, counter is in 0..0x7f_ffff. When odd, counter is in 0x80_0000..0xFF_FFFF | ||
| 52 | /// This allows for now() to return the correct value even if it races an overflow. | ||
| 53 | /// | ||
| 54 | /// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches | ||
| 55 | /// the expected range for the `period` parity, we're done. If it doesn't, this means that | ||
| 56 | /// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value | ||
| 57 | /// corresponds to the next period. | ||
| 58 | /// | ||
| 59 | /// `period` is a 32bit integer, so It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 | ||
| 60 | /// years. For comparison, flash memory like the one containing your firmware is usually rated to retain | ||
| 61 | /// data for only 10-20 years. 34865 years is long enough! | ||
| 35 | fn calc_now(period: u32, counter: u32) -> u64 { | 62 | fn calc_now(period: u32, counter: u32) -> u64 { |
| 36 | ((period as u64) << 23) + ((counter ^ ((period & 1) << 23)) as u64) | 63 | ((period as u64) << 23) + ((counter ^ ((period & 1) << 23)) as u64) |
| 37 | } | 64 | } |
diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs index a21a29d46..29256aab5 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs | |||
| @@ -80,8 +80,15 @@ impl AlarmHandle { | |||
| 80 | /// Time driver | 80 | /// Time driver |
| 81 | pub trait Driver: Send + Sync + 'static { | 81 | pub trait Driver: Send + Sync + 'static { |
| 82 | /// Return the current timestamp in ticks. | 82 | /// Return the current timestamp in ticks. |
| 83 | /// This is guaranteed to be monotonic, i.e. a call to now() will always return | 83 | /// |
| 84 | /// a greater or equal value than earler calls. | 84 | /// Implementations MUST ensure that: |
| 85 | /// - This is guaranteed to be monotonic, i.e. a call to now() will always return | ||
| 86 | /// a greater or equal value than earler calls. Time can't "roll backwards". | ||
| 87 | /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say | ||
| 88 | /// in 10_000 years (Human civilization is likely to already have self-destructed | ||
| 89 | /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers | ||
| 90 | /// you MUST extend them to 64-bit, for example by counting overflows in software, | ||
| 91 | /// or chaining multiple timers together. | ||
| 85 | fn now(&self) -> u64; | 92 | fn now(&self) -> u64; |
| 86 | 93 | ||
| 87 | /// Try allocating an alarm handle. Returns None if no alarms left. | 94 | /// Try allocating an alarm handle. Returns None if no alarms left. |
