From nobody Mon Oct 6 20:59:00 2025 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 4F9DF288510 for ; Thu, 17 Jul 2025 09:08:39 +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=1752743321; cv=none; b=ZeF4qrxukKOhcEFhey4ILuO6X9/l6WM8xKWcEVWfvqZknh7tZsfwMS0p/lkJ8yi8ZWY4aN+OsgMsKg6UvNXGwX/E458HBgKvoDsF4gYKQXYuKrkF+N3xuG2FWPjauUytc+2gbAyZQ8Uoi+dBuLDLaPzf8Np3n9fE2lwAx5PoW7w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752743321; c=relaxed/simple; bh=bGbkeH3UzSnkKV/TrdEg2b6wrUlUl3RzjeZN571VM9o=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=XnH3JFWHwqipd5lDu/lHQYwt2XQSS8BhONPToJQv0/NE9h+pnkF/pzZxQcGSGitTYhCu7l+4sklEqHiTD87N//l5qOY2FJvBPeHlrP3LzMyjJy+jad/vH1KxjsiRzzzRtq0oSZxMSdhNaaTqFCMHwf3tMJh58a9axEsOocV55nY= 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=o2motPbh; 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="o2motPbh" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout1.w1.samsung.com (KnoxPortal) with ESMTP id 20250717090831euoutp01354c20c2bdec3d87a127b04812f8bf3c~S-sFzKcsX0848008480euoutp01H for ; Thu, 17 Jul 2025 09:08:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout1.w1.samsung.com 20250717090831euoutp01354c20c2bdec3d87a127b04812f8bf3c~S-sFzKcsX0848008480euoutp01H DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1752743311; bh=nDSmSNpkyDqN+0uDTeiaM6bqnry5ClWU0W8WQ15siTk=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=o2motPbhm36QUgClLstkmJ71FnCsOblOikU3CBdS6drDSzEefOnO2VXf4t5fEDfZq bwiLlex21BG2k2XWnN04qNaVPAfvuzv4Y5MBfM1FbzODbJNVkD1M6gT2Kuirqt7sRO Ndop7P3M4N4BHFGhA5gddhoVdHd/ivmV2HJ9O7N8= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250717090830eucas1p191fbd4e0baa40468884307a1b6277be3~S-sEtNIw52894328943eucas1p1k; Thu, 17 Jul 2025 09:08:30 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250717090829eusmtip219cf93ca91b35a023e836ca35c24a3eb~S-sDm3ayG0473704737eusmtip2k; Thu, 17 Jul 2025 09:08:29 +0000 (GMT) From: Michal Wilczynski Date: Thu, 17 Jul 2025 11:08:23 +0200 Subject: [PATCH v12 1/3] pwm: Export `pwmchip_release` for external use 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: <20250717-rust-next-pwm-working-fan-for-sending-v12-1-40f73defae0c@samsung.com> In-Reply-To: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@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 , Drew Fustini , 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 X-Mailer: b4 0.15-dev X-CMS-MailID: 20250717090830eucas1p191fbd4e0baa40468884307a1b6277be3 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250717090830eucas1p191fbd4e0baa40468884307a1b6277be3 X-EPHeader: CA X-CMS-RootMailID: 20250717090830eucas1p191fbd4e0baa40468884307a1b6277be3 References: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@samsung.com> The upcoming Rust abstraction layer for the PWM subsystem uses a custom `dev->release` handler to safely manage the lifetime of its driver data. To prevent leaking the memory of the `struct pwm_chip` (allocated by `pwmchip_alloc`), this custom handler must also call the original `pwmchip_release` function to complete the cleanup. Make `pwmchip_release` a global, exported function so that it can be called from the Rust FFI bridge. This involves removing the `static` keyword, adding a prototype to the public header, and exporting the symbol. Signed-off-by: Michal Wilczynski --- drivers/pwm/core.c | 3 ++- include/linux/pwm.h | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 4d842c69219445ff1c97ebdb9f1f64031c183a84..bbf729b95c7b94b05bc25bdc1fe= bafb0f2437121 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -1598,12 +1598,13 @@ void pwmchip_put(struct pwm_chip *chip) } EXPORT_SYMBOL_GPL(pwmchip_put); =20 -static void pwmchip_release(struct device *pwmchip_dev) +void pwmchip_release(struct device *pwmchip_dev) { struct pwm_chip *chip =3D pwmchip_from_dev(pwmchip_dev); =20 kfree(chip); } +EXPORT_SYMBOL_GPL(pwmchip_release); =20 struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, s= ize_t sizeof_priv) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 63a17d2b4ec8d0d34a02b05240efb75b637dd99f..82707802ff93432086e5a8f915b= 519b987c68041 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -480,6 +480,12 @@ int __pwmchip_add(struct pwm_chip *chip, struct module= *owner); #define pwmchip_add(chip) __pwmchip_add(chip, THIS_MODULE) void pwmchip_remove(struct pwm_chip *chip); =20 +/* + * For FFI wrapper use only: + * The Rust PWM abstraction needs this to properly free the pwm_chip. + */ +void pwmchip_release(struct device *dev); + int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct m= odule *owner); #define devm_pwmchip_add(dev, chip) __devm_pwmchip_add(dev, chip, THIS_MOD= ULE) =20 --=20 2.34.1 From nobody Mon Oct 6 20:59:00 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (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 B04CE288CAC for ; Thu, 17 Jul 2025 09:08:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752743323; cv=none; b=bEOkeE4vwUMoW9f2S1FptFJ267yRQ3i/d28BHzT6AHnmEu6pl7YoQZrq4nT756gnaRZpnUEbi3AcQZ5NvMxw5hmRXUfkERstq8H3r7mxTrs3OzpbChfDO2Qn35l+fkaY2m+oE9U2pdonW+GTh0cUsJJ/EBMDV/kf37OXyxalYRU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752743323; c=relaxed/simple; bh=0eHfiCvPuZiyjkmwrvr2UPkkByEj4STBt/lkt3aOKTM=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=a9tiVE17PL+xX+CqxkbTIM0/IRmquYcgonLQcgL997sxeoxzRUobPwTggvCH2gLddK6BhojQ/ZDzQ1LCh1e14yMuE3pPH3QDkBh0TfegRZgD1LJr9n/nSVEUJ33RXzoDThozRb3oAxy/6uNmxc35xfEMn0651xRtB1npnSkAwLE= 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=Vfh4ph1y; arc=none smtp.client-ip=210.118.77.12 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="Vfh4ph1y" Received: from eucas1p2.samsung.com (unknown [182.198.249.207]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250717090832euoutp0248c150679ac901409470e5170c440f9b~S-sGfO4Nq1532015320euoutp02I for ; Thu, 17 Jul 2025 09:08:32 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250717090832euoutp0248c150679ac901409470e5170c440f9b~S-sGfO4Nq1532015320euoutp02I DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1752743312; bh=YoMdpZl5sMxRXvIHkMoqJzDQgW+p/4naLd+FG+aZZQw=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=Vfh4ph1yQVw6NmWubLDMZvUQgAxSTTRQv/VI+Z/azWWrO/iY1dsQ2Ueqmfcw+wxxv Cix9v5C+x0T9dO+UWSmG5tzOH5uunrMJLWRcupwOWUscxfpLFJA8Fd3VzNV3NT/3/v uD/o5MvG+cqT1dipWY1PAtiAvji6PpqECYkmOGUI= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p2.samsung.com (KnoxPortal) with ESMTPA id 20250717090831eucas1p282ac3df2e2f1fc2a46e12c440abfbba1~S-sF8pz1C1865818658eucas1p2P; Thu, 17 Jul 2025 09:08:31 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250717090830eusmtip28f4c445e1a1c01012ec8bffef0611173~S-sExOL6F0443704437eusmtip2e; Thu, 17 Jul 2025 09:08:30 +0000 (GMT) From: Michal Wilczynski Date: Thu, 17 Jul 2025 11:08:24 +0200 Subject: [PATCH v12 2/3] rust: pwm: Add Kconfig and basic data structures 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: <20250717-rust-next-pwm-working-fan-for-sending-v12-2-40f73defae0c@samsung.com> In-Reply-To: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@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 , Drew Fustini , 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 X-Mailer: b4 0.15-dev X-CMS-MailID: 20250717090831eucas1p282ac3df2e2f1fc2a46e12c440abfbba1 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250717090831eucas1p282ac3df2e2f1fc2a46e12c440abfbba1 X-EPHeader: CA X-CMS-RootMailID: 20250717090831eucas1p282ac3df2e2f1fc2a46e12c440abfbba1 References: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@samsung.com> Introduce the foundational support for PWM abstractions in Rust. This commit adds the `RUST_PWM_ABSTRACTIONS` Kconfig option to enable the feature, along with the necessary build-system support and C helpers. It also introduces the first set of safe wrappers for the PWM subsystem, covering the basic data carrying C structs and enums: - `Polarity`: A safe wrapper for `enum pwm_polarity`. - `Waveform`: A wrapper for `struct pwm_waveform`. - `Args`: A wrapper for `struct pwm_args`. - `State`: A wrapper for `struct pwm_state`. These types provide memory safe, idiomatic Rust representations of the core PWM data structures and form the building blocks for the abstractions that will follow. Tested-by: Drew Fustini Signed-off-by: Michal Wilczynski Reviewed-by: Daniel Almeida --- MAINTAINERS | 8 +++ 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 | 137 ++++++++++++++++++++++++++++++++++++= ++++ 7 files changed, 182 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa163f9fe8fe3f04bf66426f9a894409..778c668066dda09f304b19e2366= e9011a6843dab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20070,6 +20070,14 @@ 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 +L: linux-pwm@vger.kernel.org +L: rust-for-linux@vger.kernel.org +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..cfddeae0eab3523f04f361fb41c= cd1345c0c937b 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 for + writing PWM controller drivers in Rust. + + The abstractions handle resource management (like memory and reference + 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 bc494745f67b82e7a3a6f53055ece0fc3acf6e0d..e794dada5537c53f8aeae425d18= 1ec339c10f9d0 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -63,6 +63,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 6b4774b2b1c37f4da1866e993be6230bc6715841..ce1d08b14e456905dbe7b625bbb= 8ca8b08deae2a 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -105,6 +105,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..3fad101406eac728d9b12083fad= 7abf7b7f89b25 --- /dev/null +++ b/rust/kernel/pwm.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! PWM subsystem abstractions. +//! +//! C header: [`include/linux/pwm.h`](srctree/include/linux/pwm.h). + +use crate::{ + bindings, + prelude::*, + types::Opaque, +}; +use core::convert::TryFrom; + +/// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.= h). +#[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 TryFrom for Polarity { + type Error =3D Error; + + fn try_from(polarity: bindings::pwm_polarity) -> Result { + match polarity { + bindings::pwm_polarity_PWM_POLARITY_NORMAL =3D> Ok(Polarity::N= ormal), + bindings::pwm_polarity_PWM_POLARITY_INVERSED =3D> Ok(Polarity:= :Inversed), + _ =3D> Err(EINVAL), + } + } +} + +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 [`struct pwm_waveform`](srctree/include/linux/pwm.h). +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Waveform { + /// Total duration of one complete PWM cycle, in nanoseconds. + pub period_length_ns: u64, + + /// Duty-cycle active time, in nanoseconds. + /// + /// For a typical normal polarity configuration (active-high) this is = the + /// high time of the signal. + pub duty_length_ns: u64, + + /// Duty-cycle start offset, in nanoseconds. + /// + /// Delay from the beginning of the period to the first active edge. + /// In most simple PWM setups this is `0`, so the duty cycle starts + /// immediately at each period=E2=80=99s start. + 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`](srctree/= include/linux/pwm.h). +#[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) -> Result { + // 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::try_from(raw_polarity) + } +} + +/// Wrapper for PWM state [`struct pwm_state`](srctree/include/linux/pwm.h= ). +#[repr(transparent)] +pub struct State(bindings::pwm_state); + +impl State { + /// 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) + } + + /// Returns `true` if the PWM signal is enabled. + pub fn enabled(&self) -> bool { + self.0.enabled + } +} --=20 2.34.1 From nobody Mon Oct 6 20:59:00 2025 Received: from mailout2.w1.samsung.com (mailout2.w1.samsung.com [210.118.77.12]) (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 54965288C0F for ; Thu, 17 Jul 2025 09:08:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=210.118.77.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752743325; cv=none; b=lgXNf5XW1CrOzMHFnmz9uG9elPwA69ZqPkTo3/XSlDiKkQVU2qxK2P/NC6BV5G5DtG239URQsIq0nlbLoB8TCg/gbYUX+2DHe9XzkLkvtdX1FdHBb7+Hox9Zd7bv+zGlMg6FTMOdO+LX+hCrh4ReHPq2RD6DDSvxzh1k8AxF05c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752743325; c=relaxed/simple; bh=fcCctAVamiktSqZkVJIGGPejcyrPKJYCKPpbtdYDRuo=; h=From:Date:Subject:MIME-Version:Message-Id:In-Reply-To:To:Cc: Content-Type:References; b=LZpvIKppqUEnEmP+GRSCTNAeDz0+2jZFOm+5c//EDpck1cGAQvxm+q/FSGJD/hdTZTsrPr/mZMOTf4beNcpuU34Vz+ZgmNzQdawG0RoXIesnIbg919l3ADDOvUsSPHbv8D9aCU8GYMllSFfVdu7CUjmTp8uWqWQG/jV7qfZWzQ8= 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=DGdLO9RE; arc=none smtp.client-ip=210.118.77.12 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="DGdLO9RE" Received: from eucas1p1.samsung.com (unknown [182.198.249.206]) by mailout2.w1.samsung.com (KnoxPortal) with ESMTP id 20250717090833euoutp029212e518b2ce449c1fc9ac3f345a0a8d~S-sHsR5OY1321913219euoutp02O for ; Thu, 17 Jul 2025 09:08:33 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.w1.samsung.com 20250717090833euoutp029212e518b2ce449c1fc9ac3f345a0a8d~S-sHsR5OY1321913219euoutp02O DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1752743313; bh=rEzwQFdTni6I8Rs9sCq6TEPTgPeHbJkT/fMwu5ZLKR0=; h=From:Date:Subject:In-Reply-To:To:Cc:References:From; b=DGdLO9RE3YanEgY03NwlGPnoK8WaLGDjT3XucqopaNFYAlutcWHppEe5ajioErm8k lnkSnTGI7r1hMEcqnh3xyZ0WVKZ7Q/8Sd/cbsDALarbrkJARh5vu15pFxxP9V1J3hx LkDHUOGJ9H1nv6s3YGc2bw9V+9XCGFSiZgoSN0lg= Received: from eusmtip2.samsung.com (unknown [203.254.199.222]) by eucas1p1.samsung.com (KnoxPortal) with ESMTPA id 20250717090833eucas1p16c916450b59a77d81bd013527755cb21~S-sHI4P102400324003eucas1p12; Thu, 17 Jul 2025 09:08:33 +0000 (GMT) Received: from AMDC4942.eu.corp.samsungelectronics.net (unknown [106.210.136.40]) by eusmtip2.samsung.com (KnoxPortal) with ESMTPA id 20250717090832eusmtip27509537107c00fad2dd9ff18e3d6772d~S-sGAwNSV0588205882eusmtip2T; Thu, 17 Jul 2025 09:08:32 +0000 (GMT) From: Michal Wilczynski Date: Thu, 17 Jul 2025 11:08:25 +0200 Subject: [PATCH v12 3/3] rust: pwm: Add complete abstraction layer 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: <20250717-rust-next-pwm-working-fan-for-sending-v12-3-40f73defae0c@samsung.com> In-Reply-To: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@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 , Drew Fustini , 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 X-Mailer: b4 0.15-dev X-CMS-MailID: 20250717090833eucas1p16c916450b59a77d81bd013527755cb21 X-Msg-Generator: CA Content-Type: text/plain; charset="utf-8" X-RootMTR: 20250717090833eucas1p16c916450b59a77d81bd013527755cb21 X-EPHeader: CA X-CMS-RootMailID: 20250717090833eucas1p16c916450b59a77d81bd013527755cb21 References: <20250717-rust-next-pwm-working-fan-for-sending-v12-0-40f73defae0c@samsung.com> Introduce a comprehensive abstraction layer for the PWM subsystem to enable writing drivers in Rust. Because `Device`, `Chip`, and `PwmOps` all refer to each other, they form a single, indivisible unit with circular dependencies. They are introduced together in this single commit to create a complete, compilable abstraction layer. The main components are: - Data Wrappers: Safe, idiomatic wrappers for core C types like `pwm_device`, and `pwm_chip`. - PwmOps Trait: An interface that drivers can implement to provide their hardware-specific logic, mirroring the C `pwm_ops` interface. - FFI VTable and Adapter: A bridge to connect the high-level PwmOps trait to the C kernel's pwm_ops vtable. - Allocation and Lifetime Management: A high-level `Chip::new()` API to safely allocate a chip and a `Registration` guard that integrates with `devres` to manage the chip's registration with the PWM core. An `AlwaysRefCounted` implementation and a custom release handler prevent memory leaks by managing the chip's lifetime and freeing driver data correctly. Reviewed-by: Danilo Krummrich Signed-off-by: Michal Wilczynski --- rust/kernel/pwm.rs | 653 +++++++++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 651 insertions(+), 2 deletions(-) diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs index 3fad101406eac728d9b12083fad7abf7b7f89b25..d881f662f0758fb0a8678386081= e8cc237980871 100644 --- a/rust/kernel/pwm.rs +++ b/rust/kernel/pwm.rs @@ -8,10 +8,14 @@ =20 use crate::{ bindings, + container_of, + device::{self, Bound}, + devres, + error::{self, to_result}, prelude::*, - types::Opaque, + types::{ARef, AlwaysRefCounted, Opaque}, }; -use core::convert::TryFrom; +use core::{convert::TryFrom, marker::PhantomData, ptr::NonNull}; =20 /// PWM polarity. Mirrors [`enum pwm_polarity`](srctree/include/linux/pwm.= h). #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -135,3 +139,648 @@ pub fn enabled(&self) -> bool { self.0.enabled } } + +/// Describes the outcome of a `round_waveform` operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RoundingOutcome { + /// The requested waveform was achievable exactly or by rounding value= s down. + ExactOrRoundedDown, + + /// The requested waveform could only be achieved by rounding up. + RoundedUp, +} + +/// Wrapper for a PWM device [`struct pwm_device`](srctree/include/linux/p= wm.h). +#[repr(transparent)] +pub struct Device(Opaque); + +impl Device { + /// Creates a reference to a [`Device`] from a valid C pointer. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` is valid and remains valid for t= he lifetime of the + /// returned [`Device`] reference. + pub(crate) unsafe fn as_ref<'a>(ptr: *mut bindings::pwm_device) -> &'a= Self { + // SAFETY: The safety requirements guarantee the validity of the d= ereference, while the + // `Device` type being transparent makes the cast ok. + unsafe { &*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::as_ref's safety conditions must be met. + unsafe { Chip::::as_ref((*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 }) + } + + /// Sets the PWM waveform configuration and enables the PWM signal. + pub fn set_waveform(&self, wf: &Waveform, exact: bool) -> Result { + let c_wf =3D bindings::pwm_waveform::from(*wf); + + // SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` poin= ter. + // `&c_wf` is a valid pointer to a `pwm_waveform` struct. The C fu= nction + // handles all necessary internal locking. + let ret =3D unsafe { bindings::pwm_set_waveform_might_sleep(self.a= s_raw(), &c_wf, exact) }; + to_result(ret) + } + + /// Queries the hardware for the configuration it would apply for a gi= ven + /// request. + pub fn round_waveform(&self, wf: &mut Waveform) -> Result { + let mut c_wf =3D bindings::pwm_waveform::from(*wf); + + // SAFETY: `self.as_raw()` provides a valid `*mut pwm_device` poin= ter. + // `&mut c_wf` is a valid pointer to a mutable `pwm_waveform` stru= ct that + // the C function will update. + let ret =3D unsafe { bindings::pwm_round_waveform_might_sleep(self= .as_raw(), &mut c_wf) }; + + to_result(ret)?; + + *wf =3D Waveform::from(c_wf); + + if ret =3D=3D 1 { + Ok(RoundingOutcome::RoundedUp) + } else { + Ok(RoundingOutcome::ExactOrRoundedDown) + } + } + + /// Reads the current waveform configuration directly from the hardwar= e. + pub fn get_waveform(&self) -> Result { + let mut c_wf =3D bindings::pwm_waveform::default(); + + // SAFETY: `self.as_raw()` is a valid pointer. We provide a valid = pointer + // to a stack-allocated `pwm_waveform` struct for the kernel to fi= ll. + let ret =3D unsafe { bindings::pwm_get_waveform_might_sleep(self.a= s_raw(), &mut c_wf) }; + + to_result(ret)?; + + Ok(Waveform::from(c_wf)) + } +} + +/// 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 `PWM_WFHWS= IZE`. + type WfHw: Copy + Default; + + /// Optional hook for when a PWM device is requested. + fn request( + _chip: &Chip, + _pwm: &Device, + _parent_dev: &device::Device, + ) -> Result { + Ok(()) + } + + /// Optional hook for capturing a PWM signal. + fn capture( + _chip: &Chip, + _pwm: &Device, + _result: &mut bindings::pwm_capture, + _timeout: usize, + _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: &Chip, + _pwm: &Device, + _wf: &Waveform, + ) -> Result<(c_int, 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: &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: &Chip, + _pwm: &Device, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Write a hardware-specific waveform configuration to the hardware. + fn write_waveform( + _chip: &Chip, + _pwm: &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 { + const VTABLE: PwmOpsVTable =3D create_pwm_ops::(); + + /// # Safety + /// + /// `wfhw_ptr` must be valid for writes of `size_of::()` byte= s. + unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut c_void) -> Res= ult { + let size =3D core::mem::size_of::(); + if size > bindings::PWM_WFHWSIZE as usize { + return Err(EINVAL); + } + + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping( + core::ptr::from_ref::(wfhw).cast::(), + wfhw_ptr.cast::(), + size, + ); + } + + Ok(()) + } + + /// # Safety + /// + /// `wfhw_ptr` must be valid for reads of `size_of::()` bytes. + unsafe fn deserialize_wfhw(wfhw_ptr: *const c_void) -> Result= { + let size =3D core::mem::size_of::(); + if size > bindings::PWM_WFHWSIZE as usize { + 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::(), + core::ptr::from_mut::(&mut wfhw).cast::(), + size, + ); + } + + Ok(wfhw) + } + + /// # Safety + /// + /// `dev` must be a valid pointer to a `bindings::device` embedded wit= hin a + /// `bindings::pwm_chip`. This function is called by the device core w= hen the + /// last reference to the device is dropped. + unsafe extern "C" fn release_callback(dev: *mut bindings::device) { + // SAFETY: The function's contract guarantees that `dev` points to= a `device` + // field embedded within a valid `pwm_chip`. `container_of!` can t= herefore + // safely calculate the address of the containing struct. + let c_chip_ptr =3D unsafe { container_of!(dev, bindings::pwm_chip,= dev) }; + + // SAFETY: `c_chip_ptr` is a valid pointer to a `pwm_chip` as esta= blished + // above. Calling this FFI function is safe. + let drvdata_ptr =3D unsafe { bindings::pwmchip_get_drvdata(c_chip_= ptr) }; + + // SAFETY: The driver data was initialized in `new`. We run its de= structor here. + unsafe { core::ptr::drop_in_place(drvdata_ptr.cast::()) }; + + // Now, call the original release function to free the `pwm_chip` = itself. + // SAFETY: `dev` is the valid pointer passed into this callback, w= hich is + // the expected argument for `pwmchip_release`. + unsafe { bindings::pwmchip_release(dev); } + } + + /// # Safety + /// + /// Pointers from C must be valid. + unsafe extern "C" fn request_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + ) -> c_int { + // SAFETY: PWM core guarentees `c` and `p` are valid pointers. + let (chip, pwm) =3D unsafe { (Chip::::as_ref(c), Device::as_ref= (p)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + match T::request(chip, pwm, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// 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, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, result) =3D + unsafe { (Chip::::as_ref(c), Device::as_ref(p), &mut *res) = }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_device() }; + match T::capture(chip, pwm, result, timeout, bound_parent) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + /// # Safety + /// + /// 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 c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm, wf) =3D unsafe { + ( + Chip::::as_ref(c), + Device::as_ref(p), + Waveform::from(*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 + /// + /// 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 c_void, + w: *mut bindings::pwm_waveform, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::::as_ref(c), Device::as_ref= (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 + /// + /// 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 c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::::as_ref(c), Device::as_ref= (p)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_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 + /// + /// 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 c_void, + ) -> c_int { + // SAFETY: Relies on the function's contract that `c` and `p` are = valid pointers. + let (chip, pwm) =3D unsafe { (Chip::::as_ref(c), Device::as_ref= (p)) }; + + // SAFETY: The PWM core guarantees the parent device exists and is= bound during callbacks. + let bound_parent =3D unsafe { chip.bound_parent_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`](srctree/include/linux/pwm.h). +#[repr(transparent)] +pub struct PwmOpsVTable(bindings::pwm_ops); + +// 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 + } +} + +/// 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.request =3D Some(Adapter::::request_callback); + ops.capture =3D Some(Adapter::::capture_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(ops) +} + +/// Wrapper for a PWM chip/controller ([`struct pwm_chip`](srctree/include= /linux/pwm.h)). +#[repr(transparent)] +pub struct Chip(Opaque, PhantomData); + +impl Chip { + /// Creates a reference to a [`Chip`] from a valid pointer. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` is valid and remains valid for t= he lifetime of the + /// returned [`Chip`] reference. + pub(crate) unsafe fn as_ref<'a>(ptr: *mut bindings::pwm_chip) -> &'a S= elf { + // SAFETY: The safety requirements guarantee the validity of the d= ereference, while the + // `Chip` type being transparent makes the cast ok. + unsafe { &*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`. + unsafe { device::Device::as_ref(&raw mut (*self.as_raw()).dev) } + } + + /// Gets the *typed* driver specific data associated with this chip's = embedded device. + pub fn drvdata(&self) -> &T { + // SAFETY: `pwmchip_get_drvdata` returns the pointer to the privat= e data area, + // which we know holds our `T`. The pointer is valid for the lifet= ime of `self`. + unsafe { &*bindings::pwmchip_get_drvdata(self.as_raw()).cast::(= ) } + } + + /// Returns a reference to the parent device of this PWM chip's device. + /// + /// # Safety + /// + /// The caller must guarantee that the parent device exists and is bou= nd. + /// This is guaranteed by the PWM core during `PwmOps` callbacks. + unsafe fn bound_parent_device(&self) -> &device::Device { + // SAFETY: Per the function's safety contract, the parent device e= xists. + let parent =3D unsafe { self.device().parent().unwrap_unchecked() = }; + + // SAFETY: Per the function's safety contract, the parent device i= s bound. + // The pointer is cast from `&Device` to `&Device`. + unsafe { &*core::ptr::from_ref(parent).cast::>() } + } + + /// Allocates and wraps a PWM chip using `bindings::pwmchip_alloc`. + /// + /// Returns an [`ARef`] managing the chip's lifetime via refcoun= ting + /// on its embedded `struct device`. + pub fn new( + parent_dev: &device::Device, + npwm: u32, + data: impl pin_init::PinInit, + ) -> Result> { + + let sizeof_priv =3D core::mem::size_of::(); + // SAFETY: `pwmchip_alloc` allocates memory for the C struct and o= ur private data. + let c_chip_ptr_raw =3D unsafe { + bindings::pwmchip_alloc(parent_dev.as_raw(), npwm, sizeof_priv) + }; + + let c_chip_ptr: *mut bindings::pwm_chip =3D error::from_err_ptr(c_= chip_ptr_raw)?; + + // SAFETY: The `drvdata` pointer is the start of the private area,= which is where + // we will construct our `T` object. + let drvdata_ptr =3D unsafe { bindings::pwmchip_get_drvdata(c_chip_= ptr) }; + + // SAFETY: We construct the `T` object in-place in the allocated p= rivate memory. + unsafe { data.__pinned_init(drvdata_ptr.cast())? }; + + // SAFETY: `c_chip_ptr` points to a valid chip. + unsafe { (*c_chip_ptr).dev.release =3D Some(Adapter::::release_= callback); } + + // SAFETY: `c_chip_ptr` points to a valid chip. + // The `Adapter`'s `VTABLE` has a 'static lifetime, so the pointer + // returned by `as_raw()` is always valid. + unsafe { (*c_chip_ptr).ops =3D Adapter::::VTABLE.as_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(&raw mut (*self.0.get()).dev); } + } + + #[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(&raw 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 {} + +/// A resource guard that ensures `pwmchip_remove` is called on drop. +/// +/// This struct is intended to be managed by the `devres` framework by tra= nsferring its ownership +/// via [`Devres::register`]. This ties the lifetime of the PWM chip regis= tration +/// to the lifetime of the underlying device. +pub struct Registration { + chip: ARef>, +} + +impl Registration { + /// Registers a PWM chip with the PWM subsystem. + /// + /// Transfers its ownership to the `devres` framework, which ties its = lifetime + /// to the parent device. + /// On unbind of the parent device, the `devres` entry will be dropped= , automatically + /// calling `pwmchip_remove`. This function should be called from the = driver's `probe`. + pub fn register( + dev: &device::Device, + chip: ARef>, + ) -> Result { + let chip_parent =3D chip.device().parent().ok_or(EINVAL)?; + if dev.as_raw() !=3D chip_parent.as_raw() { + return Err(EINVAL); + } + + let c_chip_ptr =3D chip.as_raw(); + + // SAFETY: `c_chip_ptr` points to a valid chip with its ops initia= lized. + // `__pwmchip_add` is the C function to register the chip with the= PWM core. + unsafe { + to_result(bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_= mut()))?; + } + + let registration =3D Registration { chip }; + + devres::Devres::new_foreign_owned(dev, registration, GFP_KERNEL) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + let chip_raw =3D self.chip.as_raw(); + + // SAFETY: `chip_raw` points to a chip that was successfully regis= tered. + // `bindings::pwmchip_remove` is the correct C function to unregis= ter it. + // This `drop` implementation is called automatically by `devres` = on driver unbind. + unsafe { bindings::pwmchip_remove(chip_raw); } + } +} --=20 2.34.1