watchdog_core/
platform.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Platform abstraction layer for watchdog timer devices.
5//!
6//! This module defines the interface between generic watchdog device implementations
7//! (like UEFI watchdog) and the specific host environment where they run (OpenVMM,
8//! OpenHCL, etc). The abstraction allows watchdog devices to remain platform-agnostic
9//! while enabling each platform to handle timeouts appropriately - whether that's
10//! logging, generating crash dumps, resetting VMs, or sending interrupts.
11//!
12//! The key traits are:
13//! - [`WatchdogCallback`]: Implement this to define custom timeout behavior
14//! - [`WatchdogPlatform`]: The interface that watchdog devices use to handle timeouts
15
16use cvm_tracing::CVM_ALLOWED;
17use vmcore::non_volatile_store::NonVolatileStore;
18use watchdog_vmgs_format::WatchdogVmgsFormatStore;
19use watchdog_vmgs_format::WatchdogVmgsFormatStoreError;
20
21/// Trait for responding to watchdog timeouts.
22///
23/// Implement this trait whenever you want to respond to watchdog timeouts, then pass
24/// your instance to add_callback() on the [`WatchdogPlatform`] trait.
25#[async_trait::async_trait]
26pub trait WatchdogCallback: Send + Sync {
27    /// Called when the watchdog timer expires
28    async fn on_timeout(&mut self);
29}
30
31/// Blanket implementation of [`WatchdogCallback`] for closures.
32///
33/// This allows you to pass simple closures directly as callbacks without
34/// needing to create a struct.
35#[async_trait::async_trait]
36impl<F> WatchdogCallback for F
37where
38    F: FnMut() + Send + Sync,
39{
40    async fn on_timeout(&mut self) {
41        self();
42    }
43}
44
45/// Defines the watchdog platform interface.
46#[async_trait::async_trait]
47pub trait WatchdogPlatform: Send {
48    /// Callback fired when the timer expires.
49    async fn on_timeout(&mut self);
50
51    // Check the vmgs store for boot status and clear it.
52    async fn read_and_clear_boot_status(&mut self) -> bool;
53
54    /// Add a callback, which executes when the watchdog times out
55    fn add_callback(&mut self, callback: Box<dyn WatchdogCallback>);
56}
57
58/// A base implementation of [`WatchdogPlatform`], used by OpenVMM and OpenHCL.
59pub struct BaseWatchdogPlatform {
60    /// Whether the watchdog has timed out or not.
61    watchdog_expired: bool,
62    /// Callbacks to execute when the watchdog times out.
63    callbacks: Vec<Box<dyn WatchdogCallback>>,
64    /// The VMGS store used to persist the watchdog status.
65    store: WatchdogVmgsFormatStore,
66}
67
68impl BaseWatchdogPlatform {
69    pub async fn new(
70        store: Box<dyn NonVolatileStore>,
71    ) -> Result<Self, WatchdogVmgsFormatStoreError> {
72        Ok(BaseWatchdogPlatform {
73            watchdog_expired: false,
74            callbacks: Vec::new(),
75            store: WatchdogVmgsFormatStore::new(store).await?,
76        })
77    }
78}
79
80#[async_trait::async_trait]
81impl WatchdogPlatform for BaseWatchdogPlatform {
82    async fn on_timeout(&mut self) {
83        self.watchdog_expired = true;
84
85        // Persist the watchdog status to the VMGS store
86        let result = self.store.set_boot_failure().await;
87        if let Err(e) = result {
88            tracing::error!(
89                CVM_ALLOWED,
90                error = &e as &dyn std::error::Error,
91                "error persisting watchdog status"
92            );
93        }
94
95        // Invoke all callbacks
96        for callback in &mut self.callbacks {
97            callback.on_timeout().await;
98        }
99    }
100
101    async fn read_and_clear_boot_status(&mut self) -> bool {
102        let res = self.store.read_and_clear_boot_status().await;
103        match res {
104            Ok(status) => status,
105            Err(e) => {
106                tracing::error!(
107                    CVM_ALLOWED,
108                    error = &e as &dyn std::error::Error,
109                    "error reading watchdog status"
110                );
111                // assume no failure
112                false
113            }
114        }
115    }
116
117    fn add_callback(&mut self, callback: Box<dyn WatchdogCallback>) {
118        self.callbacks.push(callback);
119    }
120}