Rust for Linux List
 help / color / mirror / Atom feed
From: Fiona Behrens <me@kloenk.dev>
To: Miguel Ojeda <ojeda@kernel.org>,
	Alex Gaynor <alex.gaynor@gmail.com>, Pavel Machek <pavel@ucw.cz>,
	Lee Jones <lee@kernel.org>, Jean Delvare <jdelvare@suse.com>
Cc: "Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <benno.lossin@proton.me>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Fiona Behrens" <me@kloenk.dev>,
	"Mark Pearson" <mpearson-lenovo@squebb.ca>,
	"Peter Koch" <pkoch@lenovo.com>,
	rust-for-linux@vger.kernel.org, linux-leds@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v2 3/5] rust: leds: Add hardware trigger support for hardware-controlled LEDs
Date: Mon, 13 Jan 2025 13:16:18 +0100	[thread overview]
Message-ID: <20250113121620.21598-4-me@kloenk.dev> (raw)
In-Reply-To: <20250113121620.21598-1-me@kloenk.dev>

Adds abstraction for hardware trigger support in LEDs, enabling LEDs to
be controlled by external hardware events.

An `Arc` is embedded within the `led_classdev` to manage the lifecycle
of the hardware trigger, ensuring proper reference counting and cleanup
when the LED is dropped.

Signed-off-by: Fiona Behrens <me@kloenk.dev>
---
 MAINTAINERS                  |   1 +
 rust/kernel/leds.rs          |  95 +++++++++++++++++++++++---
 rust/kernel/leds/triggers.rs | 128 +++++++++++++++++++++++++++++++++++
 3 files changed, 214 insertions(+), 10 deletions(-)
 create mode 100644 rust/kernel/leds/triggers.rs

diff --git a/MAINTAINERS b/MAINTAINERS
index cef929b57159..954dbd311a55 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13020,6 +13020,7 @@ F:	drivers/leds/
 F:	include/dt-bindings/leds/
 F:	include/linux/leds.h
 F:	rust/kernel/leds.rs
+F:	rust/kernel/leds/
 
 LEGO MINDSTORMS EV3
 R:	David Lechner <david@lechnology.com>
diff --git a/rust/kernel/leds.rs b/rust/kernel/leds.rs
index 980af7c405d4..f10a10b56e23 100644
--- a/rust/kernel/leds.rs
+++ b/rust/kernel/leds.rs
@@ -10,9 +10,14 @@
 use crate::error::from_result;
 use crate::ffi::c_ulong;
 use crate::prelude::*;
+#[cfg(CONFIG_LEDS_TRIGGERS)]
+use crate::sync::Arc;
 use crate::time::Delta;
 use crate::types::Opaque;
 
+#[cfg(CONFIG_LEDS_TRIGGERS)]
+pub mod triggers;
+
 /// Color of an LED.
 #[allow(missing_docs)]
 #[derive(Copy, Clone)]
@@ -110,12 +115,34 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
 }
 
 /// Data used for led registration.
-#[derive(Clone)]
-pub struct LedConfig<'name> {
+pub struct LedConfig<'name, T> {
     /// Name to give the led.
     pub name: Option<&'name CStr>,
     /// Color of the LED.
     pub color: Color,
+    /// Private data of the LED.
+    pub data: T,
+
+    /// Default trigger name.
+    pub default_trigger: Option<&'static CStr>,
+    /// Hardware trigger.
+    ///
+    /// Setting this to some also defaults the default trigger to this hardware trigger.
+    /// Use `default_trigger: Some("none")` to overwrite this.
+    #[cfg(CONFIG_LEDS_TRIGGERS)]
+    pub hardware_trigger: Option<Arc<triggers::Hardware<T>>>,
+}
+
+impl<'name, T> LedConfig<'name, T> {
+    /// Create a new LedConfig
+    pub fn new(color: Color, data: T) -> Self {
+        Self {
+            color,
+            data,
+            // SAFETY: all other fields are valid with zeroes.
+            ..unsafe { core::mem::zeroed() }
+        }
+    }
 }
 
 /// A Led backed by a C `struct led_classdev`, additionally offering
@@ -141,8 +168,7 @@ impl<T> Led<T>
     #[cfg(CONFIG_LEDS_CLASS)]
     pub fn register<'a>(
         device: Option<&'a Device>,
-        config: &'a LedConfig<'a>,
-        data: T,
+        config: LedConfig<'a, T>,
     ) -> impl PinInit<Self, Error> + 'a
     where
         T: 'a,
@@ -188,14 +214,46 @@ pub fn register<'a>(
                 unsafe { ptr::write(set_fn_ptr, Some(blink_set::<T>)) };
             }
 
