Skip to main content

rivet/
watchdog.rs

1//! Watchdog policy — arch/board-independent.
2//!
3//! The actual watchdog hardware (or lack of it) is entirely a board fact,
4//! reached through [`crate::port::board`]: [`init`]/[`feed`] forward to
5//! `__rivet_board_wdt_init`/`__rivet_board_wdt_feed`; boards with a real
6//! hardware watchdog implement those directly, boards without one arm a
7//! software deadline and check it from `__rivet_board_wdt_check` (called
8//! every tick via [`on_tick`]) — see `docs/porting.md` for the full
9//! contract. Note independence: a hardware watchdog keeps counting even
10//! if the CPU wedges with interrupts off; a software one, checked from the
11//! tick, cannot catch a hang that stops ticks.
12//!
13//! Task-level watchdogs are a separate, purely kernel-side mechanism: a
14//! task that calls [`checkin`] in its main loop opts in; the tick handler
15//! flags it if it goes silent longer than [`enable_checkins`].
16
17use core::sync::atomic::{AtomicU32, Ordering};
18
19/// Task checkin timeout in microseconds (0 = task checkins disabled).
20static CHECKIN_TIMEOUT_US: AtomicU32 = AtomicU32::new(0);
21
22/// Initialize the watchdog with the given period.
23pub fn init(period: crate::time::Duration) {
24    crate::port::board::wdt_init(period.as_micros() as u32);
25}
26
27/// Kick the watchdog. Call periodically from the main loop / a high-
28/// priority task.
29pub fn feed() {
30    crate::port::board::wdt_feed();
31}
32
33/// Called by the tick handler every tick: gives the board a chance to
34/// check a software watchdog deadline, then scans task-level checkins.
35pub fn on_tick() {
36    crate::port::board::wdt_check();
37    check_task_checkins();
38}
39
40/// Opt a task into the task-level watchdog: record the current time as its
41/// last checkin. Called periodically from the task's own main loop.
42pub fn checkin() {
43    if CHECKIN_TIMEOUT_US.load(Ordering::Acquire) == 0 {
44        return;
45    }
46    if let Some(id) = crate::preempt::sched::current() {
47        if let Some(t) = crate::preempt::tcb::get(id) {
48            t.last_checkin
49                .store(crate::port::board::now_us() as u32, Ordering::Release);
50        }
51    }
52}
53
54/// Enable task-level checkin monitoring with the given timeout.
55pub fn enable_checkins(timeout: crate::time::Duration) {
56    CHECKIN_TIMEOUT_US.store(timeout.as_micros() as u32, Ordering::Release);
57}
58
59/// Scan tasks that opted into checkins; reset if any has been silent too
60/// long. (Per-task *isolation* of an unresponsive task is separate fault-
61/// policy work; here the recovery is a diagnosed reset.)
62fn check_task_checkins() {
63    let timeout = CHECKIN_TIMEOUT_US.load(Ordering::Acquire);
64    if timeout == 0 {
65        return;
66    }
67    let now = crate::port::board::now_us() as u32;
68    for (id, t) in crate::preempt::tcb::TASKS.iter().enumerate() {
69        if !t.used.load(Ordering::Acquire) {
70            continue;
71        }
72        let last = t.last_checkin.load(Ordering::Acquire);
73        if last != 0 && now.wrapping_sub(last) > timeout {
74            crate::console::write_str("RIVET TASK CHECKIN TIMEOUT task=");
75            print_dec(id);
76            crate::console::write_str("\n");
77            crate::port::board::reset();
78        }
79    }
80}
81
82fn print_dec(mut n: usize) {
83    if n == 0 {
84        crate::console::write_str("0");
85        return;
86    }
87    let mut digits = [0u8; 10];
88    let mut i = 0;
89    while n > 0 {
90        digits[i] = b'0' + (n % 10) as u8;
91        n /= 10;
92        i += 1;
93    }
94    let mut out = [0u8; 10];
95    for j in 0..i {
96        out[j] = digits[i - 1 - j];
97    }
98    if let Ok(s) = core::str::from_utf8(&out[..i]) {
99        crate::console::write_str(s);
100    }
101}
102
103/// Test-only reset (host).
104#[cfg(feature = "test-support")]
105pub(crate) fn reset_for_test() {
106    CHECKIN_TIMEOUT_US.store(0, Ordering::Release);
107}