From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-lf1-f53.google.com (mail-lf1-f53.google.com [209.85.167.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 21D863DDDCE for ; Mon, 23 Mar 2026 19:52:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.53 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774295557; cv=none; b=dYHfmlZ9b+2QxorQeYOEIpZrEAz5aK5GWsxSBtb+vxYUwq1DZsvZgzjhh76GFxpzXW1T6x0UzQjWF/IiwkOZoeoVLfqRgm9FrdJLy7H3pfQOHhlzLlz37ShO6+yEOZh7uoTzTZlnqu6ad1FZZDQ/UcLIzcHJ2ch0q7SA3Ywapkk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774295557; c=relaxed/simple; bh=tKw51w6DAtP6sT2Nw5qJcEgJiVYZChneAHfolCmNXBA=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=S9WOttvB3tg7P2VfCQ/C4CYsFVVzodtVU2aiMA3HXJXq2sJ3xTNDsBl8b1Xdf9bVUhSiE+rMIYXaS0DU2gx5GHF4Ez02mg+DYTS7RPIVuw4fnHHxVA8RD30vhICjbjALmfj6nlIjqdxgg540rGsl/P95pYc2FXrjeXUup2lcl8I= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Wf9j6aUQ; arc=none smtp.client-ip=209.85.167.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Wf9j6aUQ" Received: by mail-lf1-f53.google.com with SMTP id 2adb3069b0e04-59e4989dacdso4630671e87.1 for ; Mon, 23 Mar 2026 12:52:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774295553; x=1774900353; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=zKouc+qGgGKCL4jWF0ezEEdpSbdgPjSFLMiZGkwTb4k=; b=Wf9j6aUQ1aX+Ukoq3Tubn8HrQf9t8nbeZiN/ACK2FgIT/0Ek8NZvCUqHk/ECrIQxg7 UPQ0dlnK1v/PDvsoPj4oamOrw4xqJpazWXkwksRKLJ/Yef/ENV3hHBERC19ZGTfEbFlR 0qWslQw2ezZ8DQ79iFHboyiJ2cgc97/UkusbBQ7gy/yCbCj0Zw3rqH1QuoYYbbWl9U/T n2n1cSCu1X03I99AjJuOnDAUje4dn89aSNer/Z/Rw+DHKpLMyD2zGTfqGIHQnbWOeoib eLYD775bTz4O5Kn6HYIA3qphDVA8LMKZ/J01IOWQSoCi0wJayJNIsVOerB2h4SURA3ry jepw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774295553; x=1774900353; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=zKouc+qGgGKCL4jWF0ezEEdpSbdgPjSFLMiZGkwTb4k=; b=I90FhC9UUesF1TgWlAXOptU/17GYiKcksUdC4za+b1M6We80hYJDryr/MnHDuY0ehS yamziMrjcG3yYt7juvS6NXx371QM3bWbOjTch3Ae5m5Y1MGIo0LRxf7O7HXthO/0zjxQ T3PIdH8ePJLHBZXiDPRZzRjZs2MAKqs6RkBK9lGCIYirC5asqDpJOayvveK35DKnN84M MIOXqn2N9ff97cSoxAkIB9V29Hmq0rl9VQ6JWcp3AbZLaUj9DanqGbD8u94PxnUBTlhx 2OdUGGwQfYVDdtMhU4MnMFamghGgkjybz1MAFClT5sN1BTEz91LYuV2+R/QMpuvJCG0J 1IBQ== X-Gm-Message-State: AOJu0YxYqtW9D2OWsac7y0PVc8TfqduhN31oheE8Tj2Xj8jYXv3kYRZD 68hl5NG9W8us1X8IS7/01ZclDD+CzWgWre1qTyN6T5JfriBa9O3fOqSBmUH49bX0 X-Gm-Gg: ATEYQzz+hxHoKu6bm9ygs4h1VsF2cI7l1clP1OBf7SV9dPIQqN6fjGr/08cav0DaflL BSCWht3ROIIa0s3caHyzGBzIfyuXjBYDxCNglQsRVdq14bDS4mHnNssHpdWgxvXW0VeImmF2BuY sduUW2W6WJg+UyMlGXsFqouaeOz8mUZYg7GcbyoyfkkNNP1AcdKGxibGMWEiD0NV12FpvwtXsTH mex9EWHu/fj8LSHemM+LUDoI/O/ongTCgUF9pRGdavRixAg4pLe8mXuKGo2uiI6IY7kUg7z08pj yJSjTIgTiz75mIOtuzUXN5MiV5WoaywssgwABMTHx0fAWaU1UhK1Ifuz2RAkT0O82KVGzoueZXM GJ+atFxE+NA/B4mi/v3/DvAxKQhRNqNT16/UT5AA89qhsdqnlqqZbKBdk5PFHtgMYMqB5imJW+5 DfZVbeQB6s1POnlykujL/uaI7gB4eF4Frp/q16J3YwJUiaFJAZyu8yO2mMmw6/5yh+i/KY4cD9M 64glDK/pF8fI0XYHw== X-Received: by 2002:a05:6512:4007:b0:5a1:3bd2:7330 with SMTP id 2adb3069b0e04-5a285af4a19mr4209687e87.2.1774295552457; Mon, 23 Mar 2026 12:52:32 -0700 (PDT) Received: from localhost.localdomain (46-138-191-69.dynamic.spd-mgts.ru. [46.138.191.69]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-5a285192b5esm2713762e87.16.2026.03.23.12.52.31 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 12:52:31 -0700 (PDT) From: Artem Lytkin To: linux-kernel@vger.kernel.org Subject: [PATCH 1/2] rust: watchdog: add watchdog device abstraction Date: Mon, 23 Mar 2026 22:52:08 +0300 Message-ID: <20260323195209.5581-1-iprintercanon@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 #include #include +#include #include #include #include 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 + +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); + +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::(); + // 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 { + _p: PhantomData, +} + +impl Adapter { + /// # 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::()) }; + 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() -> bindings::watchdog_ops { + bindings::watchdog_ops { + owner: core::ptr::null_mut(), + start: Some(Adapter::::start_callback), + stop: if T::HAS_STOP { + Some(Adapter::::stop_callback) + } else { + None + }, + ping: if T::HAS_PING { + Some(Adapter::::ping_callback) + } else { + None + }, + status: None, + set_timeout: if T::HAS_SET_TIMEOUT { + Some(Adapter::::set_timeout_callback) + } else { + None + }, + set_pretimeout: if T::HAS_SET_PRETIMEOUT { + Some(Adapter::::set_pretimeout_callback) + } else { + None + }, + get_timeleft: if T::HAS_GET_TIMELEFT { + Some(Adapter::::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>, +} + +// 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 { + // 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