+        #[cfg(CONFIG_LEDS_TRIGGERS)]
+        if let Some(trigger) = config.hardware_trigger {
+            let trigger = trigger.into_raw();
+            // SAFETY: `place` is pointing to a live allocation.
+            let trigger_type_ptr = unsafe { ptr::addr_of_mut!((*place).trigger_type) };
+            // SAFETY: `trigger` is a valid pointer
+            let hw_trigger = unsafe { ptr::addr_of!((*trigger).hw_type) };
+            // SAFETY: `trigger_type_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(trigger_type_ptr, hw_trigger.cast_mut().cast()) };
+
+            // SAFETY: trigger points to a valid hardware trigger struct.
+            let trigger_name_ptr = unsafe { Opaque::raw_get(ptr::addr_of!( (*trigger).trigger)) };
+            // SAFETY: trigger points to a valid hardware trigger struct.
+            let trigger_name_ptr = unsafe { (*trigger_name_ptr).name };
+            // SAFETY: `place` is pointing to a live allocation.
+            let default_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).default_trigger) };
+            // SAFETY: `default_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(default_trigger_ptr, trigger_name_ptr) };
+
+            // SAFETY: `place` is pointing to a live allocation.
+            let hw_ctrl_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).hw_control_trigger) };
+            // SAFETY: `hw_ctrl_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(hw_ctrl_trigger_ptr, trigger_name_ptr) };
+        }
+
+        // After hw trigger impl, to overwrite default trigger
+        if let Some(default_trigger) = config.default_trigger {
+            // SAFETY: `place` is pointing to a live allocation.
+            let default_trigger_ptr = unsafe { ptr::addr_of_mut!((*place).default_trigger) };
+            // SAFETY: `default_trigger_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(default_trigger_ptr, default_trigger.as_char_ptr()) };
+        }
 
-            let dev = device.map(|dev| dev.as_raw()).unwrap_or(ptr::null_mut());
-            // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`.
-            crate::error::to_result(unsafe {
-                bindings::led_classdev_register_ext(dev, place, ptr::null_mut())
-            })
+        let dev = device.map(|dev| dev.as_raw()).unwrap_or(ptr::null_mut());
+        // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`.
+        crate::error::to_result(unsafe {
+                    bindings::led_classdev_register_ext(dev, place, ptr::null_mut())
+        })
             }),
-            data: data,
+            data: config.data,
         })
     }
 }
@@ -220,6 +278,23 @@ fn drop(self: Pin<&mut Self>) {
         unsafe {
             bindings::led_classdev_unregister(self.led.get())
         }
+
+        // drop trigger if there is a hw trigger defined.
+        #[cfg(CONFIG_LEDS_TRIGGERS)]
+        {
+            // SAFETY: `self.led` is a valid led.
+            let hw_trigger_type =
+                unsafe { ptr::read(ptr::addr_of!((*self.led.get()).trigger_type)) };
+            if !hw_trigger_type.is_null() {
+                // SAFETY: hw_trigger_type is a valid and non null pointer into a Hardware trigger.
+                let hw_trigger_type = unsafe {
+                    crate::container_of!(hw_trigger_type, triggers::Hardware<T>, hw_type)
+                };
+                // SAFETY: `hw_trigger_type` is a valid pointer that came from an arc.
+                let hw_trigger_type = unsafe { Arc::from_raw(hw_trigger_type) };
+                drop(hw_trigger_type);
+            }
+        }
     }
 }
 
