[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20260106-rust_leds-v10-2-e0a1564884f9@posteo.de>
Date: Tue, 06 Jan 2026 01:42:53 +0000
From: Markus Probst <markus.probst@...teo.de>
To: Lee Jones <lee@...nel.org>, Pavel Machek <pavel@...nel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Dave Ertman <david.m.ertman@...el.com>, Ira Weiny <ira.weiny@...el.com>,
Leon Romanovsky <leon@...nel.org>, Miguel Ojeda <ojeda@...nel.org>,
Alex Gaynor <alex.gaynor@...il.com>, Boqun Feng <boqun.feng@...il.com>,
Gary Guo <gary@...yguo.net>,
Björn Roy Baron <bjorn3_gh@...tonmail.com>,
Benno Lossin <lossin@...nel.org>, Andreas Hindborg <a.hindborg@...nel.org>,
Alice Ryhl <aliceryhl@...gle.com>, Trevor Gross <tmgross@...ch.edu>,
Danilo Krummrich <dakr@...nel.org>, "Rafael J. Wysocki" <rafael@...nel.org>,
Bjorn Helgaas <bhelgaas@...gle.com>,
Krzysztof Wilczyński <kwilczynski@...nel.org>
Cc: rust-for-linux@...r.kernel.org, linux-leds@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-pci@...r.kernel.org,
Markus Probst <markus.probst@...teo.de>
Subject: [PATCH v10 2/3] rust: leds: split generic and normal led classdev
abstractions up
Move code specific to normal led class devices into a separate file and
introduce the `led::Mode` trait to allow for other types of led class
devices in `led::LedOps`.
Signed-off-by: Markus Probst <markus.probst@...teo.de>
---
MAINTAINERS | 1 +
rust/kernel/led.rs | 242 ++++++----------------------------------------
rust/kernel/led/normal.rs | 223 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 256 insertions(+), 210 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c8648a26f93..d17c8f577ff3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14275,6 +14275,7 @@ L: linux-leds@...r.kernel.org
L: rust-for-linux@...r.kernel.org
S: Maintained
F: rust/kernel/led.rs
+F: rust/kernel/led/
LEGO MINDSTORMS EV3
R: David Lechner <david@...hnology.com>
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
index cdd9b0300b72..a9eb2e8e480c 100644
--- a/rust/kernel/led.rs
+++ b/rust/kernel/led.rs
@@ -33,15 +33,9 @@
}, //
};
-/// The led class device representation.
-///
-/// This structure represents the Rust abstraction for a C `struct led_classdev`.
-#[pin_data(PinnedDrop)]
-pub struct Device<T: LedOps> {
- ops: T,
- #[pin]
- classdev: Opaque<bindings::led_classdev>,
-}
+mod normal;
+
+pub use normal::{Device, Normal};
/// The led init data representation.
///
@@ -134,6 +128,7 @@ pub fn color(self, color: Color) -> Self {
/// #[vtable]
/// impl led::LedOps for MyLedOps {
/// type Bus = platform::Device<device::Bound>;
+/// type Mode = led::Normal;
/// const BLOCKING: bool = false;
/// const MAX_BRIGHTNESS: u32 = 255;
///
@@ -164,6 +159,12 @@ pub trait LedOps: Send + 'static + Sized {
/// The bus device required by the implementation.
#[allow(private_bounds)]
type Bus: AsBusDevice<Bound>;
+
+ /// The led mode to use.
+ ///
+ /// See [`Mode`].
+ type Mode: Mode;
+
/// If set true, [`LedOps::brightness_set`] and [`LedOps::blink_set`] must perform the
/// operation immediately. If set false, they must not sleep.
const BLOCKING: bool;
@@ -176,12 +177,17 @@ pub trait LedOps: Send + 'static + Sized {
fn brightness_set(
&self,
dev: &Self::Bus,
- classdev: &Device<Self>,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
brightness: u32,
) -> Result<()>;
/// Gets the current brightness level.
- fn brightness_get(&self, _dev: &Self::Bus, _classdev: &Device<Self>) -> u32 {
+ fn brightness_get(
+ &self,
+ dev: &Self::Bus,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
+ ) -> u32 {
+ let _ = (dev, classdev);
build_error!(VTABLE_DEFAULT_ERROR)
}
@@ -195,11 +201,12 @@ fn brightness_get(&self, _dev: &Self::Bus, _classdev: &Device<Self>) -> u32 {
/// See also [`LedOps::BLOCKING`].
fn blink_set(
&self,
- _dev: &Self::Bus,
- _classdev: &Device<Self>,
- _delay_on: &mut usize,
- _delay_off: &mut usize,
+ dev: &Self::Bus,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
) -> Result<()> {
+ let _ = (dev, classdev, delay_on, delay_off);
build_error!(VTABLE_DEFAULT_ERROR)
}
}
@@ -250,201 +257,16 @@ fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
}
}
-// SAFETY: A `led::Device` can be unregistered from any thread.
-unsafe impl<T: LedOps + Send> Send for Device<T> {}
-
-// SAFETY: `led::Device` can be shared among threads because all methods of `led::Device`
-// are thread safe.
-unsafe impl<T: LedOps + Sync> Sync for Device<T> {}
-
-impl<T: LedOps> Device<T> {
- /// Registers a new led classdev.
- ///
- /// The [`Device`] will be unregistered on drop.
- pub fn new<'a>(
- parent: &'a T::Bus,
- init_data: InitData<'a>,
- ops: T,
- ) -> impl PinInit<Devres<Self>, Error> + 'a {
- Devres::new(
- parent.as_ref(),
- try_pin_init!(Self {
- ops,
- classdev <- Opaque::try_ffi_init(|ptr: *mut bindings::led_classdev| {
- // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write.
- // `led_classdev` gets fully initialized in-place by
- // `led_classdev_register_ext` including `mutex` and `list_head`.
- unsafe {
- ptr.write(bindings::led_classdev {
- brightness_set: (!T::BLOCKING)
- .then_some(Adapter::<T>::brightness_set_callback),
- brightness_set_blocking: T::BLOCKING
- .then_some(Adapter::<T>::brightness_set_blocking_callback),
- brightness_get: T::HAS_BRIGHTNESS_GET
- .then_some(Adapter::<T>::brightness_get_callback),
- blink_set: T::HAS_BLINK_SET.then_some(Adapter::<T>::blink_set_callback),
- max_brightness: T::MAX_BRIGHTNESS,
- brightness: init_data.initial_brightness,
- default_trigger: init_data.default_trigger
- .map_or(core::ptr::null(), CStrExt::as_char_ptr),
- color: init_data.color as u32,
- ..bindings::led_classdev::default()
- })
- };
-
- let mut init_data_raw = bindings::led_init_data {
- fwnode: init_data.fwnode
- .as_ref()
- .map_or(core::ptr::null_mut(), |fwnode| fwnode.as_raw()),
- default_label: core::ptr::null(),
- devicename: init_data
- .devicename
- .map_or(core::ptr::null(), CStrExt::as_char_ptr),
- devname_mandatory: init_data.devname_mandatory,
- };
-
- // SAFETY:
- // - `parent.as_raw()` is guaranteed to be a pointer to a valid `device`
- // or a null pointer.
- // - `ptr` is guaranteed to be a pointer to an initialized `led_classdev`.
- to_result(unsafe {
- bindings::led_classdev_register_ext(
- parent.as_ref().as_raw(),
- ptr,
- &raw mut init_data_raw,
- )
- })?;
-
- core::mem::forget(init_data.fwnode); // keep the reference count incremented
-
- Ok::<_, Error>(())
- }),
- }),
- )
- }
-
- /// # Safety
- /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
- /// `led::Device`.
- unsafe fn from_raw<'a>(led_cdev: *mut bindings::led_classdev) -> &'a Self {
- // SAFETY: The function's contract guarantees that `led_cdev` points to a `led_classdev`
- // field embedded within a valid `led::Device`. `container_of!` can therefore
- // safely calculate the address of the containing struct.
- unsafe { &*container_of!(Opaque::cast_from(led_cdev), Self, classdev) }
- }
-
- fn parent(&self) -> &device::Device<Bound> {
- // SAFETY:
- // - `self.classdev.get()` is guaranteed to be a valid pointer to `led_classdev`.
- unsafe { device::Device::from_raw((*(*self.classdev.get()).dev).parent) }
- }
-}
-
-struct Adapter<T: LedOps> {
- _p: PhantomData<T>,
-}
-
-impl<T: LedOps> Adapter<T> {
- /// # Safety
- /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
- /// `led::Device`.
- /// This function is called on setting the brightness of a led.
- unsafe extern "C" fn brightness_set_callback(
- led_cdev: *mut bindings::led_classdev,
- brightness: u32,
- ) {
- // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
- // `led_classdev` embedded within a `led::Device`.
- let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
- // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
- let parent = unsafe { T::Bus::from_device(classdev.parent()) };
-
- let _ = classdev.ops.brightness_set(parent, classdev, brightness);
- }
-
- /// # Safety
- /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
- /// `led::Device`.
- /// This function is called on setting the brightness of a led immediately.
- unsafe extern "C" fn brightness_set_blocking_callback(
- led_cdev: *mut bindings::led_classdev,
- brightness: u32,
- ) -> i32 {
- from_result(|| {
- // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
- // `led_classdev` embedded within a `led::Device`.
- let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
- // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
- let parent = unsafe { T::Bus::from_device(classdev.parent()) };
-
- classdev.ops.brightness_set(parent, classdev, brightness)?;
- Ok(0)
- })
- }
-
- /// # Safety
- /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
- /// `led::Device`.
- /// This function is called on getting the brightness of a led.
- unsafe extern "C" fn brightness_get_callback(led_cdev: *mut bindings::led_classdev) -> u32 {
- // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
- // `led_classdev` embedded within a `led::Device`.
- let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
- // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
- let parent = unsafe { T::Bus::from_device(classdev.parent()) };
-
- classdev.ops.brightness_get(parent, classdev)
- }
-
- /// # Safety
- /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
- /// `led::Device`.
- /// `delay_on` and `delay_off` must be valid pointers to `usize` and have
- /// exclusive access for the period of this function.
- /// This function is called on enabling hardware accelerated blinking.
- unsafe extern "C" fn blink_set_callback(
- led_cdev: *mut bindings::led_classdev,
- delay_on: *mut usize,
- delay_off: *mut usize,
- ) -> i32 {
- from_result(|| {
- // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
- // `led_classdev` embedded within a `led::Device`.
- let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
- // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
- let parent = unsafe { T::Bus::from_device(classdev.parent()) };
-
- classdev.ops.blink_set(
- parent,
- classdev,
- // SAFETY: The function's contract guarantees that `delay_on` points to a `usize`
- // and is exclusive for the period of this function.
- unsafe { &mut *delay_on },
- // SAFETY: The function's contract guarantees that `delay_off` points to a `usize`
- // and is exclusive for the period of this function.
- unsafe { &mut *delay_off },
- )?;
- Ok(0)
- })
- }
+/// The led mode.
+///
+/// Each led mode has its own led class device type with different capabilities.
+///
+/// See [`Normal`].
+pub trait Mode: private::Sealed {
+ /// The class device for the led mode.
+ type Device<T: LedOps<Mode = Self>>;
}
-#[pinned_drop]
-impl<T: LedOps> PinnedDrop for Device<T> {
- fn drop(self: Pin<&mut Self>) {
- let raw = self.classdev.get();
- // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
- // valid `struct led_classdev`.
- let dev: &device::Device = unsafe { device::Device::from_raw((*raw).dev) };
-
- let _fwnode = dev
- .fwnode()
- // SAFETY: the reference count of `fwnode` has previously been
- // incremented in `led::Device::new`.
- .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });
-
- // SAFETY: The existence of `self` guarantees that `self.classdev` has previously been
- // successfully registered with `led_classdev_register_ext`.
- unsafe { bindings::led_classdev_unregister(self.classdev.get()) };
- }
+mod private {
+ pub trait Sealed {}
}
diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs
new file mode 100644
index 000000000000..710ae438a088
--- /dev/null
+++ b/rust/kernel/led/normal.rs
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Led mode for the `struct led_classdev`.
+//!
+//! C header: [`include/linux/leds.h`](srctree/include/linux/leds.h)
+
+use super::*;
+
+/// The led mode for the `struct led_classdev`. Leds with this mode can only have a fixed color.
+pub enum Normal {}
+
+impl Mode for Normal {
+ type Device<T: LedOps<Mode = Self>> = Device<T>;
+}
+impl private::Sealed for Normal {}
+
+/// The led class device representation.
+///
+/// This structure represents the Rust abstraction for a led class device.
+#[pin_data(PinnedDrop)]
+pub struct Device<T: LedOps<Mode = Normal>> {
+ ops: T,
+ #[pin]
+ classdev: Opaque<bindings::led_classdev>,
+}
+
+impl<T: LedOps<Mode = Normal>> Device<T> {
+ /// Registers a new led classdev.
+ ///
+ /// The [`Device`] will be unregistered on drop.
+ pub fn new<'a>(
+ parent: &'a T::Bus,
+ init_data: InitData<'a>,
+ ops: T,
+ ) -> impl PinInit<Devres<Self>, Error> + 'a {
+ Devres::new(
+ parent.as_ref(),
+ try_pin_init!(Self {
+ ops,
+ classdev <- Opaque::try_ffi_init(|ptr: *mut bindings::led_classdev| {
+ // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write.
+ // `led_classdev` gets fully initialized in-place by
+ // `led_classdev_register_ext` including `mutex` and `list_head`.
+ unsafe {
+ ptr.write(bindings::led_classdev {
+ brightness_set: (!T::BLOCKING)
+ .then_some(Adapter::<T>::brightness_set_callback),
+ brightness_set_blocking: T::BLOCKING
+ .then_some(Adapter::<T>::brightness_set_blocking_callback),
+ brightness_get: T::HAS_BRIGHTNESS_GET
+ .then_some(Adapter::<T>::brightness_get_callback),
+ blink_set: T::HAS_BLINK_SET.then_some(Adapter::<T>::blink_set_callback),
+ max_brightness: T::MAX_BRIGHTNESS,
+ brightness: init_data.initial_brightness,
+ default_trigger: init_data.default_trigger
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ color: init_data.color as u32,
+ ..bindings::led_classdev::default()
+ })
+ };
+
+ let mut init_data_raw = bindings::led_init_data {
+ fwnode: init_data.fwnode
+ .as_ref()
+ .map_or(core::ptr::null_mut(), |fwnode| fwnode.as_raw()),
+ default_label: core::ptr::null(),
+ devicename: init_data
+ .devicename
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ devname_mandatory: init_data.devname_mandatory,
+ };
+
+ // SAFETY:
+ // - `parent.as_ref().as_raw()` is guaranteed to be a pointer to a valid
+ // `device`.
+ // - `ptr` is guaranteed to be a pointer to an initialized `led_classdev`.
+ to_result(unsafe {
+ bindings::led_classdev_register_ext(
+ parent.as_ref().as_raw(),
+ ptr,
+ &raw mut init_data_raw,
+ )
+ })?;
+
+ core::mem::forget(init_data.fwnode); // keep the reference count incremented
+
+ Ok::<_, Error>(())
+ }),
+ }),
+ )
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ unsafe fn from_raw<'a>(led_cdev: *mut bindings::led_classdev) -> &'a Self {
+ // SAFETY: The function's contract guarantees that `led_cdev` points to a `led_classdev`
+ // field embedded within a valid `led::Device`. `container_of!` can therefore
+ // safely calculate the address of the containing struct.
+ unsafe { &*container_of!(Opaque::cast_from(led_cdev), Self, classdev) }
+ }
+
+ fn parent(&self) -> &device::Device<Bound> {
+ // SAFETY: `self.classdev.get()` is guaranteed to be a valid pointer to `led_classdev`.
+ unsafe { device::Device::from_raw((*(*self.classdev.get()).dev).parent) }
+ }
+}
+
+// SAFETY: A `led::Device` can be unregistered from any thread.
+unsafe impl<T: LedOps<Mode = Normal> + Send> Send for Device<T> {}
+
+// SAFETY: `led::Device` can be shared among threads because all methods of `led::Device`
+// are thread safe.
+unsafe impl<T: LedOps<Mode = Normal> + Sync> Sync for Device<T> {}
+
+struct Adapter<T: LedOps<Mode = Normal>> {
+ _p: PhantomData<T>,
+}
+
+impl<T: LedOps<Mode = Normal>> Adapter<T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on setting the brightness of a led.
+ unsafe extern "C" fn brightness_set_callback(
+ led_cdev: *mut bindings::led_classdev,
+ brightness: u32,
+ ) {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ let _ = classdev.ops.brightness_set(parent, classdev, brightness);
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on setting the brightness of a led immediately.
+ unsafe extern "C" fn brightness_set_blocking_callback(
+ led_cdev: *mut bindings::led_classdev,
+ brightness: u32,
+ ) -> i32 {
+ from_result(|| {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.brightness_set(parent, classdev, brightness)?;
+ Ok(0)
+ })
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on getting the brightness of a led.
+ unsafe extern "C" fn brightness_get_callback(led_cdev: *mut bindings::led_classdev) -> u32 {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.brightness_get(parent, classdev)
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// `delay_on` and `delay_off` must be valid pointers to `usize` and have
+ /// exclusive access for the period of this function.
+ /// This function is called on enabling hardware accelerated blinking.
+ unsafe extern "C" fn blink_set_callback(
+ led_cdev: *mut bindings::led_classdev,
+ delay_on: *mut usize,
+ delay_off: *mut usize,
+ ) -> i32 {
+ from_result(|| {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.blink_set(
+ parent,
+ classdev,
+ // SAFETY: The function's contract guarantees that `delay_on` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_on },
+ // SAFETY: The function's contract guarantees that `delay_off` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_off },
+ )?;
+ Ok(0)
+ })
+ }
+}
+
+#[pinned_drop]
+impl<T: LedOps<Mode = Normal>> PinnedDrop for Device<T> {
+ fn drop(self: Pin<&mut Self>) {
+ let raw = self.classdev.get();
+ // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
+ // valid `led_classdev`.
+ let dev: &device::Device = unsafe { device::Device::from_raw((*raw).dev) };
+
+ let _fwnode = dev
+ .fwnode()
+ // SAFETY: the reference count of `fwnode` has previously been
+ // incremented in `led::Device::new`.
+ .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });
+
+ // SAFETY: The existence of `self` guarantees that `self.classdev` has previously been
+ // successfully registered with `led_classdev_register_ext`.
+ unsafe { bindings::led_classdev_unregister(raw) };
+ }
+}
--
2.51.2
Powered by blists - more mailing lists