From nobody Fri Jul 24 05:21:29 2026 Received: from mail-lj1-f172.google.com (mail-lj1-f172.google.com [209.85.208.172]) (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 AEAE728C854 for ; Thu, 23 Jul 2026 16:16:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823375; cv=none; b=HCaIKwVC1Djmj4BeeWOEAqXSAI2ceOCDP+o6XXB5SpwtTuxZ5Xw/Xlag5TMjNC/LC3a0QuEvwm9biaqdB6aTAG11Ur/bLZ+WmupArmnZMZEWy4Gc5suMzwT67iXy3wu4F9WncWANE5U87UGgBfdobzCXQ1T35ftgh47+MlCFtLk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823375; c=relaxed/simple; bh=Nc16yMP7bKLgpTlJCafYhThY7V5BbPQMQQCfT008bao=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dtZfNWQS2X970a6wGHzO5Mg+iOUswqPz7Cq7SNJj8SKbdSp8ml2Eo7e7/g+tnczuA3jkLGoyVTnZ91imjPvMEU4Oz7PawUDYRXKY4gNKNeMIhgL/0ZNOOMoAr3bKmCq2yKy1+mmr+ICWqWM2OE1nsBGp1L2DBHNIabX+JmYexk0= 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=RIRgTSLJ; arc=none smtp.client-ip=209.85.208.172 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="RIRgTSLJ" Received: by mail-lj1-f172.google.com with SMTP id 38308e7fff4ca-39c923b8c2aso8846871fa.2 for ; Thu, 23 Jul 2026 09:16:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784823354; x=1785428154; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=GC2pBU7SWAmu3jI5sN/Z9z0x7aU14fmy5WI7sRRsOcQ=; b=RIRgTSLJ8iRlI74PE/9sPCQC9RX2ByuOcAHfHkeI11e8NppxDY61ZBjSMdF7NzGDNH K/EiNxjYhR/twDx/V/fdPZbTe2cfoyaXZaMa2gf1rN8WajHXithJZt9CwBiXOsnQgbDS 8T23Q68kfrrj7npQTZkbIwbXDQZIvkpw1qwOd5IxB4GVWO3VyzN+FoKToNvmfwGv1pyT rDJU2r7Ojg0ztf3zj3fbbmGBbquIx8032MrPSpYrIFtRTTVrakhRNQ/W2b8qaRwOT89W LoBN9OvkTjKIpSkrkWae78IvaDen8a0XtNxemahfFgZF4oSjGEVx7LZG42g3RdBCucrY ZZcg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784823354; x=1785428154; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=GC2pBU7SWAmu3jI5sN/Z9z0x7aU14fmy5WI7sRRsOcQ=; b=TpEqc7enEzv3OnQkrsgM8WJSXJNr79GW7aWTwzftYR5L+VDLi43ZYK0L783HRAU+Fh lj9iuaV8qJtygjz6XXEJ3txsGXBX4Vyy6FNAw0FopGraEw+deQGypSfJIi4oR1U4PGC3 Wo44Dp5MAR03Wwpgyjby1TYIfWh3acO6YSQ+Gp7PUFoRkIy+mFe1v1ry0rtCqzoVd0mq psXanXjKEVLoFuuSRdinpWVvRTvB/PEk+1+HpcmYzXD7NryB+NH0bRbXd8y2VzI9Ts/u /rJ202mwXWIC+EV1/xw37QgpikjHI8o810a+PqKFd9BZhiS+TFDorUjbOTG80gdLPVAX 9rng== X-Gm-Message-State: AOJu0YwxY1z4qIoIju3yhOyHw1VGgviNr/8A5GgO9KT7Y9QsPWV3m3gT V7RE1P7Bc8YOfh+fEiTzvbSCNZeQoJr0vFzjHEvFT6JBj6dFc8cwYbu/ X-Gm-Gg: AR+sD111kDh44r+Stcp5bFgjXr/ZlyDwPjDp2eWtjlyAHHKKZUjLLYSb6d6m0e5QLBX 5oglJsTeDKKA+Sm750G/JmjAljWpdRHhVW4ryQuaPdlV/nNdPIyRqhp76mRl8ga+aiEb/qSgh0y niWfZfs9B7FZi1L2ByAo9L2JsL2vgUAjrAoXvaOL5zS6DMvwa0kyboUr+EdPtA4+AO8aBDZwQgn A6r++EZze/ujCEjCgdmgE5MEhJeV154Bs3Vlht2SWceeDMzuljINFsvPBcBTKg6qRhC46iXIBsh zAytyc0ak89a2jt3zudOKXJeRfRQiXjumFYXX2v/tfBAkF+FdgZZ4zuyYTTKMrUuXmlXArlKJHj Tpxzj3L9/71ZZaBiIQpLKXCPRc0j5xyjnbLM6dl+Y5Tc/1H2fFrVNJvbtlqWacoz1guAtkHOMfs Wdjfynr7bwtFNKXdVHvzmqo24y45iRVw2BO3SC7Vm+FjbM7bZvLRfaXuyOMPFlmoU2Y3SjV2FrA mrjCjczhbwJAfo= X-Received: by 2002:a05:651c:a05:b0:38f:20cc:2bb0 with SMTP id 38308e7fff4ca-39f07f10d7dmr8127071fa.23.1784823353528; Thu, 23 Jul 2026 09:15:53 -0700 (PDT) Received: from localhost.localdomain (46-138-176-102.dynamic.spd-mgts.ru. [46.138.176.102]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-39ef6d8c40fsm9875561fa.41.2026.07.23.09.15.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 09:15:53 -0700 (PDT) From: Artem Lytkin To: linux-watchdog@vger.kernel.org, rust-for-linux@vger.kernel.org Cc: linux-kernel@vger.kernel.org, wim@linux-watchdog.org, linux@roeck-us.net, ojeda@kernel.org, miguel.ojeda.sandonis@gmail.com, dakr@kernel.org, aliceryhl@google.com, a.hindborg@kernel.org, lossin@kernel.org Subject: [PATCH v2 1/3] rust: watchdog: add watchdog device abstraction Date: Thu, 23 Jul 2026 19:15:27 +0300 Message-ID: <20260723161529.23759-2-iprintercanon@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260723161529.23759-1-iprintercanon@gmail.com> References: <20260723161529.23759-1-iprintercanon@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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` 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 --- 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 =20 WAVE5 VPU CODEC DRIVER M: Nas Chung diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 1124785e210b3..e692d142c8608 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include 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 + +__rust_helper bool rust_helper_watchdog_active(const struct watchdog_devic= e *wdd) +{ + return watchdog_active(wdd); +} + +__rust_helper bool rust_helper_watchdog_hw_running(const struct watchdog_d= evice *wdd) +{ + return watchdog_hw_running(wdd); +} + +__rust_helper void rust_helper_watchdog_set_nowayout(struct watchdog_devic= e *wdd, bool nowayout) +{ + watchdog_set_nowayout(wdd, nowayout); +} + +__rust_helper void rust_helper_watchdog_stop_on_reboot(struct watchdog_dev= ice *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_devic= e *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 =3D "y")] pub mod usb; +#[cfg(CONFIG_WATCHDOG)] +pub mod watchdog; pub mod workqueue; pub mod xarray; =20 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); + +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 Sel= f { + // CAST: `Self` is a `repr(transparent)` wrapper around + // `bindings::watchdog_device`. + let ptr =3D ptr.cast::(); + // 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 referen= ce + // is allowed; the C core accesses this field the same way. + unsafe { (*self.as_raw()).timeout =3D 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 =3D bindings::WDIOF_SETTIMEOUT; + /// The watchdog supports keepalive pings. + pub const KEEPALIVEPING: u32 =3D bindings::WDIOF_KEEPALIVEPING; + /// The watchdog supports the magic close character. + pub const MAGICCLOSE: u32 =3D bindings::WDIOF_MAGICCLOSE; + /// The watchdog supports pretimeouts. + pub const PRETIMEOUT: u32 =3D 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 =3D [0u8; 32]; + let bytes =3D identity.as_bytes(); + let mut i =3D 0; + while i < bytes.len() && i < id.len() - 1 { + id[i] =3D bytes[i]; + i +=3D 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`] implementati= on +/// 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 { + _p: PhantomData, +} + +impl Adapter { + /// 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::() } + } + + /// # Safety + /// + /// `wdd` must be passed by the corresponding callback in `watchdog_op= s`. + unsafe extern "C" fn start_callback(wdd: *mut bindings::watchdog_devic= e) -> c_int { + from_result(|| { + // SAFETY: The pointer is valid for the duration of the callba= ck. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D unsafe { Self::data_from_raw(wdd) }; + T::start(dev, data)?; + Ok(0) + }) + } + + /// # Safety + /// + /// `wdd` must be passed by the corresponding callback in `watchdog_op= s`. + 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 callba= ck. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D unsafe { Self::data_from_raw(wdd) }; + T::stop(dev, data)?; + Ok(0) + }) + } + + /// # Safety + /// + /// `wdd` must be passed by the corresponding callback in `watchdog_op= s`. + 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 callba= ck. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D unsafe { Self::data_from_raw(wdd) }; + T::ping(dev, data)?; + Ok(0) + }) + } + + /// # Safety + /// + /// `wdd` must be passed by the corresponding callback in `watchdog_op= s`. + 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 callba= ck. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D 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_op= s`. + 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 callba= ck. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D 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_op= s`. + unsafe extern "C" fn get_timeleft_callback(wdd: *mut bindings::watchdo= g_device) -> c_uint { + // SAFETY: The pointer is valid for the duration of the callback. + let dev =3D unsafe { Device::from_raw(wdd) }; + // SAFETY: `wdd` was registered by `Registration`. + let data =3D 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 ty= pe +/// [`WatchdogOps::Data`], which is passed to [`Registration::register`] a= nd +/// 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) -> Re= sult { + 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() -> 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, + } +} + +/// 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 { + wdd: Opaque, + 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 { + inner: *mut Inner, +} + +// 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 Send for Registration {} + +// SAFETY: `Registration` has no `&self` methods, so sharing references to +// it across threads gives access to nothing. +unsafe impl Sync for Registration {} + +impl Registration { + /// Creates and registers a new watchdog device. + /// + /// `module` is used to set the `owner` field of the watchdog operatio= ns, + /// ensuring correct module reference counting while `/dev/watchdog` is + /// open. `data` is the driver's private data, passed to every callbac= k. + /// + /// 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 { + // The core silently ignores the stop-on-reboot notifier for devic= es + // without a stop operation; reject the combination instead. + if options.stop_on_reboot && !T::HAS_STOP { + return Err(EINVAL); + } + + let mut ops =3D create_watchdog_ops::(); + ops.owner =3D module.as_ptr(); + + let wdd =3D 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 =3D 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 =3D 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 l= ive + // allocation and the field projection stays in bounds. + let wdd_ptr =3D Opaque::cast_into(unsafe { &raw const (*inner).wdd= }); + // SAFETY: As above. + let ops_ptr: *const bindings::watchdog_ops =3D unsafe { &raw const= (*inner).ops }; + // SAFETY: As above. + let data_ptr: *const T::Data =3D unsafe { &raw const (*inner).data= }; + + // SAFETY: `wdd_ptr` points at a valid `watchdog_device` that is n= ot + // yet registered, so we have exclusive access. + unsafe { + (*wdd_ptr).ops =3D ops_ptr; + bindings::watchdog_set_drvdata(wdd_ptr, data_ptr.cast_mut().ca= st()); + 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_devic= e`. + let ret =3D unsafe { bindings::watchdog_register_device(wdd_ptr) }; + if ret !=3D 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 Drop for Registration { + 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 co= nst (*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 --=20 2.43.0 From nobody Fri Jul 24 05:21:29 2026 Received: from mail-lj1-f178.google.com (mail-lj1-f178.google.com [209.85.208.178]) (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 143704570EA for ; Thu, 23 Jul 2026 16:16:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823372; cv=none; b=dwSpd94fGiLdTkS2jhgXqPB5GtKnmq6phllT97c6fkn4a34K0FlRbpBX9QikleOWXrgGWen2z7aDD1JADFMc4vbZFW2FmBn+uNLEK+P6kGFMcDv50IaTC/pSqcPZZg0HN+RzghI1Kcas/yEAvZ1I9xMRyFTqdfMlRWjPcClYcng= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823372; c=relaxed/simple; bh=eAAR8QRYAU5FUSVKssILFt1d8jQIeSkpT5UQzrNNA4k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=reuUC/izznERE4AbqnQxBGdamYeG9ksR0HQ6QTvNcbterJsrkAonOiUhjSm0VqTM2rj8DsN/Yj9/XjNY+VpWs0GySbvp1w/nSqRPBfzVP6+omtMCUNZKVdZtGjklh+btIhuuSfRN85bXkklWw84MM0C3ZRnR76diV1Pvl66P4/0= 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=PnUV4n3Z; arc=none smtp.client-ip=209.85.208.178 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="PnUV4n3Z" Received: by mail-lj1-f178.google.com with SMTP id 38308e7fff4ca-39c94fccf3eso7249041fa.0 for ; Thu, 23 Jul 2026 09:16:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784823355; x=1785428155; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=3WF4Dbs6zRO0d71RsrP4HchYRm5rd3x9Pi7RivnUfa4=; b=PnUV4n3ZjPgXwvfA/iB0SYwzfhdZ3CzGFiIBm8oGGgiinJ7GyTc4ug6WG7USjcwq57 wMQ+9aEDbhtXEC4NNFemw4jX7D6smXBVKEY/woD87KAIFs8qjwShNh6YnL6ZhBH4Dblb s9sA15bRtq3Tj0hlkAvKtw0GTZN1/AazLTARGuV50ungvPBptovE6tGtUYP2gTizs3mz aJ1UCmQJ7gSMs726fkgzhkG0UEZOSWmPkHW6KEuMUvtLcoSCzmxZHq6WJFp1f83KSVX6 HI59u13xI2V4gSGNhqcoILOqvkAb+eKq++q4OpIWy9hoPV5jwjTdiVnYc9OdfFudVDsT 1gmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784823355; x=1785428155; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=3WF4Dbs6zRO0d71RsrP4HchYRm5rd3x9Pi7RivnUfa4=; b=Tqn1iw/nEvfxrLRSeS9LSSdlOONfxDHbQeU9mkeGTEZK64cch55KNGuXiE4lPeqgqW hE+vJYklkVjYx7MYf4gxmxPGMxK7cXP4QGSXKN2go+x3TwoXepSl0uomMHMryaCD4/4r VEsD0cIK6eL085s1gwSHSGJH1X5zSiBC1dKQ+r7jmcwYyAuFz4br0GNIMq+gr6m0Thg5 kGFvVVnOknkPfes8/CZRW9w9gmiVrRdbgjcJKSBZVvOndyREXRe7FIpl0TTN3dxt0G98 83TfE8+KuaOT1slIl3Pfeom8wwrvkiRfjJwyHp2ol9cqyAph0NhJatIfcdE9XAa09WxR V8/A== X-Gm-Message-State: AOJu0Ywil5o7CRwBZZsXzJDlK0A6WqKel9eaeTEcYgOA1NeR4cFowCsD 6+yEApdKbpaxBIGkozXQz8Mir7wqHtJ+fzMTYgWfMKP0HnOEXbRmbWAg X-Gm-Gg: AR+sD13v4q8nhPSyUglzLxN44HN8YFJBdHrjs8eUKaS63dMHoxQqJP88xqLVganUBze ONDy7Of8CLhNcCUHmljfOGlPSzHGIIq+m8Sp+bmvj+H2Muc/t0rkL6QLNDCccXAzKlR6MeiOOyx unZF+TQamLPcGz6Dzo7Y3afjslM6V7vqqGC9r27+sa7aQj1TbytNxw+Em3/ieU3ygFWqzSYAi+N hnkWVAx2fKp5EvhMlG0phm9cLRNTcHRM6IxxQ/nBGWvmWEGpNOJ2F2opi2t2860KwPeNqkvAfzR wNy11v+7rTMyXs2lCRtvJNqBLK4Bre4n8WqFXukzrT2D+M2b5DXoWnqysq6ieTiUK6xUJUtwdT1 UtQWHrhPtFJmCoi5JYaq7i1w1ePUlB4ZmGYPapaFe+NPl4ljMGyTk9BNpXxI8PTkYT4gWRR3dRE tCtUQKgsdKkbo8syM8Bm4WTcooVY5p+B9vkR2lErIV2hlZAz77I5KYFgDUnc6mFu5CQWj/Xg/P4 n+A5aa5tFf+Rcs= X-Received: by 2002:a2e:a588:0:b0:39b:1b6d:5027 with SMTP id 38308e7fff4ca-39f07c70ba8mr7958461fa.11.1784823354940; Thu, 23 Jul 2026 09:15:54 -0700 (PDT) Received: from localhost.localdomain (46-138-176-102.dynamic.spd-mgts.ru. [46.138.176.102]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-39ef6d8c40fsm9875561fa.41.2026.07.23.09.15.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 09:15:54 -0700 (PDT) From: Artem Lytkin To: linux-watchdog@vger.kernel.org, rust-for-linux@vger.kernel.org Cc: linux-kernel@vger.kernel.org, wim@linux-watchdog.org, linux@roeck-us.net, ojeda@kernel.org, miguel.ojeda.sandonis@gmail.com, dakr@kernel.org, aliceryhl@google.com, a.hindborg@kernel.org, lossin@kernel.org Subject: [PATCH v2 2/3] rust: reboot: add emergency_restart() wrapper Date: Thu, 23 Jul 2026 19:15:28 +0300 Message-ID: <20260723161529.23759-3-iprintercanon@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260723161529.23759-1-iprintercanon@gmail.com> References: <20260723161529.23759-1-iprintercanon@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- 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_helpe= r.h index e692d142c8608..89f0e1f65f6d7 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include 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 contex= t. +pub fn emergency_restart() { + // SAFETY: `emergency_restart` has no preconditions and is documented = as + // safe to call from any context. + unsafe { bindings::emergency_restart() } +} --=20 2.43.0 From nobody Fri Jul 24 05:21:29 2026 Received: from mail-lf1-f43.google.com (mail-lf1-f43.google.com [209.85.167.43]) (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 76D6647CA7A for ; Thu, 23 Jul 2026 16:16:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823375; cv=none; b=B3OmWLzOJ8OINrLbJDpFePkWiHfRhwUjZvfxl4ys0VXliTwxEm8OoMzyxvA7DndP1BEVzaKgD2EjcopRXAvCg5ig+wSgxmU+o1yjdid/PoznbW7JFKHcsHGEoHZKksToJwCgpWRjelKd71QvJtB19lFLdhxQ1NYHzYxSIGpJxa4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784823375; c=relaxed/simple; bh=aF9Eskw4wP/Mk/gPSa+pEH/ArdSYufriaP9yc1cpeXU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MTau6gXBuruW7x4C7cG8y0hr69wvOqo5a5DpZuixkjy4+E5OaYNn2jId6Wr+mSy1T8xd+TBzktklCZ529cL8GkcPWsHYHl6wgXLc+C6v5kdPgVJUgNEnXLFy1Yur/rO66NaLJi5JkAY2+Wzg+z36coaiyJM9VM1czdzYvTBNZYQ= 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=YhXUGKDn; arc=none smtp.client-ip=209.85.167.43 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="YhXUGKDn" Received: by mail-lf1-f43.google.com with SMTP id 2adb3069b0e04-5b2a44a3b66so847085e87.1 for ; Thu, 23 Jul 2026 09:16:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784823356; x=1785428156; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=WSFkoNus/+V1J3x0Yh+fiQrb5iWsjELtWkobo5nQNog=; b=YhXUGKDnYPOidm1tunISKVa8tG5krDw3PGbI0TrgM3XCrpwHXRtDymperxSuyQRnGj HSV2YrfKo1N+dwTK8b48HOwTW6DuVy9S67wUq30n97Ax2nie9WV9ln5NNLZNFQckHlry wR9u16NGY1Ob2KXL5KysaH1+Gh5YjIB7AcQW50d5ch9ctjQfWN3YmUTQEY3XnT/m1wZr 18tpfQVjDo2AaC8mad+xwM8UoaBbXOsiyxaDkr0YW+K3yjM43+DM4P/z6+SyGTDLuyRE AH4fooC+vZLaGc48tHLS8C+jfqnu0Q0tpYKL4eNOqmcBoiH4cfuD7CP7bKr7GMwLMxv7 gpNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784823356; x=1785428156; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=WSFkoNus/+V1J3x0Yh+fiQrb5iWsjELtWkobo5nQNog=; b=qHRptQQs0RI/CSDueb4iEYmaIcXBqtrEtg0fvh/Y3Bspw7SjCnwwEviDa4gZzxxwAB NTCpaLhpAcwSSUQILnfb3xkjQxnNrhyKGaq0ruE6RAZNjyZdHUxpQ0pRjp3PMDib82EE W8G0+drEIDvpaWLC6qYI7YK/QZ0GwcR/OU46LnxGc0X9uTQxThpn1/1MHdes+NDd+NAN oJvHMnjPhw0b4ii9c7xWkItAC/jxmHsxUXkCYmN+V4WLm0ydfwQsD5baTpQcGViQu90r OCZDq3ITqg3WnZocF/W1M4rr0EeD8rHhHr30Kj1ZcRjU/uOOfTNZpO3d5uPc09LsoFYv U3/g== X-Gm-Message-State: AOJu0Yxj/aqtSRYZuzv/jFs9RGoXP0XCgGzwmR7NSk0nA62UTdUTdbnu w7kHu8bioxq6vk9aCkf9N/HInJGd5UscyITmzvKz7ZTRZdQ+5K4nFIWW X-Gm-Gg: AR+sD10WW9o+iQAbybg9tzMPr2EjxI024z+vm14dzANRC1RvINRJaCoKbhWoJcQGPrS DiVjI9ik5PocKPmt9ZrEUgMH08WuVsYhUmtm1i0U6SyMC/GTI82naDqGkQ8qI+G8hOn2b9FufrY EwZ5cdurHuasv8pze6iTwvlfktdjDFkXHm0PzBT4f6G5kViVofiKvyQjFc02UfTNxA+KXoXvR16 UXk3tN9YtievxM0o6M2J9vZqTEgBTB8a+lOvyIGR3UIMai56YkNogbxC6vtjgKOwRvoKk1jeX/w 8Xm3qKwy1MQ78UwDEWJm3LVhQrtxvUYJUHIy07XMt2L8yFtdxMCi+b2OEr5dGSlClH/XZ5k093l D+U3QzECmXp85Eb3gChilnpHDv5D9PYeyD1fe/iGFQL7tH0zLKcrpJwf6SoNCYENeaOdR70dpwp UyTZ/JdcF8f6tdtLx3lFnHriiDHCwYKE3XltgITYd4Pw7Vs4jMfok2nNDo6dC4QGuJ64VJ0utP+ pt4 X-Received: by 2002:a05:6512:3d21:b0:5b1:5fe4:6f64 with SMTP id 2adb3069b0e04-5b2b2f77bddmr912875e87.45.1784823355629; Thu, 23 Jul 2026 09:15:55 -0700 (PDT) Received: from localhost.localdomain (46-138-176-102.dynamic.spd-mgts.ru. [46.138.176.102]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-39ef6d8c40fsm9875561fa.41.2026.07.23.09.15.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 09:15:55 -0700 (PDT) From: Artem Lytkin To: linux-watchdog@vger.kernel.org, rust-for-linux@vger.kernel.org Cc: linux-kernel@vger.kernel.org, wim@linux-watchdog.org, linux@roeck-us.net, ojeda@kernel.org, miguel.ojeda.sandonis@gmail.com, dakr@kernel.org, aliceryhl@google.com, a.hindborg@kernel.org, lossin@kernel.org Subject: [PATCH v2 3/3] watchdog: softdog_rs: add Rust software watchdog driver Date: Thu, 23 Jul 2026 19:15:29 +0300 Message-ID: <20260723161529.23759-4-iprintercanon@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260723161529.23759-1-iprintercanon@gmail.com> References: <20260723161529.23759-1-iprintercanon@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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=3Dn (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 --- 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. =20 +config SOFT_WATCHDOG_RS + tristate "Rust software watchdog" + depends on RUST && SOFT_WATCHDOG=3Dn + 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) +=3D max77620_wdt.o obj-$(CONFIG_NCT6694_WATCHDOG) +=3D nct6694_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) +=3D ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) +=3D softdog.o +obj-$(CONFIG_SOFT_WATCHDOG_RS) +=3D softdog_rs.o obj-$(CONFIG_MENF21BMC_WATCHDOG) +=3D menf21bmc_wdt.o obj-$(CONFIG_MENZ069_WATCHDOG) +=3D menz69_wdt.o obj-$(CONFIG_RAVE_SP_WATCHDOG) +=3D 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, HrTimerCallbackCon= text, HrTimerPointer, + HrTimerRestart, RelativeMode, + }, + Delta, Monotonic, + }, + watchdog::{self, flags}, +}; + +const DEFAULT_MARGIN: u32 =3D 60; +const MAX_MARGIN: u32 =3D 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 notifi= er +/// `stop` against an in-flight ioctl), so the handle is protected by a +/// mutex. +#[pin_data] +struct Softdog { + #[pin] + timer: HrTimer, + #[pin] + handle: Mutex>>, +} + +impl Softdog { + fn new() -> impl PinInit { + pin_init!(Self { + timer <- HrTimer::new(), + handle <- new_mutex!(None), + }) + } + + /// (Re)arms the countdown to fire in `timeout` seconds. + fn arm(this: &Arc, timeout: u32) { + let mut guard =3D this.handle.lock(); + // Drop the previous handle first: dropping a handle cancels the + // timer, so this must not happen after the new arming. + *guard =3D None; + *guard =3D Some(this.clone().start(Delta::from_secs(i64::from(time= out)))); + } + + /// Cancels the countdown. + fn disarm(this: &Arc) { + // Dropping the handle cancels the timer and also breaks the + // reference cycle `Softdog -> handle -> Arc`. + *this.handle.lock() =3D None; + } +} + +impl_has_hr_timer! { + impl HasHrTimer for Softdog { + mode: RelativeMode, field: self.timer + } +} + +impl HrTimerCallback for Softdog { + type Pointer<'a> =3D Arc; + + fn run(_this: ArcBorrow<'_, Self>, _ctx: HrTimerCallbackContext<'_, Se= lf>) -> 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 =3D Arc; + + fn start(dev: &watchdog::Device, data: &Arc) -> Result { + Softdog::arm(data, dev.timeout()); + Ok(()) + } + + fn ping(dev: &watchdog::Device, data: &Arc) -> Result { + Softdog::arm(data, dev.timeout()); + Ok(()) + } + + fn stop(_dev: &watchdog::Device, data: &Arc) -> Result { + Softdog::disarm(data); + Ok(()) + } +} + +static SOFTDOG_INFO: watchdog::Info =3D watchdog::Info::new( + flags::SETTIMEOUT | flags::KEEPALIVEPING | flags::MAGICCLOSE, + "Rust Software Watchdog", +); + +struct SoftdogModule { + _reg: watchdog::Registration, +} + +impl kernel::Module for SoftdogModule { + fn init(module: &'static ThisModule) -> Result { + let data =3D Arc::pin_init(Softdog::new(), GFP_KERNEL)?; + + let options =3D watchdog::Options { + timeout: DEFAULT_MARGIN, + min_timeout: 1, + max_timeout: MAX_MARGIN, + // Stop the countdown on reboot so that an orderly reboot is n= ot + // interrupted by the watchdog firing, like the C softdog does. + stop_on_reboot: true, + ..Default::default() + }; + + let reg =3D watchdog::Registration::register(module, None, &SOFTDO= G_INFO, &options, data)?; + + pr_info!("initialized (timeout=3D{}s)\n", DEFAULT_MARGIN); + + Ok(SoftdogModule { _reg: reg }) + } +} --=20 2.43.0