Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH v2 0/3] rust: add watchdog abstraction and Rust softdog driver
@ 2026-07-23 16:15 Artem Lytkin
  2026-07-23 16:15 ` [PATCH v2 1/3] rust: watchdog: add watchdog device abstraction Artem Lytkin
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-07-23 16:15 UTC (permalink / raw)
  To: linux-watchdog, rust-for-linux
  Cc: linux-kernel, wim, linux, ojeda, miguel.ojeda.sandonis, dakr,
	aliceryhl, a.hindborg, lossin

This series adds Rust abstractions for the watchdog subsystem and a
Rust software watchdog driver functionally equivalent to the core of
the C softdog.

Patch 1 adds the watchdog abstraction: a Device wrapper, an Info
wrapper with option flags, an Options configuration struct, a
#[vtable] WatchdogOps trait with an associated Data type for driver
private data, and a generic Registration that owns the device, the
ops table and the driver data in a single heap allocation.

Patch 2 adds a minimal kernel::reboot module wrapping
emergency_restart(), needed by the softdog driver.

Patch 3 adds the softdog_rs driver: an hrtimer is armed on start and
re-armed on every keepalive ping; if userspace stops pinging, the
timer expires and the system is restarted via emergency_restart().

Changes since v1 [1]:

Most of v2 is a redesign addressing the review feedback from Guenter
(via the Sashiko review [2]), Miguel, and the kernel test robot:

- The ops table is no longer a &'static mut supplied by the driver;
  it is built by the abstraction and embedded in the per-device heap
  allocation, so multiple device instances need no static mutable
  state (Sashiko).
- Driver private data is now supported via an associated Data type;
  every callback receives a reference to it (Sashiko).
- Callbacks now take only shared references and Data must be Sync:
  the watchdog core does not hold its lock on every callback path
  (the first start/ping on open, the reboot notifier stop, and the
  stop on unregistration run without it), so exclusive references
  would be unsound.
- max_hw_heartbeat_ms is configurable and the stop()-or-heartbeat
  registration requirement is documented (Sashiko).
- Removed the Registration::device() accessor that allowed aliasing
  with the references used in callbacks (Sashiko).
- stop_on_reboot is now opt-in and rejected with EINVAL for drivers
  without stop(); nowayout defaults to CONFIG_WATCHDOG_NOWAYOUT
  (Sashiko).
- The watchdog is always stopped on unregistration so no callback can
  run after the driver data is freed.
- softdog_rs now actually bites: it arms an hrtimer and calls
  emergency_restart() on expiry, instead of relying on the (wrong)
  assumption that the core handles expiry (Sashiko).
- Kconfig mutual exclusion fixed to SOFT_WATCHDOG=n; !SOFT_WATCHDOG
  still allowed both drivers as modules (Sashiko).
- C helpers marked __rust_helper (Miguel), and updated for the
  const-qualified watchdog_active()/watchdog_hw_running().
- Fixed rustfmt and clippy issues reported by the kernel test robot.
- Drivers no longer use raw bindings: added the Info wrapper, the
  flags module, and the kernel::reboot module.
- Added the new rust watchdog files to MAINTAINERS.
- Rebased onto current mainline (post-v7.2-rc4).

[1] https://lore.kernel.org/linux-watchdog/20260323195138.5541-1-iprintercanon@gmail.com/
[2] https://sashiko.dev/#/patchset/20260323195138.5541-1-iprintercanon%40gmail.com

Artem Lytkin (3):
  rust: watchdog: add watchdog device abstraction
  rust: reboot: add emergency_restart() wrapper
  watchdog: softdog_rs: add Rust software watchdog driver

 MAINTAINERS                     |   2 +
 drivers/watchdog/Kconfig        |  12 +
 drivers/watchdog/Makefile       |   1 +
 drivers/watchdog/softdog_rs.rs  | 143 +++++++++++
 rust/bindings/bindings_helper.h |   2 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/watchdog.c         |  38 +++
 rust/kernel/lib.rs              |   3 +
 rust/kernel/reboot.rs           |  19 ++
 rust/kernel/watchdog.rs         | 522 ++++++++++++++++++++++++++++++++++++++++
 10 files changed, 743 insertions(+)
 create mode 100644 drivers/watchdog/softdog_rs.rs
 create mode 100644 rust/helpers/watchdog.c
 create mode 100644 rust/kernel/reboot.rs
 create mode 100644 rust/kernel/watchdog.rs

-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v2 1/3] rust: watchdog: add watchdog device abstraction
  2026-07-23 16:15 [PATCH v2 0/3] rust: add watchdog abstraction and Rust softdog driver Artem Lytkin
@ 2026-07-23 16:15 ` Artem Lytkin
  2026-07-23 16:15 ` [PATCH v2 2/3] rust: reboot: add emergency_restart() wrapper Artem Lytkin
  2026-07-23 16:15 ` [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
  2 siblings, 0 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-07-23 16:15 UTC (permalink / raw)
  To: linux-watchdog, rust-for-linux
  Cc: linux-kernel, wim, linux, ojeda, miguel.ojeda.sandonis, dakr,
	aliceryhl, a.hindborg, lossin

Add Rust abstractions for the Linux watchdog subsystem, enabling
watchdog drivers to be written in Rust:

  - A `Device` wrapper around `struct watchdog_device` providing
    accessors for timeout, pretimeout, and status fields.

  - An `Info` wrapper around `struct watchdog_info` with a const
    constructor, plus option flags in the `flags` module, so drivers do
    not need to touch raw bindings.

  - An `Options` struct describing timeouts, `max_hw_heartbeat_ms`,
    `nowayout` (defaulting to CONFIG_WATCHDOG_NOWAYOUT), and
    `stop_on_reboot` policy.

  - A `#[vtable] WatchdogOps` trait with an associated `Data` type for
    driver private data. `start()` is mandatory; `stop()`, `ping()`,
    `set_timeout()`, `set_pretimeout()`, and `get_timeleft()` are
    optional.

  - A generic `Registration<T>` that owns the `watchdog_device`, the
    `watchdog_ops` (with `owner` set from `ThisModule` for correct
    module refcounting), and the driver data in a single heap
    allocation, so no static mutable state is needed in drivers and
    multiple device instances are naturally supported.

The watchdog core does not hold its lock on every callback path (the
first start/ping on open, the reboot notifier stop, and the stop on
unregistration run without it), so callbacks receive only shared
references and the driver data must be Sync; mutable driver state
needs its own synchronisation.

The watchdog is configured to stop on unregistration so that a
stoppable watchdog does not keep running unattended. Requesting
stop_on_reboot without a stop() implementation is rejected with EINVAL
since the core would only warn and skip the notifier.

Also add C helper functions (marked __rust_helper) for the inline
watchdog functions, include linux/watchdog.h in the bindings helper
header, and add the new files to the WATCHDOG DEVICE DRIVERS section
of MAINTAINERS.

Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 MAINTAINERS                     |   2 +
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/watchdog.c         |  38 +++
 rust/kernel/lib.rs              |   2 +
 rust/kernel/watchdog.rs         | 522 ++++++++++++++++++++++++++++++++
 6 files changed, 566 insertions(+)
 create mode 100644 rust/helpers/watchdog.c
 create mode 100644 rust/kernel/watchdog.rs

diff --git a/MAINTAINERS b/MAINTAINERS
index 1ab8736850ea3..214d92057c447 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29027,6 +29027,8 @@ F:	drivers/watchdog/
 F:	include/linux/watchdog.h
 F:	include/trace/events/watchdog.h
 F:	include/uapi/linux/watchdog.h
+F:	rust/helpers/watchdog.c
+F:	rust/kernel/watchdog.rs
 
 WAVE5 VPU CODEC DRIVER
 M:	Nas Chung <nas.chung@chipsnmedia.com>
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b3..e692d142c8608 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -91,6 +91,7 @@
 #include <linux/tracepoint.h>
 #include <linux/usb.h>
 #include <linux/wait.h>
+#include <linux/watchdog.h>
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
 #include <trace/events/rust_sample.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 998e31052e660..b670a315a8cca 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -98,5 +98,6 @@
 #include "usb.c"
 #include "vmalloc.c"
 #include "wait.c"
+#include "watchdog.c"
 #include "workqueue.c"
 #include "xarray.c"
diff --git a/rust/helpers/watchdog.c b/rust/helpers/watchdog.c
new file mode 100644
index 0000000000000..0d92e4bec0526
--- /dev/null
+++ b/rust/helpers/watchdog.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/watchdog.h>
+
+__rust_helper bool rust_helper_watchdog_active(const struct watchdog_device *wdd)
+{
+	return watchdog_active(wdd);
+}
+
+__rust_helper bool rust_helper_watchdog_hw_running(const struct watchdog_device *wdd)
+{
+	return watchdog_hw_running(wdd);
+}
+
+__rust_helper void rust_helper_watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
+{
+	watchdog_set_nowayout(wdd, nowayout);
+}
+
+__rust_helper void rust_helper_watchdog_stop_on_reboot(struct watchdog_device *wdd)
+{
+	watchdog_stop_on_reboot(wdd);
+}
+
+__rust_helper void rust_helper_watchdog_stop_on_unregister(struct watchdog_device *wdd)
+{
+	watchdog_stop_on_unregister(wdd);
+}
+
+__rust_helper void rust_helper_watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
+{
+	watchdog_set_drvdata(wdd, data);
+}
+
+__rust_helper void *rust_helper_watchdog_get_drvdata(struct watchdog_device *wdd)
+{
+	return watchdog_get_drvdata(wdd);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 9512af7156df2..a1130c4b82881 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -134,6 +134,8 @@
 pub mod uaccess;
 #[cfg(CONFIG_USB = "y")]
 pub mod usb;
+#[cfg(CONFIG_WATCHDOG)]
+pub mod watchdog;
 pub mod workqueue;
 pub mod xarray;
 
diff --git a/rust/kernel/watchdog.rs b/rust/kernel/watchdog.rs
new file mode 100644
index 0000000000000..155eecc679238
--- /dev/null
+++ b/rust/kernel/watchdog.rs
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Watchdog device support.
+//!
+//! C headers: [`include/linux/watchdog.h`](srctree/include/linux/watchdog.h).
+
+use crate::{bindings, device, error::*, prelude::*, types::Opaque};
+use core::marker::PhantomData;
+
+/// A watchdog device.
+///
+/// Wraps the kernel's [`struct watchdog_device`].
+///
+/// # Invariants
+///
+/// The pointer is valid for the duration of a callback invocation.
+///
+/// # Concurrency
+///
+/// The watchdog core serialises most operations via its internal lock, but
+/// not all of them: the first `start`/`ping` on open, the reboot notifier
+/// `stop`, and the `stop` on unregistration run without it. Therefore only
+/// shared references are handed to drivers, all mutation goes through
+/// interior mutability, and driver data must be [`Sync`].
+///
+/// [`struct watchdog_device`]: srctree/include/linux/watchdog.h
+#[repr(transparent)]
+pub struct Device(Opaque<bindings::watchdog_device>);
+
+impl Device {
+    /// Creates a new [`Device`] reference from a raw pointer.
+    ///
+    /// # Safety
+    ///
+    /// - `ptr` must point at a valid `watchdog_device`.
+    /// - The returned reference must not outlive the callback invocation.
+    unsafe fn from_raw<'a>(ptr: *mut bindings::watchdog_device) -> &'a Self {
+        // CAST: `Self` is a `repr(transparent)` wrapper around
+        // `bindings::watchdog_device`.
+        let ptr = ptr.cast::<Self>();
+        // SAFETY: By the function requirements the pointer is valid.
+        unsafe { &*ptr }
+    }
+
+    /// Returns a raw pointer to the underlying `watchdog_device`.
+    fn as_raw(&self) -> *mut bindings::watchdog_device {
+        self.0.get()
+    }
+
+    /// Returns the current timeout in seconds.
+    pub fn timeout(&self) -> u32 {
+        // SAFETY: The struct invariant ensures the pointer is valid. The
+        // read of this `unsigned int` field mirrors how the C core and C
+        // drivers access it without synchronisation.
+        unsafe { (*self.as_raw()).timeout }
+    }
+
+    /// Sets the current timeout in seconds.
+    pub fn set_timeout(&self, timeout: u32) {
+        // SAFETY: The struct invariant ensures the pointer is valid. The
+        // field lives in an `Opaque`, so writing through a shared reference
+        // is allowed; the C core accesses this field the same way.
+        unsafe { (*self.as_raw()).timeout = timeout };
+    }
+
+    /// Returns the current pretimeout in seconds.
+    pub fn pretimeout(&self) -> u32 {
+        // SAFETY: See `timeout`.
+        unsafe { (*self.as_raw()).pretimeout }
+    }
+
+    /// Returns the minimum timeout in seconds.
+    pub fn min_timeout(&self) -> u32 {
+        // SAFETY: See `timeout`.
+        unsafe { (*self.as_raw()).min_timeout }
+    }
+
+    /// Returns the maximum timeout in seconds.
+    pub fn max_timeout(&self) -> u32 {
+        // SAFETY: See `timeout`.
+        unsafe { (*self.as_raw()).max_timeout }
+    }
+
+    /// Returns `true` if the watchdog is active.
+    pub fn is_active(&self) -> bool {
+        // SAFETY: The struct invariant ensures the pointer is valid.
+        unsafe { bindings::watchdog_active(self.as_raw()) }
+    }
+
+    /// Returns `true` if the hardware watchdog is running.
+    pub fn is_hw_running(&self) -> bool {
+        // SAFETY: The struct invariant ensures the pointer is valid.
+        unsafe { bindings::watchdog_hw_running(self.as_raw()) }
+    }
+}
+
+/// Flags describing the capabilities of a watchdog device, for use in
+/// [`super::Info::new`].
+pub mod flags {
+    /// The watchdog supports setting the timeout.
+    pub const SETTIMEOUT: u32 = bindings::WDIOF_SETTIMEOUT;
+    /// The watchdog supports keepalive pings.
+    pub const KEEPALIVEPING: u32 = bindings::WDIOF_KEEPALIVEPING;
+    /// The watchdog supports the magic close character.
+    pub const MAGICCLOSE: u32 = bindings::WDIOF_MAGICCLOSE;
+    /// The watchdog supports pretimeouts.
+    pub const PRETIMEOUT: u32 = bindings::WDIOF_PRETIMEOUT;
+}
+
+/// Watchdog identity information.
+///
+/// Wraps the kernel's [`struct watchdog_info`].
+///
+/// [`struct watchdog_info`]: srctree/include/uapi/linux/watchdog.h
+#[repr(transparent)]
+pub struct Info(bindings::watchdog_info);
+
+impl Info {
+    /// Creates a new [`Info`] with the given option [`flags`] and identity
+    /// string.
+    ///
+    /// The identity is truncated to 31 bytes to fit the fixed size,
+    /// NUL-terminated C field.
+    pub const fn new(options: u32, identity: &str) -> Self {
+        let mut id = [0u8; 32];
+        let bytes = identity.as_bytes();
+        let mut i = 0;
+        while i < bytes.len() && i < id.len() - 1 {
+            id[i] = bytes[i];
+            i += 1;
+        }
+        Self(bindings::watchdog_info {
+            options,
+            firmware_version: 0,
+            identity: id,
+        })
+    }
+}
+
+/// Configuration for registering a watchdog device.
+///
+/// All timeouts are in seconds; `max_hw_heartbeat_ms` is in milliseconds.
+///
+/// The watchdog core requires either a [`WatchdogOps::stop`] implementation
+/// or a non-zero `max_hw_heartbeat_ms`, otherwise registration fails with
+/// `EINVAL`.
+pub struct Options {
+    /// The default timeout in seconds.
+    pub timeout: u32,
+    /// The minimum settable timeout in seconds.
+    pub min_timeout: u32,
+    /// The maximum settable timeout in seconds.
+    pub max_timeout: u32,
+    /// Hardware limit for the maximum timeout, in milliseconds.
+    /// Zero means no hardware limit.
+    pub max_hw_heartbeat_ms: u32,
+    /// If `true`, the watchdog cannot be stopped once started.
+    pub nowayout: bool,
+    /// If `true`, the watchdog is stopped on system reboot. Requires a
+    /// [`WatchdogOps::stop`] implementation.
+    pub stop_on_reboot: bool,
+}
+
+impl Default for Options {
+    fn default() -> Self {
+        Self {
+            timeout: 0,
+            min_timeout: 0,
+            max_timeout: 0,
+            max_hw_heartbeat_ms: 0,
+            nowayout: cfg!(CONFIG_WATCHDOG_NOWAYOUT),
+            stop_on_reboot: false,
+        }
+    }
+}
+
+/// An adapter for the registration of a watchdog driver.
+struct Adapter<T: WatchdogOps> {
+    _p: PhantomData<T>,
+}
+
+impl<T: WatchdogOps> Adapter<T> {
+    /// Returns a reference to the driver data of a registered device.
+    ///
+    /// # Safety
+    ///
+    /// - `wdd` must be a `watchdog_device` registered via [`Registration`],
+    ///   so that its driver data points at a valid `T::Data`.
+    /// - The returned reference must not outlive the callback invocation.
+    unsafe fn data_from_raw<'a>(wdd: *mut bindings::watchdog_device) -> &'a T::Data {
+        // SAFETY: `Registration::register` stored a pointer to the heap
+        // allocated `T::Data` as driver data of this device, valid until
+        // unregistration tears down the paths that invoke callbacks.
+        unsafe { &*bindings::watchdog_get_drvdata(wdd).cast::<T::Data>() }
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn start_callback(wdd: *mut bindings::watchdog_device) -> c_int {
+        from_result(|| {
+            // SAFETY: The pointer is valid for the duration of the callback.
+            let dev = unsafe { Device::from_raw(wdd) };
+            // SAFETY: `wdd` was registered by `Registration`.
+            let data = unsafe { Self::data_from_raw(wdd) };
+            T::start(dev, data)?;
+            Ok(0)
+        })
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn stop_callback(wdd: *mut bindings::watchdog_device) -> c_int {
+        from_result(|| {
+            // SAFETY: The pointer is valid for the duration of the callback.
+            let dev = unsafe { Device::from_raw(wdd) };
+            // SAFETY: `wdd` was registered by `Registration`.
+            let data = unsafe { Self::data_from_raw(wdd) };
+            T::stop(dev, data)?;
+            Ok(0)
+        })
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn ping_callback(wdd: *mut bindings::watchdog_device) -> c_int {
+        from_result(|| {
+            // SAFETY: The pointer is valid for the duration of the callback.
+            let dev = unsafe { Device::from_raw(wdd) };
+            // SAFETY: `wdd` was registered by `Registration`.
+            let data = unsafe { Self::data_from_raw(wdd) };
+            T::ping(dev, data)?;
+            Ok(0)
+        })
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn set_timeout_callback(
+        wdd: *mut bindings::watchdog_device,
+        timeout: c_uint,
+    ) -> c_int {
+        from_result(|| {
+            // SAFETY: The pointer is valid for the duration of the callback.
+            let dev = unsafe { Device::from_raw(wdd) };
+            // SAFETY: `wdd` was registered by `Registration`.
+            let data = unsafe { Self::data_from_raw(wdd) };
+            T::set_timeout(dev, data, timeout)?;
+            Ok(0)
+        })
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn set_pretimeout_callback(
+        wdd: *mut bindings::watchdog_device,
+        pretimeout: c_uint,
+    ) -> c_int {
+        from_result(|| {
+            // SAFETY: The pointer is valid for the duration of the callback.
+            let dev = unsafe { Device::from_raw(wdd) };
+            // SAFETY: `wdd` was registered by `Registration`.
+            let data = unsafe { Self::data_from_raw(wdd) };
+            T::set_pretimeout(dev, data, pretimeout)?;
+            Ok(0)
+        })
+    }
+
+    /// # Safety
+    ///
+    /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+    unsafe extern "C" fn get_timeleft_callback(wdd: *mut bindings::watchdog_device) -> c_uint {
+        // SAFETY: The pointer is valid for the duration of the callback.
+        let dev = unsafe { Device::from_raw(wdd) };
+        // SAFETY: `wdd` was registered by `Registration`.
+        let data = unsafe { Self::data_from_raw(wdd) };
+        T::get_timeleft(dev, data)
+    }
+}
+
+/// Watchdog device operations.
+///
+/// Implement this trait to provide the callbacks for a watchdog device.
+/// Only [`WatchdogOps::start`] is mandatory; all other operations are
+/// optional. Note that the watchdog core requires either a
+/// [`WatchdogOps::stop`] implementation or a non-zero
+/// [`Options::max_hw_heartbeat_ms`].
+///
+/// Every callback receives a reference to the driver's private data of type
+/// [`WatchdogOps::Data`], which is passed to [`Registration::register`] and
+/// freed when the [`Registration`] is dropped.
+///
+/// Callbacks may be invoked concurrently (the watchdog core does not hold
+/// its lock on every path), so the data must be [`Sync`] and any mutable
+/// driver state needs its own synchronisation.
+#[vtable]
+pub trait WatchdogOps {
+    /// Driver private data, accessible from all callbacks.
+    type Data: Send + Sync;
+
+    /// Starts the watchdog device.
+    ///
+    /// This is the only mandatory operation.
+    fn start(dev: &Device, data: &Self::Data) -> Result;
+
+    /// Stops the watchdog device.
+    fn stop(_dev: &Device, _data: &Self::Data) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sends a keepalive ping to the watchdog device.
+    fn ping(_dev: &Device, _data: &Self::Data) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the watchdog timeout in seconds.
+    fn set_timeout(_dev: &Device, _data: &Self::Data, _timeout: u32) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the watchdog pretimeout in seconds.
+    fn set_pretimeout(_dev: &Device, _data: &Self::Data, _pretimeout: u32) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Returns the time left before the watchdog fires (in seconds).
+    fn get_timeleft(_dev: &Device, _data: &Self::Data) -> u32 {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// Creates a [`bindings::watchdog_ops`] instance from [`WatchdogOps`].
+///
+/// The `owner` field is filled in by [`Registration::register`].
+const fn create_watchdog_ops<T: WatchdogOps>() -> bindings::watchdog_ops {
+    bindings::watchdog_ops {
+        owner: core::ptr::null_mut(),
+        start: Some(Adapter::<T>::start_callback),
+        stop: if T::HAS_STOP {
+            Some(Adapter::<T>::stop_callback)
+        } else {
+            None
+        },
+        ping: if T::HAS_PING {
+            Some(Adapter::<T>::ping_callback)
+        } else {
+            None
+        },
+        status: None,
+        set_timeout: if T::HAS_SET_TIMEOUT {
+            Some(Adapter::<T>::set_timeout_callback)
+        } else {
+            None
+        },
+        set_pretimeout: if T::HAS_SET_PRETIMEOUT {
+            Some(Adapter::<T>::set_pretimeout_callback)
+        } else {
+            None
+        },
+        get_timeleft: if T::HAS_GET_TIMELEFT {
+            Some(Adapter::<T>::get_timeleft_callback)
+        } else {
+            None
+        },
+        restart: None,
+        ioctl: None,
+    }
+}
+
+/// The heap allocated backing storage of a registration.
+///
+/// The watchdog core stores pointers into this structure (the device `ops`
+/// pointer points at `ops`, the device driver data points at `data`), so it
+/// is heap allocated and only reclaimed after unregistration.
+struct Inner<T: WatchdogOps> {
+    wdd: Opaque<bindings::watchdog_device>,
+    ops: bindings::watchdog_ops,
+    data: T::Data,
+}
+
+/// Watchdog device registration.
+///
+/// Registers a watchdog device with the kernel. The device is unregistered
+/// and the driver data dropped when this instance is dropped.
+///
+/// # Invariants
+///
+/// - `inner` points at a live [`Inner`] obtained from [`KBox::into_raw`],
+///   registered with the watchdog core via `watchdog_register_device`.
+/// - The registered device's `ops` pointer and driver data pointer point at
+///   the `ops` and `data` fields of that [`Inner`].
+pub struct Registration<T: WatchdogOps> {
+    inner: *mut Inner<T>,
+}
+
+// SAFETY: `bindings::watchdog_device` contains raw pointers managed
+// exclusively by the watchdog core, which is thread safe. `T::Data` is
+// `Send` per the trait bound, so the heap allocation may be dropped from
+// any thread.
+unsafe impl<T: WatchdogOps> Send for Registration<T> {}
+
+// SAFETY: `Registration` has no `&self` methods, so sharing references to
+// it across threads gives access to nothing.
+unsafe impl<T: WatchdogOps> Sync for Registration<T> {}
+
+impl<T: WatchdogOps> Registration<T> {
+    /// Creates and registers a new watchdog device.
+    ///
+    /// `module` is used to set the `owner` field of the watchdog operations,
+    /// ensuring correct module reference counting while `/dev/watchdog` is
+    /// open. `data` is the driver's private data, passed to every callback.
+    ///
+    /// The watchdog is configured to stop on unregistration. Note that the
+    /// core skips that stop under `nowayout`, and that for drivers without
+    /// a [`WatchdogOps::stop`] implementation an active hardware watchdog
+    /// keeps running after unregistration with nobody pinging it, which
+    /// leads to a system reset. In all cases the core tears down its
+    /// character device and keepalive worker before unregistration
+    /// returns, so no callback can run after the driver data has been
+    /// freed.
+    pub fn register(
+        module: &'static crate::ThisModule,
+        parent: Option<&device::Device>,
+        info: &'static Info,
+        options: &Options,
+        data: T::Data,
+    ) -> Result<Self> {
+        // The core silently ignores the stop-on-reboot notifier for devices
+        // without a stop operation; reject the combination instead.
+        if options.stop_on_reboot && !T::HAS_STOP {
+            return Err(EINVAL);
+        }
+
+        let mut ops = create_watchdog_ops::<T>();
+        ops.owner = module.as_ptr();
+
+        let wdd = bindings::watchdog_device {
+            // CAST: `Info` is a `repr(transparent)` wrapper around
+            // `bindings::watchdog_info`.
+            info: core::ptr::from_ref(info).cast(),
+            timeout: options.timeout,
+            min_timeout: options.min_timeout,
+            max_timeout: options.max_timeout,
+            max_hw_heartbeat_ms: options.max_hw_heartbeat_ms,
+            parent: parent.map_or(core::ptr::null_mut(), |p| p.as_raw()),
+            ..pin_init::zeroed()
+        };
+
+        let inner = KBox::new(
+            Inner {
+                wdd: Opaque::new(wdd),
+                ops,
+                data,
+            },
+            GFP_KERNEL,
+        )?;
+
+        // Transfer the allocation to a raw pointer so that no reference to
+        // `Inner` exists while the C core holds pointers into it. It is
+        // reclaimed with `KBox::from_raw` in `Drop` (or below on failure).
+        let inner = KBox::into_raw(inner);
+
+        // The pointers below are derived from `inner` and point into the
+        // heap allocation, so they remain valid until the allocation is
+        // reclaimed.
+
+        // SAFETY: `inner` came from `KBox::into_raw`, so it points at a live
+        // allocation and the field projection stays in bounds.
+        let wdd_ptr = Opaque::cast_into(unsafe { &raw const (*inner).wdd });
+        // SAFETY: As above.
+        let ops_ptr: *const bindings::watchdog_ops = unsafe { &raw const (*inner).ops };
+        // SAFETY: As above.
+        let data_ptr: *const T::Data = unsafe { &raw const (*inner).data };
+
+        // SAFETY: `wdd_ptr` points at a valid `watchdog_device` that is not
+        // yet registered, so we have exclusive access.
+        unsafe {
+            (*wdd_ptr).ops = ops_ptr;
+            bindings::watchdog_set_drvdata(wdd_ptr, data_ptr.cast_mut().cast());
+            bindings::watchdog_set_nowayout(wdd_ptr, options.nowayout);
+            // Stop the watchdog on unregistration so that no callback can
+            // run after `Inner` has been freed.
+            bindings::watchdog_stop_on_unregister(wdd_ptr);
+            if options.stop_on_reboot {
+                bindings::watchdog_stop_on_reboot(wdd_ptr);
+            }
+        }
+
+        // SAFETY: `wdd_ptr` points at a fully initialised `watchdog_device`.
+        let ret = unsafe { bindings::watchdog_register_device(wdd_ptr) };
+        if ret != 0 {
+            // SAFETY: `inner` came from `KBox::into_raw` above and was not
+            // registered, so we have exclusive ownership of it.
+            drop(unsafe { KBox::from_raw(inner) });
+            return Err(Error::from_errno(ret));
+        }
+
+        // INVARIANT: `inner` is registered and its `ops`/`data` fields are
+        // referenced by the registered device.
+        Ok(Registration { inner })
+    }
+}
+
+impl<T: WatchdogOps> Drop for Registration<T> {
+    fn drop(&mut self) {
+        // SAFETY: The type invariant guarantees that `self.inner` is live
+        // and registered. Unregistration tears down the character device
+        // and the core's keepalive worker before returning, so no callback
+        // can run afterwards.
+        unsafe {
+            bindings::watchdog_unregister_device(Opaque::cast_into(&raw const (*self.inner).wdd))
+        };
+        // SAFETY: `self.inner` came from `KBox::into_raw` and the device is
+        // now unregistered, so we have exclusive ownership of the
+        // allocation and can reclaim and drop it.
+        drop(unsafe { KBox::from_raw(self.inner) });
+    }
+}

base-commit: 248951ddc14de84de3910f9b13f51491a8cd91df
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v2 2/3] rust: reboot: add emergency_restart() wrapper
  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 ` Artem Lytkin
  2026-07-23 16:15 ` [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
  2 siblings, 0 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-07-23 16:15 UTC (permalink / raw)
  To: linux-watchdog, rust-for-linux
  Cc: linux-kernel, wim, linux, ojeda, miguel.ojeda.sandonis, dakr,
	aliceryhl, a.hindborg, lossin

Add a minimal reboot module wrapping emergency_restart(), which
restarts the machine immediately without syncing or unmounting
filesystems.

This is needed by watchdog drivers that must restart the system when
the watchdog expires, such as the Rust software watchdog added in a
subsequent patch. emergency_restart() is safe to call from any
context, including the interrupt context of an expiring timer.

Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/kernel/lib.rs              |  1 +
 rust/kernel/reboot.rs           | 19 +++++++++++++++++++
 3 files changed, 21 insertions(+)
 create mode 100644 rust/kernel/reboot.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index e692d142c8608..89f0e1f65f6d7 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -81,6 +81,7 @@
 #include <linux/property.h>
 #include <linux/pwm.h>
 #include <linux/random.h>
+#include <linux/reboot.h>
 #include <linux/refcount.h>
 #include <linux/regulator/consumer.h>
 #include <linux/sched.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index a1130c4b82881..e603c07dc53dd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -113,6 +113,7 @@
 #[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)]
 pub mod pwm;
 pub mod rbtree;
+pub mod reboot;
 pub mod regulator;
 pub mod revocable;
 pub mod safety;
diff --git a/rust/kernel/reboot.rs b/rust/kernel/reboot.rs
new file mode 100644
index 0000000000000..f98a85d19f85d
--- /dev/null
+++ b/rust/kernel/reboot.rs
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Reboot support.
+//!
+//! C header: [`include/linux/reboot.h`](srctree/include/linux/reboot.h).
+
+use crate::bindings;
+
+/// Restarts the machine immediately, without syncing or unmounting
+/// filesystems and without going through the reboot notifier chains.
+///
+/// This is intended for situations where the system is in a state where an
+/// orderly shutdown is no longer possible, for example when a watchdog
+/// expires. It can be called from any context, including interrupt context.
+pub fn emergency_restart() {
+    // SAFETY: `emergency_restart` has no preconditions and is documented as
+    // safe to call from any context.
+    unsafe { bindings::emergency_restart() }
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver
  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
  2 siblings, 0 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-07-23 16:15 UTC (permalink / raw)
  To: linux-watchdog, rust-for-linux
  Cc: linux-kernel, wim, linux, ojeda, miguel.ojeda.sandonis, dakr,
	aliceryhl, a.hindborg, lossin

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


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-23 16:16 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox