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}