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
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
31// Watchdog timer default period in seconds.
32const BIOS_WATCHDOG_TIMER_PERIOD_S: u32 = 1;
33
34// Watchdog timer default count (2 minutes).
35const BIOS_WATCHDOG_DEFAULT_COUNT: u32 = (2 * 60) / BIOS_WATCHDOG_TIMER_PERIOD_S;
36
37/// Values for the BIOS Watchdog Config register.
38#[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    /// Deprecated: Watchdog isn't configurable anymore
47    pub one_shot: bool,
48    #[bits(3)]
49    _reserved2: u32,
50    /// Enabled if previous reset was due to the watchdog
51    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/// [`WatchdogServices`] device registers.
71#[derive(Debug)]
72pub enum Register {
73    /// (RW) Used to configure the watchdog, set the mode, and temporarily
74    /// suspend or resume the timer.
75    Config,
76    /// (RO) Contains the resolution of the hardware timer in seconds.
77    Resolution,
78    /// (RW) Used to specify expiration of the watchdog timer.
79    ///
80    /// A recommended default value can be read after the device is reset and
81    /// after the watchdog is disabled via the Config register.
82    Count,
83}
84
85#[derive(Clone, Copy, Debug, Inspect)]
86pub struct WatchdogServicesState {
87    // register state
88    config: ConfigBits,
89    resolution: u32,
90    count: u32,
91    // internal state
92    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    // Runtime glue
110    #[inspect(skip)]
111    vmtime: VmTimeAccess,
112    #[inspect(skip)]
113    platform: Box<dyn platform::WatchdogPlatform>,
114
115    // Volatile state
116    #[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                    // Setting the boot status is the protocol to clear it.
172                    if new_config.boot_status() {
173                        new_config.set_boot_status(false);
174                    } else {
175                        // Otherwise, make sure to preserve the old value
176                        new_config.set_boot_status(self.state.config.boot_status());
177                    }
178
179                    // reset count to default if the timer is not longer configured
180                    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}