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