* [PATCH 1/2] rust: watchdog: add watchdog device abstraction
@ 2026-03-23 19:51 Artem Lytkin
2026-03-23 19:51 ` [PATCH 2/2] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-03-23 19:51 UTC (permalink / raw)
To: linux-watchdog, rust-for-linux
Cc: wim, linux, ojeda, dakr, aliceryhl, a.hindborg, lossin
Add Rust abstractions for the Linux watchdog subsystem, enabling
watchdog drivers to be written in Rust.
The abstraction follows the pattern established by the PHY driver
abstraction (rust/kernel/net/phy.rs):
- A `Device` wrapper around `struct watchdog_device` providing safe
accessors for timeout, pretimeout, and status fields.
- A `#[vtable] WatchdogOps` trait where `start()` is mandatory and
`stop()`, `ping()`, `set_timeout()`, `set_pretimeout()`, and
`get_timeleft()` are optional.
- An `Adapter` struct with `unsafe extern "C" fn` callbacks that
bridge the C watchdog core to Rust trait methods.
- A `create_watchdog_ops()` const function that populates the C
`watchdog_ops` struct at compile time.
- A `Registration` type that heap-allocates the `watchdog_device`
via `KBox` to ensure pointer stability (the C core stores
back-pointers into the device struct), and wraps
`watchdog_register_device` / `watchdog_unregister_device` with
RAII semantics.
The `Registration::register` method takes a `&'static ThisModule`
parameter to set the `owner` field in `watchdog_ops`, ensuring
correct module reference counting when userspace opens /dev/watchdog.
Also add C helper functions for inline watchdog functions that bindgen
cannot handle and include `linux/watchdog.h` in the bindings header.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
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 | 348 ++++++++++++++++++++++++++++++++
5 files changed, 390 insertions(+)
create mode 100644 rust/helpers/watchdog.c
create mode 100644 rust/kernel/watchdog.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b422..df53d03846808 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -84,6 +84,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 79c72762ad9c4..e53e9977fd3e7 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -60,5 +60,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..175cebbeb1b36
--- /dev/null
+++ b/rust/helpers/watchdog.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/watchdog.h>
+
+bool rust_helper_watchdog_active(struct watchdog_device *wdd)
+{
+ return watchdog_active(wdd);
+}
+
+bool rust_helper_watchdog_hw_running(struct watchdog_device *wdd)
+{
+ return watchdog_hw_running(wdd);
+}
+
+void rust_helper_watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
+{
+ watchdog_set_nowayout(wdd, nowayout);
+}
+
+void rust_helper_watchdog_stop_on_reboot(struct watchdog_device *wdd)
+{
+ watchdog_stop_on_reboot(wdd);
+}
+
+void rust_helper_watchdog_stop_on_unregister(struct watchdog_device *wdd)
+{
+ watchdog_stop_on_unregister(wdd);
+}
+
+void rust_helper_watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
+{
+ watchdog_set_drvdata(wdd, data);
+}
+
+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 f812cf1200428..e65b8bd721978 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -149,6 +149,8 @@
pub mod transmute;
pub mod types;
pub mod uaccess;
+#[cfg(CONFIG_WATCHDOG)]
+pub mod watchdog;
#[cfg(CONFIG_USB = "y")]
pub mod usb;
pub mod workqueue;
diff --git a/rust/kernel/watchdog.rs b/rust/kernel/watchdog.rs
new file mode 100644
index 0000000000000..e65438f004109
--- /dev/null
+++ b/rust/kernel/watchdog.rs
@@ -0,0 +1,348 @@
+// 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 and the watchdog core serialises access to the device
+/// during callback execution.
+///
+/// [`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 caller must be in a callback context where the watchdog core
+ /// serialises access.
+ /// - The returned reference must not outlive the callback invocation.
+ unsafe fn from_raw<'a>(ptr: *mut bindings::watchdog_device) -> &'a mut 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 and we have
+ // exclusive access for the duration of the callback.
+ unsafe { &mut *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 we may access this field.
+ unsafe { (*self.as_raw()).timeout }
+ }
+
+ /// Sets the current timeout in seconds.
+ pub fn set_timeout(&mut self, timeout: u32) {
+ // SAFETY: The struct invariant ensures exclusive access.
+ unsafe { (*self.as_raw()).timeout = timeout };
+ }
+
+ /// Returns the current pretimeout in seconds.
+ pub fn pretimeout(&self) -> u32 {
+ // SAFETY: The struct invariant ensures we may access this field.
+ unsafe { (*self.as_raw()).pretimeout }
+ }
+
+ /// Returns the minimum timeout in seconds.
+ pub fn min_timeout(&self) -> u32 {
+ // SAFETY: The struct invariant ensures we may access this field.
+ unsafe { (*self.as_raw()).min_timeout }
+ }
+
+ /// Returns the maximum timeout in seconds.
+ pub fn max_timeout(&self) -> u32 {
+ // SAFETY: The struct invariant ensures we may access this field.
+ 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()) }
+ }
+}
+
+/// An adapter for the registration of a watchdog driver.
+struct Adapter<T: WatchdogOps> {
+ _p: PhantomData<T>,
+}
+
+impl<T: WatchdogOps> Adapter<T> {
+ /// # Safety
+ ///
+ /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
+ unsafe extern "C" fn start_callback(
+ wdd: *mut bindings::watchdog_device,
+ ) -> core::ffi::c_int {
+ from_result(|| {
+ // SAFETY: The watchdog core serialises access to the device.
+ let dev = unsafe { Device::from_raw(wdd) };
+ T::start(dev)?;
+ 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,
+ ) -> core::ffi::c_int {
+ from_result(|| {
+ // SAFETY: The watchdog core serialises access to the device.
+ let dev = unsafe { Device::from_raw(wdd) };
+ T::stop(dev)?;
+ 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,
+ ) -> core::ffi::c_int {
+ from_result(|| {
+ // SAFETY: The watchdog core serialises access to the device.
+ let dev = unsafe { Device::from_raw(wdd) };
+ T::ping(dev)?;
+ 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: core::ffi::c_uint,
+ ) -> core::ffi::c_int {
+ from_result(|| {
+ // SAFETY: The watchdog core serialises access to the device.
+ let dev = unsafe { Device::from_raw(wdd) };
+ T::set_timeout(dev, 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: core::ffi::c_uint,
+ ) -> core::ffi::c_int {
+ from_result(|| {
+ // SAFETY: The watchdog core serialises access to the device.
+ let dev = unsafe { Device::from_raw(wdd) };
+ T::set_pretimeout(dev, 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,
+ ) -> core::ffi::c_uint {
+ // SAFETY: The watchdog core serialises access to the device.
+ // We create a shared reference since get_timeleft only needs &Device.
+ let dev = unsafe { &*(wdd.cast::<Device>()) };
+ T::get_timeleft(dev)
+ }
+}
+
+/// Watchdog device operations.
+///
+/// Implement this trait to provide the callbacks for a watchdog device.
+/// Only [`WatchdogOps::start`] is mandatory; all other operations are optional.
+#[vtable]
+pub trait WatchdogOps {
+ /// Starts the watchdog device.
+ ///
+ /// This is the only mandatory operation.
+ fn start(dev: &mut Device) -> Result;
+
+ /// Stops the watchdog device.
+ fn stop(_dev: &mut Device) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Sends a keepalive ping to the watchdog device.
+ fn ping(_dev: &mut Device) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Sets the watchdog timeout in seconds.
+ fn set_timeout(_dev: &mut Device, _timeout: u32) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Sets the watchdog pretimeout in seconds.
+ fn set_pretimeout(_dev: &mut Device, _pretimeout: u32) -> Result {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Returns the time left before the watchdog fires (in seconds).
+ fn get_timeleft(_dev: &Device) -> u32 {
+ 0
+ }
+}
+
+/// Creates a [`bindings::watchdog_ops`] instance from [`WatchdogOps`].
+///
+/// This populates the C ops struct at compile time based on which trait
+/// methods are implemented. The `owner` field must be set separately by
+/// the caller before registration.
+pub 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,
+ }
+}
+
+/// Watchdog device registration.
+///
+/// Registers a watchdog device with the kernel. The device will be
+/// unregistered when this instance is dropped.
+///
+/// The [`watchdog_device`] is heap-allocated to ensure a stable address,
+/// since the watchdog core stores pointers back into it.
+///
+/// # Invariants
+///
+/// `inner` points to a heap-allocated `watchdog_device` that is registered
+/// with the watchdog core via `watchdog_register_device`.
+///
+/// [`watchdog_device`]: srctree/include/linux/watchdog.h
+pub struct Registration {
+ inner: KBox<Opaque<bindings::watchdog_device>>,
+}
+
+// SAFETY: `watchdog_unregister_device` can be called from any context.
+// The `KBox` ensures the `watchdog_device` has a stable heap address that
+// remains valid regardless of where the `Registration` is moved.
+unsafe impl Send for Registration {}
+
+// SAFETY: The only method that returns a reference to the device (`device()`)
+// returns `&Device` (shared reference), which only exposes read-only accessors.
+// The watchdog core serialises access during callbacks.
+unsafe impl Sync for Registration {}
+
+impl Registration {
+ /// Creates and registers a new watchdog device.
+ ///
+ /// The `module` parameter is used to set the `owner` field in the
+ /// watchdog ops, ensuring correct module reference counting when
+ /// userspace opens `/dev/watchdog`.
+ pub fn register(
+ module: &'static crate::ThisModule,
+ parent: Option<&device::Device>,
+ info: &'static bindings::watchdog_info,
+ ops: &'static mut bindings::watchdog_ops,
+ timeout: u32,
+ min_timeout: u32,
+ max_timeout: u32,
+ nowayout: bool,
+ ) -> Result<Self> {
+ // Set the module owner so the kernel holds a reference to our
+ // module while /dev/watchdog is open.
+ ops.owner = module.as_ptr();
+
+ let mut wdd: bindings::watchdog_device =
+ // SAFETY: Zeroing out `watchdog_device` is valid since all fields
+ // are simple types (integers, pointers) that can be zero-initialised.
+ unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
+
+ wdd.info = info;
+ wdd.ops = ops;
+ wdd.timeout = timeout;
+ wdd.min_timeout = min_timeout;
+ wdd.max_timeout = max_timeout;
+ wdd.parent = if let Some(p) = parent {
+ p.as_raw()
+ } else {
+ core::ptr::null_mut()
+ };
+
+ // SAFETY: The watchdog_device is fully initialised.
+ unsafe { bindings::watchdog_set_nowayout(&mut wdd, nowayout) };
+ // SAFETY: The watchdog_device is fully initialised.
+ unsafe { bindings::watchdog_stop_on_reboot(&mut wdd) };
+
+ // Heap-allocate the watchdog_device so that the C core's back-pointers
+ // remain valid even if this Registration is moved.
+ let inner = KBox::new(Opaque::new(wdd), GFP_KERNEL)?;
+
+ // SAFETY: `inner` points to a properly initialised watchdog_device on
+ // the heap. The pointer will remain stable for the lifetime of the KBox.
+ to_result(unsafe { bindings::watchdog_register_device(inner.get()) })?;
+
+ Ok(Registration { inner })
+ }
+
+ /// Returns a reference to the underlying [`Device`].
+ pub fn device(&self) -> &Device {
+ // SAFETY: `inner` points to a valid `watchdog_device`.
+ unsafe { &*(self.inner.get() as *const Device) }
+ }
+}
+
+impl Drop for Registration {
+ fn drop(&mut self) {
+ // SAFETY: The type invariant guarantees that `self.inner` is registered.
+ unsafe { bindings::watchdog_unregister_device(self.inner.get()) };
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] watchdog: softdog_rs: add Rust software watchdog driver
2026-03-23 19:51 [PATCH 1/2] rust: watchdog: add watchdog device abstraction Artem Lytkin
@ 2026-03-23 19:51 ` Artem Lytkin
2026-03-26 5:02 ` [PATCH 1/2] rust: watchdog: add watchdog device abstraction Guenter Roeck
2026-03-27 9:01 ` Miguel Ojeda
2 siblings, 0 replies; 4+ messages in thread
From: Artem Lytkin @ 2026-03-23 19:51 UTC (permalink / raw)
To: linux-watchdog, rust-for-linux
Cc: wim, linux, ojeda, dakr, aliceryhl, a.hindborg, lossin
Add a Rust software watchdog driver that uses the new Rust watchdog
abstraction. This demonstrates that the abstraction is functional and
can be used to write real watchdog drivers.
The driver implements start and stop operations. Unlike the C softdog,
this simplified version does not use hrtimers for the actual countdown
-- the watchdog core handles the timeout expiry and keepalive
management. This makes it suitable as a proof-of-concept driver for the
Rust watchdog abstraction.
The driver registers as "Rust Software Watchdog" with a default timeout
of 60 seconds, matching the C softdog's defaults.
The Kconfig option SOFT_WATCHDOG_RS depends on !SOFT_WATCHDOG to
prevent both drivers from being enabled simultaneously.
Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
---
drivers/watchdog/Kconfig | 12 +++++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/softdog_rs.rs | 88 ++++++++++++++++++++++++++++++++++
3 files changed, 101 insertions(+)
create mode 100644 drivers/watchdog/softdog_rs.rs
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index d3b9df7d466b0..b7aad39f0de01 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
+ select WATCHDOG_CORE
+ help
+ A software watchdog driver written in Rust using the Rust watchdog
+ device abstraction. This is a simplified 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 ba52099b12539..3039e5068f835 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -239,6 +239,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..c8ff125e817bc
--- /dev/null
+++ b/drivers/watchdog/softdog_rs.rs
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust software watchdog driver.
+//!
+//! A simplified Rust implementation of the software watchdog, demonstrating
+//! the Rust watchdog device abstraction.
+
+use kernel::prelude::*;
+use kernel::watchdog;
+
+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",
+}
+
+struct SoftdogOps;
+
+#[vtable]
+impl watchdog::WatchdogOps for SoftdogOps {
+ fn start(dev: &mut watchdog::Device) -> Result {
+ pr_info!("watchdog started (timeout={}s)\n", dev.timeout());
+ Ok(())
+ }
+
+ fn stop(_dev: &mut watchdog::Device) -> Result {
+ pr_info!("watchdog stopped\n");
+ Ok(())
+ }
+}
+
+static SOFTDOG_INFO: bindings::watchdog_info = bindings::watchdog_info {
+ options: bindings::WDIOF_SETTIMEOUT
+ | bindings::WDIOF_KEEPALIVEPING
+ | bindings::WDIOF_MAGICCLOSE,
+ firmware_version: 0,
+ identity: {
+ let mut id = [0u8; 32];
+ let s = b"Rust Software Watchdog";
+ let mut i = 0;
+ while i < s.len() {
+ id[i] = s[i];
+ i += 1;
+ }
+ id
+ },
+};
+
+static mut SOFTDOG_OPS: bindings::watchdog_ops =
+ watchdog::create_watchdog_ops::<SoftdogOps>();
+
+struct SoftdogModule {
+ _reg: watchdog::Registration,
+}
+
+impl kernel::Module for SoftdogModule {
+ fn init(module: &'static ThisModule) -> Result<Self> {
+ // SAFETY: SOFTDOG_OPS is only mutated here, before registration,
+ // and this function is called exactly once during module init.
+ let ops = unsafe { &mut *core::ptr::addr_of_mut!(SOFTDOG_OPS) };
+
+ let reg = watchdog::Registration::register(
+ module,
+ None,
+ &SOFTDOG_INFO,
+ ops,
+ DEFAULT_MARGIN,
+ 1,
+ MAX_MARGIN,
+ false,
+ )?;
+
+ pr_info!("initialized (timeout={}s)\n", DEFAULT_MARGIN);
+
+ Ok(SoftdogModule { _reg: reg })
+ }
+}
+
+impl Drop for SoftdogModule {
+ fn drop(&mut self) {
+ pr_info!("exit\n");
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] rust: watchdog: add watchdog device abstraction
2026-03-23 19:51 [PATCH 1/2] rust: watchdog: add watchdog device abstraction Artem Lytkin
2026-03-23 19:51 ` [PATCH 2/2] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
@ 2026-03-26 5:02 ` Guenter Roeck
2026-03-27 9:01 ` Miguel Ojeda
2 siblings, 0 replies; 4+ messages in thread
From: Guenter Roeck @ 2026-03-26 5:02 UTC (permalink / raw)
To: Artem Lytkin, linux-watchdog, rust-for-linux
Cc: wim, ojeda, dakr, aliceryhl, a.hindborg, lossin
On 3/23/26 12:51, Artem Lytkin wrote:
> Add Rust abstractions for the Linux watchdog subsystem, enabling
> watchdog drivers to be written in Rust.
>
> The abstraction follows the pattern established by the PHY driver
> abstraction (rust/kernel/net/phy.rs):
>
> - A `Device` wrapper around `struct watchdog_device` providing safe
> accessors for timeout, pretimeout, and status fields.
>
> - A `#[vtable] WatchdogOps` trait where `start()` is mandatory and
> `stop()`, `ping()`, `set_timeout()`, `set_pretimeout()`, and
> `get_timeleft()` are optional.
>
> - An `Adapter` struct with `unsafe extern "C" fn` callbacks that
> bridge the C watchdog core to Rust trait methods.
>
> - A `create_watchdog_ops()` const function that populates the C
> `watchdog_ops` struct at compile time.
>
> - A `Registration` type that heap-allocates the `watchdog_device`
> via `KBox` to ensure pointer stability (the C core stores
> back-pointers into the device struct), and wraps
> `watchdog_register_device` / `watchdog_unregister_device` with
> RAII semantics.
>
> The `Registration::register` method takes a `&'static ThisModule`
> parameter to set the `owner` field in `watchdog_ops`, ensuring
> correct module reference counting when userspace opens /dev/watchdog.
>
> Also add C helper functions for inline watchdog functions that bindgen
> cannot handle and include `linux/watchdog.h` in the bindings header.
>
> Signed-off-by: Artem Lytkin <iprintercanon@gmail.com>
My knowledge and understanding of rust is pretty much zero, so I won't
provide any feedback on this patch.
However, you might want to have a look at the Sashiko feedback and
either address the concerns or add comments explaining why they
are irrelevant and how the driver works even with the limitations
outlined by the AI agent.
https://sashiko.dev/#/patchset/20260323195138.5541-1-iprintercanon%40gmail.com
Thanks,
Guenter
> ---
> 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 | 348 ++++++++++++++++++++++++++++++++
> 5 files changed, 390 insertions(+)
> create mode 100644 rust/helpers/watchdog.c
> create mode 100644 rust/kernel/watchdog.rs
>
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index a067038b4b422..df53d03846808 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -84,6 +84,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 79c72762ad9c4..e53e9977fd3e7 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -60,5 +60,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..175cebbeb1b36
> --- /dev/null
> +++ b/rust/helpers/watchdog.c
> @@ -0,0 +1,38 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/watchdog.h>
> +
> +bool rust_helper_watchdog_active(struct watchdog_device *wdd)
> +{
> + return watchdog_active(wdd);
> +}
> +
> +bool rust_helper_watchdog_hw_running(struct watchdog_device *wdd)
> +{
> + return watchdog_hw_running(wdd);
> +}
> +
> +void rust_helper_watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
> +{
> + watchdog_set_nowayout(wdd, nowayout);
> +}
> +
> +void rust_helper_watchdog_stop_on_reboot(struct watchdog_device *wdd)
> +{
> + watchdog_stop_on_reboot(wdd);
> +}
> +
> +void rust_helper_watchdog_stop_on_unregister(struct watchdog_device *wdd)
> +{
> + watchdog_stop_on_unregister(wdd);
> +}
> +
> +void rust_helper_watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
> +{
> + watchdog_set_drvdata(wdd, data);
> +}
> +
> +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 f812cf1200428..e65b8bd721978 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -149,6 +149,8 @@
> pub mod transmute;
> pub mod types;
> pub mod uaccess;
> +#[cfg(CONFIG_WATCHDOG)]
> +pub mod watchdog;
> #[cfg(CONFIG_USB = "y")]
> pub mod usb;
> pub mod workqueue;
> diff --git a/rust/kernel/watchdog.rs b/rust/kernel/watchdog.rs
> new file mode 100644
> index 0000000000000..e65438f004109
> --- /dev/null
> +++ b/rust/kernel/watchdog.rs
> @@ -0,0 +1,348 @@
> +// 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 and the watchdog core serialises access to the device
> +/// during callback execution.
> +///
> +/// [`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 caller must be in a callback context where the watchdog core
> + /// serialises access.
> + /// - The returned reference must not outlive the callback invocation.
> + unsafe fn from_raw<'a>(ptr: *mut bindings::watchdog_device) -> &'a mut 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 and we have
> + // exclusive access for the duration of the callback.
> + unsafe { &mut *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 we may access this field.
> + unsafe { (*self.as_raw()).timeout }
> + }
> +
> + /// Sets the current timeout in seconds.
> + pub fn set_timeout(&mut self, timeout: u32) {
> + // SAFETY: The struct invariant ensures exclusive access.
> + unsafe { (*self.as_raw()).timeout = timeout };
> + }
> +
> + /// Returns the current pretimeout in seconds.
> + pub fn pretimeout(&self) -> u32 {
> + // SAFETY: The struct invariant ensures we may access this field.
> + unsafe { (*self.as_raw()).pretimeout }
> + }
> +
> + /// Returns the minimum timeout in seconds.
> + pub fn min_timeout(&self) -> u32 {
> + // SAFETY: The struct invariant ensures we may access this field.
> + unsafe { (*self.as_raw()).min_timeout }
> + }
> +
> + /// Returns the maximum timeout in seconds.
> + pub fn max_timeout(&self) -> u32 {
> + // SAFETY: The struct invariant ensures we may access this field.
> + 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()) }
> + }
> +}
> +
> +/// An adapter for the registration of a watchdog driver.
> +struct Adapter<T: WatchdogOps> {
> + _p: PhantomData<T>,
> +}
> +
> +impl<T: WatchdogOps> Adapter<T> {
> + /// # Safety
> + ///
> + /// `wdd` must be passed by the corresponding callback in `watchdog_ops`.
> + unsafe extern "C" fn start_callback(
> + wdd: *mut bindings::watchdog_device,
> + ) -> core::ffi::c_int {
> + from_result(|| {
> + // SAFETY: The watchdog core serialises access to the device.
> + let dev = unsafe { Device::from_raw(wdd) };
> + T::start(dev)?;
> + 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,
> + ) -> core::ffi::c_int {
> + from_result(|| {
> + // SAFETY: The watchdog core serialises access to the device.
> + let dev = unsafe { Device::from_raw(wdd) };
> + T::stop(dev)?;
> + 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,
> + ) -> core::ffi::c_int {
> + from_result(|| {
> + // SAFETY: The watchdog core serialises access to the device.
> + let dev = unsafe { Device::from_raw(wdd) };
> + T::ping(dev)?;
> + 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: core::ffi::c_uint,
> + ) -> core::ffi::c_int {
> + from_result(|| {
> + // SAFETY: The watchdog core serialises access to the device.
> + let dev = unsafe { Device::from_raw(wdd) };
> + T::set_timeout(dev, 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: core::ffi::c_uint,
> + ) -> core::ffi::c_int {
> + from_result(|| {
> + // SAFETY: The watchdog core serialises access to the device.
> + let dev = unsafe { Device::from_raw(wdd) };
> + T::set_pretimeout(dev, 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,
> + ) -> core::ffi::c_uint {
> + // SAFETY: The watchdog core serialises access to the device.
> + // We create a shared reference since get_timeleft only needs &Device.
> + let dev = unsafe { &*(wdd.cast::<Device>()) };
> + T::get_timeleft(dev)
> + }
> +}
> +
> +/// Watchdog device operations.
> +///
> +/// Implement this trait to provide the callbacks for a watchdog device.
> +/// Only [`WatchdogOps::start`] is mandatory; all other operations are optional.
> +#[vtable]
> +pub trait WatchdogOps {
> + /// Starts the watchdog device.
> + ///
> + /// This is the only mandatory operation.
> + fn start(dev: &mut Device) -> Result;
> +
> + /// Stops the watchdog device.
> + fn stop(_dev: &mut Device) -> Result {
> + build_error!(VTABLE_DEFAULT_ERROR)
> + }
> +
> + /// Sends a keepalive ping to the watchdog device.
> + fn ping(_dev: &mut Device) -> Result {
> + build_error!(VTABLE_DEFAULT_ERROR)
> + }
> +
> + /// Sets the watchdog timeout in seconds.
> + fn set_timeout(_dev: &mut Device, _timeout: u32) -> Result {
> + build_error!(VTABLE_DEFAULT_ERROR)
> + }
> +
> + /// Sets the watchdog pretimeout in seconds.
> + fn set_pretimeout(_dev: &mut Device, _pretimeout: u32) -> Result {
> + build_error!(VTABLE_DEFAULT_ERROR)
> + }
> +
> + /// Returns the time left before the watchdog fires (in seconds).
> + fn get_timeleft(_dev: &Device) -> u32 {
> + 0
> + }
> +}
> +
> +/// Creates a [`bindings::watchdog_ops`] instance from [`WatchdogOps`].
> +///
> +/// This populates the C ops struct at compile time based on which trait
> +/// methods are implemented. The `owner` field must be set separately by
> +/// the caller before registration.
> +pub 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,
> + }
> +}
> +
> +/// Watchdog device registration.
> +///
> +/// Registers a watchdog device with the kernel. The device will be
> +/// unregistered when this instance is dropped.
> +///
> +/// The [`watchdog_device`] is heap-allocated to ensure a stable address,
> +/// since the watchdog core stores pointers back into it.
> +///
> +/// # Invariants
> +///
> +/// `inner` points to a heap-allocated `watchdog_device` that is registered
> +/// with the watchdog core via `watchdog_register_device`.
> +///
> +/// [`watchdog_device`]: srctree/include/linux/watchdog.h
> +pub struct Registration {
> + inner: KBox<Opaque<bindings::watchdog_device>>,
> +}
> +
> +// SAFETY: `watchdog_unregister_device` can be called from any context.
> +// The `KBox` ensures the `watchdog_device` has a stable heap address that
> +// remains valid regardless of where the `Registration` is moved.
> +unsafe impl Send for Registration {}
> +
> +// SAFETY: The only method that returns a reference to the device (`device()`)
> +// returns `&Device` (shared reference), which only exposes read-only accessors.
> +// The watchdog core serialises access during callbacks.
> +unsafe impl Sync for Registration {}
> +
> +impl Registration {
> + /// Creates and registers a new watchdog device.
> + ///
> + /// The `module` parameter is used to set the `owner` field in the
> + /// watchdog ops, ensuring correct module reference counting when
> + /// userspace opens `/dev/watchdog`.
> + pub fn register(
> + module: &'static crate::ThisModule,
> + parent: Option<&device::Device>,
> + info: &'static bindings::watchdog_info,
> + ops: &'static mut bindings::watchdog_ops,
> + timeout: u32,
> + min_timeout: u32,
> + max_timeout: u32,
> + nowayout: bool,
> + ) -> Result<Self> {
> + // Set the module owner so the kernel holds a reference to our
> + // module while /dev/watchdog is open.
> + ops.owner = module.as_ptr();
> +
> + let mut wdd: bindings::watchdog_device =
> + // SAFETY: Zeroing out `watchdog_device` is valid since all fields
> + // are simple types (integers, pointers) that can be zero-initialised.
> + unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
> +
> + wdd.info = info;
> + wdd.ops = ops;
> + wdd.timeout = timeout;
> + wdd.min_timeout = min_timeout;
> + wdd.max_timeout = max_timeout;
> + wdd.parent = if let Some(p) = parent {
> + p.as_raw()
> + } else {
> + core::ptr::null_mut()
> + };
> +
> + // SAFETY: The watchdog_device is fully initialised.
> + unsafe { bindings::watchdog_set_nowayout(&mut wdd, nowayout) };
> + // SAFETY: The watchdog_device is fully initialised.
> + unsafe { bindings::watchdog_stop_on_reboot(&mut wdd) };
> +
> + // Heap-allocate the watchdog_device so that the C core's back-pointers
> + // remain valid even if this Registration is moved.
> + let inner = KBox::new(Opaque::new(wdd), GFP_KERNEL)?;
> +
> + // SAFETY: `inner` points to a properly initialised watchdog_device on
> + // the heap. The pointer will remain stable for the lifetime of the KBox.
> + to_result(unsafe { bindings::watchdog_register_device(inner.get()) })?;
> +
> + Ok(Registration { inner })
> + }
> +
> + /// Returns a reference to the underlying [`Device`].
> + pub fn device(&self) -> &Device {
> + // SAFETY: `inner` points to a valid `watchdog_device`.
> + unsafe { &*(self.inner.get() as *const Device) }
> + }
> +}
> +
> +impl Drop for Registration {
> + fn drop(&mut self) {
> + // SAFETY: The type invariant guarantees that `self.inner` is registered.
> + unsafe { bindings::watchdog_unregister_device(self.inner.get()) };
> + }
> +}
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] rust: watchdog: add watchdog device abstraction
2026-03-23 19:51 [PATCH 1/2] rust: watchdog: add watchdog device abstraction Artem Lytkin
2026-03-23 19:51 ` [PATCH 2/2] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
2026-03-26 5:02 ` [PATCH 1/2] rust: watchdog: add watchdog device abstraction Guenter Roeck
@ 2026-03-27 9:01 ` Miguel Ojeda
2 siblings, 0 replies; 4+ messages in thread
From: Miguel Ojeda @ 2026-03-27 9:01 UTC (permalink / raw)
To: Artem Lytkin, Gary Guo
Cc: linux-watchdog, rust-for-linux, wim, linux, ojeda, dakr,
aliceryhl, a.hindborg, lossin
On Mon, Mar 23, 2026 at 8:52 PM Artem Lytkin <iprintercanon@gmail.com> wrote:
>
> +bool rust_helper_watchdog_active(struct watchdog_device *wdd)
> +{
> + return watchdog_active(wdd);
> +}
Without commenting on the rest and before I forget: these should
likely be marked with `__rust_helper`.
I added a comment in our initial PR for LLM prompts so that e.g.
Sashiko may notice this sort of thing too (Cc Gary).
Cheers,
Miguel
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-03-27 9:01 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-23 19:51 [PATCH 1/2] rust: watchdog: add watchdog device abstraction Artem Lytkin
2026-03-23 19:51 ` [PATCH 2/2] watchdog: softdog_rs: add Rust software watchdog driver Artem Lytkin
2026-03-26 5:02 ` [PATCH 1/2] rust: watchdog: add watchdog device abstraction Guenter Roeck
2026-03-27 9:01 ` Miguel Ojeda
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox