* [PATCH v28 0/4] rust: leds: Add led classdev abstractions
@ 2026-11-13 13:37 Markus Probst
2026-11-13 13:37 ` [PATCH v28 1/4] rust: leds: Add basic " Markus Probst
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Markus Probst @ 2026-11-13 13:37 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Leon Romanovsky, Miguel Ojeda, Alex Gaynor, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rafael J. Wysocki, Bjorn Helgaas,
Krzysztof Wilczyński, Boqun Feng, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan, Ira Weiny,
Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
The abstraction is used by
https://lore.kernel.org/rust-for-linux/20260724-synology_microp_initial-v18-0-fb2f49f10e77@posteo.de/
.
The following changes were made:
* add basic led classdev abstractions to register and unregister leds
* add basic led classdev abstractions to register and unregister
multicolor leds
Changes since v27:
* rebase onto v7.4-rc2
Changes since v26:
* use Atomic (Boqun)
Changes since v25:
* fix issues reported by Sashiko:
* fix vertical import
* add drivers/leds/*.rs to MAINTAINERS file (Kari)
* s/add/Add/ in commit message
Changes since v24:
* remove `LedOps::Bus` (Gary)
* support type-erased `Device` and `MultiColorDevice` types (Gary)
* use `Device` and `MultiColorDevice` as arbitrary self types in
callbacks. Remove separate `classdev` argument (Gary)
* remove unnecessary imports in doc example
Changes since v23:
* add separate patch for MAINTAINERS file update
Changes since v22:
* readded CStrExt import, because it is imported with `as _` in prelude.
A `# CONFIG_RUST is not set` sneaked into my .config while
development, so the compile error was unnoticed.
Changes since v21:
* use 'init for lifetime that is only alive during initialization
* remove unnecessary CStrExt import
Changes since v20:
* resolve Sashiko regressions:
* fix typo
* fix fwnode refcount decremented too early
Changes since v19:
* rebase on v7.2-rc1:
* Add `max_intensity` to `MultiColorSubLed`
* use safer `KBox::pin_slice` instead of `KVec`
(len might not equal capacity)
* explicitly call `FwNode::dec_ref` instead of dropping a reconstructed
`ARef<FwNode>`.
* remove direct access to `intensity` and `brightness` fields,
which may get mutated concurrently by the C side
* fix safety comments pointing to functions from previous revisions
Changes since v18:
* add inlines
* fix invalid documentation
* improve led color duplicate checking
Changes since v17:
* use lifetimes instead of Devres
Changes since v16:
* use for loops for duplicate checking
Changes since v15:
* fix issues reported by Sashiko bot:
* fix returning error not possible on `brightness_get` callback
Changes since v14:
* fix issues reported by Sashiko bot:
* add missing inlines
* add missing Sync trait bound
* fix vertical import layout for public export of private types
* fix potential memory leak, if a multicolor led device with over
`u32::MAX` subleds is registered
* remove default_trigger option
* fix missing CAST doc
Changes since v13:
* rebased onto v7.1-rc1
Changes since v12:
* add `led::DeviceBuilder::name()` and `DeviceBuilderState'
* add `led::Color::as_c_str`
Changes since v11:
* use `led::DeviceBuilder` instead of `led::InitData`
* use static_assert instead of const { assert!(...) }
* restructured patches to avoid moving `led::Device` from
rust/kernel/led.rs to rust/kernel/led/normal.rs in the 2. patch
Changes since v10:
* allow in-place initialization of `LedOps`
* run rustfmt for code inside `try_pin_init!`
Changes since v9:
* add missing periods in documentation
* duplicate `led::Device` and `led::Adapter` instead of using a complex
trait
* fix imports not using prelude
* adapt to CStr change
* documented `led::Color::Multi` and `led::Color::Rgb`
Changes since v8:
* accept `Option<ARef<Fwnode>>` in `led::InitData::fwnode()`
* make functions in `MultiColorSubLed` const
* drop the "rust: Add trait to convert a device reference to a bus
device reference" patch, as it has been picked into driver-core
Changes since v7:
* adjusted import style
* added classdev parameter to callback functions in `LedOps`
* implement `led::Color`
* extend `led::InitData` with
- initial_brightness
- default_trigger
- default_color
* split generic and normal led classdev abstractions up (see patch 3/4)
* add multicolor led class device abstractions (see patch 4/4)
* added MAINTAINERS entry
Changes since v6:
* fixed typos
* improved documentation
Changes since v5:
* rename `IntoBusDevice` trait into `AsBusDevice`
* fix documentation about `LedOps::BLOCKING`
* removed dependency on i2c bindings
* added `AsBusDevice` implementation for `platform::Device`
* removed `device::Device` fallback implementation
* document that `AsBusDevice` must not be used by drivers and is
intended for bus and class device abstractions only.
Changes since v4:
* add abstraction to convert a device reference to a bus device
reference
* require the bus device as parent device and provide it in class device
callbacks
* remove Pin<Vec<_>> abstraction (as not relevant for the led
abstractions)
* fixed formatting in `led::Device::new`
* fixed `LedOps::BLOCKING` did the inverse effect
Changes since v3:
* fixed kunit tests failing because of example in documentation
Changes since v2:
* return `Devres` on `led::Device` creation
* replace KBox<T> with T in struct definition
* increment and decrement reference-count of fwnode
* make a device parent mandatory for led classdev creation
* rename `led::Handler` to `led::LedOps`
* add optional `brightness_get` function to `led::LedOps`
* use `#[vtable]` instead of `const BLINK: bool`
* use `Opaque::cast_from` instead of casting a pointer
* improve documentation
* improve support for older rust versions
* use `&Device<Bound>` for parent
Changes since v1:
* fixed typos noticed by Onur Özkan
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Markus Probst (4):
rust: leds: Add basic led classdev abstractions
rust: leds: Add Mode trait
rust: leds: Add multicolor classdev abstractions
MAINTAINERS: rust: leds: Add rust abstraction entry
MAINTAINERS | 9 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/led.rs | 317 ++++++++++++++++++++++++++++
rust/kernel/led/multicolor.rs | 442 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/led/normal.rs | 230 +++++++++++++++++++++
rust/kernel/lib.rs | 1 +
6 files changed, 1000 insertions(+)
---
base-commit: fd9230005e85472b81c1c7c16afe93dd878996b1
change-id: 20251114-rust_leds-a959f7c2f7f9
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v28 1/4] rust: leds: Add basic led classdev abstractions
2026-11-13 13:37 [PATCH v28 0/4] rust: leds: Add led classdev abstractions Markus Probst
@ 2026-11-13 13:37 ` Markus Probst
2026-11-13 14:02 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 2/4] rust: leds: Add Mode trait Markus Probst
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Markus Probst @ 2026-11-13 13:37 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Leon Romanovsky, Miguel Ojeda, Alex Gaynor, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rafael J. Wysocki, Bjorn Helgaas,
Krzysztof Wilczyński, Boqun Feng, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan, Ira Weiny,
Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
Implement the core abstractions needed for led class devices, including:
* `led::LedOps` - the trait for handling leds, including
`brightness_set`, `brightness_get` and `blink_set`
* `led::DeviceBuilder` - the builder for the led class device
* `led::Device` - a safe wrapper around `led_classdev`
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
rust/kernel/led.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++++
rust/kernel/led/normal.rs | 222 ++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
3 files changed, 488 insertions(+)
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
new file mode 100644
index 000000000000..deda8cc548a8
--- /dev/null
+++ b/rust/kernel/led.rs
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the leds driver model.
+//!
+//! C header: [`include/linux/leds.h`](srctree/include/linux/leds.h)
+
+use core::{
+ marker::PhantomData,
+ mem::transmute,
+ ptr::NonNull, //
+};
+
+use crate::{
+ container_of,
+ device::{
+ self,
+ property::FwNode,
+ Bound, //
+ },
+ error::{
+ from_result,
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ macros::vtable,
+ prelude::*,
+ str::CStrExt,
+ sync::aref::ARef,
+ types::Opaque, //
+};
+
+mod normal;
+
+pub use normal::Device;
+
+/// The name of the led is determined by the driver.
+pub enum Named {}
+/// The name of the led is determined by its fwnode.
+pub enum Unnamed {}
+
+/// How the name of the led should be determined.
+pub trait DeviceBuilderState: private::Sealed {}
+
+impl DeviceBuilderState for Named {}
+impl private::Sealed for Named {}
+impl DeviceBuilderState for Unnamed {}
+impl private::Sealed for Unnamed {}
+
+/// The builder to register a led class device.
+///
+/// See [`LedOps`].
+pub struct DeviceBuilder<'init, S> {
+ fwnode: Option<ARef<FwNode>>,
+ name: Option<&'init CStr>,
+ devicename: Option<&'init CStr>,
+ devname_mandatory: bool,
+ initial_brightness: u32,
+ color: Color,
+ _p: PhantomData<S>,
+}
+
+impl<S: DeviceBuilderState> DeviceBuilder<'static, S> {
+ /// Creates a new [`DeviceBuilder`].
+ #[inline]
+ #[expect(
+ clippy::new_without_default,
+ reason = "no need and derive is prevented by S"
+ )]
+ pub fn new() -> Self {
+ Self {
+ fwnode: None,
+ name: None,
+ devicename: None,
+ devname_mandatory: false,
+ initial_brightness: 0,
+ color: Color::default(),
+ _p: PhantomData,
+ }
+ }
+}
+
+impl<'init> DeviceBuilder<'init, Unnamed> {
+ /// Sets the firmware node.
+ #[inline]
+ pub fn fwnode(self, fwnode: Option<ARef<FwNode>>) -> Self {
+ Self { fwnode, ..self }
+ }
+
+ /// Sets the device name.
+ #[inline]
+ pub fn devicename(self, devicename: &'init CStr) -> Self {
+ Self {
+ devicename: Some(devicename),
+ ..self
+ }
+ }
+
+ /// Sets if a device name is mandatory.
+ #[inline]
+ pub fn devicename_mandatory(self, mandatory: bool) -> Self {
+ Self {
+ devname_mandatory: mandatory,
+ ..self
+ }
+ }
+}
+
+impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
+ /// Sets the initial brightness value for the led.
+ ///
+ /// The default brightness is 0.
+ /// If [`LedOps::brightness_get`] is implemented, this value will be ignored.
+ #[inline]
+ pub fn initial_brightness(self, brightness: u32) -> Self {
+ Self {
+ initial_brightness: brightness,
+ ..self
+ }
+ }
+
+ /// Sets the color of the led.
+ ///
+ /// This value can be overwritten by the "color" fwnode property.
+ #[inline]
+ pub fn color(self, color: Color) -> Self {
+ Self { color, ..self }
+ }
+}
+
+impl<'init> DeviceBuilder<'init, Named> {
+ /// Sets the name of the led.
+ ///
+ /// Setting this will prevent the fwnode from being used and prevents automatic name
+ /// composition.
+ #[inline]
+ pub fn name(self, name: &'init CStr) -> Self {
+ Self {
+ name: Some(name),
+ ..self
+ }
+ }
+}
+
+/// Trait defining the operations for a LED driver.
+///
+/// # Examples
+/// ```
+/// use kernel::{
+/// led,
+/// macros::vtable,
+/// prelude::*, //
+/// };
+///
+/// struct MyLedOps;
+///
+///
+/// #[vtable]
+/// impl led::LedOps for MyLedOps {
+/// const BLOCKING: bool = false;
+/// const MAX_BRIGHTNESS: u32 = 255;
+///
+/// fn brightness_set<'bound>(
+/// self: &led::Device<'bound, Self>,
+/// _brightness: u32
+/// ) -> Result<()> {
+/// // Set the brightness for the led here
+/// Ok(())
+/// }
+/// }
+/// ```
+/// Led drivers must implement this trait in order to register and handle a [`Device`].
+#[vtable]
+pub trait LedOps: Send + Sync + Sized {
+ /// 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;
+ /// The max brightness level.
+ const MAX_BRIGHTNESS: u32;
+
+ /// Sets the brightness level.
+ ///
+ /// See also [`LedOps::BLOCKING`].
+ fn brightness_set<'bound>(self: &Device<'bound, Self>, brightness: u32) -> Result<()>;
+
+ /// Gets the current brightness level.
+ fn brightness_get<'bound>(self: &Device<'bound, Self>) -> Result<u32> {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Activates hardware accelerated blinking.
+ ///
+ /// delays are in milliseconds. If both are zero, a sensible default should be chosen.
+ /// The caller should adjust the timings in that case and if it can't match the values
+ /// specified exactly. Setting the brightness to 0 will disable the hardware accelerated
+ /// blinking.
+ ///
+ /// See also [`LedOps::BLOCKING`].
+ fn blink_set<'bound>(
+ self: &Device<'bound, Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ let _ = (delay_on, delay_off);
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+/// Led colors.
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[repr(u32)]
+#[non_exhaustive]
+#[expect(
+ missing_docs,
+ reason = "it shouldn't be necessary to document each color"
+)]
+pub enum Color {
+ #[default]
+ White = bindings::LED_COLOR_ID_WHITE,
+ Red = bindings::LED_COLOR_ID_RED,
+ Green = bindings::LED_COLOR_ID_GREEN,
+ Blue = bindings::LED_COLOR_ID_BLUE,
+ Amber = bindings::LED_COLOR_ID_AMBER,
+ Violet = bindings::LED_COLOR_ID_VIOLET,
+ Yellow = bindings::LED_COLOR_ID_YELLOW,
+ Ir = bindings::LED_COLOR_ID_IR,
+ Multi = bindings::LED_COLOR_ID_MULTI,
+ Rgb = bindings::LED_COLOR_ID_RGB,
+ Purple = bindings::LED_COLOR_ID_PURPLE,
+ Orange = bindings::LED_COLOR_ID_ORANGE,
+ Pink = bindings::LED_COLOR_ID_PINK,
+ Cyan = bindings::LED_COLOR_ID_CYAN,
+ Lime = bindings::LED_COLOR_ID_LIME,
+}
+static_assert!(bindings::LED_COLOR_ID_MAX == 15);
+
+impl Color {
+ /// Name of the color
+ #[inline]
+ pub fn as_c_str(self) -> &'static CStr {
+ // SAFETY:
+ // - `self as u8` is a valid led color id.
+ // - `led_get_color_name` always returns a valid C string pointer.
+ unsafe { CStr::from_char_ptr(bindings::led_get_color_name(self as u8)) }
+ }
+}
+
+impl TryFrom<u32> for Color {
+ type Error = Error;
+
+ fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
+ if value < bindings::LED_COLOR_ID_MAX {
+ // SAFETY:
+ // - `Color` is represented as `u32`
+ // - the static_assert above guarantees that no additional color has been added
+ // - `value` is guaranteed to be in the color id range
+ Ok(unsafe { transmute::<u32, Color>(value) })
+ } else {
+ Err(EINVAL)
+ }
+ }
+}
+
+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..a22c29cfd262
--- /dev/null
+++ b/rust/kernel/led/normal.rs
@@ -0,0 +1,222 @@
+// 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 core::ops::Deref;
+
+use super::*;
+
+/// The led class device representation.
+///
+/// This structure represents the Rust abstraction for a led class device.
+#[pin_data(PinnedDrop)]
+pub struct Device<'bound, T: 'bound = ()> {
+ #[pin]
+ ops: T,
+ #[pin]
+ classdev: Opaque<bindings::led_classdev>,
+ _p: PhantomData<&'bound ()>,
+}
+
+impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
+ /// Registers a new [`Device`].
+ pub fn build<'bound: 'init, T: LedOps + 'bound>(
+ self,
+ parent: &'bound device::Device<Bound>,
+ ops: impl PinInit<T, Error> + 'init,
+ ) -> impl PinInit<Device<'bound, T>, Error> + 'init {
+ const_assert!(T::MAX_BRIGHTNESS <= i32::MAX.unsigned_abs() || !T::HAS_BRIGHTNESS_GET);
+
+ try_pin_init!(Device {
+ ops <- 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: self.initial_brightness,
+ color: self.color as u32,
+ name: self.name.map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ ..bindings::led_classdev::default()
+ })
+ };
+
+ let mut init_data = bindings::led_init_data {
+ fwnode: self
+ .fwnode
+ .as_ref()
+ .map_or(core::ptr::null_mut(), |fwnode| fwnode.as_raw()),
+ default_label: core::ptr::null(),
+ devicename: self
+ .devicename
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ devname_mandatory: self.devname_mandatory,
+ };
+
+ // SAFETY:
+ // - `parent.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_raw(),
+ ptr,
+ if self.name.is_none() {
+ &raw mut init_data
+ } else {
+ core::ptr::null_mut()
+ },
+ )
+ })?;
+
+ core::mem::forget(self.fwnode); // keep the reference count incremented
+
+ Ok::<_, Error>(())
+ }),
+ _p: PhantomData,
+ })
+ }
+}
+
+impl<'bound, T: 'bound> Device<'bound, T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ #[inline]
+ 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) }
+ }
+}
+
+impl<'bound, T: LedOps + 'bound> Deref for Device<'bound, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.ops
+ }
+}
+
+// SAFETY: A `led::Device` can be unregistered from any thread.
+unsafe impl<'bound, T: 'bound + Send> Send for Device<'bound, T> {}
+
+// SAFETY: `led::Device` can be shared among threads because all methods of `led::Device`
+// are thread safe.
+unsafe impl<'bound, T: 'bound + Sync> Sync for Device<'bound, T> {}
+
+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) };
+
+ let _ = classdev.brightness_set(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) };
+
+ classdev.brightness_set(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) };
+
+ // CAST: Resulting value will be casted back to i32 in the led subsystem.
+ from_result(|| {
+ classdev
+ .brightness_get()
+ .inspect(|val| debug_assert!(*val <= T::MAX_BRIGHTNESS))
+ .and_then(|val| Ok(i32::try_from(val)?))
+ }) as u32
+ }
+
+ /// # 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) };
+
+ classdev.blink_set(
+ // 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<'bound, T: 'bound> PinnedDrop for Device<'bound, 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::DeviceBuilder::build`.
+ .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) };
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 1ab41129d557..3b831cb6488d 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -98,6 +98,7 @@
pub mod jump_label;
#[cfg(CONFIG_KUNIT)]
pub mod kunit;
+pub mod led;
pub mod list;
pub mod maple_tree;
pub mod mem;
--
2.56.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v28 2/4] rust: leds: Add Mode trait
2026-11-13 13:37 [PATCH v28 0/4] rust: leds: Add led classdev abstractions Markus Probst
2026-11-13 13:37 ` [PATCH v28 1/4] rust: leds: Add basic " Markus Probst
@ 2026-11-13 13:37 ` Markus Probst
2026-11-13 13:43 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions Markus Probst
2026-11-13 13:37 ` [PATCH v28 4/4] MAINTAINERS: rust: leds: Add rust abstraction entry Markus Probst
3 siblings, 1 reply; 8+ messages in thread
From: Markus Probst @ 2026-11-13 13:37 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Leon Romanovsky, Miguel Ojeda, Alex Gaynor, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rafael J. Wysocki, Bjorn Helgaas,
Krzysztof Wilczyński, Boqun Feng, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan, Ira Weiny,
Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
Add the `led::Mode` trait to allow for other types of led class devices
in `led::LedOps`.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
rust/kernel/led.rs | 32 ++++++++++++++++++++++++++------
rust/kernel/led/normal.rs | 12 ++++++++++--
2 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
index deda8cc548a8..c17f8ef75006 100644
--- a/rust/kernel/led.rs
+++ b/rust/kernel/led.rs
@@ -7,6 +7,7 @@
use core::{
marker::PhantomData,
mem::transmute,
+ ops::Deref,
ptr::NonNull, //
};
@@ -31,7 +32,10 @@
mod normal;
-pub use normal::Device;
+pub use normal::{
+ Device,
+ Normal, //
+};
/// The name of the led is determined by the driver.
pub enum Named {}
@@ -156,6 +160,7 @@ pub fn name(self, name: &'init CStr) -> Self {
///
/// #[vtable]
/// impl led::LedOps for MyLedOps {
+/// type Mode = led::Normal;
/// const BLOCKING: bool = false;
/// const MAX_BRIGHTNESS: u32 = 255;
///
@@ -176,16 +181,21 @@ pub trait LedOps: Send + Sync + Sized {
const BLOCKING: bool;
/// The max brightness level.
const MAX_BRIGHTNESS: u32;
-
/// Sets the brightness level.
///
/// See also [`LedOps::BLOCKING`].
- fn brightness_set<'bound>(self: &Device<'bound, Self>, brightness: u32) -> Result<()>;
-
+ fn brightness_set<'bound>(
+ self: &<Self::Mode as Mode>::Device<'bound, Self>,
+ brightness: u32,
+ ) -> Result<()>;
/// Gets the current brightness level.
- fn brightness_get<'bound>(self: &Device<'bound, Self>) -> Result<u32> {
+ fn brightness_get<'bound>(self: &<Self::Mode as Mode>::Device<'bound, Self>) -> Result<u32> {
build_error!(VTABLE_DEFAULT_ERROR)
}
+ /// The led mode to use.
+ ///
+ /// See [`Mode`].
+ type Mode: Mode;
/// Activates hardware accelerated blinking.
///
@@ -196,7 +206,7 @@ fn brightness_get<'bound>(self: &Device<'bound, Self>) -> Result<u32> {
///
/// See also [`LedOps::BLOCKING`].
fn blink_set<'bound>(
- self: &Device<'bound, Self>,
+ self: &<Self::Mode as Mode>::Device<'bound, Self>,
delay_on: &mut usize,
delay_off: &mut usize,
) -> Result<()> {
@@ -260,6 +270,16 @@ fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
}
}
+/// 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<'bound, T: LedOps<Mode = Self> + 'bound>: Deref<Target = T>;
+}
+
mod private {
pub trait Sealed {}
}
diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs
index a22c29cfd262..992353319522 100644
--- a/rust/kernel/led/normal.rs
+++ b/rust/kernel/led/normal.rs
@@ -8,6 +8,14 @@
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<'bound, T: LedOps<Mode = Self> + 'bound> = Device<'bound, T>;
+}
+impl private::Sealed for Normal {}
+
/// The led class device representation.
///
/// This structure represents the Rust abstraction for a led class device.
@@ -22,7 +30,7 @@ pub struct Device<'bound, T: 'bound = ()> {
impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
/// Registers a new [`Device`].
- pub fn build<'bound: 'init, T: LedOps + 'bound>(
+ pub fn build<'bound: 'init, T: LedOps<Mode = Normal> + 'bound>(
self,
parent: &'bound device::Device<Bound>,
ops: impl PinInit<T, Error> + 'init,
@@ -120,7 +128,7 @@ struct Adapter<T: LedOps> {
_p: PhantomData<T>,
}
-impl<T: LedOps> Adapter<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`.
--
2.56.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions
2026-11-13 13:37 [PATCH v28 0/4] rust: leds: Add led classdev abstractions Markus Probst
2026-11-13 13:37 ` [PATCH v28 1/4] rust: leds: Add basic " Markus Probst
2026-11-13 13:37 ` [PATCH v28 2/4] rust: leds: Add Mode trait Markus Probst
@ 2026-11-13 13:37 ` Markus Probst
2026-11-13 14:02 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 4/4] MAINTAINERS: rust: leds: Add rust abstraction entry Markus Probst
3 siblings, 1 reply; 8+ messages in thread
From: Markus Probst @ 2026-11-13 13:37 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Leon Romanovsky, Miguel Ojeda, Alex Gaynor, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rafael J. Wysocki, Bjorn Helgaas,
Krzysztof Wilczyński, Boqun Feng, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan, Ira Weiny,
Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
Implement the abstractions needed for multicolor led class devices,
including:
* `led::MultiColor` - the led mode implementation
* `MultiColorSubLed` - a safe wrapper arround `mc_subled`
* `led::MultiColorDevice` - a safe wrapper around `led_classdev_mc`
* `led::DeviceBuilder::build_multicolor` - a function to register a new
multicolor led class device
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/led.rs | 34 +++-
rust/kernel/led/multicolor.rs | 442 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 476 insertions(+), 1 deletion(-)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 3791af0aadc7..3dba24b364ad 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -70,6 +70,7 @@
#include <linux/iosys-map.h>
#include <linux/jiffies.h>
#include <linux/jump_label.h>
+#include <linux/led-class-multicolor.h>
#include <linux/mdio.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
index c17f8ef75006..4b66fe41a80c 100644
--- a/rust/kernel/led.rs
+++ b/rust/kernel/led.rs
@@ -30,8 +30,16 @@
types::Opaque, //
};
+#[cfg(CONFIG_LEDS_CLASS_MULTICOLOR)]
+mod multicolor;
mod normal;
+#[cfg(CONFIG_LEDS_CLASS_MULTICOLOR)]
+pub use multicolor::{
+ MultiColor,
+ MultiColorDevice,
+ MultiColorSubLed, //
+};
pub use normal::{
Device,
Normal, //
@@ -233,7 +241,24 @@ pub enum Color {
Violet = bindings::LED_COLOR_ID_VIOLET,
Yellow = bindings::LED_COLOR_ID_YELLOW,
Ir = bindings::LED_COLOR_ID_IR,
+ #[cfg_attr(
+ CONFIG_LEDS_CLASS_MULTICOLOR,
+ doc = "Use this color for a [`MultiColor`] led."
+ )]
+ #[cfg_attr(
+ not(CONFIG_LEDS_CLASS_MULTICOLOR),
+ doc = "Use this color for a `MultiColor` led."
+ )]
+ /// If the led supports RGB, use [`Color::Rgb`] instead.
Multi = bindings::LED_COLOR_ID_MULTI,
+ #[cfg_attr(
+ CONFIG_LEDS_CLASS_MULTICOLOR,
+ doc = "Use this color for a [`MultiColor`] led with rgb support."
+ )]
+ #[cfg_attr(
+ not(CONFIG_LEDS_CLASS_MULTICOLOR),
+ doc = "Use this color for a `MultiColor` led with rgb support."
+ )]
Rgb = bindings::LED_COLOR_ID_RGB,
Purple = bindings::LED_COLOR_ID_PURPLE,
Orange = bindings::LED_COLOR_ID_ORANGE,
@@ -274,7 +299,14 @@ fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
///
/// Each led mode has its own led class device type with different capabilities.
///
-/// See [`Normal`].
+#[cfg_attr(
+ CONFIG_LEDS_CLASS_MULTICOLOR,
+ doc = "See [`Normal`] and [`MultiColor`]."
+)]
+#[cfg_attr(
+ not(CONFIG_LEDS_CLASS_MULTICOLOR),
+ doc = "See [`Normal`] and `MultiColor`."
+)]
pub trait Mode: private::Sealed {
/// The class device for the led mode.
type Device<'bound, T: LedOps<Mode = Self> + 'bound>: Deref<Target = T>;
diff --git a/rust/kernel/led/multicolor.rs b/rust/kernel/led/multicolor.rs
new file mode 100644
index 000000000000..58dd1a12bc3c
--- /dev/null
+++ b/rust/kernel/led/multicolor.rs
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Led mode for the `struct led_classdev_mc`.
+//!
+//! C header: [`include/linux/led-class-multicolor.h`](srctree/include/linux/led-class-multicolor.h)
+
+use core::{
+ num::NonZero,
+ ptr, //
+};
+
+use crate::{
+ sync::atomic::{
+ Atomic,
+ Relaxed, //
+ },
+ types::ScopeGuard, //
+};
+
+use super::*;
+
+/// The led mode for the `struct led_classdev_mc`. Leds with this mode can have multiple colors.
+pub enum MultiColor {}
+impl Mode for MultiColor {
+ type Device<'bound, T: LedOps<Mode = Self> + 'bound> = MultiColorDevice<'bound, T>;
+}
+impl private::Sealed for MultiColor {}
+
+/// The multicolor sub led info representation.
+///
+/// This structure represents the Rust abstraction for a C `struct mc_subled`.
+#[repr(C)]
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct MultiColorSubLed {
+ /// The color of the sub led
+ pub color: Color,
+ brightness: Atomic<u32>,
+ intensity: Atomic<u32>,
+ /// The maximum supported intensity value.
+ ///
+ /// If None the maximum intensity equals to [`LedOps::MAX_BRIGHTNESS`].
+ pub max_intensity: Option<NonZero<u32>>,
+ /// Arbitrary data for the driver to store.
+ pub channel: u32,
+}
+
+// SAFETY: `MultiColorSubLed` can be shared among threads.
+unsafe impl Sync for MultiColorSubLed {}
+
+impl Clone for MultiColorSubLed {
+ fn clone(&self) -> Self {
+ Self {
+ color: self.color,
+ brightness: Atomic::new(self.brightness()),
+ intensity: Atomic::new(self.intensity()),
+ max_intensity: self.max_intensity,
+ channel: self.channel,
+ }
+ }
+}
+
+// We directly pass a reference to the `subled_info` field in `led_classdev_mc` to the driver via
+// `Device::subleds()`.
+// We need safeguards to ensure `MultiColorSubLed` and `mc_subled` stay identical.
+const _: () = {
+ use core::mem::offset_of;
+
+ const fn assert_same_type<T>(_: &T, _: &T) {}
+
+ let rust_zeroed = MultiColorSubLed {
+ color: Color::White,
+ brightness: Atomic::new(0),
+ intensity: Atomic::new(0),
+ max_intensity: None,
+ channel: 0,
+ };
+ let c_zeroed = bindings::mc_subled {
+ color_index: 0,
+ brightness: 0,
+ intensity: 0,
+ max_intensity: 0,
+ channel: 0,
+ };
+
+ assert!(offset_of!(MultiColorSubLed, color) == offset_of!(bindings::mc_subled, color_index));
+ assert!(
+ offset_of!(MultiColorSubLed, brightness) == offset_of!(bindings::mc_subled, brightness)
+ );
+ assert!(offset_of!(MultiColorSubLed, intensity) == offset_of!(bindings::mc_subled, intensity));
+ assert!(
+ offset_of!(MultiColorSubLed, max_intensity)
+ == offset_of!(bindings::mc_subled, max_intensity)
+ );
+ assert!(offset_of!(MultiColorSubLed, channel) == offset_of!(bindings::mc_subled, channel));
+
+ assert_same_type(&0u32, &c_zeroed.color_index);
+ assert_same_type(&0u32, &c_zeroed.brightness);
+ assert_same_type(&0u32, &c_zeroed.intensity);
+ assert!(size_of_val(&rust_zeroed.max_intensity) == size_of_val(&c_zeroed.max_intensity));
+ assert_same_type(&rust_zeroed.channel, &c_zeroed.channel);
+
+ assert!(size_of::<MultiColorSubLed>() == size_of::<bindings::mc_subled>());
+};
+
+impl MultiColorSubLed {
+ /// Create a new multicolor sub led info.
+ #[inline]
+ pub const fn new(color: Color) -> Self {
+ Self {
+ color,
+ brightness: Atomic::new(0),
+ intensity: Atomic::new(0),
+ max_intensity: None,
+ channel: 0,
+ }
+ }
+
+ /// Set arbitrary data for the driver.
+ #[inline]
+ pub const fn channel(mut self, channel: u32) -> Self {
+ self.channel = channel;
+ self
+ }
+
+ /// Set the initial intensity of the subled.
+ #[inline]
+ pub const fn initial_intensity(mut self, intensity: u32) -> Self {
+ self.intensity = Atomic::new(intensity);
+ self
+ }
+
+ /// Set the maximum supported intensity of the subled.
+ #[inline]
+ pub const fn max_intensity(mut self, max_intensity: NonZero<u32>) -> Self {
+ self.max_intensity = Some(max_intensity);
+ self
+ }
+
+ /// The intensity of the sub led.
+ #[inline]
+ pub fn intensity(&self) -> u32 {
+ self.intensity.load(Relaxed)
+ }
+
+ /// The brightness of the sub led.
+ #[inline]
+ pub fn brightness(&self) -> u32 {
+ self.brightness.load(Relaxed)
+ }
+}
+
+/// The multicolor led class device representation.
+///
+/// This structure represents the Rust abstraction for a multicolor led class device.
+#[pin_data(PinnedDrop)]
+pub struct MultiColorDevice<'bound, T: 'bound = ()> {
+ #[pin]
+ ops: T,
+ #[pin]
+ classdev: Opaque<bindings::led_classdev_mc>,
+ _p: PhantomData<&'bound ()>,
+}
+
+impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
+ /// Registers a new [`MulticolorDevice`].
+ pub fn build_multicolor<'bound: 'init, T: LedOps<Mode = MultiColor> + 'bound>(
+ self,
+ parent: &'bound device::Device<Bound>,
+ ops: impl PinInit<T, Error> + 'init,
+ subleds: &'init [MultiColorSubLed],
+ ) -> impl PinInit<MultiColorDevice<'bound, T>, Error> + 'init {
+ const_assert!(T::MAX_BRIGHTNESS <= i32::MAX.unsigned_abs() || !T::HAS_BRIGHTNESS_GET);
+
+ try_pin_init!(MultiColorDevice {
+ ops <- ops,
+ classdev <- Opaque::try_ffi_init(|ptr: *mut bindings::led_classdev_mc| {
+ let mut colors = [false; bindings::LED_COLOR_ID_MAX as usize];
+ for subled in subleds {
+ if colors[subled.color as usize] {
+ dev_err!(parent.as_ref(), "duplicate color in multicolor led\n");
+ return Err(EINVAL);
+ }
+ colors[subled.color as usize] = true;
+ }
+ let subleds_box = KBox::pin_slice::<_, _, Error, Error>(
+ |index| Ok(subleds[index].clone()),
+ subleds.len(),
+ GFP_KERNEL,
+ )?;
+ let subleds_box_raw = KBox::into_raw(Pin::into_inner(subleds_box));
+
+ let subled_guard = ScopeGuard::new(|| {
+ // SAFETY: `subleds_box_raw` is guaranteed to be a valid pointer to
+ // `[MultiColorSubLed]`.
+ drop(unsafe { <KBox<[MultiColorSubLed]>>::from_raw(subleds_box_raw) });
+ });
+
+ // SAFETY: `try_ffi_init` guarantees that `ptr` is valid for write.
+ // `led_classdev_mc` gets fully initialized in-place by
+ // `led_classdev_multicolor_register_ext` including `mutex` and `list_head`.
+ unsafe {
+ ptr.write(bindings::led_classdev_mc {
+ led_cdev: 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: self.initial_brightness,
+ color: self.color as u32,
+ name: self.name.map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ ..bindings::led_classdev::default()
+ },
+ num_colors: u32::try_from(subleds_box_raw.len())?,
+ // CAST: The safeguards in the const block ensure that
+ // `MultiColorSubLed` has an identical layout to `mc_subled`.
+ subled_info: subleds_box_raw.cast::<bindings::mc_subled>(),
+ })
+ };
+
+ let mut init_data = bindings::led_init_data {
+ fwnode: self
+ .fwnode
+ .as_ref()
+ .map_or(core::ptr::null_mut(), |fwnode| fwnode.as_raw()),
+ default_label: core::ptr::null(),
+ devicename: self
+ .devicename
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ devname_mandatory: self.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_mc`.
+ to_result(unsafe {
+ bindings::led_classdev_multicolor_register_ext(
+ parent.as_ref().as_raw(),
+ ptr,
+ if self.name.is_none() {
+ &raw mut init_data
+ } else {
+ core::ptr::null_mut()
+ },
+ )
+ })?;
+
+ subled_guard.dismiss();
+
+ core::mem::forget(self.fwnode); // keep the reference count incremented
+
+ Ok::<_, Error>(())
+ }),
+ _p: PhantomData,
+ })
+ }
+}
+
+impl<'bound, T: 'bound> MultiColorDevice<'bound, T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ #[inline]
+ 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::MultiColorDevice`. `container_of!` can therefore
+ // safely calculate the address of the containing struct.
+ let led_mc_cdev = unsafe { container_of!(led_cdev, bindings::led_classdev_mc, led_cdev) };
+
+ // SAFETY: It is guaranteed that `led_mc_cdev` points to a `led_classdev_mc`
+ // field embedded within a valid `led::MultiColorDevice`. `container_of!` can therefore
+ // safely calculate the address of the containing struct.
+ unsafe { &*container_of!(Opaque::cast_from(led_mc_cdev), Self, classdev) }
+ }
+
+ /// Returns the subleds passed to [`Device::new_multicolor`].
+ #[inline]
+ pub fn subleds(&self) -> &[MultiColorSubLed] {
+ // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
+ // valid `led_classdev_mc`.
+ let raw = unsafe { &*self.classdev.get() };
+ // SAFETY: `raw.subled_info` is a valid pointer to `mc_subled[num_colors]`.
+ // CAST: The safeguards in the const block ensure that `MultiColorSubLed` has an identical
+ // layout to `mc_subled`.
+ unsafe {
+ core::slice::from_raw_parts(
+ raw.subled_info.cast::<MultiColorSubLed>(),
+ // CAST: It is guaranteed that `num_colors` fits into an `usize`.
+ raw.num_colors as usize,
+ )
+ }
+ }
+}
+
+impl<'bound, T: LedOps + 'bound> Deref for MultiColorDevice<'bound, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.ops
+ }
+}
+
+// SAFETY: A `led::MultiColorDevice` can be unregistered from any thread.
+unsafe impl<'bound, T: 'bound + Send> Send for MultiColorDevice<'bound, T> {}
+
+// SAFETY: `led::MultiColorDevice` can be shared among threads because all methods of `led::Device`
+// are thread safe.
+unsafe impl<'bound, T: 'bound + Sync> Sync for MultiColorDevice<'bound, T> {}
+
+struct Adapter<T: LedOps<Mode = MultiColor>> {
+ _p: PhantomData<T>,
+}
+
+impl<T: LedOps<Mode = MultiColor>> Adapter<T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ /// 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::MultiColorDevice`.
+ let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
+
+ // SAFETY: `classdev.classdev.get()` is guaranteed to be a pointer to a valid
+ // `led_classdev_mc`.
+ unsafe { bindings::led_mc_calc_color_components(classdev.classdev.get(), brightness) };
+
+ let _ = classdev.brightness_set(brightness);
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ /// 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::MultiColorDevice`.
+ let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
+
+ // SAFETY: `classdev.classdev.get()` is guaranteed to be a pointer to a valid
+ // `led_classdev_mc`.
+ unsafe { bindings::led_mc_calc_color_components(classdev.classdev.get(), brightness) };
+
+ classdev.brightness_set(brightness)?;
+ Ok(0)
+ })
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ /// 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::MultiColorDevice`.
+ let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
+
+ // CAST: Resulting value will be casted back to i32 in the led subsystem.
+ from_result(|| {
+ classdev
+ .brightness_get()
+ .inspect(|val| debug_assert!(*val <= T::MAX_BRIGHTNESS))
+ .and_then(|val| Ok(i32::try_from(val)?))
+ }) as u32
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ /// `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::MultiColorDevice`.
+ let classdev = unsafe { MultiColorDevice::<T>::from_raw(led_cdev) };
+
+ classdev.blink_set(
+ // 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<'bound, T: 'bound> PinnedDrop for MultiColorDevice<'bound, 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_mc`.
+ let dev: &device::Device = unsafe { device::Device::from_raw((*raw).led_cdev.dev) };
+
+ let _fwnode = dev
+ .fwnode()
+ // SAFETY: the reference count of `fwnode` has previously been
+ // incremented in `led::DeviceBuilder::build_multicolor`.
+ .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_multicolor_register_ext`.
+ unsafe { bindings::led_classdev_multicolor_unregister(raw) };
+
+ // SAFETY: `raw` is guaranteed to be a valid pointer to `led_classdev_mc`.
+ let led_cdev = unsafe { &*raw };
+
+ // SAFETY: `led_cdev.subled_info` is guaranteed to be a valid pointer to
+ // `[MultiColorSubLed]` with length of `led_cdev.num_colors`.
+ // See `led::DeviceBuilder::build_multicolor`.
+ drop(unsafe {
+ KBox::from_raw(ptr::slice_from_raw_parts_mut(
+ led_cdev.subled_info.cast::<MultiColorSubLed>(),
+ // CAST: `num_colors` was originally converted from a `usize` in
+ // `DeviceBuilder::build_multicolor`. Thus it is guaranteed that
+ // `num_colors` fits into an usize.
+ led_cdev.num_colors as usize,
+ ))
+ });
+ }
+}
--
2.56.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v28 4/4] MAINTAINERS: rust: leds: Add rust abstraction entry
2026-11-13 13:37 [PATCH v28 0/4] rust: leds: Add led classdev abstractions Markus Probst
` (2 preceding siblings ...)
2026-11-13 13:37 ` [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions Markus Probst
@ 2026-11-13 13:37 ` Markus Probst
3 siblings, 0 replies; 8+ messages in thread
From: Markus Probst @ 2026-11-13 13:37 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Leon Romanovsky, Miguel Ojeda, Alex Gaynor, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rafael J. Wysocki, Bjorn Helgaas,
Krzysztof Wilczyński, Boqun Feng, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan, Ira Weiny,
Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
Add self as maintainer.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 222b7fc2c3aa..1f547c7a7d0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15148,6 +15148,15 @@ F: drivers/leds/
F: include/dt-bindings/leds/
F: include/linux/leds.h
+LED SUBSYSTEM [RUST]
+M: Markus Probst <markus.probst@posteo.de>
+L: linux-leds@vger.kernel.org
+L: rust-for-linux@vger.kernel.org
+S: Maintained
+F: drivers/leds/*.rs
+F: rust/kernel/led.rs
+F: rust/kernel/led/
+
LEGO MINDSTORMS EV3
R: David Lechner <david@lechnology.com>
S: Maintained
--
2.56.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v28 2/4] rust: leds: Add Mode trait
2026-11-13 13:37 ` [PATCH v28 2/4] rust: leds: Add Mode trait Markus Probst
@ 2026-11-13 13:43 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-13 13:43 UTC (permalink / raw)
To: Markus Probst; +Cc: linux-leds, linux-pci, lee
> Add the `led::Mode` trait to allow for other types of led class devices
> in `led::LedOps`.
>
> Signed-off-by: Markus Probst <markus.probst@posteo.de>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-rust_leds-v28-0-b8cf2f74fa57@posteo.de?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions
2026-11-13 13:37 ` [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions Markus Probst
@ 2026-11-13 14:02 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-13 14:02 UTC (permalink / raw)
To: Markus Probst; +Cc: linux-leds, linux-pci, lee, ojeda, gary
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] rust: leds: unsound shared reference to Opaque in
MultiColorDevice::subleds()
- [Low] rust: leds: broken intra-doc links in build_multicolor() and
subleds()
- [Low] rust: leds: missing #[inline] on MultiColorDevice::deref() and
clone()
--
commit c1c84e805b37bfbeeb46b779f3b5faebdc6abfeb
Author: Markus Probst <markus.probst@posteo.de>
rust: leds: Add multicolor classdev abstractions
This commit adds Rust abstractions for multicolor LED class devices,
including MultiColor, MultiColorSubLed, MultiColorDevice, and
DeviceBuilder::build_multicolor().
> diff --git a/rust/kernel/led/multicolor.rs b/rust/kernel/led/multicolor.rs
> new file mode 100644
> index 0000000000000..58dd1a12bc3c0
> --- /dev/null
> +++ b/rust/kernel/led/multicolor.rs
[ ... ]
> +impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
> + /// Registers a new [`MulticolorDevice`].
> + pub fn build_multicolor<'bound: 'init, T: LedOps<Mode = MultiColor> + 'bound>(
[ ... ]
> + /// Returns the subleds passed to [`Device::new_multicolor`].
> + #[inline]
> + pub fn subleds(&self) -> &[MultiColorSubLed] {
[Severity: Low]
Will these two intra-doc links break rustdoc builds when
CONFIG_LEDS_CLASS_MULTICOLOR is enabled?
The struct is named MultiColorDevice (with a capital C) rather than
MulticolorDevice, and Device::new_multicolor does not exist; subleds are
passed to DeviceBuilder::build_multicolor.
> + // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
> + // valid `led_classdev_mc`.
> + let raw = unsafe { &*self.classdev.get() };
> + // SAFETY: `raw.subled_info` is a valid pointer to `mc_subled[num_colors]`.
> + // CAST: The safeguards in the const block ensure that `MultiColorSubLed` has an identical
> + // layout to `mc_subled`.
> + unsafe {
> + core::slice::from_raw_parts(
> + raw.subled_info.cast::<MultiColorSubLed>(),
> + // CAST: It is guaranteed that `num_colors` fits into an `usize`.
> + raw.num_colors as usize,
> + )
> + }
> + }
> +}
[Severity: Medium]
Is it sound to create a shared reference &bindings::led_classdev_mc from
self.classdev.get() here?
MultiColorDevice wraps bindings::led_classdev_mc in Opaque because the
embedded struct led_classdev contains fields (such as work_flags,
blink_timer, set_brightness_work, and led_access) that the C LED core
mutates without UnsafeCell wrappers. Materializing &*self.classdev.get()
asserts that no bytes within bindings::led_classdev_mc are concurrently
mutated while the reference is live, which can race with C updates when a
driver calls subleds() on a shared &MultiColorDevice (for example from
brightness_set_callback() running in workqueue or timer context).
PinnedDrop::drop() also does let led_cdev = unsafe { &*raw }. Both sites
can read (*raw).subled_info and (*raw).num_colors directly through the raw
pointer instead of creating a shared struct reference.
> +
> +impl<'bound, T: LedOps + 'bound> Deref for MultiColorDevice<'bound, T> {
> + type Target = T;
> +
> + fn deref(&self) -> &Self::Target {
> + &self.ops
> + }
> +}
[Severity: Low]
This isn't a bug, but small functions in kernel crate abstractions should
be marked #[inline]; both MultiColorDevice::deref() and
MultiColorSubLed::clone() are missing the attribute.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-rust_leds-v28-0-b8cf2f74fa57@posteo.de?part=3
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v28 1/4] rust: leds: Add basic led classdev abstractions
2026-11-13 13:37 ` [PATCH v28 1/4] rust: leds: Add basic " Markus Probst
@ 2026-11-13 14:02 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-11-13 14:02 UTC (permalink / raw)
To: Markus Probst; +Cc: linux-leds, linux-pci, lee, ojeda, gary
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] rust: leds: kCFI failure in Adapter callbacks due to u32
brightness type
- [Medium] rust: leds: unsound safe DeviceBuilder::build() allows
forgetting Device
- [Medium] rust: leds: missing Kconfig guard on led module and missing
linux/leds.h include
- [Medium] rust: leds: aliased mutable references in
Adapter::blink_set_callback()
- [Low] rust: leds: missing #[inline] on Color::try_from() and
Device::deref()
--
commit 3ca798e46e685904b91dd1d2cbeeeb11efdd4ebe
Author: Markus Probst <markus.probst@posteo.de>
rust: leds: Add basic led classdev abstractions
This commit adds core Rust abstractions for LED class devices, including
the LedOps trait, DeviceBuilder, and the Device wrapper around struct
led_classdev.
> diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
> new file mode 100644
> index 0000000000000..deda8cc548a8c
> --- /dev/null
> +++ b/rust/kernel/led.rs
[ ... ]
> +impl TryFrom<u32> for Color {
> + type Error = Error;
> +
> + fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
> + if value < bindings::LED_COLOR_ID_MAX {
> + // SAFETY:
> + // - `Color` is represented as `u32`
> + // - the static_assert above guarantees that no additional color has been added
> + // - `value` is guaranteed to be in the color id range
> + Ok(unsafe { transmute::<u32, Color>(value) })
> + } else {
> + Err(EINVAL)
> + }
> + }
> +}
[Severity: Low]
Could Color::try_from() and Device::deref() in rust/kernel/led/normal.rs
be marked #[inline], matching other small functions in the kernel crate?
[ ... ]
> diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs
> new file mode 100644
> index 0000000000000..a22c29cfd2628
> --- /dev/null
> +++ b/rust/kernel/led/normal.rs
> @@ -0,0 +1,222 @@
> +// 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 core::ops::Deref;
> +
> +use super::*;
> +
> +/// The led class device representation.
> +///
> +/// This structure represents the Rust abstraction for a led class device.
> +#[pin_data(PinnedDrop)]
> +pub struct Device<'bound, T: 'bound = ()> {
> + #[pin]
> + ops: T,
> + #[pin]
> + classdev: Opaque<bindings::led_classdev>,
> + _p: PhantomData<&'bound ()>,
> +}
> +
> +impl<'init, S: DeviceBuilderState> DeviceBuilder<'init, S> {
> + /// Registers a new [`Device`].
> + pub fn build<'bound: 'init, T: LedOps + 'bound>(
> + self,
> + parent: &'bound device::Device<Bound>,
> + ops: impl PinInit<T, Error> + 'init,
> + ) -> impl PinInit<Device<'bound, T>, Error> + 'init {
[ ... ]
> + // SAFETY:
> + // - `parent.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_raw(),
> + ptr,
> + if self.name.is_none() {
> + &raw mut init_data
> + } else {
> + core::ptr::null_mut()
> + },
> + )
> + })?;
> +
> + core::mem::forget(self.fwnode); // keep the reference count incremented
> +
> + Ok::<_, Error>(())
> + }),
> + _p: PhantomData,
> + })
> + }
> +}
[Severity: Medium]
What prevents safe code from initializing Device<'bound, T> in a KBox and
passing it to core::mem::forget()?
Because led_classdev_unregister() is only called in PinnedDrop::drop(),
forgetting the Device leaves the C led_classdev registered after parent
unbinds and 'bound expires. Subsequent sysfs accesses or trigger events
then invoke Adapter::<T> callbacks on dangling 'bound references in T, and
led_module_get() or led_put() dereferences led_cdev->dev->parent->driver
after parent->driver becomes NULL. Other lifetime-bound in-place
registrations Either mark construction unsafe or register cleanup via
devres.
[ ... ]
> +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) };
> +
> + let _ = classdev.brightness_set(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) };
> +
> + classdev.brightness_set(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 {
[Severity: High]
Will brightness_set_callback(), brightness_set_blocking_callback(), and
brightness_get_callback() trip a kCFI failure under CONFIG_CFI_CLANG?
In include/linux/leds.h, struct led_classdev declares these callbacks
with enum led_brightness rather than u32:
include/linux/leds.h:
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
int (*brightness_set_blocking)(struct led_classdev *led_cdev,
enum led_brightness brightness);
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
Clang kCFI encodes enum led_brightness by its enum type name
(14led_brightness) rather than as a plain integer. Because these Rust
callbacks use u32 and enum led_brightness is not listed as a
--newtype-enum in rust/bindgen_parameters, indirect calls from
led_update_brightness() or __led_set_brightness() will fail the kCFI type
check and panic.
[ ... ]
> + 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) };
> +
> + classdev.blink_set(
> + // 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)
> + })
> + }
> +}
[Severity: Medium]
Can blink_set_callback() create aliased &mut usize references to
led_cdev->blink_delay_on and led_cdev->blink_delay_off?
In drivers/leds/trigger/ledtrig-timer.c, led_delay_on_store() passes
&led_cdev->blink_delay_off directly to led_blink_set() without holding a
lock:
drivers/leds/trigger/ledtrig-timer.c:led_delay_on_store() {
...
led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
led_cdev->blink_delay_on = state;
...
}
CPU0 CPU1
led_delay_on_store() led_delay_on_store()
led_blink_set() led_blink_set()
... ...
blink_set_callback() blink_set_callback()
&mut *delay_off &mut *delay_off <-- aliased &mut
Concurrent writes to delay_on or delay_off, unlocked sysfs reads in
led_delay_on_show() and led_delay_off_show(), or
led_stop_software_blink() zeroing both fields from set_brightness_delayed()
all access those fields without synchronization while &mut usize is live,
causing undefined behavior.
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index 1ab41129d557a..3b831cb6488d7 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -98,6 +98,7 @@
> pub mod jump_label;
> #[cfg(CONFIG_KUNIT)]
> pub mod kunit;
> +pub mod led;
> pub mod list;
> pub mod maple_tree;
> pub mod mem;
[Severity: Medium]
Does unconditionally compiling pub mod led break the build when
CONFIG_NEW_LEDS is disabled or CONFIG_LEDS_CLASS is not built-in?
Color::as_c_str() calls led_get_color_name() (built under CONFIG_NEW_LEDS)
and DeviceBuilder::build() and PinnedDrop::drop() call
led_classdev_register_ext() and led_classdev_unregister() (built under
tristate CONFIG_LEDS_CLASS), none of which have inline stubs in
include/linux/leds.h. In addition, rust/bindings/bindings_helper.h does
not explicitly include linux/leds.h, relying on a transitive include.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-rust_leds-v28-0-b8cf2f74fa57@posteo.de?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-13 14:02 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 13:37 [PATCH v28 0/4] rust: leds: Add led classdev abstractions Markus Probst
2026-11-13 13:37 ` [PATCH v28 1/4] rust: leds: Add basic " Markus Probst
2026-11-13 14:02 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 2/4] rust: leds: Add Mode trait Markus Probst
2026-11-13 13:43 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 3/4] rust: leds: Add multicolor classdev abstractions Markus Probst
2026-11-13 14:02 ` sashiko-bot
2026-11-13 13:37 ` [PATCH v28 4/4] MAINTAINERS: rust: leds: Add rust abstraction entry Markus Probst
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox