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}