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