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