From patchwork Wed Oct 9 10:57:58 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fiona Behrens X-Patchwork-Id: 835306 Received: from gimli.kloenk.de (gimli.kloenk.de [49.12.72.200]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E7D7B180A76; Wed, 9 Oct 2024 10:58:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=49.12.72.200 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728471514; cv=none; b=GFQEma9c5/P2jlaQKNhtaeQA3b2t9OSxlWeqoYEDTG2DmOKsJsUtKYJpfedSvG1FTdE/3HXVLZX642s21Q+8WcXiiJ9pzI+02LvRQ+brnWz9Tb2FDqDwrTro1w8oThuFkT8/gK/pfA/AU0PbRCSdPiXeI1kkX6Yb5FWEo04X3I4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728471514; c=relaxed/simple; bh=PArS4hlJp5vABoJZsm8LNrE+8f6yfqryjBSUoHpBSBw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ETuhnkiFzLRsoETUavvH6RMQSJMgFUtbfV4dc/8Xl8CJ47NyrxgV33AjLysy+KieOMiOxbpONxUwPwpPplnMSk5FZfLv9lqTQUXH2hmGmdCmqq0GzWoENq/PiZcmRMed07Lt1aLnjsCPn0wm8lQgD20Unbe7FjkoUqkLdtFKk6A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=kloenk.dev; spf=pass smtp.mailfrom=kloenk.dev; dkim=pass (1024-bit key) header.d=kloenk.dev header.i=@kloenk.dev header.b=lcHD6w76; arc=none smtp.client-ip=49.12.72.200 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=kloenk.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=kloenk.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=kloenk.dev header.i=@kloenk.dev header.b="lcHD6w76" From: Fiona Behrens DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kloenk.dev; s=mail; t=1728471502; bh=c6Ab05vHf0g89gSXiF5Z/OuQ2xCSXFcvbnfFpVG9ZLM=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=lcHD6w76ZGzGKZGTeJ67kJr4ioJzHjR0M8DqCyqZ5aKmDaq9eMGPa1ykLruJ/CcAp aSVhBupdZy5Xm5ioZJS/8hC3cZm4KVqWR9ryC5VC4tdQ8kx5NbKFaQmhMJR1aLdcw8 lhssdcyWFmQpT2Afx+Bc5rmrk+bQ9AZrJQb/geQQ= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , FUJITA Tomonori , linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Fiona Behrens Subject: [RFC PATCH 1/2] rust: LED abstraction Date: Wed, 9 Oct 2024 12:57:58 +0200 Message-ID: <20241009105759.579579-2-me@kloenk.dev> In-Reply-To: <20241009105759.579579-1-me@kloenk.dev> References: <20241009105759.579579-1-me@kloenk.dev> Precedence: bulk X-Mailing-List: linux-leds@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add abstractions for a simple LED, supporting Single color LED and hardware accelerated blinking. This is implemented using a pinned Led struct which holds driver data and the C led_classdev struct. The driver data then implements a vtable which currently provides `brightness_set`, `brightness_get` and `blink_set`. This LED is then registered with the LED core and unregistered when it is dropped. Signed-off-by: Fiona Behrens --- rust/kernel/leds.rs | 399 ++++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 2 files changed, 401 insertions(+) create mode 100644 rust/kernel/leds.rs diff --git a/rust/kernel/leds.rs b/rust/kernel/leds.rs new file mode 100644 index 000000000000..5348c1af5b31 --- /dev/null +++ b/rust/kernel/leds.rs @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! LED subsystem abstractions. +//! +//! C header: [`includes/linux/leds.h`](srctree/include/linux/leds.h) + +use core::ffi::c_ulong; +use core::num::NonZeroU8; +use core::ptr; +use core::time::Duration; + +use crate::device::Device; +use crate::{error::from_result, init::PinInit, prelude::*, types::Opaque}; + +/// Color of an LED. +#[derive(Copy, Clone)] +pub enum Color { + /// White + White, + /// Red + Red, + /// Green + Green, + /// Blue + Blue, + /// Amber + Amber, + /// Violet + Violet, + /// Yellow + Yellow, + /// Purple + Purple, + /// Orange + Orange, + /// Pink + Pink, + /// Cyan + Cyan, + /// Lime + Lime, + + /// Infrared + IR, + /// Multicolor LEDs + Multi, + /// Multicolor LEDs that can do arbitrary color, + /// so this would include `RGBW` and similar + RGB, +} + +impl Color { + /// Get String representation of the Color. + pub fn name_cstr(&self) -> Option<&'static CStr> { + // SAFETY: C function call, enum is valid argument. + let name = unsafe { bindings::led_get_color_name(u32::from(self) as u8) }; + if name.is_null() { + return None; + } + // SAFETY: pointer from above, valid for static lifetime. + Some(unsafe { CStr::from_char_ptr(name) }) + } + + /// Get String representation of the Color. + #[inline] + pub fn name(&self) -> Option<&'static str> { + // SAFETY: name from C name array which is valid UTF-8. + self.name_cstr() + .map(|name| unsafe { name.as_str_unchecked() }) + } +} + +impl core::fmt::Display for Color { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.name().unwrap_or("Invalid color")) + } +} + +impl From for u32 { + fn from(color: Color) -> Self { + match color { + Color::White => bindings::LED_COLOR_ID_WHITE, + Color::Red => bindings::LED_COLOR_ID_RED, + Color::Green => bindings::LED_COLOR_ID_GREEN, + Color::Blue => bindings::LED_COLOR_ID_BLUE, + Color::Amber => bindings::LED_COLOR_ID_AMBER, + Color::Violet => bindings::LED_COLOR_ID_VIOLET, + Color::Yellow => bindings::LED_COLOR_ID_YELLOW, + Color::Purple => bindings::LED_COLOR_ID_PURPLE, + Color::Orange => bindings::LED_COLOR_ID_ORANGE, + Color::Pink => bindings::LED_COLOR_ID_PINK, + Color::Cyan => bindings::LED_COLOR_ID_CYAN, + Color::Lime => bindings::LED_COLOR_ID_LIME, + Color::IR => bindings::LED_COLOR_ID_IR, + Color::Multi => bindings::LED_COLOR_ID_MULTI, + Color::RGB => bindings::LED_COLOR_ID_RGB, + } + } +} + +impl From<&Color> for u32 { + fn from(color: &Color) -> Self { + (*color).into() + } +} + +impl TryFrom for Color { + type Error = Error; + + fn try_from(value: u32) -> Result { + Ok(match value { + bindings::LED_COLOR_ID_WHITE => Color::White, + bindings::LED_COLOR_ID_RED => Color::Red, + bindings::LED_COLOR_ID_GREEN => Color::Green, + bindings::LED_COLOR_ID_BLUE => Color::Blue, + bindings::LED_COLOR_ID_AMBER => Color::Amber, + bindings::LED_COLOR_ID_VIOLET => Color::Violet, + bindings::LED_COLOR_ID_YELLOW => Color::Yellow, + bindings::LED_COLOR_ID_PURPLE => Color::Purple, + bindings::LED_COLOR_ID_ORANGE => Color::Orange, + bindings::LED_COLOR_ID_PINK => Color::Pink, + bindings::LED_COLOR_ID_CYAN => Color::Cyan, + bindings::LED_COLOR_ID_LIME => Color::Lime, + bindings::LED_COLOR_ID_IR => Color::IR, + bindings::LED_COLOR_ID_MULTI => Color::Multi, + bindings::LED_COLOR_ID_RGB => Color::RGB, + _ => return Err(EINVAL), + }) + } +} + +/// Config for the LED. +/// +/// Some fields are optional and only used depending on how the led is registered. +pub struct LedConfig { + /// Color of the LED. + pub color: Color, +} + +/// A Led backed by a C `struct led_classdev`, additionally offering +/// driver data storage. +/// +/// The LED is unregistered once this LED struct is dropped. +// TODO: add devm register variant +#[pin_data(PinnedDrop)] +pub struct Led { + #[pin] + led: Opaque, + /// Driver private data + pub data: T, +} + +impl Led { + /// Return a raw reference to `Self` from a raw `struct led_classdev` C pointer. + /// + /// # Safety + /// + /// `ptr` must point to a [`bindings::led_classdev`] field in a struct of type `Self`. + unsafe fn led_container_of(ptr: *mut bindings::led_classdev) -> *mut Self { + let ptr = ptr.cast::>(); + + // SAFETY: By the safety requirement of this function ptr is embedded in self. + unsafe { crate::container_of!(ptr, Self, led).cast_mut() } + } +} + +impl<'a, T> Led +where + T: Operations + 'a, +{ + /// Register a new LED with a predefine name. + pub fn register_with_name( + name: &'a CStr, + device: Option<&'a Device>, + config: &'a LedConfig, + data: T, + ) -> impl PinInit + 'a { + try_pin_init!( Self { + led <- Opaque::try_ffi_init(move |place: *mut bindings::led_classdev| { + // SAFETY: `place` is a pointer to a live allocation, so erasing is valid. + unsafe { place.write_bytes(0, 1) }; + + // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`. + unsafe { Self::build_with_name(place, name) }; + + // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`. + unsafe { Self::build_config(place, config) }; + + // SAFETY: `place` is a pointer to a live allocation of `bindings::led_classdev`. + unsafe { Self::build_vtable(place) }; + + 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, + }) + } + + /// Add nameto the led_classdev. + /// + /// # Safety + /// + /// `ptr` has to be valid. + unsafe fn build_with_name(ptr: *mut bindings::led_classdev, name: &'a CStr) { + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let name_ptr = unsafe { ptr::addr_of_mut!((*ptr).name) }; + // SAFETY: `name_ptr` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(name_ptr, name.as_char_ptr()) }; + } + + /// Add config to led_classdev. + /// + /// # Safety + /// + /// `ptr` has to be valid. + unsafe fn build_config(ptr: *mut bindings::led_classdev, config: &'a LedConfig) { + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let color_ptr = unsafe { ptr::addr_of_mut!((*ptr).color) }; + // SAFETY: `color_ptr` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(color_ptr, config.color.into()) }; + } +} + +impl Led +where + T: Operations, +{ + /// Add the Operations vtable to the `led_classdev` struct. + /// + /// # Safety + /// + /// `ptr` has to be valid. + unsafe fn build_vtable(ptr: *mut bindings::led_classdev) { + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let max_brightness = unsafe { ptr::addr_of_mut!((*ptr).max_brightness) }; + // SAFETY: `max_brightness` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(max_brightness, T::MAX_BRIGHTNESS as _) }; + + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let set_fn: *mut Option<_> = unsafe { ptr::addr_of_mut!((*ptr).brightness_set) }; + // SAFETY: `set_fn` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(set_fn, Some(brightness_set::)) } + + if T::HAS_BRIGHTNESS_GET { + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let get_fn: *mut Option<_> = unsafe { ptr::addr_of_mut!((*ptr).brightness_get) }; + // SAFETY: `set_fn` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(get_fn, Some(brightness_get::)) } + } + + if T::HAS_BLINK_SET { + // SAFETY: `ptr` is pointing to a live allocation, so the deref is safe. + let blink_fn: *mut Option<_> = unsafe { ptr::addr_of_mut!((*ptr).blink_set) }; + // SAFETY: `set_fn` points to a valid allocation and we have exclusive access. + unsafe { ptr::write(blink_fn, Some(blink_set::)) } + } + } +} + +#[pinned_drop] +impl PinnedDrop for Led { + fn drop(self: Pin<&mut Self>) { + // SAEFTY: led is pointing to a live allocation + unsafe { bindings::led_classdev_unregister(self.led.get()) } + } +} + +// SAFETY: `struct led_classdev` holds a mutex. +unsafe impl Send for Led {} +// SAFETY: `struct led_classdev` holds a mutex. +unsafe impl Sync for Led {} + +/// LED brightness. +#[derive(Debug, Copy, Clone)] +pub enum Brightness { + /// LED off. + Off, + /// Led set to the given value. + On(NonZeroU8), +} + +impl Brightness { + /// Half LED brightness + // SAFETY: constant value non zero + pub const HALF: Self = Self::On(unsafe { NonZeroU8::new_unchecked(127) }); + /// Full LED brightness. + pub const FULL: Self = Self::On(NonZeroU8::MAX); + + /// Convert C brightness value to Rust brightness enum + fn from_c_enum(brightness: bindings::led_brightness) -> Self { + u8::try_from(brightness) + .map(NonZeroU8::new) + .map(|brightness| brightness.map(Self::On).unwrap_or(Self::Off)) + .inspect_err(|_| pr_warn!("Brightness out of u8 range\n")) + .unwrap_or(Self::FULL) + } + + /// Convert rust brightness enum to C value + fn as_c_enum(&self) -> bindings::led_brightness { + u8::from(self) as bindings::led_brightness + } +} + +impl From<&Brightness> for u8 { + fn from(brightness: &Brightness) -> Self { + match brightness { + Brightness::Off => 0, + Brightness::On(v) => v.get(), + } + } +} + +/// Led operations vtable. +// TODO: add blocking variant (either via const generic or second trait) +#[macros::vtable] +pub trait Operations: Sized { + /// The maximum brightnes this led supports. + const MAX_BRIGHTNESS: u8; + + /// Set LED brightness level. + /// This functions must not sleep. + fn brightness_set(this: &mut Led, brightness: Brightness); + + /// Get the LED brightness level. + fn brightness_get(_this: &mut Led) -> Brightness { + crate::build_error(crate::error::VTABLE_DEFAULT_ERROR) + } + + /// Activae hardware accelerated blink, delays are in milliseconds + fn blink_set( + _this: &mut Led, + _delay_on: Duration, + _delay_off: Duration, + ) -> Result<(Duration, Duration)> { + crate::build_error(crate::error::VTABLE_DEFAULT_ERROR) + } +} + +unsafe extern "C" fn brightness_set( + led_cdev: *mut bindings::led_classdev, + brightness: bindings::led_brightness, +) where + T: Operations, +{ + // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `Led` + // struct. + let led = unsafe { &mut *(Led::::led_container_of(led_cdev.cast())) }; + let brightness = Brightness::from_c_enum(brightness); + T::brightness_set(led, brightness); +} + +unsafe extern "C" fn brightness_get( + led_cdev: *mut bindings::led_classdev, +) -> bindings::led_brightness +where + T: Operations, +{ + // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a `Led` + // struct. + let led = unsafe { &mut *(Led::::led_container_of(led_cdev.cast())) }; + T::brightness_get(led).as_c_enum() +} + +unsafe extern "C" fn blink_set( + led_cdev: *mut bindings::led_classdev, + delay_on: *mut c_ulong, + delay_off: *mut c_ulong, +) -> i32 +where + T: Operations, +{ + from_result(|| { + // SAFETY: By the safety requirement of this function `led_cdev` is embedded inside a + // `Led` struct. + let led = unsafe { &mut *(Led::::led_container_of(led_cdev.cast())) }; + + // SAFETY: By the safety requirement of this function `delay_on` is pointing to a valid + // `c_uint` + let on = Duration::from_millis(unsafe { *delay_on } as u64); + // SAFETY: By the safety requirement of this function `delay_off` is pointing to a valid + // `c_uint` + let off = Duration::from_millis(unsafe { *delay_off } as u64); + + let (on, off) = T::blink_set(led, on, off)?; + + // writing back return values + // SAFETY: By the safety requirement of this function `delay_on` is pointing to a valid + // `c_uint` + unsafe { ptr::write(delay_on, on.as_millis() as c_ulong) }; + // SAFETY: By the safety requirement of this function `delay_off` is pointing to a valid + // `c_uint` + unsafe { ptr::write(delay_off, off.as_millis() as c_ulong) }; + + Ok(0) + }) +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index b5f4b3ce6b48..8df5f1cdf426 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -38,6 +38,8 @@ pub mod ioctl; #[cfg(CONFIG_KUNIT)] pub mod kunit; +#[cfg(CONFIG_NEW_LEDS)] +pub mod leds; pub mod list; #[cfg(CONFIG_NET)] pub mod net; From patchwork Wed Oct 9 10:57:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fiona Behrens X-Patchwork-Id: 834368 Received: from gimli.kloenk.de (gimli.kloenk.de [49.12.72.200]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DA35418BC36; Wed, 9 Oct 2024 10:58:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=49.12.72.200 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728471514; cv=none; b=lyRnXYcbjsDatS/J2fcoOrY96CbPAImCYtnvYXVppG1KDRA4r2ZJOgpy4K8kLxarKg5OKvCpbBuI1s8L7ojC9yXPCjoKTNDzh/7Rt4RIVw9Tw4pmR63Nly3oGw3IA3Uv+ki1C0+WmCXjO1TkwMugY1BkPaIHuKJxe1IEtIA3pQg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728471514; c=relaxed/simple; bh=/L0HeoREtlbZRvzxpfTCUjU7nLGJzk145c5MdADPt6Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NY3EkEQRhfEYpmlSQtWRLx5xAjUd7dRnnlootDjehyTm/dSKFIaqET7DSiS8Ik/TSb3KPvzb6Od2LH/2WoY7RK4ains+MPuqtJTcsMvbvEm1kMg28YyWsp6VAU0mP6wSUhxniP6xYTLC188eYfOyrnbQG+4kKh65wXd1eyOhhDs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=kloenk.dev; spf=pass smtp.mailfrom=kloenk.dev; dkim=pass (1024-bit key) header.d=kloenk.dev header.i=@kloenk.dev header.b=ZyGEMPci; arc=none smtp.client-ip=49.12.72.200 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=kloenk.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=kloenk.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=kloenk.dev header.i=@kloenk.dev header.b="ZyGEMPci" From: Fiona Behrens DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kloenk.dev; s=mail; t=1728471503; bh=YCDuk3KEgjZaCFi5Pt7v/P59ADMvQb3dHrp/CUXem8U=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=ZyGEMPci225eQjGaKqKHqbAzI/arjNl+dBz8irFHyPXiPesqrq62SsX4dXfVxQ4TZ ps3bvUOmtunpRdtNZ/B5bkIaRPdca3hS5O5T0ZnI4Yqh5EXjkECG0OY5vmVvo2vkT1 q210b13efooSpHBfQt2LkoC8Yw3E3uijHQKwrYMQ= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?utf-8?q?B?= =?utf-8?q?j=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , FUJITA Tomonori , linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Fiona Behrens Subject: [RFC PATCH 2/2] samples: rust: led sample Date: Wed, 9 Oct 2024 12:57:59 +0200 Message-ID: <20241009105759.579579-3-me@kloenk.dev> In-Reply-To: <20241009105759.579579-1-me@kloenk.dev> References: <20241009105759.579579-1-me@kloenk.dev> Precedence: bulk X-Mailing-List: linux-leds@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Provide an initial sample LED driver that just prints the current requested LED state. This is not intended to be merged, but as a placeholder until the abstactions for hardware access are written. Signed-off-by: Fiona Behrens --- samples/rust/Kconfig | 10 ++++ samples/rust/Makefile | 1 + samples/rust/rust_led.rs | 103 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 samples/rust/rust_led.rs diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index b0f74a81c8f9..910f15ef6951 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -30,6 +30,16 @@ config SAMPLE_RUST_PRINT If unsure, say N. +config SAMPLE_RUST_LED + tristate "Led subsystem" + help + This option builds the Rust LED subsystem sample. + + To compile this as a module, choose M here: + the module will be called rust_led. + + If unsure, say N. + config SAMPLE_RUST_HOSTPROGS bool "Host programs" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 03086dabbea4..1299ca1e9ebb 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o +obj-$(CONFIG_SAMPLE_RUST_LED) += rust_led.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_led.rs b/samples/rust/rust_led.rs new file mode 100644 index 000000000000..0085f7484ea1 --- /dev/null +++ b/samples/rust/rust_led.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +//! Rust LED sample. + +use core::time::Duration; + +use kernel::c_str; +use kernel::leds::{Brightness, Color, Led, LedConfig, Operations}; +use kernel::prelude::*; + +module! { + type: RustLed, + name: "rust_led", + author: "Rust for Linux Contributors", + description: "Rust led sample", + license: "GPL", +} + +/// Rust LED sample driver +// Hold references in scope as droping would unregister the LED +#[allow(dead_code)] +struct RustLed { + /// LED which just supports on/off. + on_off: Pin>>, + /// LED which supports brightness levels and blinking. + blink: Pin>>, +} + +impl kernel::Module for RustLed { + fn init(_module: &'static ThisModule) -> Result { + pr_info!("registering on_off led\n"); + let on_off = Box::pin_init( + Led::register_with_name( + c_str!("sample:red:on_off"), + None, + &LedConfig { color: Color::Red }, + LedOnOff, + ), + GFP_KERNEL, + )?; + + let blink = Box::pin_init( + Led::register_with_name( + c_str!("sample:green:blink"), + None, + &LedConfig { + color: Color::Green, + }, + LedBlinking, + ), + GFP_KERNEL, + )?; + + Ok(Self { on_off, blink }) + } +} + +struct LedOnOff; + +#[vtable] +impl Operations for LedOnOff { + const MAX_BRIGHTNESS: u8 = 1; + + fn brightness_set(_this: &mut Led, brightness: Brightness) { + match brightness { + Brightness::Off => pr_info!("Switching led off\n"), + Brightness::On(v) => pr_info!("Switching led on: {}\n", v.get()), + } + } +} + +struct LedBlinking; + +impl LedBlinking { + const HW_DURATION: Duration = Duration::from_millis(1000); +} + +#[vtable] +impl Operations for LedBlinking { + const MAX_BRIGHTNESS: u8 = 255; + + fn brightness_set(_this: &mut Led, brightness: Brightness) { + match brightness { + Brightness::Off => pr_info!("blinking: off\n"), + Brightness::On(v) => pr_info!("blinking: on at {}\n", v.get()), + } + } + + fn blink_set( + _this: &mut Led, + delay_on: Duration, + delay_off: Duration, + ) -> Result<(Duration, Duration)> { + pr_info!("blinking: try delay {delay_on:?} {delay_off:?}\n"); + if !(delay_on.is_zero() && delay_off.is_zero()) + && !(delay_on == Self::HW_DURATION && delay_off == Self::HW_DURATION) + { + return Err(EINVAL); + } + + pr_info!("blinking: setting dealy\n"); + Ok((Self::HW_DURATION, Self::HW_DURATION)) + } +}