public inbox for rust-for-linux@vger.kernel.org
 help / color / mirror / Atom feed
From: Artem Lytkin <iprintercanon@gmail.com>
To: linux-watchdog@vger.kernel.org, rust-for-linux@vger.kernel.org
Cc: wim@linux-watchdog.org, linux@roeck-us.net, ojeda@kernel.org,
	dakr@kernel.org, aliceryhl@google.com, a.hindborg@kernel.org,
	lossin@kernel.org
Subject: [PATCH 1/2] rust: watchdog: add watchdog device abstraction
Date: Mon, 23 Mar 2026 22:51:37 +0300	[thread overview]
Message-ID: <20260323195138.5541-1-iprintercanon@gmail.com> (raw)

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


             reply	other threads:[~2026-03-23 19:52 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-23 19:51 Artem Lytkin [this message]
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

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=20260323195138.5541-1-iprintercanon@gmail.com \
    --to=iprintercanon@gmail.com \
    --cc=a.hindborg@kernel.org \
    --cc=aliceryhl@google.com \
    --cc=dakr@kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=lossin@kernel.org \
    --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