diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index b14a670b0..96a03d635 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -41,7 +41,7 @@ //! ``` use core::fmt::{self, Write}; -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, Ordering}; // The store's format, in one file, shared with userspace. // @@ -302,8 +302,6 @@ const BOOT_FLAGS: u16 = 1 << 7; /// needs. History would be a second record per boot, and nobody has asked for one. const BOOT_POINT: (u64, u64, u64) = (0, 0, 0); -/// Whether this boot has already been recorded. One attempt, claimed with a swap so two callers -/// racing into their first write cannot write two records. /// How many attempts this boot has made at recording itself, and the ceiling on them. /// /// A count rather than a flag, because a flag that is claimed *before* the write cannot un-claim @@ -316,6 +314,28 @@ const BOOT_POINT: (u64, u64, u64) = (0, 0, 0); static BOOT_RECORD_ATTEMPTS: AtomicU32 = AtomicU32::new(0); const BOOT_RECORD_MAX_ATTEMPTS: u32 = 4; +/// The wall clock and the monotonic clock as they stood when this boot's record was written. +/// +/// Kept so a later write can tell whether the *epoch* in that record is still the truth. The record +/// is written at the first write of a boot, which is exactly when the wall clock is least likely to +/// have been corrected: this box arrives from a night powered off with a clock tens of seconds out, +/// so the first record carried a time it had no way to qualify. Two clocks are the whole mechanism, +/// and neither needs a reference: the wall clock can be set from anywhere, the monotonic clock +/// cannot be set at all, so a divergence between them over the same interval is a clock correction +/// and nothing else. `uptime` in the record comes from the monotonic side, which means the instant +/// the boot began (`boot - uptime`) stays recoverable however wrong the wall clock was. +static BOOT_RECORD_WALL: AtomicI64 = AtomicI64::new(0); +static BOOT_RECORD_MONO: AtomicI64 = AtomicI64::new(0); + +/// Whether the record has been rewritten after a correction, and how many tries that has taken. +static BOOT_RECORD_CLOCK_SET: AtomicBool = AtomicBool::new(false); +static BOOT_RECORD_REFRESHES: AtomicU32 = AtomicU32::new(0); +const BOOT_RECORD_MAX_REFRESHES: u32 = 2; + +/// How far the wall clock must move relative to the monotonic clock before it counts as corrected. +/// Both are whole seconds read one after the other, so a second of slack belongs here. +const CLOCK_SET_TOLERANCE_SECS: i64 = 2; + /// FNV-1a offset basis. const FNV_OFFSET: u64 = cube_format::FNV_OFFSET; @@ -1972,24 +1992,116 @@ fn c_len(p: *const u8, max: usize) -> usize { n } +/// Which clock the epoch in a boot record was read from. +/// +/// `Raw` is the reading the first write of the boot took, before anything has had a chance to +/// correct the clock. `Set` is a reading taken after a correction was *observed*, which is the only +/// way this record can claim a time it knows was not provisional. +#[derive(Clone, Copy)] +enum ClockState { + Raw, + Set, +} + +impl ClockState { + fn word(self) -> &'static [u8] { + match self { + ClockState::Raw => b"raw", + ClockState::Set => b"set", + } + } +} + +/// Both clocks, read as close together as two calls can be. +fn now_clocks() -> (i64, i64) { + let mut wall = bindings::timespec64 { + tv_sec: 0, + tv_nsec: 0, + }; + let mut mono = bindings::timespec64 { + tv_sec: 0, + tv_nsec: 0, + }; + // SAFETY: both are live, writable `timespec64`s, and neither call retains a pointer to one. + unsafe { bindings::ktime_get_real_ts64(&mut wall) }; + // SAFETY: as above. + unsafe { bindings::ktime_get_ts64(&mut mono) }; + (wall.tv_sec as i64, mono.tv_sec as i64) +} + +/// Whether the wall clock has been set or adjusted since this boot's record was written. +/// +/// The wall clock can be set from anywhere and the monotonic clock cannot be set at all, so if the +/// wall clock has moved by a different amount than the monotonic clock over the same interval, then +/// something set the wall clock — and the epoch in the record is a reading from before it. +fn wall_clock_was_set() -> bool { + let wall_at = BOOT_RECORD_WALL.load(Ordering::Acquire); + if wall_at == 0 { + return false; + } + let mono_at = BOOT_RECORD_MONO.load(Ordering::Acquire); + let (wall, mono) = now_clocks(); + ((wall - wall_at) - (mono - mono_at)).abs() > CLOCK_SET_TOLERANCE_SECS +} + +/// Rewrite this boot's record when the clock it was read from has since been corrected. +/// +/// The record lives at one coordinate and each boot overwrites the last, so this is the same write +/// the first one was, with a clock reading that is no longer provisional. It is bounded like the +/// first: a store that will not take the rewrite logs and stops asking. +fn refresh_boot_record_if_the_clock_was_set() { + if BOOT_RECORD_CLOCK_SET.load(Ordering::Acquire) || !wall_clock_was_set() { + return; + } + if BOOT_RECORD_REFRESHES.fetch_add(1, Ordering::AcqRel) >= BOOT_RECORD_MAX_REFRESHES { + return; + } + let (wall, mono) = now_clocks(); + let value = match boot_record_value(ClockState::Set, wall, mono) { + Some(v) => v, + None => return, + }; + let mutation = Mutation { + space: BOOT_SPACE, + key: morton_encode(BOOT_POINT.0, BOOT_POINT.1, BOOT_POINT.2), + flags: BOOT_FLAGS, + value, + }; + match append_mutation(&mutation, 1) { + Ok(_) => { + BOOT_RECORD_CLOCK_SET.store(true, Ordering::Release); + BOOT_RECORD_WALL.store(wall, Ordering::Release); + BOOT_RECORD_MONO.store(mono, Ordering::Release); + pr_info!("cubelinux: the clock was set, so this boot's record was rewritten with the corrected time\n"); + } + Err(_) => pr_warn!("cubelinux: could not rewrite this boot's record after a clock correction\n"), + } +} + /// The kernel's own account of the boot it is having, as one line: -/// `boot= device= kernel=`. +/// `boot= uptime= clock= device= kernel=`. /// /// The banner is last and unquoted because it contains spaces, so everything after the final `=` /// is the kernel's own words rather than a field this code parsed. The time is raw epoch seconds: /// rendering a calendar date in the kernel is date arithmetic, and a caller with a clock can do it /// without a kernel bug being the reason a timestamp is wrong. -fn boot_record_value() -> Option> { +/// +/// `uptime` is carried beside it because the epoch alone does not say whether it was read at the +/// start of the boot or an hour into it, and because `boot - uptime` is the instant the boot began +/// irrespective of what the wall clock was doing. `clock` says which of the two readings this is — +/// see [`ClockState`]. A reader that wants a time it can trust takes `clock=set`; a reader that +/// wants the boot instant takes `boot - uptime`; a reader that wants both takes them from the same +/// line and does not have to guess which one it got. +fn boot_record_value(clock: ClockState, wall: i64, mono: i64) -> Option> { let mut out = KVVec::::new(); - let mut ts = bindings::timespec64 { - tv_sec: 0, - tv_nsec: 0, - }; - // SAFETY: `ts` is a live, writable `timespec64`, and the call retains no pointer to it. - unsafe { bindings::ktime_get_real_ts64(&mut ts) }; push(&mut out, b"boot=")?; - push_dec(&mut out, ts.tv_sec as u64)?; + push_dec(&mut out, wall.max(0) as u64)?; + push(&mut out, b" uptime=")?; + push_dec(&mut out, mono.max(0) as u64)?; + push(&mut out, b" clock=")?; + push(&mut out, clock.word())?; push(&mut out, b" device=")?; // SAFETY: `store_device` returns a static NUL-terminated buffer, valid for the life of the @@ -2042,14 +2154,20 @@ fn ensure_boot_record() { // cannot both write a record: the loser returns, exactly as the swap did. The winner's attempt // is counted whether it succeeds or fails, which is what makes a failure retryable. let made = BOOT_RECORD_ATTEMPTS.load(Ordering::Acquire); - if made >= BOOT_RECORD_MAX_ATTEMPTS - || BOOT_RECORD_ATTEMPTS - .compare_exchange(made, made + 1, Ordering::AcqRel, Ordering::Acquire) - .is_err() + if made >= BOOT_RECORD_MAX_ATTEMPTS { + // The record is in the store. The last thing that can make it untrue is the clock it was + // read from, so that is the only thing still checked from here on. + refresh_boot_record_if_the_clock_was_set(); + return; + } + if BOOT_RECORD_ATTEMPTS + .compare_exchange(made, made + 1, Ordering::AcqRel, Ordering::Acquire) + .is_err() { return; } - let value = match boot_record_value() { + let (wall, mono) = now_clocks(); + let value = match boot_record_value(ClockState::Raw, wall, mono) { Some(v) => v, None => { pr_warn!("cubelinux: not enough memory to build this boot's record\n"); @@ -2066,7 +2184,11 @@ fn ensure_boot_record() { // log's head, not the image. match append_mutation(&mutation, 1) { Ok(_) => { - // Recorded. Park the count at the ceiling so no later write asks again. + // Recorded, with the two clock readings that go with it, so a later write can tell + // whether the epoch it just wrote is still the truth. Park the count at the ceiling so + // no later write asks again. + BOOT_RECORD_WALL.store(wall, Ordering::Release); + BOOT_RECORD_MONO.store(mono, Ordering::Release); BOOT_RECORD_ATTEMPTS.store(BOOT_RECORD_MAX_ATTEMPTS, Ordering::Release); pr_info!("cubelinux: recorded this boot in the store\n"); }