Skip to main content

watchdog_core/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A watchdog timer device.
5//!
6//! This is not based on any real hardware, and is a bespoke to Hyper-V.
7//!
8//! This implementation is used by both the Hyper-V UEFI helper device, and the
9//! Guest Watchdog device.
10
11#![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
33// Watchdog timer default period in seconds.
34const BIOS_WATCHDOG_TIMER_PERIOD_S: u32 = 1;
35
36// Watchdog timer default count (2 minutes).
37const BIOS_WATCHDOG_DEFAULT_COUNT: u32 = (2 * 60) / BIOS_WATCHDOG_TIMER_PERIOD_S;
38
39/// Values for the BIOS Watchdog Config register.
40#[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    /// Deprecated: Watchdog isn't configurable anymore
49    pub one_shot: bool,
50    #[bits(3)]
51    _reserved2: u32,
52    /// Enabled if previous reset was due to the watchdog
53    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/// [`WatchdogServices`] device registers.
73#[derive(Debug)]
74pub enum Register {
75    /// (RW) Used to configure the watchdog, set the mode, and temporarily
76    /// suspend or resume the timer.
77    Config,
78    /// (RO) Contains the resolution of the hardware timer in seconds.
79    Resolution,
80    /// (RW) Used to specify expiration of the watchdog timer.
81    ///
82    /// A recommended default value can be read after the device is reset and
83    /// after the watchdog is disabled via the Config register.
84    Count,
85}
86
87#[derive(Clone, Copy, Debug, Inspect)]
88pub struct WatchdogServicesState {
89    // register state
90    config: ConfigBits,
91    resolution: u32,
92    count: u32,
93    // internal state
94    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    // Runtime glue
112    #[inspect(skip)]
113    vmtime: VmTimeAccess,
114    #[inspect(skip)]
115    platform: Box<dyn platform::WatchdogPlatform>,
116
117    // Volatile state
118    #[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                    // Setting the boot status is the protocol to clear it.
174                    if new_config.boot_status() {
175                        new_config.set_boot_status(false);
176                    } else {
177                        // Otherwise, make sure to preserve the old value
178                        new_config.set_boot_status(self.state.config.boot_status());
179                    }
180
181                    // reset count to default if the timer is not longer configured
182                    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}