diff --git a/rust/kernel/leds/triggers.rs b/rust/kernel/leds/triggers.rs
new file mode 100644
index 000000000000..d5f2b8252645
--- /dev/null
+++ b/rust/kernel/leds/triggers.rs
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! LED trigger abstractions.
+
+use core::marker::PhantomData;
+use core::ptr;
+
+use crate::error::{from_result, to_result};
+use crate::prelude::*;
+use crate::types::Opaque;
+
+use super::FromLedClassdev;
+
+/// LED Hardware trigger.
+///
+/// Used to impement a hardware operation mode for an LED.
+#[pin_data(PinnedDrop)]
+pub struct Hardware<T> {
+    #[pin]
+    pub(crate) hw_type: Opaque<bindings::led_hw_trigger_type>,
+    #[pin]
+    pub(crate) trigger: Opaque<bindings::led_trigger>,
+    _t: PhantomData<T>,
+}
+
+impl<T> Hardware<T>
+where
+    T: HardwareOperations,
+{
+    /// Register a new hardware Trigger with a given name.
+    pub fn register(name: &'static CStr) -> impl PinInit<Self, Error> {
+        try_pin_init!( Self {
+            // SAFETY: `led_hw_trigger_type` is valid with all zeroes.
+            hw_type: Opaque::new(unsafe { core::mem::zeroed() }),
+            trigger <- Opaque::try_ffi_init(move |place: *mut bindings::led_trigger| {
+            // SAFETY: `place` is a pointer to a live allocation, so erasing is valid.
+            unsafe { place.write_bytes(0, 1) };
+
+            // Add name
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let name_ptr = unsafe { ptr::addr_of_mut!((*place).name) };
+            // SAFETY: `name_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(name_ptr, name.as_char_ptr()) };
+
+            // Add fn pointers
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let activate_fn_ptr: *mut Option<_> = unsafe { ptr::addr_of_mut!((*place).activate) };
+            // SAFETY: `activate_fn_ptr` points to a valid allocation and we have exclusive access.
+            unsafe { ptr::write(activate_fn_ptr, Some(trigger_activate::<T>)) };
+
+            if T::HAS_DEACTIVATE {
+                // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+                let deactivate_fn_ptr: *mut Option<_> = unsafe { ptr::addr_of_mut!((*place).deactivate) };
+                // SAFETY: `deactivate_fn_ptr` points to a valid allocation and we have exclusive access.
+                unsafe { ptr::write(deactivate_fn_ptr, Some(trigger_deactivate::<T>)) };
+            }
+
+            // Add hardware trigger
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let trigger_type_ptr = unsafe { ptr::addr_of_mut!((*place).trigger_type) };
+            // SAFETY: `place` is pointing to a live allocation, so the deref is safe.
+            let trigger_type = unsafe { crate::container_of!(place, Self, trigger).cast_mut() };
+            // SAFETY: `trigger_type` is pointing to a live allocation of Self.
+            let trigger_type = unsafe { ptr::addr_of!((*trigger_type).hw_type) };
+            // SAFETY: `trigger_type_ptr` points to a valid allocation and we have exclusive access.
+            unsafe{ ptr::write(trigger_type_ptr, Opaque::raw_get(trigger_type)) };
+
+        // SAFETY: ffi call, `place` is sufficently filled with data at this point
+            to_result(unsafe {
+                bindings::led_trigger_register(place)
+            })
+            }),
+            _t: PhantomData,
+        })
+    }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Hardware<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: trigger is pointing to a live and registered allocation
+        unsafe {
+            bindings::led_trigger_unregister(self.trigger.get());
+        }
+    }
+}
+
+/// Operations for the Hardware trigger
+#[macros::vtable]
+pub trait HardwareOperations: super::Operations {
+    /// Activate the hardware trigger.
+    fn activate(this: &mut Self::This) -> Result;
+    /// Deactivate the hardware trigger.
+    fn deactivate(_this: &mut Self::This) {
+        crate::build_error(crate::error::VTABLE_DEFAULT_ERROR)
+    }
+}
+
+/// `trigger_activate` function pointer
+///
+/// # Safety
+///
+/// `led_cdev` must be passed by the corresponding callback in `led_trigger`.
+unsafe extern "C" fn trigger_activate<T>(led_cdev: *mut bindings::led_classdev) -> i32
+where
+    T: HardwareOperations,
+{
+    from_result(|| {
+        // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `T::This<T>`.
+        let led = unsafe { &mut *(T::This::led_container_of(led_cdev.cast())) };
+        T::activate(led)?;
+        Ok(0)
+    })
+}
+
+/// `trigger_deactivate` function pointer
+///
+/// # Safety
+///
+/// `led_cdev` must be passed by the corresponding callback in `led_trigger`.
+unsafe extern "C" fn trigger_deactivate<T>(led_cdev: *mut bindings::led_classdev)
+where
+    T: HardwareOperations,
+{
+    // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `T::This<T>`.
+    let led = unsafe { &mut *(T::This::led_container_of(led_cdev.cast())) };
+    T::deactivate(led)
+}
-- 
2.47.0


  parent reply	other threads:[~2025-01-13 12:24 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-13 12:16 [PATCH v2 0/5] Rust LED Driver Abstractions and Lenovo SE10 Driver with DMI and I/O Port Support Fiona Behrens
2025-01-13 12:16 ` [PATCH v2 1/5] rust: dmi: Add abstractions for DMI Fiona Behrens
2025-01-13 12:16 ` [PATCH v2 2/5] rust: leds: Add Rust bindings for LED subsystem Fiona Behrens
2025-01-13 13:10   ` Miguel Ojeda
2025-01-13 12:16 ` Fiona Behrens [this message]
2025-01-20 10:35   ` [PATCH v2 3/5] rust: leds: Add hardware trigger support for hardware-controlled LEDs Marek Behún
2025-01-20 10:59     ` Fiona Behrens
2025-01-20 14:18       ` Marek Behún
2025-01-13 12:16 ` [PATCH v2 4/5] rust: add I/O port abstractions with resource management Fiona Behrens
2025-01-13 13:15   ` Daniel Almeida
2025-01-13 13:28     ` Fiona Behrens
2025-01-13 12:16 ` [PATCH v2 5/5] leds: leds_lenovo_se10: LED driver for Lenovo SE10 platform Fiona Behrens
2025-01-17 17:21   ` Mark Pearson
2025-01-17 17:31     ` Fiona Behrens
2025-01-17 18:02       ` Mark Pearson
2025-01-17 17:43     ` Miguel Ojeda
2025-01-20 10:47   ` Marek Behún

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250113121620.21598-4-me@kloenk.dev \
    --to=me@kloenk.dev \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=benno.lossin@proton.me \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=gary@garyguo.net \
    --cc=jdelvare@suse.com \
    --cc=lee@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=mpearson-lenovo@squebb.ca \
    --cc=ojeda@kernel.org \
    --cc=pavel@ucw.cz \
    --cc=pkoch@lenovo.com \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox