diff options
| author | Dario Nieuwenhuis <[email protected]> | 2022-02-23 05:14:32 +0100 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2022-02-23 05:16:30 +0100 |
| commit | fdb6e66b4b7aa5a6b72ec2b926ea9e5de728c657 (patch) | |
| tree | b1f41a260ad1148a825bce55f075e268fe127f88 /embassy-nrf/src | |
| parent | 78795d6f56a3c0d6c5a03e6453cd827821a42825 (diff) | |
time: better docs explaining overflow handling.
Diffstat (limited to 'embassy-nrf/src')
| -rw-r--r-- | embassy-nrf/src/time_driver.rs | 63 |
1 files changed, 45 insertions, 18 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 | } |
