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 <fustini@kernel.org>
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
---
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..778c668066dda09f304b19e2366e9011a6843dab 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)
+PWM SUBSYSTEM BINDINGS [RUST]
+M: Michal Wilczynski <m.wilczynski@samsung.com>
+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 <robert.jarzmik@free.fr>
L: linux-gpio@vger.kernel.org
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index d9bcd1e8413eaed1602d6686873e263767c58f5f..cfddeae0eab3523f04f361fb41ccd1345c0c937b 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.
+ config RUST_PWM_ABSTRACTIONS
+ bool "Rust PWM abstractions support"
+ depends on RUST
+ depends on PWM=y
+ 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_helper.h
index bc494745f67b82e7a3a6f53055ece0fc3acf6e0d..e794dada5537c53f8aeae425d181ec339c10f9d0 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -63,6 +63,7 @@
#include <linux/pm_opp.h>
#include <linux/poll.h>
#include <linux/property.h>
+#include <linux/pwm.h>
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/security.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 0f1b5d11598591bc62bb6439747211af164b76d6..73902d8bd87e93cb3bc3c501360c37e29e8dde19 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..d75c588863685d3990b525bb1b84aa4bc35ac397
--- /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 <m.wilczynski@samsung.com>
+
+#include <linux/pwm.h>
+
+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..ce1d08b14e456905dbe7b625bbb8ca8b08deae2a 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..3fad101406eac728d9b12083fad7abf7b7f89b25
--- /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 <m.wilczynski@samsung.com>
+
+//! 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<bindings::pwm_polarity> for Polarity {
+ type Error = Error;
+
+ fn try_from(polarity: bindings::pwm_polarity) -> Result<Self, Error> {
+ match polarity {
+ bindings::pwm_polarity_PWM_POLARITY_NORMAL => Ok(Polarity::Normal),
+ bindings::pwm_polarity_PWM_POLARITY_INVERSED => Ok(Polarity::Inversed),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl From<Polarity> for bindings::pwm_polarity {
+ fn from(polarity: Polarity) -> Self {
+ match polarity {
+ Polarity::Normal => bindings::pwm_polarity_PWM_POLARITY_NORMAL,
+ Polarity::Inversed => bindings::pwm_polarity_PWM_POLARITY_INVERSED,
+ }
+ }
+}
+
+/// 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’s start.
+ pub duty_offset_ns: u64,
+}
+
+impl From<bindings::pwm_waveform> 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<Waveform> 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<bindings::pwm_args>);
+
+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 pointer
+ /// 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` owns a copy.
+ unsafe { (*self.0.get()).period }
+ }
+
+ /// Returns the polarity of the PWM signal.
+ pub fn polarity(&self) -> Result<Polarity, Error> {
+ // 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 = 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` value.
+ 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
+ }
+}
--
2.34.1
Hi Michal, Overall looks good, a few minor comments: […] > + > +/// Wrapper for board-dependent PWM arguments [`struct pwm_args`](srctree/include/linux/pwm.h). > +#[repr(transparent)] > +pub struct Args(Opaque<bindings::pwm_args>); > + > +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 pointer > + /// 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 })) > + } from_raw() > + > + /// 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` owns a copy. > + unsafe { (*self.0.get()).period } > + } > + > + /// Returns the polarity of the PWM signal. > + pub fn polarity(&self) -> Result<Polarity, Error> { > + // 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 = 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); No Opaque<T>? > + > +impl State { > + /// Creates a `State` wrapper by taking ownership of a C `pwm_state` value. > + 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 > + } > +} > > -- > 2.34.1 > > If the lack of Opaque<T> is not a problem for whatever reason: Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com> — Daniel
On 7/25/25 17:08, Daniel Almeida wrote: > Hi Michal, > > > Overall looks good, a few minor comments: Hi, Congratulations for getting your IoMem series merged. Thank you for your work, as it will allow me to proceed and re-add the driver part for this series. > > […] > >> + >> +/// Wrapper for board-dependent PWM arguments [`struct pwm_args`](srctree/include/linux/pwm.h). >> +#[repr(transparent)] >> +pub struct Args(Opaque<bindings::pwm_args>); >> + >> +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 pointer >> + /// 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 })) >> + } > > from_raw() > > >> + >> + /// 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` owns a copy. >> + unsafe { (*self.0.get()).period } >> + } >> + >> + /// Returns the polarity of the PWM signal. >> + pub fn polarity(&self) -> Result<Polarity, Error> { >> + // 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 = 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); > > No Opaque<T>? Since this is a copy of the state it's fine. The Args above should follow similar pattern, the divergence stemmed when iterating with this series. So I would rather fix the Args to also not be Opaque as it doesn't need to be, since it's also a copy of the original (since it's so small and read only). > >> + >> +impl State { >> + /// Creates a `State` wrapper by taking ownership of a C `pwm_state` value. >> + 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 >> + } >> +} >> >> -- >> 2.34.1 >> >> > > If the lack of Opaque<T> is not a problem for whatever reason: > > Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com> > > — Daniel > > Best regards, -- Michal Wilczynski <m.wilczynski@samsung.com>
© 2016 - 2025 Red Hat, Inc.