From nobody Tue Feb 10 13:24:28 2026 Received: from mailout1.w1.samsung.com (mailout1.w1.samsung.com [210.118.77.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5DBB32C2AA2 for ; Tue, 10 Jun 2025 12:53:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1749560019; cv=none; b=ft/sEInmnmlVJA7R4NFmMxOM45KWpbfN9haSP7O9G8sZZSvex9h2SlpKflpNeNwWnPHMeUBeMz73gxk6KjQ4DOlwH6wDGYFJWBq1/ZPMKYplz8dS785eV+Etn7wSuEkOi8cK7GxF+0gsREAWxPLjCWYZOjMRZVwmTsc6KCBl+Cs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1749560019; c=relaxed/simple; bh=uu1ICBCvjDVZqf0CSaxS0dFjXYTB7w47PthcJ/AVjh0=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=dh0W7SlEYFIXUHuZD9zF8KmeGRkw9XJbG6G5cabGfJY2vRAIJVvWQMTAURDJ9Rjxxm2jpc2bW3cv781QC5sfnDRw8DPxBtZsLCxaEaIm5NFlioIqZuYZRU/wkL1XOMr4p75aQBICZEnrABlbJyXV8gvIvnoblNOiCgk+EmnMuCE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com; spf=pass smtp.mailfrom=samsung.com; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b=BYdBzJls; arc=none smtp.client-ip=210.118.77.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=samsung.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=samsung.com header.i=@samsung.com header.b="BYdBzJls" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250610125332euoutp0197b7d3e0ee55b1b32a681012a8e63f59~Hr4-nPk2o1190611906euoutp01T for ; Tue, 10 Jun 2025 12:53:32 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250610125332euoutp0197b7d3e0ee55b1b32a681012a8e63f59~Hr4-nPk2o1190611906euoutp01T DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1749560012; bh=SlvixoLgo7166EMoVtRGLcHTj2lOCLeU5xYXQU6Q38Y=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=BYdBzJlsPRs4bO4dkMhixuhjWkWEl6dsIi8QsANLaUb512pJykL/EDr9qDmoCEXZz RlBVzygWMgo1zsIV6J/HmO08/hjMGK2ipWzUw85p5sDKc/tlZ9BjwRjAO3KKaeHOAM Yzn0Y3Gl1scTed0/FnYhZPBMQ9+23D1UXTut5u/8= Received: from eusmtip1.samsung.com (unknown [203.254.199.221]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250610125332eucas1p2da441aa44760236527afc82495af95d1~Hr4_5eA5w1177811778eucas1p2z; Tue, 10 Jun 2025 12:53:32 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip1.samsung.com (KnoxPortal) with ESMTPA id 20250610125330eusmtip1020b3091342afe55161202b2809bcb66~Hr49rpmA_0451804518eusmtip1C; Tue, 10 Jun 2025 12:53:30 +0000 (GMT) From: Michal Wilczynski Date: Tue, 10 Jun 2025 14:52:49 +0200 Subject: [PATCH v2 1/7] rust: Add basic PWM abstractions 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 Message-Id: <20250610-rust-next-pwm-working-fan-for-sending-v2-1-753e2955f110@samsung.com> In-Reply-To: <20250610-rust-next-pwm-working-fan-for-sending-v2-0-753e2955f110@samsung.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Michal Wilczynski , Drew Fustini , Guo Ren , Fu Wei , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Paul Walmsley , Palmer Dabbelt , Albert Ou , Alexandre Ghiti , Marek Szyprowski , Benno Lossin , Michael Turquette , Stephen Boyd , Benno Lossin Cc: linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-riscv@lists.infradead.org, devicetree@vger.kernel.org, linux-clk@vger.kernel.org X-Mailer: b4 0.15-dev X-CMS-MailID: 20250610125332eucas1p2da441aa44760236527afc82495af95d1 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250610125332eucas1p2da441aa44760236527afc82495af95d1 X-EPHeader: CA X-CMS-RootMailID: 20250610125332eucas1p2da441aa44760236527afc82495af95d1 References: <20250610-rust-next-pwm-working-fan-for-sending-v2-0-753e2955f110@samsung.com> Introduce safe Rust abstractions for the Linux PWM subsystem. These abstractions provide ergonomic, lifetime managed wrappers around the core C data structures and functions, enabling the development of PWM chip drivers in safe Rust. This initial version provides the core building blocks for writing a PWM chip provider driver, with a focus on safety, resource management, and idiomatic Rust patterns. The main components are: Ownership and Lifetime Management: - The pwm::Chip type, an ARef managed wrapper for struct pwm_chip, correctly handles the object's lifetime by using the embedded struct device's reference counter. - A pwm::Registration RAII guard ensures that a call to register a chip (pwmchip_add) is always paired with a call to unregister it (pwmchip_remove), preventing resource leaks. Safe Type Wrappers: - Safe, idiomatic Rust types (Polarity, Waveform, State, Args, Device) are provided to abstract away the raw C structs and enums. The State wrapper holds its data by value, avoiding unnecessary heap allocations. Driver Operations (PwmOps): - A generic PwmOps trait allows drivers to implement the standard PWM operations. It uses an associated type (WfHw) for the driver's hardware specific waveform data, moving unsafe serialization logic into the abstraction layer. The trait exposes the modern waveform API (round_waveform_tohw, write_waveform, etc.) as well as the other standard kernel callbacks (get_state, request, apply). - A create_pwm_ops function generates a C-compatible vtable from a PwmOps implementor. This foundational layer is designed to be used by subsequent patches to implement specific PWM chip drivers in Rust. Signed-off-by: Michal Wilczynski --- MAINTAINERS | 6 + drivers/pwm/Kconfig | 13 + rust/bindings/bindings_helper.h | 1 + rust/helpers/helpers.c | 1 + rust/helpers/pwm.c | 20 + rust/kernel/lib.rs | 2 + rust/kernel/pwm.rs | 864 ++++++++++++++++++++++++++++++++++++= ++++ 7 files changed, 907 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f2668b81115cb85bc94275e9554330969989fabf..5589c0d2253bcb04e78d7b89ef6= ef0ed41121d77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19990,6 +19990,12 @@ F: include/linux/pwm.h F: include/linux/pwm_backlight.h K: pwm_(config|apply_might_sleep|apply_atomic|ops) =20 +PWM SUBSYSTEM BINDINGS [RUST] +M: Michal Wilczynski +S: Maintained +F: rust/helpers/pwm.c +F: rust/kernel/pwm.rs + PXA GPIO DRIVER M: Robert Jarzmik L: linux-gpio@vger.kernel.org diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d9bcd1e8413eaed1602d6686873e263767c58f5f..03c5a100a03e2acdccf8a46b9c7= 0b736b630bd3a 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -790,4 +790,17 @@ config PWM_XILINX To compile this driver as a module, choose M here: the module will be called pwm-xilinx. =20 + config RUST_PWM_ABSTRACTIONS + bool "Rust PWM abstractions support" + depends on RUST + depends on PWM=3Dy + help + This option enables the safe Rust abstraction layer for the PWM + subsystem. It provides idiomatic wrappers and traits necessary f= or + writing PWM controller drivers in Rust. + + The abstractions handle resource management (like memory and ref= erence + counting) and provide safe interfaces to the underlying C core, + allowing driver logic to be written in safe Rust. + endif diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 693cdd01f9290fa01375cf78cac0e5a90df74c6c..6fe7dd529577952bf7adb4fe052= 6b0d5fbd6f3bd 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 0f1b5d11598591bc62bb6439747211af164b76d6..73902d8bd87e93cb3bc3c501360= c37e29e8dde19 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -30,6 +30,7 @@ #include "platform.c" #include "pci.c" #include "pid_namespace.c" +#include "pwm.c" #include "rbtree.c" #include "rcu.c" #include "refcount.c" diff --git a/rust/helpers/pwm.c b/rust/helpers/pwm.c new file mode 100644 index 0000000000000000000000000000000000000000..d75c588863685d3990b525bb1b8= 4aa4bc35ac397 --- /dev/null +++ b/rust/helpers/pwm.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +#include + +struct device *rust_helper_pwmchip_parent(const struct pwm_chip *chip) +{ + return pwmchip_parent(chip); +} + +void *rust_helper_pwmchip_get_drvdata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +void rust_helper_pwmchip_set_drvdata(struct pwm_chip *chip, void *data) +{ + pwmchip_set_drvdata(chip, data); +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index d652c92633b82525f37e5cd8a040d268e0c191d1..d634593d5e8084049cc22daadb5= 019de139599fe 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -106,6 +106,8 @@ pub mod seq_file; pub mod sizes; mod static_assert; +#[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)] +pub mod pwm; #[doc(hidden)] pub mod std_vendor; pub mod str; diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs new file mode 100644 index 0000000000000000000000000000000000000000..b5839703c49ed7b9aa61723a7e5= 06f5cb4dd665d --- /dev/null +++ b/rust/kernel/pwm.rs @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! PWM (Pulse Width Modulator) abstractions. +//! +//! This module provides safe Rust abstractions for working with the Linux +//! kernel's PWM subsystem, leveraging types generated by `bindgen` +//! from `` and `drivers/pwm/core.c`. + +use crate::{ + bindings, + device::{self, Bound}, + error::{self, to_result, Result}, + prelude::*, + str::CStr, + types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, +}; +use core::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; + +/// Maximum size for the hardware-specific waveform representation buffer. +/// From C: #define WFHWSIZE 20 +pub const WFHW_MAX_SIZE: usize =3D 20; + +/// PWM polarity. Mirrors `enum pwm_polarity`. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Polarity { + /// Normal polarity (duty cycle defines the high period of the signal) + Normal, + /// Inversed polarity (duty cycle defines the low period of the signal) + Inversed, +} + +impl From for Polarity { + fn from(polarity: bindings::pwm_polarity) -> Self { + match polarity { + bindings::pwm_polarity_PWM_POLARITY_NORMAL =3D> Polarity::Norm= al, + bindings::pwm_polarity_PWM_POLARITY_INVERSED =3D> Polarity::In= versed, + _ =3D> Polarity::Normal, + } + } +} + +impl From for bindings::pwm_polarity { + fn from(polarity: Polarity) -> Self { + match polarity { + Polarity::Normal =3D> bindings::pwm_polarity_PWM_POLARITY_NORM= AL, + Polarity::Inversed =3D> bindings::pwm_polarity_PWM_POLARITY_IN= VERSED, + } + } +} + +/// Represents a PWM waveform configuration. Mirrors struct pwm_waveform. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Waveform { + /// Total duration of one complete PWM cycle, in nanoseconds. + pub period_length_ns: u64, + + /// Duration the PWM signal is in its "active" state during one period, + /// in nanoseconds. For a typical "normal" polarity configuration wher= e active is high, + /// this represents the high time of the signal. + pub duty_length_ns: u64, + + /// Time delay from the start of the period to the first active edge + /// of the duty cycle, in nanoseconds. For many simpler PWM configurat= ions, + /// this is 0, meaning the duty cycle's active phase starts at the beg= inning + /// of the period. + pub duty_offset_ns: u64, +} + +impl From for Waveform { + fn from(wf: bindings::pwm_waveform) -> Self { + Waveform { + period_length_ns: wf.period_length_ns, + duty_length_ns: wf.duty_length_ns, + duty_offset_ns: wf.duty_offset_ns, + } + } +} + +impl From for bindings::pwm_waveform { + fn from(wf: Waveform) -> Self { + bindings::pwm_waveform { + period_length_ns: wf.period_length_ns, + duty_length_ns: wf.duty_length_ns, + duty_offset_ns: wf.duty_offset_ns, + } + } +} + +/// Wrapper for board-dependent PWM arguments (`struct pwm_args`). +#[repr(transparent)] +pub struct Args(Opaque); + +impl Args { + /// Creates an `Args` wrapper from a C struct pointer. + /// + /// # Safety + /// The caller must ensure that `c_args_ptr` is a valid, non-null poin= ter + /// to `bindings::pwm_args` and that the pointed-to data is valid + /// for the duration of this function call (as data is copied). + unsafe fn from_c_ptr(c_args_ptr: *const bindings::pwm_args) -> Self { + // SAFETY: Caller guarantees `c_args_ptr` is valid. We dereference= it to copy. + Args(Opaque::new(unsafe { *c_args_ptr })) + } + + /// Returns the period of the PWM signal in nanoseconds. + pub fn period(&self) -> u64 { + // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_= args` + // managed by the `Opaque` wrapper. This pointer is guaranteed to = be + // valid and aligned for the lifetime of `self` because `Opaque` o= wns a copy. + unsafe { (*self.0.get()).period } + } + + /// Returns the polarity of the PWM signal. + pub fn polarity(&self) -> Polarity { + // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_= args` + // managed by the `Opaque` wrapper. This pointer is guaranteed to = be + // valid and aligned for the lifetime of `self`. + let raw_polarity =3D unsafe { (*self.0.get()).polarity }; + Polarity::from(raw_polarity) + } +} + +/// Wrapper for PWM state (`struct pwm_state`). +#[repr(transparent)] +pub struct State(bindings::pwm_state); + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl State { + /// Creates a new zeroed `State`. + pub fn new() -> Self { + State(bindings::pwm_state::default()) + } + + /// Creates a `State` wrapper by taking ownership of a C `pwm_state` v= alue. + pub(crate) fn from_c(c_state: bindings::pwm_state) -> Self { + State(c_state) + } + + /// Gets the period of the PWM signal in nanoseconds. + pub fn period(&self) -> u64 { + self.0.period + } + + /// Sets the period of the PWM signal in nanoseconds. + pub fn set_period(&mut self, period_ns: u64) { + self.0.period =3D period_ns; + } + + /// Gets the duty cycle of the PWM signal in nanoseconds. + pub fn duty_cycle(&self) -> u64 { + self.0.duty_cycle + } + + /// Sets the duty cycle of the PWM signal in nanoseconds. + pub fn set_duty_cycle(&mut self, duty_ns: u64) { + self.0.duty_cycle =3D duty_ns; + } + + /// Returns `true` if the PWM signal is enabled. + pub fn enabled(&self) -> bool { + self.0.enabled + } + + /// Sets the enabled state of the PWM signal. + pub fn set_enabled(&mut self, enabled: bool) { + self.0.enabled =3D enabled; + } + + /// Gets the polarity of the PWM signal. + pub fn polarity(&self) -> Polarity { + Polarity::from(self.0.polarity) + } + + /// Sets the polarity of the PWM signal. + pub fn set_polarity(&mut self, polarity: Polarity) { + self.0.polarity =3D polarity.into(); + } + + /// Returns `true` if the PWM signal is configured for power usage hin= t. + pub fn usage_power(&self) -> bool { + self.0.usage_power + } + + /// Sets the power usage hint for the PWM signal. + pub fn set_usage_power(&mut self, usage_power: bool) { + self.0.usage_power =3D usage_power; + } +} + +/// Wrapper for a PWM device/channel (`struct pwm_device`). +#[repr(transparent)] +pub struct Device(Opaque); + +impl Device { + /// Creates a temporary `&mut Device` from a raw C pointer for use in = callbacks. + /// + /// It returns a mutable reference (`&mut Self`) because the underlyin= g C APIs + /// for PWM operations use non-const pointers (`struct pwm_device *`).= This + /// signals that the functions in the vtable are permitted to mutate t= he + /// device's state (e.g., by writing to hardware registers). Using `&m= ut` + /// allows the `PwmOps` trait to accurately model this behavior and le= verage + /// Rust's aliasing rules for greater safety. + /// + /// # Safety + /// The caller must ensure that `ptr` is a valid, non-null pointer to + /// `bindings::pwm_device` that is properly initialized. + /// The `pwm_device` must remain valid for the lifetime `'a`. + /// The caller must also ensure that Rust's aliasing rules are upheld. + pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::pwm_device) -> &= 'a mut Self { + // SAFETY: Caller guarantees `ptr` is valid and meets lifetime/ali= asing. + // `Self` is `#[repr(transparent)]`, so casting is valid. + unsafe { &mut *ptr.cast::() } + } + + /// Returns a raw pointer to the underlying `pwm_device`. + fn as_raw(&self) -> *mut bindings::pwm_device { + self.0.get() + } + + /// Gets the hardware PWM index for this device within its chip. + pub fn hwpwm(&self) -> u32 { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s l= ifetime. + unsafe { (*self.as_raw()).hwpwm } + } + + /// Gets a reference to the parent `Chip` that this device belongs to. + pub fn chip(&self) -> &Chip { + // SAFETY: `self.as_raw()` provides a valid pointer. (*self.as_raw= ()).chip + // is assumed to be a valid pointer to `pwm_chip` managed by the k= ernel. + // Chip::from_ptr's safety conditions must be met. + unsafe { Chip::from_ptr((*self.as_raw()).chip) } + } + + /// Gets the label for this PWM device, if any. + pub fn label(&self) -> Option<&CStr> { + // SAFETY: self.as_raw() provides a valid pointer. + let label_ptr =3D unsafe { (*self.as_raw()).label }; + if label_ptr.is_null() { + None + } else { + // SAFETY: label_ptr is non-null and points to a C string + // managed by the kernel, valid for the lifetime of the PWM de= vice. + Some(unsafe { CStr::from_char_ptr(label_ptr) }) + } + } + + /// Gets a copy of the board-dependent arguments for this PWM device. + pub fn args(&self) -> Args { + // SAFETY: self.as_raw() gives a valid pointer to `pwm_device`. + // The `args` field is a valid `pwm_args` struct embedded within `= pwm_device`. + // `Args::from_c_ptr`'s safety conditions are met by providing thi= s pointer. + unsafe { Args::from_c_ptr(&(*self.as_raw()).args) } + } + + /// Gets a copy of the current state of this PWM device. + pub fn state(&self) -> State { + // SAFETY: `self.as_raw()` gives a valid pointer. `(*self.as_raw()= ).state` + // is a valid `pwm_state` struct. `State::from_c` copies this data. + State::from_c(unsafe { (*self.as_raw()).state }) + } + + /// Returns `true` if the PWM signal is currently enabled based on its= state. + pub fn is_enabled(&self) -> bool { + self.state().enabled() + } +} + +/// Wrapper for a PWM chip/controller (`struct pwm_chip`). +#[repr(transparent)] +pub struct Chip(Opaque); + +impl Chip { + /// Creates a temporary `&mut Chip` from a raw C pointer for use in ca= llbacks. + /// + /// It returns a mutable reference (`&mut Self`) because the underlyin= g C APIs + /// for PWM operations use non-const pointers (`struct pwm_chip *`). T= his + /// signals that the functions in the vtable are permitted to mutate t= he + /// chip's state (e.g., by calling `set_drvdata` or through operations= that + /// modify hardware registers). Using `&mut` is essential for these ca= ses. + /// + /// # Safety + /// The caller must ensure that `ptr` is a valid, non-null pointer to + /// `bindings::pwm_chip` that is properly initialized. + /// The `pwm_chip` must remain valid for the lifetime `'a`. + /// The caller must also ensure that Rust's aliasing rules are upheld. + pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::pwm_chip) -> &'a= mut Self { + // SAFETY: Caller guarantees `ptr` is valid and meets lifetime/ali= asing. + // `Self` is `#[repr(transparent)]`, so casting is valid. + unsafe { &mut *ptr.cast::() } + } + + /// Returns a raw pointer to the underlying `pwm_chip`. + pub(crate) fn as_raw(&self) -> *mut bindings::pwm_chip { + self.0.get() + } + + /// Gets the number of PWM channels (hardware PWMs) on this chip. + pub fn npwm(&self) -> u32 { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s l= ifetime. + unsafe { (*self.as_raw()).npwm } + } + + /// Returns `true` if the chip supports atomic operations for configur= ation. + pub fn is_atomic(&self) -> bool { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s l= ifetime. + unsafe { (*self.as_raw()).atomic } + } + + /// Returns a reference to the embedded `struct device` abstraction. + pub fn device(&self) -> &device::Device { + // SAFETY: `self.as_raw()` provides a valid pointer to `bindings::= pwm_chip`. + // The `dev` field is an instance of `bindings::device` embedded w= ithin `pwm_chip`. + // Taking a pointer to this embedded field is valid. + // `device::Device` is `#[repr(transparent)]`. + // The lifetime of the returned reference is tied to `self`. + let dev_field_ptr =3D unsafe { core::ptr::addr_of!((*self.as_raw()= ).dev) }; + // SAFETY: `dev_field_ptr` is a valid pointer to `bindings::device= `. + // Casting and dereferencing is safe due to `repr(transparent)` an= d lifetime. + unsafe { &*(dev_field_ptr.cast::()) } + } + + /// Returns a reference to the parent device of this PWM chip's device. + pub fn parent_device(&self) -> Option<&device::Device> { + self.device().parent() + } + + /// Gets the *typed* driver-specific data associated with this chip's = embedded device. + pub fn drvdata(&self) -> Option<&T> { + // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer. + // `bindings::pwmchip_get_drvdata` is the C function to retrieve d= river data. + let ptr =3D unsafe { bindings::pwmchip_get_drvdata(self.as_raw()) = }; + if ptr.is_null() { + None + } else { + // SAFETY: `ptr` is non-null. Caller ensures `T` is the correc= t type. + // Lifetime of data is managed by the driver that set it. + unsafe { Some(&*(ptr.cast::())) } + } + } + + /// Sets the *typed* driver-specific data associated with this chip's = embedded device. + pub fn set_drvdata(&self, data: T) { + // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer. + // `bindings::pwmchip_set_drvdata` is the C function to set driver= data. + // `data.into_foreign()` provides a valid `*mut c_void`. + unsafe { bindings::pwmchip_set_drvdata(self.as_raw(), data.into_fo= reign().cast()) } + } + + /// Allocates and wraps a PWM chip using `bindings::pwmchip_alloc`. + /// + /// Returns an `ARef` managing the chip's lifetime via refcounti= ng + /// on its embedded `struct device`. + pub fn new(parent_dev: &device::Device, npwm: u32, sizeof_priv: usize)= -> Result> { + // SAFETY: `parent_device_for_dev_field.as_raw()` is valid. + // `bindings::pwmchip_alloc` returns a valid `*mut bindings::pwm_c= hip` (refcount 1) + // or an ERR_PTR. + let c_chip_ptr_raw =3D + unsafe { bindings::pwmchip_alloc(parent_dev.as_raw(), npwm, si= zeof_priv) }; + + let c_chip_ptr: *mut bindings::pwm_chip =3D error::from_err_ptr(c_= chip_ptr_raw)?; + + // Cast the `*mut bindings::pwm_chip` to `*mut Chip`. This is vali= d because + // `Chip` is `repr(transparent)` over `Opaque`= , and + // `Opaque` is `repr(transparent)` over `T`. + let chip_ptr_as_self =3D c_chip_ptr.cast::(); + + // SAFETY: `chip_ptr_as_self` points to a valid `Chip` (layout-com= patible with + // `bindings::pwm_chip`) whose embedded device has refcount 1. + // `ARef::from_raw` takes this pointer and manages it via `AlwaysR= efCounted`. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(chip_ptr_as_self= )) }) + } +} + +// SAFETY: Implements refcounting for `Chip` using the embedded `struct de= vice`. +unsafe impl AlwaysRefCounted for Chip { + #[inline] + fn inc_ref(&self) { + // SAFETY: `self.0.get()` points to a valid `pwm_chip` because `se= lf` exists. + // The embedded `dev` is valid. `get_device` increments its refcou= nt. + unsafe { + bindings::get_device(core::ptr::addr_of_mut!((*self.0.get()).d= ev)); + } + } + + #[inline] + unsafe fn dec_ref(obj: NonNull) { + let c_chip_ptr =3D obj.cast::().as_ptr(); + + // SAFETY: `obj` is a valid pointer to a `Chip` (and thus `binding= s::pwm_chip`) + // with a non-zero refcount. `put_device` handles decrement and fi= nal release. + unsafe { + bindings::put_device(core::ptr::addr_of_mut!((*c_chip_ptr).dev= )); + } + } +} + +// SAFETY: `Chip` is a wrapper around `*mut bindings::pwm_chip`. The under= lying C +// structure's state is managed and synchronized by the kernel's device mo= del +// and PWM core locking mechanisms. Therefore, it is safe to move the `Chi= p` +// wrapper (and the pointer it contains) across threads. +unsafe impl Send for Chip {} + +// SAFETY: It is safe for multiple threads to have shared access (`&Chip`)= because +// the `Chip` data is immutable from the Rust side without holding the app= ropriate +// kernel locks, which the C core is responsible for. Any interior mutabil= ity is +// handled and synchronized by the C kernel code. +unsafe impl Sync for Chip {} + +/// Manages the registration of a PWM chip, ensuring `pwmchip_remove` is c= alled on drop. +pub struct Registration { + chip: ManuallyDrop>, +} + +impl Registration { + /// Registers a PWM chip (obtained via `Chip::new`) with the PWM subsy= stem. + /// + /// Takes an `ARef`. On `Drop` of the returned `Registration` ob= ject, + /// `pwmchip_remove` is called for the chip. + pub fn new(chip: ARef, ops_vtable: &'static PwmOpsVTable) -> Res= ult { + // Get the raw C pointer from ARef. + let c_chip_ptr =3D chip.as_raw().cast::(); + + // SAFETY: `c_chip_ptr` is valid (guaranteed by ARef existing). + // `ops_vtable.as_raw()` provides a valid `*const bindings::pwm_op= s`. + // `bindings::__pwmchip_add` preconditions (valid pointers, ops se= t on chip) are met. + unsafe { + (*c_chip_ptr).ops =3D ops_vtable.as_raw(); + to_result(bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_= mut()))?; + } + Ok(Registration { + chip: ManuallyDrop::new(chip), + }) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + let chip =3D &**self.chip; + let chip_raw: *mut bindings::pwm_chip =3D chip.as_raw(); + + // SAFETY: `chip_raw` points to a chip that was successfully regis= tered via `Self::new`. + // `bindings::pwmchip_remove` is the correct C function to unregis= ter it. + unsafe { + bindings::pwmchip_remove(chip_raw); + ManuallyDrop::drop(&mut self.chip); // Drops the ARef + } + } +} + +/// Trait defining the operations for a PWM driver. +pub trait PwmOps: 'static + Sized { + /// The driver-specific hardware representation of a waveform. + /// This type must be `Copy`, `Default`, and fit within `WFHW_MAX_SIZE= `. + type WfHw: Copy + Default; + + /// Optional hook to atomically apply a new PWM config. + fn apply( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook for when a PWM device is requested. + fn request(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::= Device) -> Result { + Ok(()) + } + + /// Optional hook for when a PWM device is freed. + fn free(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::Dev= ice) {} + + /// Optional hook for capturing a PWM signal. + fn capture( + _chip: &mut Chip, + _pwm: &mut Device, + _result: &mut bindings::pwm_capture, + _timeout: usize, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook to get the current hardware state. + fn get_state( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &mut State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Convert a generic waveform to the hardware-specific representation. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_tohw( + _chip: &mut Chip, + _pwm: &mut Device, + _wf: &Waveform, + ) -> Result<(i32, Self::WfHw)> { + Err(ENOTSUPP) + } + + /// Convert a hardware-specific representation back to a generic wavef= orm. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_fromhw( + _chip: &mut Chip, + _pwm: &Device, + _wfhw: &Self::WfHw, + _wf: &mut Waveform, + ) -> Result { + Err(ENOTSUPP) + } + + /// Read the current hardware configuration into the hardware-specific= representation. + fn read_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Write a hardware-specific waveform configuration to the hardware. + fn write_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _wfhw: &Self::WfHw, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } +} +/// Bridges Rust `PwmOps` to the C `pwm_ops` vtable. +struct Adapter { + _p: PhantomData, +} + +impl Adapter { + /// # Safety + /// `wfhw_ptr` must be valid for writes of `size_of::()` byte= s. + unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut core::ffi::c_v= oid) -> Result { + let size =3D core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw as *const _ as *const u8, = wfhw_ptr.cast(), size); + } + + Ok(()) + } + + /// # Safety + /// `wfhw_ptr` must be valid for reads of `size_of::()` bytes. + unsafe fn deserialize_wfhw(wfhw_ptr: *const core::ffi::c_void) -> Resu= lt { + let size =3D core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + let mut wfhw =3D T::WfHw::default(); + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw_ptr.cast(), &mut wfhw as *= mut _ as *mut u8, size); + } + + Ok(wfhw) + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn apply_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *const bindings::pwm_state, + ) -> i32 { + // SAFETY: This block relies on the function's safety contract: th= e C caller + // provides valid pointers. `Chip::from_ptr` and `Device::from_ptr= ` are `unsafe fn` + // whose preconditions are met by this contract. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + // SAFETY: The PWM core guarantees callbacks only happen on a live= , bound device. + let bound_parent =3D + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + + // SAFETY: The state provided by the callback is guaranteed to be = valid + let state =3D State::from_c(unsafe { *s }); + match T::apply(chip, pwm, &state, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn request_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + ) -> i32 { + // SAFETY: PWM core guarentees `c` and `p` are valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::request(chip, pwm, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn free_callback(c: *mut bindings::pwm_chip, p: *mut= bindings::pwm_device) { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return; + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + T::free(chip, pwm, bound_parent); + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn capture_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + res: *mut bindings::pwm_capture, + timeout: usize, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, result) =3D unsafe { (Chip::from_ptr(c), Device::f= rom_ptr(p), &mut *res) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during cal= lbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::capture(chip, pwm, result, timeout, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn get_state_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *mut bindings::pwm_state, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + let mut rust_state =3D State::new(); + match T::get_state(chip, pwm, &mut rust_state, bound_parent) { + Ok(()) =3D> { + // SAFETY: `s` is guaranteed valid by the C caller. + unsafe { + *s =3D rust_state.0; + }; + 0 + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn round_waveform_tohw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + w: *const bindings::pwm_waveform, + wh: *mut core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, wf) =3D + unsafe { (Chip::from_ptr(c), Device::from_ptr(p), Waveform::fr= om(*w)) }; + match T::round_waveform_tohw(chip, pwm, &wf) { + Ok((status, wfhw)) =3D> { + // SAFETY: `wh` is valid per this function's safety contra= ct. + if unsafe { Self::serialize_wfhw(&wfhw, wh) }.is_err() { + return EINVAL.to_errno(); + } + status + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn round_waveform_fromhw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const core::ffi::c_void, + w: *mut bindings::pwm_waveform, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + // SAFETY: `deserialize_wfhw`'s safety contract is met by this fun= ction's contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + + let mut rust_wf =3D Waveform::default(); + match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) { + Ok(ret) =3D> { + // SAFETY: `w` is guaranteed valid by the C caller. + unsafe { + *w =3D rust_wf.into(); + }; + ret + } + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn read_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *mut core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + match T::read_waveform(chip, pwm, bound_parent) { + // SAFETY: `wh` is valid per this function's safety contract. + Ok(wfhw) =3D> match unsafe { Self::serialize_wfhw(&wfhw, wh) }= { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + }, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn write_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::from_ptr(c), Device::from_ptr(= p)) }; + let parent_dev =3D match chip.parent_device() { + Some(dev) =3D> dev, + None =3D> { + return EINVAL.to_errno(); + } + }; + + let bound_parent =3D + // SAFETY: The PWM core guarantees the device is bound during cal= lbacks. + unsafe { &*(parent_dev as *const device::Device as *const devi= ce::Device) }; + // SAFETY: `wh` is valid per this function's safety contract. + let wfhw =3D match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) =3D> v, + Err(e) =3D> return e.to_errno(), + }; + match T::write_waveform(chip, pwm, &wfhw, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } +} +/// VTable structure wrapper for PWM operations. Mirrors `struct pwm_ops`. +#[repr(transparent)] +pub struct PwmOpsVTable(Opaque); + +// SAFETY: PwmOpsVTable is Send. The vtable contains only function pointers +// and a size, which are simple data types that can be safely moved across +// threads. The thread-safety of calling these functions is handled by the +// kernel's locking mechanisms. +unsafe impl Send for PwmOpsVTable {} +// SAFETY: PwmOpsVTable is Sync. The vtable is immutable after it is creat= ed, +// so it can be safely referenced and accessed concurrently by multiple th= reads +// e.g. to read the function pointers. +unsafe impl Sync for PwmOpsVTable {} + +impl PwmOpsVTable { + /// Returns a raw pointer to the underlying `pwm_ops` struct. + pub(crate) fn as_raw(&self) -> *const bindings::pwm_ops { + self.0.get() + } +} + +/// Creates a PWM operations vtable for a type `T` that implements `PwmOps= `. +/// +/// This is used to bridge Rust trait implementations to the C `struct pwm= _ops` +/// expected by the kernel. +pub const fn create_pwm_ops() -> PwmOpsVTable { + // SAFETY: `core::mem::zeroed()` is unsafe. For `pwm_ops`, all fields = are + // `Option` or data, so a zeroed pattern (None/0) = is valid initially. + let mut ops: bindings::pwm_ops =3D unsafe { core::mem::zeroed() }; + + ops.apply =3D Some(Adapter::::apply_callback); + ops.request =3D Some(Adapter::::request_callback); + ops.free =3D Some(Adapter::::free_callback); + ops.capture =3D Some(Adapter::::capture_callback); + ops.get_state =3D Some(Adapter::::get_state_callback); + + ops.round_waveform_tohw =3D Some(Adapter::::round_waveform_tohw_cal= lback); + ops.round_waveform_fromhw =3D Some(Adapter::::round_waveform_fromhw= _callback); + ops.read_waveform =3D Some(Adapter::::read_waveform_callback); + ops.write_waveform =3D Some(Adapter::::write_waveform_callback); + ops.sizeof_wfhw =3D core::mem::size_of::(); + + PwmOpsVTable(Opaque::new(ops)) +} --=20 2.34.1