Rust for Linux List
 help / color / mirror / Atom feed
From: Artem Lytkin <iprintercanon@gmail.com>
To: linux-watchdog@vger.kernel.org, rust-for-linux@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, wim@linux-watchdog.org,
	linux@roeck-us.net, ojeda@kernel.org,
	miguel.ojeda.sandonis@gmail.com, dakr@kernel.org,
	aliceryhl@google.com, a.hindborg@kernel.org, lossin@kernel.org
Subject: [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver
Date: Thu, 23 Jul 2026 19:15:29 +0300	[thread overview]
Message-ID: <20260723161529.23759-4-iprintercanon@gmail.com> (raw)
In-Reply-To: <20260723161529.23759-1-iprintercanon@gmail.com>

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@gmail.com>
---
 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


      parent reply	other threads:[~2026-07-23 16:16 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-23 16:15 [PATCH v2 0/3] rust: add watchdog abstraction and Rust softdog driver Artem Lytkin
2026-07-23 16:15 ` [PATCH v2 1/3] rust: watchdog: add watchdog device abstraction Artem Lytkin
2026-07-23 16:15 ` [PATCH v2 2/3] rust: reboot: add emergency_restart() wrapper Artem Lytkin
2026-07-23 16:15 ` Artem Lytkin [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260723161529.23759-4-iprintercanon@gmail.com \
    --to=iprintercanon@gmail.com \
    --cc=a.hindborg@kernel.org \
    --cc=aliceryhl@google.com \
    --cc=dakr@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=lossin@kernel.org \
    --cc=miguel.ojeda.sandonis@gmail.com \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=wim@linux-watchdog.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox