[PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver
From: Artem Lytkin
Date: Thu Jul 23 2026 - 12:25:04 EST
Add a Rust software watchdog driver using the Rust watchdog
abstraction, functionally equivalent to the core of the C softdog
driver.
An hrtimer is armed on start and re-armed on every keepalive ping for
the currently configured timeout. If userspace stops pinging, the
timer expires and the system is restarted via emergency_restart(),
matching the C softdog default behaviour. Stopping the watchdog
cancels the timer. The timer handle is protected by a mutex since
watchdog callbacks may run concurrently.
Two implementation notes:
- Re-arming cancels the previous timer before starting it again
(the hrtimer handle API cancels on drop), so a ping can briefly
block on a concurrently firing callback and there is a tiny
disarmed window during re-arm. A future handle restart API would
eliminate this.
- The C softdog pins the module manually while the timer is armed;
this driver gets equivalent protection from the watchdog core,
which holds the module reference while the device is open or the
hardware watchdog is marked running, so module unload is blocked
while the timer is armed.
The timeout is adjustable from userspace via WDIOC_SETTIMEOUT; since
the driver has no set_timeout operation, the watchdog core updates the
timeout directly and the new value takes effect on the next ping.
The Kconfig option uses SOFT_WATCHDOG=n (rather than !SOFT_WATCHDOG,
which would still allow both as modules) so that the C and Rust
software watchdogs are mutually exclusive.
Signed-off-by: Artem Lytkin <iprintercanon@xxxxxxxxx>
---
drivers/watchdog/Kconfig | 12 +++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/softdog_rs.rs | 143 +++++++++++++++++++++++++++++++++
3 files changed, 156 insertions(+)
create mode 100644 drivers/watchdog/softdog_rs.rs
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 08cb8612d41fe..7dcbb285c6f16 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -160,6 +160,18 @@ config SOFT_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called softdog.
+config SOFT_WATCHDOG_RS
+ tristate "Rust software watchdog"
+ depends on RUST && SOFT_WATCHDOG=n
+ select WATCHDOG_CORE
+ help
+ A software watchdog driver written in Rust using the Rust watchdog
+ device abstraction. This is a Rust equivalent of the C softdog
+ driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called softdog_rs.
+
config SOFT_WATCHDOG_PRETIMEOUT
bool "Software watchdog pretimeout governor support"
depends on SOFT_WATCHDOG && WATCHDOG_PRETIMEOUT_GOV
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index bc1d52220f223..397b16d648eca 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -236,6 +236,7 @@ obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o
obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+obj-$(CONFIG_SOFT_WATCHDOG_RS) += softdog_rs.o
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
diff --git a/drivers/watchdog/softdog_rs.rs b/drivers/watchdog/softdog_rs.rs
new file mode 100644
index 0000000000000..45fd76c4fd195
--- /dev/null
+++ b/drivers/watchdog/softdog_rs.rs
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust software watchdog driver.
+//!
+//! A software watchdog implemented with an hrtimer: when the timer expires
+//! before the next keepalive ping, the system is restarted.
+//!
+//! C version of this driver:
+//! [`drivers/watchdog/softdog.c`](srctree/drivers/watchdog/softdog.c)
+
+use kernel::{
+ impl_has_hr_timer, new_mutex,
+ prelude::*,
+ reboot,
+ sync::{Arc, ArcBorrow, Mutex},
+ time::{
+ hrtimer::{
+ ArcHrTimerHandle, HrTimer, HrTimerCallback, HrTimerCallbackContext, HrTimerPointer,
+ HrTimerRestart, RelativeMode,
+ },
+ Delta, Monotonic,
+ },
+ watchdog::{self, flags},
+};
+
+const DEFAULT_MARGIN: u32 = 60;
+const MAX_MARGIN: u32 = 65535;
+
+module! {
+ type: SoftdogModule,
+ name: "softdog_rs",
+ authors: ["Artem Lytkin"],
+ description: "Rust Software Watchdog Device Driver",
+ license: "GPL",
+}
+
+/// The countdown state: the hrtimer and the handle of its last arming.
+///
+/// Watchdog callbacks may run concurrently (for example the reboot notifier
+/// `stop` against an in-flight ioctl), so the handle is protected by a
+/// mutex.
+#[pin_data]
+struct Softdog {
+ #[pin]
+ timer: HrTimer<Self>,
+ #[pin]
+ handle: Mutex<Option<ArcHrTimerHandle<Softdog>>>,
+}
+
+impl Softdog {
+ fn new() -> impl PinInit<Self> {
+ pin_init!(Self {
+ timer <- HrTimer::new(),
+ handle <- new_mutex!(None),
+ })
+ }
+
+ /// (Re)arms the countdown to fire in `timeout` seconds.
+ fn arm(this: &Arc<Self>, timeout: u32) {
+ let mut guard = this.handle.lock();
+ // Drop the previous handle first: dropping a handle cancels the
+ // timer, so this must not happen after the new arming.
+ *guard = None;
+ *guard = Some(this.clone().start(Delta::from_secs(i64::from(timeout))));
+ }
+
+ /// Cancels the countdown.
+ fn disarm(this: &Arc<Self>) {
+ // Dropping the handle cancels the timer and also breaks the
+ // reference cycle `Softdog -> handle -> Arc<Softdog>`.
+ *this.handle.lock() = None;
+ }
+}
+
+impl_has_hr_timer! {
+ impl HasHrTimer<Self> for Softdog {
+ mode: RelativeMode<Monotonic>, field: self.timer
+ }
+}
+
+impl HrTimerCallback for Softdog {
+ type Pointer<'a> = Arc<Self>;
+
+ fn run(_this: ArcBorrow<'_, Self>, _ctx: HrTimerCallbackContext<'_, Self>) -> HrTimerRestart {
+ pr_crit!("Initiating system reboot\n");
+ reboot::emergency_restart();
+ // Only reached if the machine failed to restart.
+ HrTimerRestart::NoRestart
+ }
+}
+
+struct SoftdogOps;
+
+#[vtable]
+impl watchdog::WatchdogOps for SoftdogOps {
+ type Data = Arc<Softdog>;
+
+ fn start(dev: &watchdog::Device, data: &Arc<Softdog>) -> Result {
+ Softdog::arm(data, dev.timeout());
+ Ok(())
+ }
+
+ fn ping(dev: &watchdog::Device, data: &Arc<Softdog>) -> Result {
+ Softdog::arm(data, dev.timeout());
+ Ok(())
+ }
+
+ fn stop(_dev: &watchdog::Device, data: &Arc<Softdog>) -> Result {
+ Softdog::disarm(data);
+ Ok(())
+ }
+}
+
+static SOFTDOG_INFO: watchdog::Info = watchdog::Info::new(
+ flags::SETTIMEOUT | flags::KEEPALIVEPING | flags::MAGICCLOSE,
+ "Rust Software Watchdog",
+);
+
+struct SoftdogModule {
+ _reg: watchdog::Registration<SoftdogOps>,
+}
+
+impl kernel::Module for SoftdogModule {
+ fn init(module: &'static ThisModule) -> Result<Self> {
+ let data = Arc::pin_init(Softdog::new(), GFP_KERNEL)?;
+
+ let options = watchdog::Options {
+ timeout: DEFAULT_MARGIN,
+ min_timeout: 1,
+ max_timeout: MAX_MARGIN,
+ // Stop the countdown on reboot so that an orderly reboot is not
+ // interrupted by the watchdog firing, like the C softdog does.
+ stop_on_reboot: true,
+ ..Default::default()
+ };
+
+ let reg = watchdog::Registration::register(module, None, &SOFTDOG_INFO, &options, data)?;
+
+ pr_info!("initialized (timeout={}s)\n", DEFAULT_MARGIN);
+
+ Ok(SoftdogModule { _reg: reg })
+ }
+}
--
2.43.0