watchdog_core/
lib.rs
1#![expect(missing_docs)]
12
13pub mod platform;
14use inspect::Inspect;
15use std::task::Context;
16use std::task::Poll;
17use std::time::Duration;
18use thiserror::Error;
19use vmcore::vmtime::VmTimeAccess;
20
21#[derive(Debug, Error)]
22pub enum WatchdogServiceError {
23 #[error("attempted to set config with invalid bits: {0:08x?}")]
24 InvalidConfigBits(u32),
25 #[error("attempted to start watchdog with count set to zero")]
26 ZeroCount,
27 #[error("attempted to write to read-only Resolution register")]
28 WriteResolution,
29}
30
31const BIOS_WATCHDOG_TIMER_PERIOD_S: u32 = 1;
33
34const BIOS_WATCHDOG_DEFAULT_COUNT: u32 = (2 * 60) / BIOS_WATCHDOG_TIMER_PERIOD_S;
36
37#[derive(Inspect)]
39#[inspect(debug)]
40#[bitfield_struct::bitfield(u32)]
41struct ConfigBits {
42 pub configured: bool,
43 pub enabled: bool,
44 #[bits(2)]
45 _reserved: u32,
46 pub one_shot: bool,
48 #[bits(3)]
49 _reserved2: u32,
50 pub boot_status: bool,
52 #[bits(23)]
53 _reserved3: u32,
54}
55
56impl ConfigBits {
57 pub fn contains_unsupported_bits(&self) -> bool {
58 u32::from(*self)
59 & !u32::from(
60 Self::new()
61 .with_configured(true)
62 .with_enabled(true)
63 .with_one_shot(true)
64 .with_boot_status(true),
65 )
66 != 0
67 }
68}
69
70#[derive(Debug)]
72pub enum Register {
73 Config,
76 Resolution,
78 Count,
83}
84
85#[derive(Clone, Copy, Debug, Inspect)]
86pub struct WatchdogServicesState {
87 config: ConfigBits,
89 resolution: u32,
90 count: u32,
91 configured_count: u32,
93}
94
95impl WatchdogServicesState {
96 fn new() -> Self {
97 Self {
98 config: ConfigBits::new(),
99 resolution: BIOS_WATCHDOG_TIMER_PERIOD_S,
100 count: BIOS_WATCHDOG_DEFAULT_COUNT,
101 configured_count: BIOS_WATCHDOG_DEFAULT_COUNT,
102 }
103 }
104}
105
106#[derive(Inspect)]
107pub struct WatchdogServices {
108 debug_id: String,
109 #[inspect(skip)]
111 vmtime: VmTimeAccess,
112 #[inspect(skip)]
113 platform: Box<dyn platform::WatchdogPlatform>,
114
115 #[inspect(flatten)]
117 state: WatchdogServicesState,
118}
119
120impl WatchdogServices {
121 pub async fn new(
122 debug_id: impl Into<String>,
123 vmtime: VmTimeAccess,
124 platform: Box<dyn platform::WatchdogPlatform>,
125 is_restoring: bool,
126 ) -> WatchdogServices {
127 let mut watchdog = WatchdogServices {
128 debug_id: debug_id.into(),
129 vmtime,
130 platform,
131 state: WatchdogServicesState::new(),
132 };
133
134 if !is_restoring {
135 watchdog
136 .state
137 .config
138 .set_boot_status(watchdog.platform.read_and_clear_boot_status().await);
139 }
140
141 watchdog
142 }
143
144 pub fn reset(&mut self) {
145 self.state = WatchdogServicesState::new();
146 }
147
148 pub fn read(&mut self, reg: Register) -> Result<u32, WatchdogServiceError> {
149 tracing::debug!(?reg, "read");
150
151 let val = match reg {
152 Register::Config => self.state.config.into(),
153 Register::Resolution => self.state.resolution,
154 Register::Count => self.state.count,
155 };
156
157 Ok(val)
158 }
159
160 pub fn write(&mut self, reg: Register, val: u32) -> Result<(), WatchdogServiceError> {
161 tracing::debug!(?reg, "write {:x}", val);
162
163 match reg {
164 Register::Config => {
165 self.state.config = {
166 let mut new_config = ConfigBits::from(val);
167 if new_config.contains_unsupported_bits() {
168 return Err(WatchdogServiceError::InvalidConfigBits(val));
169 }
170
171 if new_config.boot_status() {
173 new_config.set_boot_status(false);
174 } else {
175 new_config.set_boot_status(self.state.config.boot_status());
177 }
178
179 if !new_config.configured() {
181 self.state.count = 0;
182 }
183
184 new_config
185 };
186
187 if self.state.config.configured() && self.state.config.enabled() {
188 self.start_timer()?
189 } else {
190 self.stop_timer()
191 }
192 }
193 Register::Resolution => return Err(WatchdogServiceError::WriteResolution),
194 Register::Count => {
195 self.state.count = val;
196 self.state.configured_count = val;
197 }
198 }
199
200 Ok(())
201 }
202
203 fn start_timer(&mut self) -> Result<(), WatchdogServiceError> {
204 let seconds = self.state.count * self.state.resolution;
205
206 let next_tick = self
207 .vmtime
208 .now()
209 .wrapping_add(Duration::from_secs(seconds as u64));
210 self.state.count = self.state.configured_count;
211
212 self.vmtime.set_timeout(next_tick);
213 Ok(())
214 }
215
216 fn stop_timer(&mut self) {
217 self.vmtime.cancel_timeout();
218 }
219
220 pub fn poll(&mut self, cx: &mut Context<'_>) {
221 while let Poll::Ready(_now) = self.vmtime.poll_timeout(cx) {
222 tracing::error!(name = self.debug_id, "Encountered a watchdog timeout");
223 self.state.config.set_configured(false);
224 self.state.config.set_enabled(false);
225 pal_async::local::block_on(self.platform.on_timeout());
226 }
227 }
228}
229
230mod save_restore {
231 use super::*;
232 use vmcore::save_restore::RestoreError;
233 use vmcore::save_restore::SaveError;
234 use vmcore::save_restore::SaveRestore;
235
236 mod state {
237 use mesh::payload::Protobuf;
238
239 #[derive(Protobuf)]
240 #[mesh(package = "chipset.watchdog.core")]
241 pub struct SavedState {
242 #[mesh(1)]
243 pub config: u32,
244 #[mesh(2)]
245 pub resolution: u32,
246 #[mesh(3)]
247 pub count: u32,
248 #[mesh(4)]
249 pub configured_count: u32,
250 }
251 }
252
253 impl SaveRestore for WatchdogServices {
254 type SavedState = state::SavedState;
255
256 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
257 let WatchdogServicesState {
258 config,
259 resolution,
260 count,
261 configured_count,
262 } = self.state;
263
264 let saved_state = state::SavedState {
265 config: config.into(),
266 resolution,
267 count,
268 configured_count,
269 };
270
271 Ok(saved_state)
272 }
273
274 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
275 let state::SavedState {
276 config,
277 resolution,
278 count,
279 configured_count,
280 } = state;
281
282 self.state = WatchdogServicesState {
283 config: ConfigBits::from(config),
284 resolution,
285 count,
286 configured_count,
287 };
288
289 Ok(())
290 }
291 }
292}