* Re: [PATCH v6] kbuild: host: use single executable for rustc -C linker
From: Mohamad Alsadhan @ 2026-04-12 14:19 UTC (permalink / raw)
To: Nicolas Schier, Nathan Chancellor, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Yoann Congal,
linux-kbuild, linux-kernel, rust-for-linux
In-Reply-To: <adVpQ_ZvXTPUegig@levanger>
On 26/04/07 10:29pm, Nicolas Schier wrote:
>
> What is the reason for re-adding the 'exec'?
I was under the impression of that it won't hurt, unless I am missing
something.
'exec' would replace the shell process entirely rather than leaving
it alive waiting for the compiler to finish.
Best regards,
Mo
^ permalink raw reply
* Re: [PATCH v7 2/2] dt-bindings: embedded-controller: Add synology microp devices
From: Krzysztof Kozlowski @ 2026-04-12 13:22 UTC (permalink / raw)
To: Markus Probst
Cc: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, platform-driver-x86, linux-leds,
devicetree, linux-kernel, rust-for-linux
In-Reply-To: <485ab9e829e902e3f29172059be8c3203062d06b.camel@posteo.de>
On 12/04/2026 15:21, Markus Probst wrote:
> On Sun, 2026-04-12 at 10:26 +0200, Krzysztof Kozlowski wrote:
>> On Sat, Apr 11, 2026 at 05:27:35PM +0200, Markus Probst wrote:
>>> +properties:
>>> + compatible:
>>> + enum:
>>> + - synology,ds923p-microp
>>> + - synology,ds918p-microp
>>> + - synology,ds214play-microp
>>> + - synology,ds225p-microp
>>> + - synology,ds425p-microp
>>> + - synology,ds710p-microp
>>> + - synology,ds1010p-microp
>>> + - synology,ds723p-microp
>>> + - synology,ds1522p-microp
>>> + - synology,rs422p-microp
>>> + - synology,ds725p-microp
>>> + - synology,ds118-microp
>>> + - synology,ds124-microp
>>> + - synology,ds223-microp
>>> + - synology,ds223j-microp
>>> + - synology,ds1823xsp-microp
>>> + - synology,rs822p-microp
>>> + - synology,rs1221p-microp
>>> + - synology,rs1221rpp-microp
>>> + - synology,ds925p-microp
>>> + - synology,ds1525p-microp
>>> + - synology,ds1825p-microp
>>
>> Previous comment is not resolved. For example you stated that ds723p is
>> compatible with ds725p, so this should be expressed.
> Using this expression?
>
> properties:
> compatible:
> oneOf:
> - enum:
> - synology,ds923p-microp
> - synology,ds1522p-microp
> - enum:
> - synology,ds918p-microp
> - synology,ds415p-microp
> - const: synology,ds214play-microp
> ...
> ?
> If so shall there each be a description?
No, you changed nothing. You need fallbacks, please read example-schema
or DTS101 slides.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v7 2/2] dt-bindings: embedded-controller: Add synology microp devices
From: Markus Probst @ 2026-04-12 13:21 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, platform-driver-x86, linux-leds,
devicetree, linux-kernel, rust-for-linux
In-Reply-To: <20260412-cuddly-taipan-of-reputation-1cafe0@quoll>
[-- Attachment #1: Type: text/plain, Size: 2578 bytes --]
On Sun, 2026-04-12 at 10:26 +0200, Krzysztof Kozlowski wrote:
> On Sat, Apr 11, 2026 at 05:27:35PM +0200, Markus Probst wrote:
> > +properties:
> > + compatible:
> > + enum:
> > + - synology,ds923p-microp
> > + - synology,ds918p-microp
> > + - synology,ds214play-microp
> > + - synology,ds225p-microp
> > + - synology,ds425p-microp
> > + - synology,ds710p-microp
> > + - synology,ds1010p-microp
> > + - synology,ds723p-microp
> > + - synology,ds1522p-microp
> > + - synology,rs422p-microp
> > + - synology,ds725p-microp
> > + - synology,ds118-microp
> > + - synology,ds124-microp
> > + - synology,ds223-microp
> > + - synology,ds223j-microp
> > + - synology,ds1823xsp-microp
> > + - synology,rs822p-microp
> > + - synology,rs1221p-microp
> > + - synology,rs1221rpp-microp
> > + - synology,ds925p-microp
> > + - synology,ds1525p-microp
> > + - synology,ds1825p-microp
>
> Previous comment is not resolved. For example you stated that ds723p is
> compatible with ds725p, so this should be expressed.
Using this expression?
properties:
compatible:
oneOf:
- enum:
- synology,ds923p-microp
- synology,ds1522p-microp
- enum:
- synology,ds918p-microp
- synology,ds415p-microp
- const: synology,ds214play-microp
...
?
If so shall there each be a description?
Also ds723p and ds725p are not compatible. ds723p has a system current
sensor, ds725p does not. This will be relevant when implementing the
hwmon part of the driver.
>
> ds918p and ds415p as well. ds925p and several others you EXPLICITLY
> wrote they are compatible:
>
> "ds925p, ds1525p, ds1825p, ds1823xsp:
> - supports fan rpm report via an adt7475 chip and therefore does not
> have gpios for fan failure
> - no system current sensor"
Yes.
>
> Probably many more cases, I did not verify all of them.
>
> If there is going to be new version, please organize the patch
> documenting the compatible (DT bindings) before the patch using that
> compatible.
> See also: https://elixir.bootlin.com/linux/v6.14-rc6/source/Documentation/devicetree/bindings/submitting-patches.rst#L46
Ok.
Thanks
- Markus Probst
>
> > +
> > + fan-failure-gpios:
> > + description: GPIOs needed to determine which fans stopped working on a fan failure event.
> > + minItems: 2
> > + maxItems: 3
> > +
> > +required:
> > + - compatible
>
> Best regards,
> Krzysztof
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]
^ permalink raw reply
* Re: [PATCH v7 2/2] dt-bindings: embedded-controller: Add synology microp devices
From: Krzysztof Kozlowski @ 2026-04-12 8:26 UTC (permalink / raw)
To: Markus Probst
Cc: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, platform-driver-x86, linux-leds,
devicetree, linux-kernel, rust-for-linux
In-Reply-To: <20260411-synology_microp_initial-v7-2-9a3a094e763a@posteo.de>
On Sat, Apr 11, 2026 at 05:27:35PM +0200, Markus Probst wrote:
> +properties:
> + compatible:
> + enum:
> + - synology,ds923p-microp
> + - synology,ds918p-microp
> + - synology,ds214play-microp
> + - synology,ds225p-microp
> + - synology,ds425p-microp
> + - synology,ds710p-microp
> + - synology,ds1010p-microp
> + - synology,ds723p-microp
> + - synology,ds1522p-microp
> + - synology,rs422p-microp
> + - synology,ds725p-microp
> + - synology,ds118-microp
> + - synology,ds124-microp
> + - synology,ds223-microp
> + - synology,ds223j-microp
> + - synology,ds1823xsp-microp
> + - synology,rs822p-microp
> + - synology,rs1221p-microp
> + - synology,rs1221rpp-microp
> + - synology,ds925p-microp
> + - synology,ds1525p-microp
> + - synology,ds1825p-microp
Previous comment is not resolved. For example you stated that ds723p is
compatible with ds725p, so this should be expressed.
ds918p and ds415p as well. ds925p and several others you EXPLICITLY
wrote they are compatible:
"ds925p, ds1525p, ds1825p, ds1823xsp:
- supports fan rpm report via an adt7475 chip and therefore does not
have gpios for fan failure
- no system current sensor"
Probably many more cases, I did not verify all of them.
If there is going to be new version, please organize the patch
documenting the compatible (DT bindings) before the patch using that
compatible.
See also: https://elixir.bootlin.com/linux/v6.14-rc6/source/Documentation/devicetree/bindings/submitting-patches.rst#L46
> +
> + fan-failure-gpios:
> + description: GPIOs needed to determine which fans stopped working on a fan failure event.
> + minItems: 2
> + maxItems: 3
> +
> +required:
> + - compatible
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 1/1] rust: seq_file: add puts, putc, write, and hex_dump methods
From: kernel test robot @ 2026-04-12 0:45 UTC (permalink / raw)
To: Christian Benton, rust-for-linux, linux-kernel, linux-fsdevel
Cc: oe-kbuild-all, aliceryhl, ojeda, boqun, gary, bjorn3_gh, lossin,
a.hindborg, tmgross, dakr, viro, brauner, jack, Christian Benton
In-Reply-To: <20260408215530.446994-2-t1bur0n.kernel.org@protonmail.ch>
Hi Christian,
kernel test robot noticed the following build warnings:
[auto build test WARNING on brauner-vfs/vfs.all]
[also build test WARNING on next-20260410]
[cannot apply to linus/master v6.16-rc1]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Christian-Benton/rust-seq_file-add-puts-putc-write-and-hex_dump-methods/20260412-035855
base: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git vfs.all
patch link: https://lore.kernel.org/r/20260408215530.446994-2-t1bur0n.kernel.org%40protonmail.ch
patch subject: [PATCH v2 1/1] rust: seq_file: add puts, putc, write, and hex_dump methods
config: x86_64-rhel-9.4-rust (https://download.01.org/0day-ci/archive/20260412/202604120236.nzYgh7vO-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260412/202604120236.nzYgh7vO-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604120236.nzYgh7vO-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> warning: methods with the following characteristics: (`to_*` and `self` type is not `Copy`) usually take `self` by reference
--> rust/kernel/seq_file.rs:29:17
|
29 | fn to_c_int(self) -> ffi::c_int {
| ^^^^
|
= help: consider choosing a less ambiguous name
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention
= note: `-W clippy::wrong-self-convention` implied by `-W clippy::all`
= help: to override `-W clippy::all` add `#[allow(clippy::wrong_self_convention)]`
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: Re: [RFC PATCH 0/2] rust: block: add a borrowed blk-mq timeout callback
From: Miguel Ojeda @ 2026-04-11 22:47 UTC (permalink / raw)
To: wenzhaoliao
Cc: Greg KH, Andreas Hindborg, Jens Axboe, Miguel Ojeda, linux-block,
rust-for-linux, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Alice Ryhl, Trevor Gross, Danilo Krummrich,
linux-kernel
In-Reply-To: <ABUAdgD1KKnjiCpcrxWXD4pE.3.1775914622691.Hmail.2023000929@ruc.edu.cn>
On Sat, Apr 11, 2026 at 3:37 PM wenzhaoliao <wenzhaoliao@ruc.edu.cn> wrote:
>
> Learning from you that module options are considered 1990's technology and do not fit the modern kernel model is a great lesson for us. If we misunderstood the maintainer's intention, or if pursuing this outdated mechanism goes against modern kernel design principles, we sincerely apologize for the noise and for any burden this has caused.
>
> We are perfectly happy to drop this patch if this functionality is no longer desired in the modern kernel. We really just want to help improve the kernel, and we will try our best to sync with the maintainers to pick up tasks that are more aligned with current kernel standards (like sysfs/configfs) next time.
It appears you are replying to Andreas' message, not Greg's :)
It is true that kernel module parameters are not something that one
should generally be used when there are better alternatives, but rnull
is a bit of a special case, i.e. null block is used for benchmarking,
and Andreas may want to have parity with the C one for comparison
purposes.
But since your commit message didn't really say for what this was
meant for, then Greg rightly pointed it out :)
In any case, I wanted to thank you for reading and following the AI
and tooling kernel policies I mentioned to you in Zulip! It may also
be good to disclose the research side, at least just a link to that
Zulip thread could be good.
Thanks!
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH v7 1/2] platform: Add initial synology microp driver
From: Onur Özkan @ 2026-04-11 16:55 UTC (permalink / raw)
To: Markus Probst via B4 Relay
Cc: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, platform-driver-x86, linux-leds,
devicetree, linux-kernel, rust-for-linux, Markus Probst
In-Reply-To: <20260411-synology_microp_initial-v7-1-9a3a094e763a@posteo.de>
On Sat, 11 Apr 2026 17:27:34 +0200
Markus Probst via B4 Relay <devnull+markus.probst.posteo.de@kernel.org> wrote:
> From: Markus Probst <markus.probst@posteo.de>
>
> Add a initial synology microp driver, written in Rust.
> The driver targets a microcontroller found in Synology NAS devices. It
> currently only supports controlling of the power led, status led, alert
> led and usb led. Other components such as fan control or handling
> on-device buttons will be added once the required rust abstractions are
> there.
>
> This driver can be used both on arm and x86, thus it goes into the root
> directory of drivers/platform.
>
> Tested successfully on a Synology DS923+.
>
> Signed-off-by: Markus Probst <markus.probst@posteo.de>
> ---
> MAINTAINERS | 5 +
> drivers/platform/Kconfig | 2 +
> drivers/platform/Makefile | 1 +
> drivers/platform/synology_microp/Kconfig | 13 +
> drivers/platform/synology_microp/Makefile | 3 +
> drivers/platform/synology_microp/TODO | 7 +
> drivers/platform/synology_microp/command.rs | 55 ++++
> drivers/platform/synology_microp/led.rs | 276 +++++++++++++++++++++
> drivers/platform/synology_microp/model.rs | 49 ++++
> .../platform/synology_microp/synology_microp.rs | 109 ++++++++
> 10 files changed, 520 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a667f4efb66e..78c99d831431 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -25554,6 +25554,11 @@ F: drivers/dma-buf/sync_*
> F: include/linux/sync_file.h
> F: include/uapi/linux/sync_file.h
>
> +SYNOLOGY MICROP DRIVER
> +M: Markus Probst <markus.probst@posteo.de>
> +S: Maintained
> +F: drivers/platform/synology_microp/
> +
> SYNOPSYS ARC ARCHITECTURE
> M: Vineet Gupta <vgupta@kernel.org>
> L: linux-snps-arc@lists.infradead.org
> diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
> index 312788f249c9..996050566a4a 100644
> --- a/drivers/platform/Kconfig
> +++ b/drivers/platform/Kconfig
> @@ -22,3 +22,5 @@ source "drivers/platform/arm64/Kconfig"
> source "drivers/platform/raspberrypi/Kconfig"
>
> source "drivers/platform/wmi/Kconfig"
> +
> +source "drivers/platform/synology_microp/Kconfig"
> diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
> index fa322e7f8716..2381872e9133 100644
> --- a/drivers/platform/Makefile
> +++ b/drivers/platform/Makefile
> @@ -15,3 +15,4 @@ obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
> obj-$(CONFIG_ARM64_PLATFORM_DEVICES) += arm64/
> obj-$(CONFIG_BCM2835_VCHIQ) += raspberrypi/
> obj-$(CONFIG_ACPI_WMI) += wmi/
> +obj-$(CONFIG_SYNOLOGY_MICROP) += synology_microp/
> diff --git a/drivers/platform/synology_microp/Kconfig b/drivers/platform/synology_microp/Kconfig
> new file mode 100644
> index 000000000000..7c4d8f2808f0
> --- /dev/null
> +++ b/drivers/platform/synology_microp/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +config SYNOLOGY_MICROP
> + tristate "Synology Microp driver"
> + depends on LEDS_CLASS && LEDS_CLASS_MULTICOLOR
> + depends on RUST_SERIAL_DEV_BUS_ABSTRACTIONS
> + help
> + Enable support for the MCU found in Synology NAS devices.
> +
> + This is needed to properly shutdown and reboot the device, as well as
> + additional functionality like fan and LED control.
> +
> + This driver is work in progress and may not be fully functional.
> diff --git a/drivers/platform/synology_microp/Makefile b/drivers/platform/synology_microp/Makefile
> new file mode 100644
> index 000000000000..63585ccf76e4
> --- /dev/null
> +++ b/drivers/platform/synology_microp/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-y += synology_microp.o
> diff --git a/drivers/platform/synology_microp/TODO b/drivers/platform/synology_microp/TODO
> new file mode 100644
> index 000000000000..1961a33115db
> --- /dev/null
> +++ b/drivers/platform/synology_microp/TODO
> @@ -0,0 +1,7 @@
> +TODO:
> +- add missing components:
> + - handle on-device buttons (Power, Factory reset, "USB Copy")
> + - handle fan failure
> + - beeper
> + - fan speed control
> + - correctly perform device power-off and restart on Synology devices
> diff --git a/drivers/platform/synology_microp/command.rs b/drivers/platform/synology_microp/command.rs
> new file mode 100644
> index 000000000000..5b3dd715afac
> --- /dev/null
> +++ b/drivers/platform/synology_microp/command.rs
> @@ -0,0 +1,55 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use kernel::{
> + device::Bound,
> + error::Result,
> + serdev, //
> +};
> +
> +use crate::led;
> +
> +#[derive(Copy, Clone)]
Copy and Clone seem never used, please drop them (also see [1]).
[1]: https://rust-for-linux.zulipchat.com/#narrow/channel/509436-Nova/topic/clone.2Fcopy.20additions
> +#[expect(
> + clippy::enum_variant_names,
> + reason = "future variants will not end with Led"
> +)]
> +pub(crate) enum Command {
> + PowerLed(led::State),
> + StatusLed(led::StatusLedColor, led::State),
> + AlertLed(led::State),
> + UsbLed(led::State),
> + EsataLed(led::State),
> +}
> +
> +impl Command {
> + pub(crate) fn write(self, dev: &serdev::Device<Bound>) -> Result {
> + dev.write_all(
> + match self {
> + Self::PowerLed(led::State::On) => &[0x34],
> + Self::PowerLed(led::State::Blink) => &[0x35],
> + Self::PowerLed(led::State::Off) => &[0x36],
> +
> + Self::StatusLed(_, led::State::Off) => &[0x37],
> + Self::StatusLed(led::StatusLedColor::Green, led::State::On) => &[0x38],
> + Self::StatusLed(led::StatusLedColor::Green, led::State::Blink) => &[0x39],
> + Self::StatusLed(led::StatusLedColor::Orange, led::State::On) => &[0x3A],
> + Self::StatusLed(led::StatusLedColor::Orange, led::State::Blink) => &[0x3B],
> +
> + Self::AlertLed(led::State::On) => &[0x4C, 0x41, 0x31],
> + Self::AlertLed(led::State::Blink) => &[0x4C, 0x41, 0x32],
> + Self::AlertLed(led::State::Off) => &[0x4C, 0x41, 0x33],
> +
> + Self::UsbLed(led::State::On) => &[0x40],
> + Self::UsbLed(led::State::Blink) => &[0x41],
> + Self::UsbLed(led::State::Off) => &[0x42],
> +
> + Self::EsataLed(led::State::On) => &[0x4C, 0x45, 0x31],
> + Self::EsataLed(led::State::Blink) => &[0x4C, 0x45, 0x32],
> + Self::EsataLed(led::State::Off) => &[0x4C, 0x45, 0x33],
> + },
> + serdev::Timeout::Max,
> + )?;
> + dev.wait_until_sent(serdev::Timeout::Max);
> + Ok(())
> + }
> +}
> diff --git a/drivers/platform/synology_microp/led.rs b/drivers/platform/synology_microp/led.rs
> new file mode 100644
> index 000000000000..a78a95588456
> --- /dev/null
> +++ b/drivers/platform/synology_microp/led.rs
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use kernel::{
> + device::Bound,
> + devres::{
> + self,
> + Devres, //
> + },
> + led::{
> + self,
> + LedOps,
> + MultiColorSubLed, //
> + },
> + new_mutex,
> + prelude::*,
> + serdev,
> + str::CString,
> + sync::Mutex, //
> +};
> +use pin_init::pin_init_scope;
> +
> +use crate::{
> + command::Command,
> + model::Model, //
> +};
> +
> +#[pin_data]
> +pub(crate) struct Data {
> + #[pin]
> + status: Devres<led::MultiColorDevice<StatusLedHandler>>,
> + power_name: CString,
> + #[pin]
> + power: Devres<led::Device<LedHandler>>,
> +}
> +
> +impl Data {
> + pub(super) fn register<'a>(
> + dev: &'a serdev::Device<Bound>,
> + model: &'a Model,
> + ) -> impl PinInit<Self, Error> + 'a {
> + pin_init_scope(move || {
> + if let Some(color) = model.led_alert {
> + let name = CString::try_from_fmt(fmt!("{}:alarm", color.as_c_str().to_str()?))?;
> + devres::register(
> + dev.as_ref(),
> + led::DeviceBuilder::new().color(color).name(&name).build(
> + dev,
> + try_pin_init!(LedHandler {
> + blink <- new_mutex!(false),
> + command: Command::AlertLed,
> + }),
> + ),
> + GFP_KERNEL,
> + )?;
> + }
> +
> + if model.led_usb_copy {
> + devres::register(
> + dev.as_ref(),
> + led::DeviceBuilder::new()
> + .color(led::Color::Green)
> + .name(c"green:usb")
> + .build(
> + dev,
> + try_pin_init!(LedHandler {
> + blink <- new_mutex!(false),
> + command: Command::UsbLed,
> + }),
> + ),
> + GFP_KERNEL,
> + )?;
> + }
> +
> + if model.led_esata {
> + devres::register(
> + dev.as_ref(),
> + led::DeviceBuilder::new()
> + .color(led::Color::Green)
> + .name(c"green:esata")
> + .build(
> + dev,
> + try_pin_init!(LedHandler {
> + blink <- new_mutex!(false),
> + command: Command::EsataLed,
> + }),
> + ),
> + GFP_KERNEL,
> + )?;
> + }
> +
> + Ok(try_pin_init!(Self {
> + status <- led::DeviceBuilder::new()
> + .color(led::Color::Multi)
> + .name(c"multicolor:status")
> + .build_multicolor(
> + dev,
> + try_pin_init!(StatusLedHandler {
> + blink <- new_mutex!(false),
> + }),
> + StatusLedHandler::SUBLEDS,
> + ),
> + power_name: CString::try_from_fmt(fmt!(
> + "{}:power",
> + model.led_power.as_c_str().to_str()?
> + ))?,
> + power <- led::DeviceBuilder::new()
> + .color(model.led_power)
> + .name(power_name)
> + .build(
> + dev,
> + try_pin_init!(LedHandler {
> + blink <- new_mutex!(true),
> + command: Command::PowerLed,
> + }),
> + ),
> + }))
> + })
> + }
> +}
> +
> +#[derive(Copy, Clone)]
> +pub(crate) enum StatusLedColor {
> + Green,
> + Orange,
> +}
> +
> +#[derive(Copy, Clone)]
> +pub(crate) enum State {
> + On,
> + Blink,
> + Off,
> +}
> +
> +#[pin_data]
> +struct LedHandler {
> + #[pin]
> + blink: Mutex<bool>,
> + command: fn(State) -> Command,
> +}
> +
> +#[vtable]
> +impl LedOps for LedHandler {
> + type Bus = serdev::Device<Bound>;
> + type Mode = led::Normal;
> + const BLOCKING: bool = true;
> + const MAX_BRIGHTNESS: u32 = 1;
> +
> + fn brightness_set(
> + &self,
> + dev: &Self::Bus,
> + _classdev: &led::Device<Self>,
> + brightness: u32,
> + ) -> Result<()> {
> + let mut blink = self.blink.lock();
> + (self.command)(if brightness == 0 {
> + *blink = false;
> + State::Off
> + } else if *blink {
> + State::Blink
> + } else {
> + State::On
> + })
> + .write(dev)?;
> +
> + Ok(())
> + }
> +
> + fn blink_set(
> + &self,
> + dev: &Self::Bus,
> + _classdev: &led::Device<Self>,
> + delay_on: &mut usize,
> + delay_off: &mut usize,
> + ) -> Result<()> {
> + let mut blink = self.blink.lock();
> +
> + (self.command)(if *delay_on == 0 && *delay_off != 0 {
> + State::Off
> + } else if *delay_on != 0 && *delay_off == 0 {
> + State::On
> + } else {
> + *blink = true;
> + *delay_on = 167;
> + *delay_off = 167;
Perhaps using a named constant with a simple comment instead of a hard-coded
integer would help to clarify what 167 is.
- Onur
> +
> + State::Blink
> + })
> + .write(dev)
> + }
> +}
> +
> +#[pin_data]
> +struct StatusLedHandler {
> + #[pin]
> + blink: Mutex<bool>,
> +}
> +
> +impl StatusLedHandler {
> + const SUBLEDS: &[MultiColorSubLed] = &[
> + MultiColorSubLed::new(led::Color::Green).initial_intensity(1),
> + MultiColorSubLed::new(led::Color::Orange),
> + ];
> +}
> +
> +#[vtable]
> +impl LedOps for StatusLedHandler {
> + type Bus = serdev::Device<Bound>;
> + type Mode = led::MultiColor;
> + const BLOCKING: bool = true;
> + const MAX_BRIGHTNESS: u32 = 1;
> +
> + fn brightness_set(
> + &self,
> + dev: &Self::Bus,
> + classdev: &led::MultiColorDevice<Self>,
> + brightness: u32,
> + ) -> Result<()> {
> + let mut blink = self.blink.lock();
> + if brightness == 0 {
> + *blink = false;
> + }
> +
> + let (color, subled_brightness) = if classdev.subleds()[1].intensity == 0 {
> + (StatusLedColor::Green, classdev.subleds()[0].brightness)
> + } else {
> + (StatusLedColor::Orange, classdev.subleds()[1].brightness)
> + };
> +
> + Command::StatusLed(
> + color,
> + if subled_brightness == 0 {
> + State::Off
> + } else if *blink {
> + State::Blink
> + } else {
> + State::On
> + },
> + )
> + .write(dev)
> + }
> +
> + fn blink_set(
> + &self,
> + dev: &Self::Bus,
> + classdev: &led::MultiColorDevice<Self>,
> + delay_on: &mut usize,
> + delay_off: &mut usize,
> + ) -> Result<()> {
> + let mut blink = self.blink.lock();
> + *blink = true;
> +
> + let (color, subled_intensity) = if classdev.subleds()[1].intensity == 0 {
> + (StatusLedColor::Green, classdev.subleds()[0].intensity)
> + } else {
> + (StatusLedColor::Orange, classdev.subleds()[1].intensity)
> + };
> + Command::StatusLed(
> + color,
> + if *delay_on == 0 && *delay_off != 0 {
> + *blink = false;
> + State::Off
> + } else if subled_intensity == 0 {
> + State::Off
> + } else if *delay_on != 0 && *delay_off == 0 {
> + *blink = false;
> + State::On
> + } else {
> + *delay_on = 167;
> + *delay_off = 167;
> +
> + State::Blink
> + },
> + )
> + .write(dev)
> + }
> +}
> diff --git a/drivers/platform/synology_microp/model.rs b/drivers/platform/synology_microp/model.rs
> new file mode 100644
> index 000000000000..715d8840f56b
> --- /dev/null
> +++ b/drivers/platform/synology_microp/model.rs
> @@ -0,0 +1,49 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +use kernel::led::Color;
> +
> +pub(crate) struct Model {
> + pub(crate) led_power: Color,
> + pub(crate) led_alert: Option<Color>,
> + pub(crate) led_usb_copy: bool,
> + pub(crate) led_esata: bool,
> +}
> +
> +impl Model {
> + pub(super) const fn new() -> Self {
> + Self {
> + led_power: Color::Blue,
> + led_alert: None,
> + led_usb_copy: false,
> + led_esata: false,
> + }
> + }
> +
> + pub(super) const fn led_power(self, color: Color) -> Self {
> + Self {
> + led_power: color,
> + ..self
> + }
> + }
> +
> + pub(super) const fn led_alert(self, color: Color) -> Self {
> + Self {
> + led_alert: Some(color),
> + ..self
> + }
> + }
> +
> + pub(super) const fn led_esata(self) -> Self {
> + Self {
> + led_esata: true,
> + ..self
> + }
> + }
> +
> + pub(super) const fn led_usb_copy(self) -> Self {
> + Self {
> + led_usb_copy: true,
> + ..self
> + }
> + }
> +}
> diff --git a/drivers/platform/synology_microp/synology_microp.rs b/drivers/platform/synology_microp/synology_microp.rs
> new file mode 100644
> index 000000000000..f02c4dade76c
> --- /dev/null
> +++ b/drivers/platform/synology_microp/synology_microp.rs
> @@ -0,0 +1,109 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Synology Microp driver
> +
> +use kernel::{
> + device,
> + led::Color,
> + of::{
> + DeviceId,
> + IdTable, //
> + },
> + of_device_table,
> + prelude::*,
> + serdev, //
> +};
> +use pin_init::pin_init_scope;
> +
> +use crate::model::Model;
> +
> +pub(crate) mod command;
> +mod led;
> +mod model;
> +
> +kernel::module_serdev_device_driver! {
> + type: SynologyMicropDriver,
> + name: "synology_microp",
> + authors: ["Markus Probst <markus.probst@posteo.de>"],
> + description: "Synology Microp driver",
> + license: "GPL v2",
> +}
> +
> +#[rustfmt::skip]
> +of_device_table!(
> + OF_TABLE,
> + MODULE_OF_TABLE,
> + Model,
> + [
> + // apollolake
> + (DeviceId::new(c"synology,ds918p-microp"), Model::new()),
> +
> + // evansport
> + (DeviceId::new(c"synology,ds214play-microp"), Model::new()),
> +
> + // geminilakenk
> + (DeviceId::new(c"synology,ds225p-microp"), Model::new().led_usb_copy()),
> + (DeviceId::new(c"synology,ds425p-microp"), Model::new()),
> +
> + // pineview
> + (DeviceId::new(c"synology,ds710p-microp"), Model::new().led_esata()),
> + (DeviceId::new(c"synology,ds1010p-microp"), Model::new().led_alert(Color::Orange)),
> +
> + // r1000
> + (DeviceId::new(c"synology,ds923p-microp"), Model::new()),
> + (DeviceId::new(c"synology,ds723p-microp"), Model::new()),
> + (DeviceId::new(c"synology,ds1522p-microp"), Model::new()),
> + (DeviceId::new(c"synology,rs422p-microp"), Model::new().led_power(Color::Green)),
> +
> + // r1000nk
> + (DeviceId::new(c"synology,ds725p-microp"), Model::new()),
> +
> + // rtd1296
> + (DeviceId::new(c"synology,ds118-microp"), Model::new()),
> +
> + // rtd1619b
> + (DeviceId::new(c"synology,ds124-microp"), Model::new()),
> + (DeviceId::new(c"synolody,ds223-microp"), Model::new().led_usb_copy()),
> + (DeviceId::new(c"synology,ds223j-microp"), Model::new()),
> +
> + // v1000
> + (DeviceId::new(c"synology,ds1823xsp-microp"), Model::new()),
> + (DeviceId::new(c"synology,rs822p-microp"), Model::new().led_power(Color::Green)),
> + (DeviceId::new(c"synology,rs1221p-microp"), Model::new().led_power(Color::Green)),
> + (DeviceId::new(c"synology,rs1221rpp-microp"), Model::new().led_power(Color::Green)),
> +
> + // v1000nk
> + (DeviceId::new(c"synology,ds925p-microp"), Model::new()),
> + (DeviceId::new(c"synology,ds1525p-microp"), Model::new()),
> + (DeviceId::new(c"synology,ds1825p-microp"), Model::new()),
> + ]
> +);
> +
> +#[pin_data]
> +struct SynologyMicropDriver {
> + #[pin]
> + led: led::Data,
> +}
> +
> +#[vtable]
> +impl serdev::Driver for SynologyMicropDriver {
> + type IdInfo = Model;
> + const OF_ID_TABLE: Option<IdTable<Self::IdInfo>> = Some(&OF_TABLE);
> +
> + fn probe(
> + dev: &serdev::Device<device::Core>,
> + model: Option<&Model>,
> + ) -> impl PinInit<Self, kernel::error::Error> {
> + pin_init_scope(move || {
> + let model = model.ok_or(EINVAL)?;
> +
> + dev.set_baudrate(9600).map_err(|_| EINVAL)?;
> + dev.set_flow_control(false);
> + dev.set_parity(serdev::Parity::None)?;
> +
> + Ok(try_pin_init!(Self {
> + led <- led::Data::register(dev, model),
> + }))
> + })
> + }
> +}
>
> --
> 2.52.0
>
>
^ permalink raw reply
* [PATCH v7 1/2] platform: Add initial synology microp driver
From: Markus Probst via B4 Relay @ 2026-04-11 15:27 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman
Cc: platform-driver-x86, linux-leds, devicetree, linux-kernel,
rust-for-linux, Markus Probst
In-Reply-To: <20260411-synology_microp_initial-v7-0-9a3a094e763a@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Add a initial synology microp driver, written in Rust.
The driver targets a microcontroller found in Synology NAS devices. It
currently only supports controlling of the power led, status led, alert
led and usb led. Other components such as fan control or handling
on-device buttons will be added once the required rust abstractions are
there.
This driver can be used both on arm and x86, thus it goes into the root
directory of drivers/platform.
Tested successfully on a Synology DS923+.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
MAINTAINERS | 5 +
drivers/platform/Kconfig | 2 +
drivers/platform/Makefile | 1 +
drivers/platform/synology_microp/Kconfig | 13 +
drivers/platform/synology_microp/Makefile | 3 +
drivers/platform/synology_microp/TODO | 7 +
drivers/platform/synology_microp/command.rs | 55 ++++
drivers/platform/synology_microp/led.rs | 276 +++++++++++++++++++++
drivers/platform/synology_microp/model.rs | 49 ++++
.../platform/synology_microp/synology_microp.rs | 109 ++++++++
10 files changed, 520 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index a667f4efb66e..78c99d831431 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25554,6 +25554,11 @@ F: drivers/dma-buf/sync_*
F: include/linux/sync_file.h
F: include/uapi/linux/sync_file.h
+SYNOLOGY MICROP DRIVER
+M: Markus Probst <markus.probst@posteo.de>
+S: Maintained
+F: drivers/platform/synology_microp/
+
SYNOPSYS ARC ARCHITECTURE
M: Vineet Gupta <vgupta@kernel.org>
L: linux-snps-arc@lists.infradead.org
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 312788f249c9..996050566a4a 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -22,3 +22,5 @@ source "drivers/platform/arm64/Kconfig"
source "drivers/platform/raspberrypi/Kconfig"
source "drivers/platform/wmi/Kconfig"
+
+source "drivers/platform/synology_microp/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index fa322e7f8716..2381872e9133 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -15,3 +15,4 @@ obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
obj-$(CONFIG_ARM64_PLATFORM_DEVICES) += arm64/
obj-$(CONFIG_BCM2835_VCHIQ) += raspberrypi/
obj-$(CONFIG_ACPI_WMI) += wmi/
+obj-$(CONFIG_SYNOLOGY_MICROP) += synology_microp/
diff --git a/drivers/platform/synology_microp/Kconfig b/drivers/platform/synology_microp/Kconfig
new file mode 100644
index 000000000000..7c4d8f2808f0
--- /dev/null
+++ b/drivers/platform/synology_microp/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config SYNOLOGY_MICROP
+ tristate "Synology Microp driver"
+ depends on LEDS_CLASS && LEDS_CLASS_MULTICOLOR
+ depends on RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+ help
+ Enable support for the MCU found in Synology NAS devices.
+
+ This is needed to properly shutdown and reboot the device, as well as
+ additional functionality like fan and LED control.
+
+ This driver is work in progress and may not be fully functional.
diff --git a/drivers/platform/synology_microp/Makefile b/drivers/platform/synology_microp/Makefile
new file mode 100644
index 000000000000..63585ccf76e4
--- /dev/null
+++ b/drivers/platform/synology_microp/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += synology_microp.o
diff --git a/drivers/platform/synology_microp/TODO b/drivers/platform/synology_microp/TODO
new file mode 100644
index 000000000000..1961a33115db
--- /dev/null
+++ b/drivers/platform/synology_microp/TODO
@@ -0,0 +1,7 @@
+TODO:
+- add missing components:
+ - handle on-device buttons (Power, Factory reset, "USB Copy")
+ - handle fan failure
+ - beeper
+ - fan speed control
+ - correctly perform device power-off and restart on Synology devices
diff --git a/drivers/platform/synology_microp/command.rs b/drivers/platform/synology_microp/command.rs
new file mode 100644
index 000000000000..5b3dd715afac
--- /dev/null
+++ b/drivers/platform/synology_microp/command.rs
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::{
+ device::Bound,
+ error::Result,
+ serdev, //
+};
+
+use crate::led;
+
+#[derive(Copy, Clone)]
+#[expect(
+ clippy::enum_variant_names,
+ reason = "future variants will not end with Led"
+)]
+pub(crate) enum Command {
+ PowerLed(led::State),
+ StatusLed(led::StatusLedColor, led::State),
+ AlertLed(led::State),
+ UsbLed(led::State),
+ EsataLed(led::State),
+}
+
+impl Command {
+ pub(crate) fn write(self, dev: &serdev::Device<Bound>) -> Result {
+ dev.write_all(
+ match self {
+ Self::PowerLed(led::State::On) => &[0x34],
+ Self::PowerLed(led::State::Blink) => &[0x35],
+ Self::PowerLed(led::State::Off) => &[0x36],
+
+ Self::StatusLed(_, led::State::Off) => &[0x37],
+ Self::StatusLed(led::StatusLedColor::Green, led::State::On) => &[0x38],
+ Self::StatusLed(led::StatusLedColor::Green, led::State::Blink) => &[0x39],
+ Self::StatusLed(led::StatusLedColor::Orange, led::State::On) => &[0x3A],
+ Self::StatusLed(led::StatusLedColor::Orange, led::State::Blink) => &[0x3B],
+
+ Self::AlertLed(led::State::On) => &[0x4C, 0x41, 0x31],
+ Self::AlertLed(led::State::Blink) => &[0x4C, 0x41, 0x32],
+ Self::AlertLed(led::State::Off) => &[0x4C, 0x41, 0x33],
+
+ Self::UsbLed(led::State::On) => &[0x40],
+ Self::UsbLed(led::State::Blink) => &[0x41],
+ Self::UsbLed(led::State::Off) => &[0x42],
+
+ Self::EsataLed(led::State::On) => &[0x4C, 0x45, 0x31],
+ Self::EsataLed(led::State::Blink) => &[0x4C, 0x45, 0x32],
+ Self::EsataLed(led::State::Off) => &[0x4C, 0x45, 0x33],
+ },
+ serdev::Timeout::Max,
+ )?;
+ dev.wait_until_sent(serdev::Timeout::Max);
+ Ok(())
+ }
+}
diff --git a/drivers/platform/synology_microp/led.rs b/drivers/platform/synology_microp/led.rs
new file mode 100644
index 000000000000..a78a95588456
--- /dev/null
+++ b/drivers/platform/synology_microp/led.rs
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::{
+ device::Bound,
+ devres::{
+ self,
+ Devres, //
+ },
+ led::{
+ self,
+ LedOps,
+ MultiColorSubLed, //
+ },
+ new_mutex,
+ prelude::*,
+ serdev,
+ str::CString,
+ sync::Mutex, //
+};
+use pin_init::pin_init_scope;
+
+use crate::{
+ command::Command,
+ model::Model, //
+};
+
+#[pin_data]
+pub(crate) struct Data {
+ #[pin]
+ status: Devres<led::MultiColorDevice<StatusLedHandler>>,
+ power_name: CString,
+ #[pin]
+ power: Devres<led::Device<LedHandler>>,
+}
+
+impl Data {
+ pub(super) fn register<'a>(
+ dev: &'a serdev::Device<Bound>,
+ model: &'a Model,
+ ) -> impl PinInit<Self, Error> + 'a {
+ pin_init_scope(move || {
+ if let Some(color) = model.led_alert {
+ let name = CString::try_from_fmt(fmt!("{}:alarm", color.as_c_str().to_str()?))?;
+ devres::register(
+ dev.as_ref(),
+ led::DeviceBuilder::new().color(color).name(&name).build(
+ dev,
+ try_pin_init!(LedHandler {
+ blink <- new_mutex!(false),
+ command: Command::AlertLed,
+ }),
+ ),
+ GFP_KERNEL,
+ )?;
+ }
+
+ if model.led_usb_copy {
+ devres::register(
+ dev.as_ref(),
+ led::DeviceBuilder::new()
+ .color(led::Color::Green)
+ .name(c"green:usb")
+ .build(
+ dev,
+ try_pin_init!(LedHandler {
+ blink <- new_mutex!(false),
+ command: Command::UsbLed,
+ }),
+ ),
+ GFP_KERNEL,
+ )?;
+ }
+
+ if model.led_esata {
+ devres::register(
+ dev.as_ref(),
+ led::DeviceBuilder::new()
+ .color(led::Color::Green)
+ .name(c"green:esata")
+ .build(
+ dev,
+ try_pin_init!(LedHandler {
+ blink <- new_mutex!(false),
+ command: Command::EsataLed,
+ }),
+ ),
+ GFP_KERNEL,
+ )?;
+ }
+
+ Ok(try_pin_init!(Self {
+ status <- led::DeviceBuilder::new()
+ .color(led::Color::Multi)
+ .name(c"multicolor:status")
+ .build_multicolor(
+ dev,
+ try_pin_init!(StatusLedHandler {
+ blink <- new_mutex!(false),
+ }),
+ StatusLedHandler::SUBLEDS,
+ ),
+ power_name: CString::try_from_fmt(fmt!(
+ "{}:power",
+ model.led_power.as_c_str().to_str()?
+ ))?,
+ power <- led::DeviceBuilder::new()
+ .color(model.led_power)
+ .name(power_name)
+ .build(
+ dev,
+ try_pin_init!(LedHandler {
+ blink <- new_mutex!(true),
+ command: Command::PowerLed,
+ }),
+ ),
+ }))
+ })
+ }
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum StatusLedColor {
+ Green,
+ Orange,
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum State {
+ On,
+ Blink,
+ Off,
+}
+
+#[pin_data]
+struct LedHandler {
+ #[pin]
+ blink: Mutex<bool>,
+ command: fn(State) -> Command,
+}
+
+#[vtable]
+impl LedOps for LedHandler {
+ type Bus = serdev::Device<Bound>;
+ type Mode = led::Normal;
+ const BLOCKING: bool = true;
+ const MAX_BRIGHTNESS: u32 = 1;
+
+ fn brightness_set(
+ &self,
+ dev: &Self::Bus,
+ _classdev: &led::Device<Self>,
+ brightness: u32,
+ ) -> Result<()> {
+ let mut blink = self.blink.lock();
+ (self.command)(if brightness == 0 {
+ *blink = false;
+ State::Off
+ } else if *blink {
+ State::Blink
+ } else {
+ State::On
+ })
+ .write(dev)?;
+
+ Ok(())
+ }
+
+ fn blink_set(
+ &self,
+ dev: &Self::Bus,
+ _classdev: &led::Device<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ let mut blink = self.blink.lock();
+
+ (self.command)(if *delay_on == 0 && *delay_off != 0 {
+ State::Off
+ } else if *delay_on != 0 && *delay_off == 0 {
+ State::On
+ } else {
+ *blink = true;
+ *delay_on = 167;
+ *delay_off = 167;
+
+ State::Blink
+ })
+ .write(dev)
+ }
+}
+
+#[pin_data]
+struct StatusLedHandler {
+ #[pin]
+ blink: Mutex<bool>,
+}
+
+impl StatusLedHandler {
+ const SUBLEDS: &[MultiColorSubLed] = &[
+ MultiColorSubLed::new(led::Color::Green).initial_intensity(1),
+ MultiColorSubLed::new(led::Color::Orange),
+ ];
+}
+
+#[vtable]
+impl LedOps for StatusLedHandler {
+ type Bus = serdev::Device<Bound>;
+ type Mode = led::MultiColor;
+ const BLOCKING: bool = true;
+ const MAX_BRIGHTNESS: u32 = 1;
+
+ fn brightness_set(
+ &self,
+ dev: &Self::Bus,
+ classdev: &led::MultiColorDevice<Self>,
+ brightness: u32,
+ ) -> Result<()> {
+ let mut blink = self.blink.lock();
+ if brightness == 0 {
+ *blink = false;
+ }
+
+ let (color, subled_brightness) = if classdev.subleds()[1].intensity == 0 {
+ (StatusLedColor::Green, classdev.subleds()[0].brightness)
+ } else {
+ (StatusLedColor::Orange, classdev.subleds()[1].brightness)
+ };
+
+ Command::StatusLed(
+ color,
+ if subled_brightness == 0 {
+ State::Off
+ } else if *blink {
+ State::Blink
+ } else {
+ State::On
+ },
+ )
+ .write(dev)
+ }
+
+ fn blink_set(
+ &self,
+ dev: &Self::Bus,
+ classdev: &led::MultiColorDevice<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ let mut blink = self.blink.lock();
+ *blink = true;
+
+ let (color, subled_intensity) = if classdev.subleds()[1].intensity == 0 {
+ (StatusLedColor::Green, classdev.subleds()[0].intensity)
+ } else {
+ (StatusLedColor::Orange, classdev.subleds()[1].intensity)
+ };
+ Command::StatusLed(
+ color,
+ if *delay_on == 0 && *delay_off != 0 {
+ *blink = false;
+ State::Off
+ } else if subled_intensity == 0 {
+ State::Off
+ } else if *delay_on != 0 && *delay_off == 0 {
+ *blink = false;
+ State::On
+ } else {
+ *delay_on = 167;
+ *delay_off = 167;
+
+ State::Blink
+ },
+ )
+ .write(dev)
+ }
+}
diff --git a/drivers/platform/synology_microp/model.rs b/drivers/platform/synology_microp/model.rs
new file mode 100644
index 000000000000..715d8840f56b
--- /dev/null
+++ b/drivers/platform/synology_microp/model.rs
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::led::Color;
+
+pub(crate) struct Model {
+ pub(crate) led_power: Color,
+ pub(crate) led_alert: Option<Color>,
+ pub(crate) led_usb_copy: bool,
+ pub(crate) led_esata: bool,
+}
+
+impl Model {
+ pub(super) const fn new() -> Self {
+ Self {
+ led_power: Color::Blue,
+ led_alert: None,
+ led_usb_copy: false,
+ led_esata: false,
+ }
+ }
+
+ pub(super) const fn led_power(self, color: Color) -> Self {
+ Self {
+ led_power: color,
+ ..self
+ }
+ }
+
+ pub(super) const fn led_alert(self, color: Color) -> Self {
+ Self {
+ led_alert: Some(color),
+ ..self
+ }
+ }
+
+ pub(super) const fn led_esata(self) -> Self {
+ Self {
+ led_esata: true,
+ ..self
+ }
+ }
+
+ pub(super) const fn led_usb_copy(self) -> Self {
+ Self {
+ led_usb_copy: true,
+ ..self
+ }
+ }
+}
diff --git a/drivers/platform/synology_microp/synology_microp.rs b/drivers/platform/synology_microp/synology_microp.rs
new file mode 100644
index 000000000000..f02c4dade76c
--- /dev/null
+++ b/drivers/platform/synology_microp/synology_microp.rs
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Synology Microp driver
+
+use kernel::{
+ device,
+ led::Color,
+ of::{
+ DeviceId,
+ IdTable, //
+ },
+ of_device_table,
+ prelude::*,
+ serdev, //
+};
+use pin_init::pin_init_scope;
+
+use crate::model::Model;
+
+pub(crate) mod command;
+mod led;
+mod model;
+
+kernel::module_serdev_device_driver! {
+ type: SynologyMicropDriver,
+ name: "synology_microp",
+ authors: ["Markus Probst <markus.probst@posteo.de>"],
+ description: "Synology Microp driver",
+ license: "GPL v2",
+}
+
+#[rustfmt::skip]
+of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ Model,
+ [
+ // apollolake
+ (DeviceId::new(c"synology,ds918p-microp"), Model::new()),
+
+ // evansport
+ (DeviceId::new(c"synology,ds214play-microp"), Model::new()),
+
+ // geminilakenk
+ (DeviceId::new(c"synology,ds225p-microp"), Model::new().led_usb_copy()),
+ (DeviceId::new(c"synology,ds425p-microp"), Model::new()),
+
+ // pineview
+ (DeviceId::new(c"synology,ds710p-microp"), Model::new().led_esata()),
+ (DeviceId::new(c"synology,ds1010p-microp"), Model::new().led_alert(Color::Orange)),
+
+ // r1000
+ (DeviceId::new(c"synology,ds923p-microp"), Model::new()),
+ (DeviceId::new(c"synology,ds723p-microp"), Model::new()),
+ (DeviceId::new(c"synology,ds1522p-microp"), Model::new()),
+ (DeviceId::new(c"synology,rs422p-microp"), Model::new().led_power(Color::Green)),
+
+ // r1000nk
+ (DeviceId::new(c"synology,ds725p-microp"), Model::new()),
+
+ // rtd1296
+ (DeviceId::new(c"synology,ds118-microp"), Model::new()),
+
+ // rtd1619b
+ (DeviceId::new(c"synology,ds124-microp"), Model::new()),
+ (DeviceId::new(c"synolody,ds223-microp"), Model::new().led_usb_copy()),
+ (DeviceId::new(c"synology,ds223j-microp"), Model::new()),
+
+ // v1000
+ (DeviceId::new(c"synology,ds1823xsp-microp"), Model::new()),
+ (DeviceId::new(c"synology,rs822p-microp"), Model::new().led_power(Color::Green)),
+ (DeviceId::new(c"synology,rs1221p-microp"), Model::new().led_power(Color::Green)),
+ (DeviceId::new(c"synology,rs1221rpp-microp"), Model::new().led_power(Color::Green)),
+
+ // v1000nk
+ (DeviceId::new(c"synology,ds925p-microp"), Model::new()),
+ (DeviceId::new(c"synology,ds1525p-microp"), Model::new()),
+ (DeviceId::new(c"synology,ds1825p-microp"), Model::new()),
+ ]
+);
+
+#[pin_data]
+struct SynologyMicropDriver {
+ #[pin]
+ led: led::Data,
+}
+
+#[vtable]
+impl serdev::Driver for SynologyMicropDriver {
+ type IdInfo = Model;
+ const OF_ID_TABLE: Option<IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ dev: &serdev::Device<device::Core>,
+ model: Option<&Model>,
+ ) -> impl PinInit<Self, kernel::error::Error> {
+ pin_init_scope(move || {
+ let model = model.ok_or(EINVAL)?;
+
+ dev.set_baudrate(9600).map_err(|_| EINVAL)?;
+ dev.set_flow_control(false);
+ dev.set_parity(serdev::Parity::None)?;
+
+ Ok(try_pin_init!(Self {
+ led <- led::Data::register(dev, model),
+ }))
+ })
+ }
+}
--
2.52.0
^ permalink raw reply related
* [PATCH v7 2/2] dt-bindings: embedded-controller: Add synology microp devices
From: Markus Probst via B4 Relay @ 2026-04-11 15:27 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman
Cc: platform-driver-x86, linux-leds, devicetree, linux-kernel,
rust-for-linux, Markus Probst
In-Reply-To: <20260411-synology_microp_initial-v7-0-9a3a094e763a@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Add the Synology Microp devicetree bindings. Those devices are
microcontrollers found on Synology NAS devices. They are connected to a
serial port on the host device.
Those devices are used to control certain LEDs, fan speeds, a beeper, to
handle buttons, fan failures and to properly shutdown and reboot the
device.
The device has a different feature set depending on the Synology NAS
model, like having different number of fans, buttons and leds. Depending
on the architecture of the model, they also need a different system
shutdown behaviour.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
.../synology,ds923p-microp.yaml | 92 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 93 insertions(+)
diff --git a/Documentation/devicetree/bindings/embedded-controller/synology,ds923p-microp.yaml b/Documentation/devicetree/bindings/embedded-controller/synology,ds923p-microp.yaml
new file mode 100644
index 000000000000..0a8fb1d8f314
--- /dev/null
+++ b/Documentation/devicetree/bindings/embedded-controller/synology,ds923p-microp.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/embedded-controller/synology,ds923p-microp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synology NAS on-board Microcontroller
+
+maintainers:
+ - Markus Probst <markus.probst@posteo.de>
+
+description: |
+ Synology Microp is a microcontroller found in Synology NAS devices.
+ It is connected to a serial port on the host device.
+
+ It is necessary to properly shutdown and reboot the NAS device and
+ provides additional functionality such as led control, fan speed control,
+ a beeper and buttons on the NAS device.
+
+properties:
+ compatible:
+ enum:
+ - synology,ds923p-microp
+ - synology,ds918p-microp
+ - synology,ds214play-microp
+ - synology,ds225p-microp
+ - synology,ds425p-microp
+ - synology,ds710p-microp
+ - synology,ds1010p-microp
+ - synology,ds723p-microp
+ - synology,ds1522p-microp
+ - synology,rs422p-microp
+ - synology,ds725p-microp
+ - synology,ds118-microp
+ - synology,ds124-microp
+ - synology,ds223-microp
+ - synology,ds223j-microp
+ - synology,ds1823xsp-microp
+ - synology,rs822p-microp
+ - synology,rs1221p-microp
+ - synology,rs1221rpp-microp
+ - synology,ds925p-microp
+ - synology,ds1525p-microp
+ - synology,ds1825p-microp
+
+ fan-failure-gpios:
+ description: GPIOs needed to determine which fans stopped working on a fan failure event.
+ minItems: 2
+ maxItems: 3
+
+required:
+ - compatible
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - synology,ds214play-microp
+ - synology,ds225p-microp
+ - synology,ds710p-microp
+ - synology,ds723p-microp
+ - synology,ds725p-microp
+ - synology,ds118-microp
+ - synology,ds124-microp
+ - synology,ds223-microp
+ - synology,ds223j-microp
+ - synology,ds1823xsp-microp
+ - synology,rs822p-microp
+ - synology,rs1221p-microp
+ - synology,rs1221rpp-microp
+ - synology,ds1825p-microp
+ then:
+ properties:
+ fan-failure-gpios: false
+ else:
+ required:
+ - fan-failure-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ embedded-controller {
+ compatible = "synology,ds923p-microp";
+
+ fan-failure-gpios = <&gpio 68 GPIO_ACTIVE_HIGH>, <&gpio 69 GPIO_ACTIVE_HIGH>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 78c99d831431..72075c9a2016 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25557,6 +25557,7 @@ F: include/uapi/linux/sync_file.h
SYNOLOGY MICROP DRIVER
M: Markus Probst <markus.probst@posteo.de>
S: Maintained
+F: Documentation/devicetree/bindings/embedded-controller/synology,ds923p-microp.yaml
F: drivers/platform/synology_microp/
SYNOPSYS ARC ARCHITECTURE
--
2.52.0
^ permalink raw reply related
* [PATCH v7 0/2] Introduce Synology Microp driver
From: Markus Probst via B4 Relay @ 2026-04-11 15:27 UTC (permalink / raw)
To: Hans de Goede, Ilpo Järvinen, Bryan O'Donoghue,
Lee Jones, Pavel Machek, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman
Cc: platform-driver-x86, linux-leds, devicetree, linux-kernel,
rust-for-linux, Markus Probst
Synology uses a microcontroller in their NAS devices connected to a
serial port to control certain LEDs, fan speeds, a beeper, to handle
proper shutdown and restart, buttons and fan failures.
This patch series depends on the rust led abstraction [1] and the rust
serdev abstraction [2].
This is only a initial version of the driver able to control LEDs.
The following rust abstractions would be required, to implement the
remaining features:
- hwmon (include/linux/hwmon.h)
- input (include/linux/input.h)
- sysoff handler + hardware protection shutdown (include/linux/reboot.h)
[1] https://lore.kernel.org/rust-for-linux/20260329-rust_leds-v13-0-21a599c5b2d1@posteo.de/
[2] https://lore.kernel.org/rust-for-linux/20260411-rust_serdev-v4-0-845e960c6627@posteo.de/
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Changes in v7:
- remove list of compatible ids from commit msg
- explain what makes the different models not compatible in the commit msg
- remove unnecessary examples
- Link to v6: https://lore.kernel.org/r/20260405-synology_microp_initial-v6-0-08fde474b6c9@posteo.de
Changes in v6:
- moved devicetree bindings patch at the end of the set
- remove several patches
- move of id table from model.rs to synology_microp.rs
- remove the model! macro
- use if blocks in devicetree schema to narrow down the
fan-failure-gpios property
- add multiple devicetree examples to test if blocks
- Link to v5: https://lore.kernel.org/r/20260329-synology_microp_initial-v5-0-27cb80bdf591@posteo.de
Changes in v5:
- add esata led support
- use different compatible for each model
- add visibility modifier to of_device_table macro
- fix match data missing when using PRP0001
- Link to v4: https://lore.kernel.org/r/20260320-synology_microp_initial-v4-0-0423ddb83ca4@posteo.de
Changes in v4:
- convert to monolithic driver and moved it into drivers/platform
- removed mfd rust abstraction
- moved dt-bindings to embedded-controller
- Link to v3: https://lore.kernel.org/r/20260313-synology_microp_initial-v3-0-ad6ac463a201@posteo.de
Changes in v3:
- remove `default n` from Kconfig entry, as n is the default already.
- select RUST_SERIAL_DEV_BUS_ABSTRACTIONS in Kconfig
- add mfd rust abstraction
- split core and led parts into their own driver. It should now be considered a
MFD device.
- split led part of dt binding into its own file
- Link to v2: https://lore.kernel.org/r/20260308-synology_microp_initial-v2-0-9389963f31c5@posteo.de
Changes in v2:
- fix missing tabs in MAINTAINERS file
- remove word binding from patch subject
- add missing signed-off-by
- add missing help entry in Kconfig
- add missing spdx license headers
- remove no-check{,-cpu}-fan properties from the dt-bindings and replace
them with the check_fan module parameter
- use patternProperties for leds in dt-bindings
- license dt-binding as GPL-2.0-only OR BSD-2-Clause
- move driver from staging tree into mfd tree and mark it as work in
progress inside Kconfig
- only register alert and usb led if fwnode is present
- Link to v1: https://lore.kernel.org/r/20260306-synology_microp_initial-v1-0-fcffede6448c@posteo.de
---
Markus Probst (2):
platform: Add initial synology microp driver
dt-bindings: embedded-controller: Add synology microp devices
.../synology,ds923p-microp.yaml | 92 +++++++
MAINTAINERS | 6 +
drivers/platform/Kconfig | 2 +
drivers/platform/Makefile | 1 +
drivers/platform/synology_microp/Kconfig | 13 +
drivers/platform/synology_microp/Makefile | 3 +
drivers/platform/synology_microp/TODO | 7 +
drivers/platform/synology_microp/command.rs | 55 ++++
drivers/platform/synology_microp/led.rs | 276 +++++++++++++++++++++
drivers/platform/synology_microp/model.rs | 49 ++++
.../platform/synology_microp/synology_microp.rs | 109 ++++++++
11 files changed, 613 insertions(+)
---
base-commit: 0e5d0a0b5ca6ea4e391d6786266405c5871e0151
change-id: 20260306-synology_microp_initial-0f7dac7b7496
prerequisite-change-id: 20251217-rust_serdev-ee5481e9085c:v4
prerequisite-patch-id: 52b17274481cc770c257d8f95335293eca32a2c5
prerequisite-patch-id: eec47e5051640d08bcd34a9670b98804449cad52
prerequisite-patch-id: f24b68c71c3f69371e8ac0251efca0a023b31cc4
prerequisite-patch-id: d0686cf451ef899a06d468adfba51ccd84e6ff98
prerequisite-change-id: 20251114-rust_leds-a959f7c2f7f9:v13
prerequisite-patch-id: 818700f22dcb9676157c985f82762d7c607b861e
prerequisite-patch-id: b15ffa7d95d9260151bfb116b259c4473f721c82
prerequisite-patch-id: 8c47e0d107530f577a1be0b79f8ee791f95d3cbe
^ permalink raw reply
* Re: [BUG] android: rust binder: sleeping function called from invalid context in deferred_release()
From: Miguel Ojeda @ 2026-04-11 15:24 UTC (permalink / raw)
To: syscaller, Matthew Maurer
Cc: gregkh, aliceryhl, arve, tkjos, brauner, cmllamas, ojeda, boqun,
rust-for-linux, linux-kernel, syzkaller-bugs
In-Reply-To: <1f4fa7f2.351a.19d7cc91931.Coremail.syscaller@163.com>
On Sat, Apr 11, 2026 at 3:45 PM syscaller <syscaller@163.com> wrote:
>
> I believe the warning is caused by the following code in
> drivers/android/binder/process.rs:
>
> while let Some(delivered_death) =
> { self.inner.lock().delivered_deaths.pop_front() } {
> drop(delivered_death);
> }
>
> The temporary SpinLockGuard returned by self.inner.lock() appears to live
> across the whole while-let statement, including the loop body. As a result,
> drop(delivered_death) runs while Process::inner is still held.
Please take a look at:
https://lore.kernel.org/rust-for-linux/20260403-lockhold-v1-1-c332b56cd8ae@google.com/
Thanks for the report!
Cheers,
Miguel
^ permalink raw reply
* [PATCH v4 2/4] serdev: add rust private data to serdev_device
From: Markus Probst via B4 Relay @ 2026-04-11 15:10 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
In-Reply-To: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Add rust private data to `struct serdev_device`, as it is required by the
rust abstraction added in the following commit
(rust: add basic serial device bus abstractions).
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
include/linux/serdev.h | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index 5654c58eb73c..c74c345d60ae 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -33,12 +33,14 @@ struct serdev_device_ops {
/**
* struct serdev_device - Basic representation of an serdev device
- * @dev: Driver model representation of the device.
- * @nr: Device number on serdev bus.
- * @ctrl: serdev controller managing this device.
- * @ops: Device operations.
- * @write_comp Completion used by serdev_device_write() internally
- * @write_lock Lock to serialize access when writing data
+ * @dev: Driver model representation of the device.
+ * @nr: Device number on serdev bus.
+ * @ctrl: serdev controller managing this device.
+ * @ops: Device operations.
+ * @write_comp: Completion used by serdev_device_write() internally
+ * @write_lock: Lock to serialize access when writing data
+ * @rust_private_data: Private data for the rust abstraction. This should
+ * not be used by the C drivers.
*/
struct serdev_device {
struct device dev;
@@ -47,6 +49,7 @@ struct serdev_device {
const struct serdev_device_ops *ops;
struct completion write_comp;
struct mutex write_lock;
+ void *rust_private_data;
};
static inline struct serdev_device *to_serdev_device(struct device *d)
--
2.52.0
^ permalink raw reply related
* [PATCH v4 1/4] rust: devres: return reference in `devres::register`
From: Markus Probst via B4 Relay @ 2026-04-11 15:10 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
In-Reply-To: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Return the reference to the initialized data in the `devres::register`
function.
This is needed in a following commit (rust: add basic serial device bus
abstractions).
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
rust/kernel/cpufreq.rs | 3 ++-
rust/kernel/devres.rs | 15 +++++++++++++--
rust/kernel/drm/driver.rs | 3 ++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs
index f5adee48d40c..31bf7e685097 100644
--- a/rust/kernel/cpufreq.rs
+++ b/rust/kernel/cpufreq.rs
@@ -1052,7 +1052,8 @@ pub fn new_foreign_owned(dev: &Device<Bound>) -> Result
where
T: 'static,
{
- devres::register(dev, Self::new()?, GFP_KERNEL)
+ devres::register(dev, Self::new()?, GFP_KERNEL)?;
+ Ok(())
}
}
diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index 6afe196be42c..f882bace8601 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -326,15 +326,26 @@ fn register_foreign<P>(dev: &Device<Bound>, data: P) -> Result
/// }
///
/// fn from_bound_context(dev: &Device<Bound>) -> Result {
-/// devres::register(dev, Registration::new(), GFP_KERNEL)
+/// devres::register(dev, Registration::new(), GFP_KERNEL)?;
+/// Ok(())
/// }
/// ```
-pub fn register<T, E>(dev: &Device<Bound>, data: impl PinInit<T, E>, flags: Flags) -> Result
+pub fn register<'a, T, E>(
+ dev: &'a Device<Bound>,
+ data: impl PinInit<T, E>,
+ flags: Flags,
+) -> Result<&'a T>
where
T: Send + 'static,
Error: From<E>,
{
let data = KBox::pin_init(data, flags)?;
+ let data_ptr = &raw const *data;
+
register_foreign(dev, data)
+ // SAFETY: `dev` is valid for the lifetime of 'a. As long as there is a reference to
+ // `Device<Bound>`, it is guaranteed that the device is not unbound and data has not been
+ // dropped. Thus `data_ptr` is also valid for the lifetime of 'a.
+ .map(|()| unsafe { &*data_ptr })
}
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index e09f977b5b51..51e0c7e30cc2 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -145,7 +145,8 @@ pub fn new_foreign_owned(
let reg = Registration::<T>::new(drm, flags)?;
- devres::register(dev, reg, GFP_KERNEL)
+ devres::register(dev, reg, GFP_KERNEL)?;
+ Ok(())
}
/// Returns a reference to the `Device` instance for this registration.
--
2.52.0
^ permalink raw reply related
* [PATCH v4 4/4] samples: rust: add Rust serial device bus sample device driver
From: Markus Probst via B4 Relay @ 2026-04-11 15:10 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
In-Reply-To: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Add a sample Rust serial device bus device driver illustrating the usage
of the serial device bus abstractions.
This drivers probes through either a match of device / driver name or a
match within the OF ID table.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
samples/rust/Kconfig | 11 +++++
samples/rust/Makefile | 1 +
samples/rust/rust_driver_serdev.rs | 86 ++++++++++++++++++++++++++++++++++++++
3 files changed, 98 insertions(+)
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index c49ab9106345..31d62533ef25 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -161,6 +161,17 @@ config SAMPLE_RUST_DRIVER_AUXILIARY
If unsure, say N.
+config SAMPLE_RUST_DRIVER_SERDEV
+ tristate "Serial Device Bus Device Driver"
+ select RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+ help
+ This option builds the Rust serial device bus driver sample.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_driver_serdev.
+
+ If unsure, say N.
+
config SAMPLE_RUST_SOC
tristate "SoC Driver"
select SOC_BUS
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 6c0aaa58cccc..b986b681cde5 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_SERDEV) += rust_driver_serdev.o
obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o
obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o
diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs
new file mode 100644
index 000000000000..8cf3fb451b22
--- /dev/null
+++ b/samples/rust/rust_driver_serdev.rs
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Serial device bus device driver sample.
+
+use kernel::{
+ acpi,
+ device::{
+ Bound,
+ Core, //
+ },
+ of,
+ prelude::*,
+ serdev,
+ sync::aref::ARef, //
+};
+
+struct SampleDriver {
+ sdev: ARef<serdev::Device>,
+}
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <SampleDriver as serdev::Driver>::IdInfo,
+ [(of::DeviceId::new(c"test,rust_driver_serdev"), ())]
+);
+
+kernel::acpi_device_table!(
+ ACPI_TABLE,
+ MODULE_ACPI_TABLE,
+ <SampleDriver as serdev::Driver>::IdInfo,
+ [(acpi::DeviceId::new(c"LNUXBEEF"), ())]
+);
+
+#[vtable]
+impl serdev::Driver for SampleDriver {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+ const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = Some(&ACPI_TABLE);
+
+ fn probe(
+ sdev: &serdev::Device<Core>,
+ _info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> {
+ let dev = sdev.as_ref();
+
+ dev_dbg!(dev, "Probe Rust Serial device bus device driver sample.\n");
+
+ if sdev
+ .set_baudrate(
+ dev.fwnode()
+ .and_then(|fwnode| fwnode.property_read(c"baudrate").optional())
+ .unwrap_or(115200),
+ )
+ .is_err()
+ {
+ return Err(EINVAL);
+ }
+ sdev.set_flow_control(false);
+ sdev.set_parity(serdev::Parity::None)?;
+
+ Ok(Self { sdev: sdev.into() })
+ }
+
+ fn receive(sdev: &serdev::Device<Bound>, _this: Pin<&Self>, data: &[u8]) -> usize {
+ let _ = sdev.write_all(data, serdev::Timeout::Max);
+ data.len()
+ }
+}
+
+impl Drop for SampleDriver {
+ fn drop(&mut self) {
+ dev_dbg!(
+ self.sdev.as_ref(),
+ "Remove Rust Serial device bus device driver sample.\n"
+ );
+ }
+}
+
+kernel::module_serdev_device_driver! {
+ type: SampleDriver,
+ name: "rust_driver_serdev",
+ authors: ["Markus Probst"],
+ description: "Rust Serial device bus device driver",
+ license: "GPL v2",
+}
--
2.52.0
^ permalink raw reply related
* [PATCH v4 3/4] rust: add basic serial device bus abstractions
From: Markus Probst via B4 Relay @ 2026-04-11 15:10 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
In-Reply-To: <20260411-rust_serdev-v4-0-845e960c6627@posteo.de>
From: Markus Probst <markus.probst@posteo.de>
Implement the basic serial device bus abstractions required to write a
serial device bus device driver with or without the need for initial device
data. This includes the following data structures:
The `serdev::Driver` trait represents the interface to the driver.
The `serdev::Device` abstraction represents a `struct serdev_device`.
In order to provide the Serdev specific parts to a generic
`driver::Registration` the `driver::RegistrationOps` trait is
implemented by `serdev::Adapter`.
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
drivers/tty/serdev/Kconfig | 7 +
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/serdev.c | 22 ++
rust/kernel/lib.rs | 2 +
rust/kernel/serdev.rs | 536 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 569 insertions(+)
diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig
index 46ae732bfc68..e6dfe949ad01 100644
--- a/drivers/tty/serdev/Kconfig
+++ b/drivers/tty/serdev/Kconfig
@@ -9,6 +9,13 @@ menuconfig SERIAL_DEV_BUS
Note that you typically also want to enable TTY port controller support.
+config RUST_SERIAL_DEV_BUS_ABSTRACTIONS
+ bool "Rust Serial device bus abstractions"
+ depends on RUST
+ select SERIAL_DEV_BUS
+ help
+ This enables the Rust abstraction for the serial device bus API.
+
if SERIAL_DEV_BUS
config SERIAL_DEV_CTRL_TTYPORT
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 083cc44aa952..ab521ba42673 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -80,6 +80,7 @@
#include <linux/regulator/consumer.h>
#include <linux/sched.h>
#include <linux/security.h>
+#include <linux/serdev.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/task_work.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3c42e51f00a..9b87e9591cfd 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -53,6 +53,7 @@
#include "regulator.c"
#include "scatterlist.c"
#include "security.c"
+#include "serdev.c"
#include "signal.c"
#include "slab.c"
#include "spinlock.c"
diff --git a/rust/helpers/serdev.c b/rust/helpers/serdev.c
new file mode 100644
index 000000000000..c52b78ca3fc7
--- /dev/null
+++ b/rust/helpers/serdev.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/serdev.h>
+
+__rust_helper
+void rust_helper_serdev_device_driver_unregister(struct serdev_device_driver *sdrv)
+{
+ serdev_device_driver_unregister(sdrv);
+}
+
+__rust_helper
+void rust_helper_serdev_device_put(struct serdev_device *serdev)
+{
+ serdev_device_put(serdev);
+}
+
+__rust_helper
+void rust_helper_serdev_device_set_client_ops(struct serdev_device *serdev,
+ const struct serdev_device_ops *ops)
+{
+ serdev_device_set_client_ops(serdev, ops);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d93292d47420..5107c9c1be07 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -144,6 +144,8 @@
pub mod scatterlist;
pub mod security;
pub mod seq_file;
+#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)]
+pub mod serdev;
pub mod sizes;
pub mod slice;
#[cfg(CONFIG_SOC_BUS)]
diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs
new file mode 100644
index 000000000000..d9fea4bd4439
--- /dev/null
+++ b/rust/kernel/serdev.rs
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the serial device bus.
+//!
+//! C header: [`include/linux/serdev.h`](srctree/include/linux/serdev.h)
+
+use crate::{
+ acpi,
+ device,
+ devres,
+ driver,
+ error::{
+ from_result,
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ of,
+ prelude::*,
+ sync::Completion,
+ time::{
+ msecs_to_jiffies,
+ Jiffies,
+ Msecs, //
+ },
+ types::{
+ AlwaysRefCounted,
+ Opaque, //
+ }, //
+};
+
+use core::{
+ cell::UnsafeCell,
+ marker::PhantomData,
+ mem::offset_of,
+ num::NonZero,
+ ptr::NonNull, //
+};
+
+/// Parity bit to use with a serial device.
+#[repr(u32)]
+pub enum Parity {
+ /// No parity bit.
+ None = bindings::serdev_parity_SERDEV_PARITY_NONE,
+ /// Even partiy.
+ Even = bindings::serdev_parity_SERDEV_PARITY_EVEN,
+ /// Odd parity.
+ Odd = bindings::serdev_parity_SERDEV_PARITY_ODD,
+}
+
+/// Timeout in Jiffies.
+pub enum Timeout {
+ /// Wait for a specific amount of [`Jiffies`].
+ Jiffies(NonZero<Jiffies>),
+ /// Wait for a specific amount of [`Msecs`].
+ Milliseconds(NonZero<Msecs>),
+ /// Wait as long as possible.
+ ///
+ /// This is equivalent to [`kernel::task::MAX_SCHEDULE_TIMEOUT`].
+ Max,
+}
+
+impl Timeout {
+ fn into_jiffies(self) -> isize {
+ match self {
+ Self::Jiffies(value) => value.get().try_into().unwrap_or_default(),
+ Self::Milliseconds(value) => {
+ msecs_to_jiffies(value.get()).try_into().unwrap_or_default()
+ }
+ Self::Max => 0,
+ }
+ }
+}
+
+/// An adapter for the registration of serial device bus device drivers.
+pub struct Adapter<T: Driver>(T);
+
+// SAFETY:
+// - `bindings::serdev_device_driver` is a C type declared as `repr(C)`.
+// - `Drvdata<T>` is the type of the driver's device private data.
+// - `struct serdev_device_driver` embeds a `struct device_driver`.
+// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`.
+unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> {
+ type DriverType = bindings::serdev_device_driver;
+ type DriverData = T;
+ const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver);
+}
+
+// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if
+// a preceding call to `register` has been successful.
+unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> {
+ unsafe fn register(
+ sdrv: &Opaque<Self::DriverType>,
+ name: &'static CStr,
+ module: &'static ThisModule,
+ ) -> Result {
+ let of_table = match T::OF_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ let acpi_table = match T::ACPI_ID_TABLE {
+ Some(table) => table.as_ptr(),
+ None => core::ptr::null(),
+ };
+
+ // SAFETY: It's safe to set the fields of `struct serdev_device_driver` on initialization.
+ unsafe {
+ (*sdrv.get()).driver.name = name.as_char_ptr();
+ (*sdrv.get()).probe = Some(Self::probe_callback);
+ (*sdrv.get()).remove = Some(Self::remove_callback);
+ (*sdrv.get()).driver.of_match_table = of_table;
+ (*sdrv.get()).driver.acpi_match_table = acpi_table;
+ }
+
+ // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`.
+ to_result(unsafe { bindings::__serdev_device_driver_register(sdrv.get(), module.0) })
+ }
+
+ unsafe fn unregister(sdrv: &Opaque<Self::DriverType>) {
+ // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`.
+ unsafe { bindings::serdev_device_driver_unregister(sdrv.get()) };
+ }
+}
+
+#[pin_data]
+struct PrivateData {
+ #[pin]
+ probe_complete: Completion,
+ error: UnsafeCell<bool>,
+}
+
+impl<T: Driver + 'static> Adapter<T> {
+ const OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
+ receive_buf: if T::HAS_RECEIVE {
+ Some(Self::receive_buf_callback)
+ } else {
+ None
+ },
+ write_wakeup: Some(bindings::serdev_device_write_wakeup),
+ };
+
+ extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi::c_int {
+ // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer to
+ // a `struct serdev_device`.
+ //
+ // INVARIANT: `sdev` is valid for the duration of `probe_callback()`.
+ let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
+ let id_info = <Self as driver::Adapter>::id_info(sdev.as_ref());
+
+ from_result(|| {
+ let private_data = devres::register(
+ sdev.as_ref(),
+ try_pin_init!(PrivateData {
+ probe_complete <- Completion::new(),
+ error: false.into(),
+ }),
+ GFP_KERNEL,
+ )?;
+
+ // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
+ unsafe {
+ (*sdev.as_raw()).rust_private_data =
+ (&raw const *private_data).cast::<c_void>().cast_mut()
+ };
+
+ // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`.
+ unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::OPS) };
+
+ // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer
+ // to a `serdev_device`.
+ to_result(unsafe {
+ bindings::devm_serdev_device_open(sdev.as_ref().as_raw(), sdev.as_raw())
+ })?;
+
+ let data = T::probe(sdev, id_info);
+ let result = sdev.as_ref().set_drvdata(data);
+
+ // SAFETY: We have exclusive access to `private_data.error`.
+ unsafe { *private_data.error.get() = result.is_err() };
+
+ private_data.probe_complete.complete_all();
+
+ result.map(|()| 0)
+ })
+ }
+
+ extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) {
+ // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer
+ // to a `struct serdev_device`.
+ //
+ // INVARIANT: `sdev` is valid for the duration of `remove_callback()`.
+ let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY: `remove_callback` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { sdev.as_ref().drvdata_borrow::<T>() };
+
+ T::unbind(sdev, data);
+ }
+
+ extern "C" fn receive_buf_callback(
+ sdev: *mut bindings::serdev_device,
+ buf: *const u8,
+ length: usize,
+ ) -> usize {
+ // SAFETY: The serial device bus only ever calls the receive buf callback with a valid
+ // pointer to a `struct serdev_device`.
+ //
+ // INVARIANT: `sdev` is valid for the duration of `receive_buf_callback()`.
+ let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
+
+ // SAFETY:
+ // - The serial device bus only ever calls the receive buf callback with a valid pointer to
+ // a `struct serdev_device`.
+ // - `receive_buf_callback` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `sdev.private_data` is a pointer
+ // to a valid `PrivateData`.
+ let private_data = unsafe { &*(*sdev.as_raw()).rust_private_data.cast::<PrivateData>() };
+
+ private_data.probe_complete.wait_for_completion();
+
+ // SAFETY: No one has exclusive access to `private_data.error`.
+ if unsafe { *private_data.error.get() } {
+ return length;
+ }
+
+ // SAFETY: `receive_buf_callback` is only ever called after a successful call to
+ // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+ // and stored a `Pin<KBox<T>>`.
+ let data = unsafe { sdev.as_ref().drvdata_borrow::<T>() };
+
+ // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`.
+ let buf = unsafe { core::slice::from_raw_parts(buf, length) };
+
+ T::receive(sdev, data, buf)
+ }
+}
+
+impl<T: Driver + 'static> driver::Adapter for Adapter<T> {
+ type IdInfo = T::IdInfo;
+
+ fn of_id_table() -> Option<of::IdTable<Self::IdInfo>> {
+ T::OF_ID_TABLE
+ }
+
+ fn acpi_id_table() -> Option<acpi::IdTable<Self::IdInfo>> {
+ T::ACPI_ID_TABLE
+ }
+}
+
+/// Declares a kernel module that exposes a single serial device bus device driver.
+///
+/// # Examples
+///
+/// ```ignore
+/// kernel::module_serdev_device_driver! {
+/// type: MyDriver,
+/// name: "Module name",
+/// authors: ["Author name"],
+/// description: "Description",
+/// license: "GPL v2",
+/// }
+/// ```
+#[macro_export]
+macro_rules! module_serdev_device_driver {
+ ($($f:tt)*) => {
+ $crate::module_driver!(<T>, $crate::serdev::Adapter<T>, { $($f)* });
+ };
+}
+
+/// The serial device bus device driver trait.
+///
+/// Drivers must implement this trait in order to get a serial device bus device driver registered.
+///
+/// # Examples
+///
+///```
+/// # use kernel::{
+/// acpi,
+/// bindings,
+/// device::{
+/// Bound,
+/// Core, //
+/// },
+/// of,
+/// serdev, //
+/// };
+///
+/// struct MyDriver;
+///
+/// kernel::of_device_table!(
+/// OF_TABLE,
+/// MODULE_OF_TABLE,
+/// <MyDriver as serdev::Driver>::IdInfo,
+/// [
+/// (of::DeviceId::new(c"test,device"), ())
+/// ]
+/// );
+///
+/// kernel::acpi_device_table!(
+/// ACPI_TABLE,
+/// MODULE_ACPI_TABLE,
+/// <MyDriver as serdev::Driver>::IdInfo,
+/// [
+/// (acpi::DeviceId::new(c"LNUXBEEF"), ())
+/// ]
+/// );
+///
+/// #[vtable]
+/// impl serdev::Driver for MyDriver {
+/// type IdInfo = ();
+/// const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+/// const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = Some(&ACPI_TABLE);
+///
+/// fn probe(
+/// sdev: &serdev::Device<Core>,
+/// _id_info: Option<&Self::IdInfo>,
+/// ) -> impl PinInit<Self, Error> {
+/// sdev.set_baudrate(115200);
+/// sdev.write_all(b"Hello\n", serdev::Timeout::Max)?;
+/// Ok(MyDriver)
+/// }
+/// }
+///```
+#[vtable]
+pub trait Driver: Send {
+ /// The type holding driver private data about each device id supported by the driver.
+ // TODO: Use associated_type_defaults once stabilized:
+ //
+ // ```
+ // type IdInfo: 'static = ();
+ // ```
+ type IdInfo: 'static;
+
+ /// The table of OF device ids supported by the driver.
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = None;
+
+ /// The table of ACPI device ids supported by the driver.
+ const ACPI_ID_TABLE: Option<acpi::IdTable<Self::IdInfo>> = None;
+
+ /// Serial device bus device driver probe.
+ ///
+ /// Called when a new serial device bus device is added or discovered.
+ /// Implementers should attempt to initialize the device here.
+ fn probe(
+ sdev: &Device<device::Core>,
+ id_info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error>;
+
+ /// Serial device bus device driver unbind.
+ ///
+ /// Called when a [`Device`] is unbound from its bound [`Driver`]. Implementing this callback
+ /// is optional.
+ ///
+ /// This callback serves as a place for drivers to perform teardown operations that require a
+ /// `&Device<Core>` or `&Device<Bound>` reference. For instance.
+ ///
+ /// Otherwise, release operations for driver resources should be performed in `Self::drop`.
+ fn unbind(sdev: &Device<device::Core>, this: Pin<&Self>) {
+ let _ = (sdev, this);
+ }
+
+ /// Serial device bus device data receive callback.
+ ///
+ /// Called when data got received from device.
+ ///
+ /// Returns the number of bytes accepted.
+ fn receive(sdev: &Device<device::Bound>, this: Pin<&Self>, data: &[u8]) -> usize {
+ let _ = (sdev, this, data);
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+/// The serial device bus device representation.
+///
+/// This structure represents the Rust abstraction for a C `struct serdev_device`. The
+/// implementation abstracts the usage of an already existing C `struct serdev_device` within Rust
+/// code that we get passed from the C side.
+///
+/// # Invariants
+///
+/// A [`Device`] instance represents a valid `struct serdev_device` created by the C portion of
+/// the kernel.
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(
+ Opaque<bindings::serdev_device>,
+ PhantomData<Ctx>,
+);
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+ fn as_raw(&self) -> *mut bindings::serdev_device {
+ self.0.get()
+ }
+}
+
+impl Device<device::Bound> {
+ /// Set the baudrate in bits per second.
+ ///
+ /// Common baudrates are 115200, 9600, 19200, 57600, 4800.
+ ///
+ /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+ pub fn set_baudrate(&self, speed: u32) -> Result<(), u32> {
+ // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ let ret = unsafe { bindings::serdev_device_set_baudrate(self.as_raw(), speed) };
+ if ret == speed {
+ Ok(())
+ } else {
+ Err(ret)
+ }
+ }
+
+ /// Set if flow control should be enabled.
+ ///
+ /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+ pub fn set_flow_control(&self, enable: bool) {
+ // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ unsafe { bindings::serdev_device_set_flow_control(self.as_raw(), enable) };
+ }
+
+ /// Set parity to use.
+ ///
+ /// Use [`Device::write_flush`] before calling this if you have written data prior to this call.
+ pub fn set_parity(&self, parity: Parity) -> Result {
+ // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ to_result(unsafe { bindings::serdev_device_set_parity(self.as_raw(), parity as u32) })
+ }
+
+ /// Write data to the serial device until the controller has accepted all the data or has
+ /// been interrupted by a timeout or signal.
+ ///
+ /// Note that any accepted data has only been buffered by the controller. Use
+ /// [ Device::wait_until_sent`] to make sure the controller write buffer has actually been
+ /// emptied.
+ ///
+ /// Returns the number of bytes written (less than `data.len()` if interrupted).
+ /// [`kernel::error::code::ETIMEDOUT`] or [`kernel::error::code::ERESTARTSYS`] if interrupted
+ /// before any bytes were written.
+ pub fn write_all(&self, data: &[u8], timeout: Timeout) -> Result<usize> {
+ // SAFETY:
+ // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of
+ // `data.len()`.
+ let ret = unsafe {
+ bindings::serdev_device_write(
+ self.as_raw(),
+ data.as_ptr(),
+ data.len(),
+ timeout.into_jiffies(),
+ )
+ };
+ // CAST: negative return values are guaranteed to be between `-MAX_ERRNO` and `-1`,
+ // which always fit into a `i32`.
+ to_result(ret as i32).map(|()| ret.unsigned_abs())
+ }
+
+ /// Write data to the serial device.
+ ///
+ /// If you want to write until the controller has accepted all the data, use
+ /// [`Device::write_all`].
+ ///
+ /// Note that any accepted data has only been buffered by the controller. Use
+ /// [ Device::wait_until_sent`] to make sure the controller write buffer has actually been
+ /// emptied.
+ ///
+ /// Returns the number of bytes written (less than `data.len()` if not enough room in the
+ /// write buffer).
+ pub fn write(&self, data: &[u8]) -> Result<u32> {
+ // SAFETY:
+ // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of
+ // `data.len()`.
+ let ret =
+ unsafe { bindings::serdev_device_write_buf(self.as_raw(), data.as_ptr(), data.len()) };
+
+ to_result(ret as i32).map(|()| ret.unsigned_abs())
+ }
+
+ /// Send data to the serial device immediately.
+ ///
+ /// Note that this doesn't guarantee that the data has been transmitted.
+ /// Use [`Device::wait_until_sent`] for this purpose.
+ pub fn write_flush(&self) {
+ // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ unsafe { bindings::serdev_device_write_flush(self.as_raw()) };
+ }
+
+ /// Wait for the data to be sent.
+ ///
+ /// After this function, the write buffer of the controller should be empty.
+ pub fn wait_until_sent(&self, timeout: Timeout) {
+ // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`.
+ unsafe { bindings::serdev_device_wait_until_sent(self.as_raw(), timeout.into_jiffies()) };
+ }
+}
+
+// SAFETY: `serdev::Device` is a transparent wrapper of `struct serdev_device`.
+// The offset is guaranteed to point to a valid device field inside `serdev::Device`.
+unsafe impl<Ctx: device::DeviceContext> device::AsBusDevice<Ctx> for Device<Ctx> {
+ const OFFSET: usize = offset_of!(bindings::serdev_device, dev);
+}
+
+// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
+// argument.
+kernel::impl_device_context_deref!(unsafe { Device });
+kernel::impl_device_context_into_aref!(Device);
+
+// SAFETY: Instances of `Device` are always reference-counted.
+unsafe impl AlwaysRefCounted for Device {
+ fn inc_ref(&self) {
+ self.as_ref().inc_ref();
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::serdev_device_put(obj.cast().as_ptr()) }
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+ // `struct serdev_device`.
+ let dev = unsafe { &raw mut (*self.as_raw()).dev };
+
+ // SAFETY: `dev` points to a valid `struct device`.
+ unsafe { device::Device::from_raw(dev) }
+ }
+}
+
+// SAFETY: A `Device` is always reference-counted and can be released from any thread.
+unsafe impl Send for Device {}
+
+// SAFETY: `Device` can be shared among threads because all methods of `Device`
+// (i.e. `Device<Normal>) are thread safe.
+unsafe impl Sync for Device {}
--
2.52.0
^ permalink raw reply related
* [PATCH v4 0/4] rust: add basic serial device bus abstractions
From: Markus Probst via B4 Relay @ 2026-04-11 15:10 UTC (permalink / raw)
To: Rob Herring, Greg Kroah-Hartman, Jiri Slaby, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, Boqun Feng
Cc: linux-serial, linux-kernel, rust-for-linux, linux-pm, driver-core,
dri-devel, Markus Probst
This patch series adds the serdev device bus rust abstraction into the
kernel.
This abstraction will be used by a driver,
which targets the MCU devices in Synology devices.
Kari Argillander also messaged me, stating that he wants to write a
watchdog driver with this abstraction (needing initial device data).
@Rob: Are you willing to maintain these rust abstractions yourself,
as you are the expert on this subsystem, otherwise I would take care of
it with a "SERIAL DEVICE BUS [RUST]" section in the MAINTAINERS file. In
the second case, I assume you are going to pick those patches as-is into
your tree, after they have been reviewed?
Signed-off-by: Markus Probst <markus.probst@posteo.de>
---
Changes in v4:
- fixed not selecting rust serdev abstraction in sample
- Link to v3: https://lore.kernel.org/r/20260313-rust_serdev-v3-0-c9a3af214f7f@posteo.de
Changes in v3:
- fix vertical import style
- add Kconfig entry for the rust abstraction
- fix documentation in include/linux/serdev.h
- rename private_data to rust_private_data
- fix `complete_all` <-> `wait_for_completion` typo
- move drvdata_borrow call after the completion
- Link to v2: https://lore.kernel.org/r/20260306-rust_serdev-v2-0-e9b23b42b255@posteo.de
Changes in v2:
- fix documentation in `serdev::Driver::write` and
`serdev::Driver::write_all`
- remove use of `dev_info` in probe from the sample
- remove `properties_parse` from the sample
- add optional `baudrate` property to the sample
- remove 1. patch
- remove `TryFrom<&device::Device<Ctx>> for &serdev::Device<Ctx>`
implementation
- fix import style
- add patch to return reference in `devres::register` to fix safety
issue
- add patch to add private data to serdev_device, to fix
`Device.drvdata()` from failing
- simplify abstraction by removing ability to receive the initial
transmission. It may be added later in a separate patch series if
needed.
- Link to v1: https://lore.kernel.org/r/20251220-rust_serdev-v1-0-e44645767621@posteo.de
---
Markus Probst (4):
rust: devres: return reference in `devres::register`
serdev: add rust private data to serdev_device
rust: add basic serial device bus abstractions
samples: rust: add Rust serial device bus sample device driver
drivers/tty/serdev/Kconfig | 7 +
include/linux/serdev.h | 15 +-
rust/bindings/bindings_helper.h | 1 +
rust/helpers/helpers.c | 1 +
rust/helpers/serdev.c | 22 ++
rust/kernel/cpufreq.rs | 3 +-
rust/kernel/devres.rs | 15 +-
rust/kernel/drm/driver.rs | 3 +-
rust/kernel/lib.rs | 2 +
rust/kernel/serdev.rs | 536 +++++++++++++++++++++++++++++++++++++
samples/rust/Kconfig | 11 +
samples/rust/Makefile | 1 +
samples/rust/rust_driver_serdev.rs | 86 ++++++
13 files changed, 693 insertions(+), 10 deletions(-)
---
base-commit: c369299895a591d96745d6492d4888259b004a9e
change-id: 20251217-rust_serdev-ee5481e9085c
^ permalink raw reply
* [PATCH RESEND v13 3/3] rust: leds: add multicolor classdev abstractions
From: Markus Probst @ 2026-04-11 15:07 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Ira Weiny, 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, Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
In-Reply-To: <20260411-rust_leds-v13-0-1208a2821deb@posteo.de>
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 | 30 +++-
rust/kernel/led/multicolor.rs | 387 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 417 insertions(+), 1 deletion(-)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 083cc44aa952..3171e3e6351c 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -63,6 +63,7 @@
#include <linux/ioport.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 5035563d68a3..a862d88cab29 100644
--- a/rust/kernel/led.rs
+++ b/rust/kernel/led.rs
@@ -33,8 +33,12 @@
}, //
};
+#[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};
/// The name of the led is determined by the driver.
@@ -279,7 +283,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,
@@ -319,7 +340,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<T: LedOps<Mode = Self>>;
diff --git a/rust/kernel/led/multicolor.rs b/rust/kernel/led/multicolor.rs
new file mode 100644
index 000000000000..726fdaf068cb
--- /dev/null
+++ b/rust/kernel/led/multicolor.rs
@@ -0,0 +1,387 @@
+// 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 crate::alloc::KVec;
+
+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<T: LedOps<Mode = Self>> = MultiColorDevice<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(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub struct MultiColorSubLed {
+ /// the color of the sub led
+ pub color: Color,
+ /// the brightness of the sub led.
+ ///
+ /// The value will be automatically calculated.
+ /// See `MultiColor::pre_brightness_set`.
+ pub brightness: u32,
+ /// the intensity of the sub led.
+ pub intensity: u32,
+ /// arbitrary data for the driver to store.
+ pub channel: u32,
+}
+
+// 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: 0,
+ intensity: 0,
+ channel: 0,
+ };
+ let c_zeroed = bindings::mc_subled {
+ color_index: 0,
+ brightness: 0,
+ intensity: 0,
+ channel: 0,
+ };
+
+ assert!(offset_of!(MultiColorSubLed, color) == offset_of!(bindings::mc_subled, color_index));
+ assert_same_type(&0u32, &c_zeroed.color_index);
+
+ assert!(
+ offset_of!(MultiColorSubLed, brightness) == offset_of!(bindings::mc_subled, brightness)
+ );
+ assert_same_type(&rust_zeroed.brightness, &c_zeroed.brightness);
+
+ assert!(offset_of!(MultiColorSubLed, intensity) == offset_of!(bindings::mc_subled, intensity));
+ assert_same_type(&rust_zeroed.intensity, &c_zeroed.intensity);
+
+ assert!(offset_of!(MultiColorSubLed, channel) == offset_of!(bindings::mc_subled, channel));
+ 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.
+ pub const fn new(color: Color) -> Self {
+ Self {
+ color,
+ brightness: 0,
+ intensity: 0,
+ channel: 0,
+ }
+ }
+
+ /// Set arbitrary data for the driver.
+ pub const fn channel(mut self, channel: u32) -> Self {
+ self.channel = channel;
+ self
+ }
+
+ /// Set the initial intensity of the subled.
+ pub const fn initial_intensity(mut self, intensity: u32) -> Self {
+ self.intensity = intensity;
+ self
+ }
+}
+
+/// The multicolor led class device representation.
+///
+/// This structure represents the Rust abstraction for a multicolor led class device.
+#[pin_data(PinnedDrop)]
+pub struct MultiColorDevice<T: LedOps<Mode = MultiColor>> {
+ #[pin]
+ ops: T,
+ #[pin]
+ classdev: Opaque<bindings::led_classdev_mc>,
+}
+
+impl<'a, S: DeviceBuilderState> DeviceBuilder<'a, S> {
+ /// Registers a new [`MulticolorDevice`].
+ pub fn build_multicolor<T: LedOps<Mode = MultiColor>>(
+ self,
+ parent: &'a T::Bus,
+ ops: impl PinInit<T, Error> + 'a,
+ subleds: &'a [MultiColorSubLed],
+ ) -> impl PinInit<Devres<MultiColorDevice<T>>, Error> + 'a {
+ Devres::new(
+ parent.as_ref(),
+ try_pin_init!(MultiColorDevice {
+ ops <- ops,
+ classdev <- Opaque::try_ffi_init(|ptr: *mut bindings::led_classdev_mc| {
+ let mut used = 0;
+ if subleds.iter().any(|subled| {
+ let bit = 1 << (subled.color as u32);
+ if (used & bit) != 0 {
+ true
+ } else {
+ used |= bit;
+ false
+ }
+ }) {
+ dev_err!(parent.as_ref(), "duplicate color in multicolor led\n");
+ return Err(EINVAL);
+ }
+ let mut subleds_vec = KVec::new();
+ subleds_vec.extend_from_slice(subleds, GFP_KERNEL)?;
+ let (subled_info, num_colors, capacity) = subleds_vec.into_raw_parts();
+ debug_assert_eq!(num_colors, capacity);
+
+ // 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,
+ default_trigger: self
+ .default_trigger
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ 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(num_colors)?,
+ // CAST: The safeguards in the const block ensure that
+ // `MultiColorSubLed` has an identical layout to `mc_subled`.
+ subled_info: subled_info.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()
+ },
+ )
+ })
+ .inspect_err(|_err| {
+ // SAFETY: `subled_info` is guaranteed to be a valid array pointer to
+ // `mc_subled` with the length and capacity of `num_colors`.
+ drop(unsafe { KVec::from_raw_parts(subled_info, num_colors, num_colors) });
+ })?;
+
+ core::mem::forget(self.fwnode); // keep the reference count incremented
+
+ Ok::<_, Error>(())
+ }),
+ }),
+ )
+ }
+}
+
+impl<T: LedOps<Mode = MultiColor>> MultiColorDevice<T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::MultiColorDevice`.
+ 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) }
+ }
+
+ fn parent(&self) -> &device::Device<Bound> {
+ // SAFETY: `self.classdev.get()` is guaranteed to be a valid pointer to `led_classdev_mc`.
+ unsafe { device::Device::from_raw((*(*self.classdev.get()).led_cdev.dev).parent) }
+ }
+
+ /// Returns the subleds passed to [`Device::new_multicolor`].
+ 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>(),
+ raw.num_colors as usize,
+ )
+ }
+ }
+}
+
+// SAFETY: A `led::MultiColorDevice` can be unregistered from any thread.
+unsafe impl<T: LedOps<Mode = MultiColor> + Send> Send for MultiColorDevice<T> {}
+
+// SAFETY: `led::MultiColorDevice` can be shared among threads because all methods of `led::Device`
+// are thread safe.
+unsafe impl<T: LedOps<Mode = MultiColor> + Sync> Sync for MultiColorDevice<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.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ // 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.ops.brightness_set(parent, classdev, 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.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ // 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.ops.brightness_set(parent, classdev, 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) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.brightness_get(parent, classdev)
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::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) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.blink_set(
+ parent,
+ classdev,
+ // SAFETY: The function's contract guarantees that `delay_on` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_on },
+ // SAFETY: The function's contract guarantees that `delay_off` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_off },
+ )?;
+ Ok(0)
+ })
+ }
+}
+
+#[pinned_drop]
+impl<T: LedOps<Mode = MultiColor>> PinnedDrop for MultiColorDevice<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::Device::new`.
+ .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });
+
+ // SAFETY: The existence of `self` guarantees that `self.classdev` has previously been
+ // successfully registered with `led_classdev_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: `subled_info` is guaranteed to be a valid array pointer to `mc_subled` with the
+ // length and capacity of `led_cdev.num_colors`. See `led::MulticolorDevice::new`.
+ drop(unsafe {
+ KVec::from_raw_parts(
+ led_cdev.subled_info,
+ led_cdev.num_colors as usize,
+ led_cdev.num_colors as usize,
+ )
+ });
+ }
+}
--
2.52.0
^ permalink raw reply related
* [PATCH RESEND v13 2/3] rust: leds: add Mode trait
From: Markus Probst @ 2026-04-11 15:07 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Ira Weiny, 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, Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
In-Reply-To: <20260411-rust_leds-v13-0-1208a2821deb@posteo.de>
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 | 28 ++++++++++++++++++++++++----
rust/kernel/led/normal.rs | 24 ++++++++++++++++--------
2 files changed, 40 insertions(+), 12 deletions(-)
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
index 1fba512a804c..5035563d68a3 100644
--- a/rust/kernel/led.rs
+++ b/rust/kernel/led.rs
@@ -35,7 +35,7 @@
mod normal;
-pub use normal::Device;
+pub use normal::{Device, Normal};
/// The name of the led is determined by the driver.
pub enum Named {}
@@ -177,6 +177,7 @@ pub fn name(self, name: &'a CStr) -> Self {
/// #[vtable]
/// impl led::LedOps for MyLedOps {
/// type Bus = platform::Device<device::Bound>;
+/// type Mode = led::Normal;
/// const BLOCKING: bool = false;
/// const MAX_BRIGHTNESS: u32 = 255;
///
@@ -209,6 +210,11 @@ pub trait LedOps: Send + 'static + Sized {
#[allow(private_bounds)]
type Bus: AsBusDevice<Bound>;
+ /// The led mode to use.
+ ///
+ /// See [`Mode`].
+ type Mode: Mode;
+
/// If set true, [`LedOps::brightness_set`] and [`LedOps::blink_set`] must perform the
/// operation immediately. If set false, they must not sleep.
const BLOCKING: bool;
@@ -221,12 +227,16 @@ pub trait LedOps: Send + 'static + Sized {
fn brightness_set(
&self,
dev: &Self::Bus,
- classdev: &Device<Self>,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
brightness: u32,
) -> Result<()>;
/// Gets the current brightness level.
- fn brightness_get(&self, dev: &Self::Bus, classdev: &Device<Self>) -> u32 {
+ fn brightness_get(
+ &self,
+ dev: &Self::Bus,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
+ ) -> u32 {
let _ = (dev, classdev);
build_error!(VTABLE_DEFAULT_ERROR)
}
@@ -242,7 +252,7 @@ fn brightness_get(&self, dev: &Self::Bus, classdev: &Device<Self>) -> u32 {
fn blink_set(
&self,
dev: &Self::Bus,
- classdev: &Device<Self>,
+ classdev: &<Self::Mode as Mode>::Device<Self>,
delay_on: &mut usize,
delay_off: &mut usize,
) -> Result<()> {
@@ -305,6 +315,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<T: LedOps<Mode = Self>>;
+}
+
mod private {
pub trait Sealed {}
}
diff --git a/rust/kernel/led/normal.rs b/rust/kernel/led/normal.rs
index bd239f186c64..dda247145f25 100644
--- a/rust/kernel/led/normal.rs
+++ b/rust/kernel/led/normal.rs
@@ -6,11 +6,19 @@
use super::*;
+/// The led mode for the `struct led_classdev`. Leds with this mode can only have a fixed color.
+pub enum Normal {}
+
+impl Mode for Normal {
+ type Device<T: LedOps<Mode = Self>> = Device<T>;
+}
+impl private::Sealed for Normal {}
+
/// The led class device representation.
///
/// This structure represents the Rust abstraction for a led class device.
#[pin_data(PinnedDrop)]
-pub struct Device<T: LedOps> {
+pub struct Device<T: LedOps<Mode = Normal>> {
#[pin]
ops: T,
#[pin]
@@ -19,7 +27,7 @@ pub struct Device<T: LedOps> {
impl<'a, S: DeviceBuilderState> DeviceBuilder<'a, S> {
/// Registers a new [`Device`].
- pub fn build<T: LedOps>(
+ pub fn build<T: LedOps<Mode = Normal>>(
self,
parent: &'a T::Bus,
ops: impl PinInit<T, Error> + 'a,
@@ -89,7 +97,7 @@ pub fn build<T: LedOps>(
}
}
-impl<T: LedOps> Device<T> {
+impl<T: LedOps<Mode = Normal>> Device<T> {
/// # Safety
/// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
/// `led::Device`.
@@ -107,17 +115,17 @@ fn parent(&self) -> &device::Device<Bound> {
}
// SAFETY: A `led::Device` can be unregistered from any thread.
-unsafe impl<T: LedOps + Send> Send for Device<T> {}
+unsafe impl<T: LedOps<Mode = Normal> + Send> Send for Device<T> {}
// SAFETY: `led::Device` can be shared among threads because all methods of `led::Device`
// are thread safe.
-unsafe impl<T: LedOps + Sync> Sync for Device<T> {}
+unsafe impl<T: LedOps<Mode = Normal> + Sync> Sync for Device<T> {}
-struct Adapter<T: LedOps> {
+struct Adapter<T: LedOps<Mode = Normal>> {
_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`.
@@ -203,7 +211,7 @@ impl<T: LedOps> Adapter<T> {
}
#[pinned_drop]
-impl<T: LedOps> PinnedDrop for Device<T> {
+impl<T: LedOps<Mode = Normal>> PinnedDrop for Device<T> {
fn drop(self: Pin<&mut Self>) {
let raw = self.classdev.get();
// SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
--
2.52.0
^ permalink raw reply related
* [PATCH RESEND v13 1/3] rust: leds: add basic led classdev abstractions
From: Markus Probst @ 2026-04-11 15:07 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Ira Weiny, 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, Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
In-Reply-To: <20260411-rust_leds-v13-0-1208a2821deb@posteo.de>
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>
---
MAINTAINERS | 8 ++
rust/kernel/led.rs | 310 ++++++++++++++++++++++++++++++++++++++++++++++
rust/kernel/led/normal.rs | 223 +++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
4 files changed, 542 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 7d10988cbc62..83b5a45de729 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14439,6 +14439,14 @@ 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: rust/kernel/led.rs
+F: rust/kernel/led/
+
LEGO MINDSTORMS EV3
R: David Lechner <david@lechnology.com>
S: Maintained
diff --git a/rust/kernel/led.rs b/rust/kernel/led.rs
new file mode 100644
index 000000000000..1fba512a804c
--- /dev/null
+++ b/rust/kernel/led.rs
@@ -0,0 +1,310 @@
+// 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,
+ AsBusDevice,
+ Bound, //
+ },
+ devres::Devres,
+ error::{
+ from_result,
+ to_result,
+ VTABLE_DEFAULT_ERROR, //
+ },
+ macros::vtable,
+ prelude::*,
+ str::CStrExt,
+ types::{
+ ARef,
+ 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<'a, S> {
+ fwnode: Option<ARef<FwNode>>,
+ name: Option<&'a CStr>,
+ devicename: Option<&'a CStr>,
+ devname_mandatory: bool,
+ initial_brightness: u32,
+ default_trigger: Option<&'a CStr>,
+ 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,
+ default_trigger: None,
+ color: Color::default(),
+ _p: PhantomData,
+ }
+ }
+}
+
+impl<'a> DeviceBuilder<'a, 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: &'a 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<'a, S: DeviceBuilderState> DeviceBuilder<'a, 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
+ }
+ }
+
+ /// Set the default led trigger.
+ ///
+ /// This value can be overwritten by the "linux,default-trigger" fwnode property.
+ #[inline]
+ pub fn default_trigger(self, trigger: &'a CStr) -> Self {
+ Self {
+ default_trigger: Some(trigger),
+ ..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<'a> DeviceBuilder<'a, 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: &'a CStr) -> Self {
+ Self {
+ name: Some(name),
+ ..self
+ }
+ }
+}
+
+/// Trait defining the operations for a LED driver.
+///
+/// # Examples
+/// ```
+/// use kernel::{
+/// device,
+/// devres::Devres,
+/// led,
+/// macros::vtable,
+/// platform,
+/// prelude::*, //
+/// };
+///
+/// struct MyLedOps;
+///
+///
+/// #[vtable]
+/// impl led::LedOps for MyLedOps {
+/// type Bus = platform::Device<device::Bound>;
+/// const BLOCKING: bool = false;
+/// const MAX_BRIGHTNESS: u32 = 255;
+///
+/// fn brightness_set(
+/// &self,
+/// _dev: &platform::Device<device::Bound>,
+/// _classdev: &led::Device<Self>,
+/// _brightness: u32
+/// ) -> Result<()> {
+/// // Set the brightness for the led here
+/// Ok(())
+/// }
+/// }
+///
+/// fn register_my_led(
+/// parent: &platform::Device<device::Bound>,
+/// ) -> Result<Pin<KBox<Devres<led::Device<MyLedOps>>>>> {
+/// KBox::pin_init(led::DeviceBuilder::new()
+/// .name(c"white:test")
+/// .build(
+/// parent,
+/// Ok(MyLedOps),
+/// ), GFP_KERNEL)
+/// }
+/// ```
+/// Led drivers must implement this trait in order to register and handle a [`Device`].
+#[vtable]
+pub trait LedOps: Send + 'static + Sized {
+ /// The bus device required by the implementation.
+ #[allow(private_bounds)]
+ type Bus: AsBusDevice<Bound>;
+
+ /// 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(
+ &self,
+ dev: &Self::Bus,
+ classdev: &Device<Self>,
+ brightness: u32,
+ ) -> Result<()>;
+
+ /// Gets the current brightness level.
+ fn brightness_get(&self, dev: &Self::Bus, classdev: &Device<Self>) -> u32 {
+ let _ = (dev, classdev);
+ 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(
+ &self,
+ dev: &Self::Bus,
+ classdev: &Device<Self>,
+ delay_on: &mut usize,
+ delay_off: &mut usize,
+ ) -> Result<()> {
+ let _ = (dev, classdev, delay_on, delay_off);
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+/// Led colors.
+#[derive(Copy, Clone, Debug, Default)]
+#[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.
+ 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..bd239f186c64
--- /dev/null
+++ b/rust/kernel/led/normal.rs
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Led mode for the `struct led_classdev`.
+//!
+//! C header: [`include/linux/leds.h`](srctree/include/linux/leds.h)
+
+use super::*;
+
+/// The led class device representation.
+///
+/// This structure represents the Rust abstraction for a led class device.
+#[pin_data(PinnedDrop)]
+pub struct Device<T: LedOps> {
+ #[pin]
+ ops: T,
+ #[pin]
+ classdev: Opaque<bindings::led_classdev>,
+}
+
+impl<'a, S: DeviceBuilderState> DeviceBuilder<'a, S> {
+ /// Registers a new [`Device`].
+ pub fn build<T: LedOps>(
+ self,
+ parent: &'a T::Bus,
+ ops: impl PinInit<T, Error> + 'a,
+ ) -> impl PinInit<Devres<Device<T>>, Error> + 'a {
+ Devres::new(
+ parent.as_ref(),
+ 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,
+ default_trigger: self
+ .default_trigger
+ .map_or(core::ptr::null(), CStrExt::as_char_ptr),
+ 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_ref().as_raw()` is guaranteed to be a pointer to a valid
+ // `device`.
+ // - `ptr` is guaranteed to be a pointer to an initialized `led_classdev`.
+ to_result(unsafe {
+ bindings::led_classdev_register_ext(
+ parent.as_ref().as_raw(),
+ ptr,
+ 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>(())
+ }),
+ }),
+ )
+ }
+}
+
+impl<T: LedOps> Device<T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ unsafe fn from_raw<'a>(led_cdev: *mut bindings::led_classdev) -> &'a Self {
+ // SAFETY: The function's contract guarantees that `led_cdev` points to a `led_classdev`
+ // field embedded within a valid `led::Device`. `container_of!` can therefore
+ // safely calculate the address of the containing struct.
+ unsafe { &*container_of!(Opaque::cast_from(led_cdev), Self, classdev) }
+ }
+
+ fn parent(&self) -> &device::Device<Bound> {
+ // SAFETY: `self.classdev.get()` is guaranteed to be a valid pointer to `led_classdev`.
+ unsafe { device::Device::from_raw((*(*self.classdev.get()).dev).parent) }
+ }
+}
+
+// SAFETY: A `led::Device` can be unregistered from any thread.
+unsafe impl<T: LedOps + Send> Send for Device<T> {}
+
+// SAFETY: `led::Device` can be shared among threads because all methods of `led::Device`
+// are thread safe.
+unsafe impl<T: LedOps + Sync> Sync for Device<T> {}
+
+struct Adapter<T: LedOps> {
+ _p: PhantomData<T>,
+}
+
+impl<T: LedOps> Adapter<T> {
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on setting the brightness of a led.
+ unsafe extern "C" fn brightness_set_callback(
+ led_cdev: *mut bindings::led_classdev,
+ brightness: u32,
+ ) {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ let _ = classdev.ops.brightness_set(parent, classdev, brightness);
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on setting the brightness of a led immediately.
+ unsafe extern "C" fn brightness_set_blocking_callback(
+ led_cdev: *mut bindings::led_classdev,
+ brightness: u32,
+ ) -> i32 {
+ from_result(|| {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.brightness_set(parent, classdev, brightness)?;
+ Ok(0)
+ })
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// This function is called on getting the brightness of a led.
+ unsafe extern "C" fn brightness_get_callback(led_cdev: *mut bindings::led_classdev) -> u32 {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.brightness_get(parent, classdev)
+ }
+
+ /// # Safety
+ /// `led_cdev` must be a valid pointer to a `led_classdev` embedded within a
+ /// `led::Device`.
+ /// `delay_on` and `delay_off` must be valid pointers to `usize` and have
+ /// exclusive access for the period of this function.
+ /// This function is called on enabling hardware accelerated blinking.
+ unsafe extern "C" fn blink_set_callback(
+ led_cdev: *mut bindings::led_classdev,
+ delay_on: *mut usize,
+ delay_off: *mut usize,
+ ) -> i32 {
+ from_result(|| {
+ // SAFETY: The function's contract guarantees that `led_cdev` is a valid pointer to a
+ // `led_classdev` embedded within a `led::Device`.
+ let classdev = unsafe { Device::<T>::from_raw(led_cdev) };
+ // SAFETY: `classdev.parent()` is guaranteed to be contained in `T::Bus`.
+ let parent = unsafe { T::Bus::from_device(classdev.parent()) };
+
+ classdev.ops.blink_set(
+ parent,
+ classdev,
+ // SAFETY: The function's contract guarantees that `delay_on` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_on },
+ // SAFETY: The function's contract guarantees that `delay_off` points to a `usize`
+ // and is exclusive for the period of this function.
+ unsafe { &mut *delay_off },
+ )?;
+ Ok(0)
+ })
+ }
+}
+
+#[pinned_drop]
+impl<T: LedOps> PinnedDrop for Device<T> {
+ fn drop(self: Pin<&mut Self>) {
+ let raw = self.classdev.get();
+ // SAFETY: The existence of `self` guarantees that `self.classdev.get()` is a pointer to a
+ // valid `led_classdev`.
+ let dev: &device::Device = unsafe { device::Device::from_raw((*raw).dev) };
+
+ let _fwnode = dev
+ .fwnode()
+ // SAFETY: the reference count of `fwnode` has previously been
+ // incremented in `led::Device::new`.
+ .map(|fwnode| unsafe { ARef::from_raw(NonNull::from(fwnode)) });
+
+ // SAFETY: The existence of `self` guarantees that `self.classdev` has previously been
+ // successfully registered with `led_classdev_register_ext`.
+ unsafe { bindings::led_classdev_unregister(raw) };
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d93292d47420..d0e30d3733dd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -115,6 +115,7 @@
pub mod jump_label;
#[cfg(CONFIG_KUNIT)]
pub mod kunit;
+pub mod led;
pub mod list;
pub mod maple_tree;
pub mod miscdevice;
--
2.52.0
^ permalink raw reply related
* [PATCH RESEND v13 0/3] rust: leds: add led classdev abstractions
From: Markus Probst @ 2026-04-11 15:07 UTC (permalink / raw)
To: Lee Jones, Pavel Machek, Greg Kroah-Hartman, Dave Ertman,
Ira Weiny, 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, Boqun Feng
Cc: rust-for-linux, linux-leds, linux-kernel, linux-pci,
Markus Probst
This patch series has previously been contained in
https://lore.kernel.org/rust-for-linux/20251008181027.662616-1-markus.probst@posteo.de/T/#t
which added a rust written led driver for a microcontroller via i2c.
As the reading and writing to the i2c client via the register!
macro has not been implemented yet [1], the patch series will only
contain the additional abstractions required.
[1] https://lore.kernel.org/rust-for-linux/DDDS2V0V2NVJ.16ZKXCKUA1HUV@kernel.org/
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 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 (3):
rust: leds: add basic led classdev abstractions
rust: leds: add Mode trait
rust: leds: add multicolor classdev abstractions
MAINTAINERS | 8 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/led.rs | 358 +++++++++++++++++++++++++++++++++++++
rust/kernel/led/multicolor.rs | 387 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/led/normal.rs | 231 ++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
6 files changed, 986 insertions(+)
---
base-commit: d1d81e9d1a4dd846aee9ae77ff9ecc2800d72148
change-id: 20251114-rust_leds-a959f7c2f7f9
-----BEGIN PGP SIGNATURE-----
iQJPBAABCAA5FiEEgnQYxPSsWOdyMMRzNHYf+OetQ9IFAmnJX+IbFIAAAAAABAAO
bWFudTIsMi41KzEuMTEsMiwyAAoJEDR2H/jnrUPSKfEQAIr+nl9WCSty+mWQlJqB
D9B4+xLku25rFvvSCv/j7vEtL0VF5Rk5GJKFOhEEYg642WR6Os+VuTNnEOVAWTbK
qNfDNOP9hrn75anyf1HEOduwdsW9sTafrfF8gq36y80q0zVNWqD2SbYJ1A+Ri2Hw
uvJATuDnxMyBL7lAIPhshPjDHjO9wO7qiW6wSVY0RydD21tQZJkag3vg9gUG0Od7
v5DtM57QNf5h/7GXmC0oZ07h3Ua5ZSFNFrHBDm7MAt7YU8TllQcD1sL7f7OhcFDh
LzsQYm+A+VNQ9bL4/SdBeKGug7bEFshJywowdp6noRNJPR1Y2lCy1i3D85PnlM+j
h/NuFg6nUb1NupcB4J+ibzx/eL+80ablZDAqq5OKiskvDxiiTrQw4kC1TGlh+3rL
tFqqynKTU40N31nXRPHTG3bIfZsb3GHvXxEkJ1s1ufKKhk9WZKhCDOuSdduYrZX7
50kR5ptLn3YysJr1JTtt7xgT0ToSaxbre8ZWEKLCG8j8t2ONFDdJSskci+1VOUm3
FCkRLKILcnqFrQv7vnIa2tK6Xlg6jwcGQaVDB6KUIImGp8FatM0R6qP0iKM535+L
qAeYagvMjTUgwZXLIuUhwHWVk+1V54VjLlmBx6xfJ0a3MlfmtgdalruWfRAItSCq
ZjXW80wg0v3k/fsCPU5J+hY1
=OlH0
-----END PGP SIGNATURE-----
--
Markus Probst <markus.probst@posteo.de>
^ permalink raw reply
* [BUG] android: rust binder: sleeping function called from invalid context in deferred_release()
From: syscaller @ 2026-04-11 13:44 UTC (permalink / raw)
To: gregkh, aliceryhl
Cc: arve, tkjos, brauner, cmllamas, ojeda, boqun, rust-for-linux,
linux-kernel, syzkaller-bugs
Hello,
I hit the following issue repeatedly with a local syzkaller instance while
fuzzing Rust binder.
HEAD commit: 591cd656a1bf Linux 7.0-rc7
git tree: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
console output: https://gist.githubusercontent.com/syscallerx/1ebe64a8a4a3340fb565325ba62efe53/raw/7c5e52532a94b60f442cf588926da065c7a2b11e/console-output.txt
kernel config: https://gist.githubusercontent.com/syscallerx/1ebe64a8a4a3340fb565325ba62efe53/raw/7c5e52532a94b60f442cf588926da065c7a2b11e/kernel-config.txt
dashboard link: N/A (local syzkaller instance)
compiler: gcc (Ubuntu 14.2.0-4ubuntu2) 14.2.0, GNU ld (GNU Binutils for Ubuntu) 2.43.1
userspace arch: amd64
Note: the fuzzing kernel was based on upstream v7.0-rc7 with one local
instrumentation-only change:
drivers/android/binder/Makefile
+KCOV_INSTRUMENT := y
This change was added only to enable selective KCOV instrumentation for binder
while fuzzing.
Unfortunately, I don't have a minimized reproducer for this issue yet.
The same crash family has been observed repeatedly by the local syzkaller
instance (about 300 hits in the same crash directory), but syz-repro has not
managed to extract a stable minimal reproducer so far.
I believe the warning is caused by the following code in
drivers/android/binder/process.rs:
while let Some(delivered_death) =
{ self.inner.lock().delivered_deaths.pop_front() } {
drop(delivered_death);
}
The temporary SpinLockGuard returned by self.inner.lock() appears to live
across the whole while-let statement, including the loop body. As a result,
drop(delivered_death) runs while Process::inner is still held.
The destructor chain then reaches:
drivers/android/binder/page_range.rs:633
let mm_lock = self.mm_lock.lock();
That mutex acquisition triggers the "sleeping function called from invalid
context" / "Invalid wait context" warning.
BUG: sleeping function called from invalid context at kernel/locking/mutex.c:591
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 212920, name: kworker/3:3
preempt_count: 1, expected: 0
RCU nest depth: 0, expected: 0
3 locks held by kworker/3:3/212920:
#0: ffff888019c13948 ((wq_completion)events#2){+.+.}-{0:0}, at: process_one_work+0xfaa/0x1670 kernel/workqueue.c:3251
#1: ffffc900096a7d10 (Process::defer_work){+.+.}-{0:0}, at: process_one_work+0x85c/0x1670 kernel/workqueue.c:3252
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <kernel::sync::lock::spinlock::SpinLockBackend as kernel::sync::lock::Backend>::lock rust/kernel/sync/lock/spinlock.rs:119 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <kernel::sync::lock::Lock<rust_binder_main::process::ProcessInner, kernel::sync::lock::spinlock::SpinLockBackend>>::lock rust/kernel/sync/lock.rs:177 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <rust_binder_main::process::Process>::deferred_release drivers/android/binder/process.rs:1405 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <rust_binder_main::process::Process as kernel::workqueue::WorkItem>::run+0x2278/0x2fd0 drivers/android/binder/process.rs:498
Preemption disabled at:
[<0000000000000000>] 0x0
CPU: 3 UID: 0 PID: 212920 Comm: kworker/3:3 Tainted: G W L 7.0.0-rc7-dirty #3 PREEMPT(full)
Tainted: [W]=WARN, [L]=SOFTLOCKUP
Hardware name: QEMU Ubuntu 24.10 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: events _RNvXs8_NtCsgzhNYVB7wSz_6kernel9workqueueINtNtNtB7_4sync3arc3ArcNtNtCsdiRiAdj6M6P_16rust_binder_main7process7ProcessEINtB5_15WorkItemPointerKy0_E3runB13_
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x6f/0xb0 lib/dump_stack.c:120
__might_resched.cold+0x1ec/0x232 kernel/sched/core.c:8888
__mutex_lock_common kernel/locking/mutex.c:591 [inline]
__mutex_lock+0x10e/0x1b90 kernel/locking/mutex.c:776
<kernel::sync::lock::mutex::MutexBackend as kernel::sync::lock::Backend>::lock rust/kernel/sync/lock/mutex.rs:120 [inline]
<kernel::sync::lock::Lock<(), kernel::sync::lock::mutex::MutexBackend>>::lock rust/kernel/sync/lock.rs:177 [inline]
<rust_binder_main::page_range::ShrinkablePageRange as pin_init::PinnedDrop>::drop+0xe1/0x310 drivers/android/binder/page_range.rs:633
<rust_binder_main::page_range::ShrinkablePageRange as core::ops::drop::Drop>::drop drivers/android/binder/page_range.rs:127 [inline]
core::ptr::drop_in_place::<rust_binder_main::page_range::ShrinkablePageRange> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::process::Process>+0xcfd/0x1010 root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop rust/kernel/alloc/kbox.rs:676 [inline]
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::process::Process> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::process::Process>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::node::Node> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::DTRWrap<rust_binder_main::node::Node>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop rust/kernel/alloc/kbox.rs:676 [inline]
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::Node>> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::node::NodeDeath> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop+0xdf/0x1b0 rust/kernel/alloc/kbox.rs:676
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::list::arc::ListArc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>, 2>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::list::arc::ListArc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>, 2>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<rust_binder_main::process::Process>::deferred_release drivers/android/binder/process.rs:1406 [inline]
<rust_binder_main::process::Process as kernel::workqueue::WorkItem>::run+0x2475/0x2fd0 drivers/android/binder/process.rs:498
process_one_work+0x8df/0x1670 kernel/workqueue.c:3276
process_scheduled_works kernel/workqueue.c:3359 [inline]
worker_thread+0x4ea/0xd60 kernel/workqueue.c:3440
kthread+0x30d/0x3f0 kernel/kthread.c:436
ret_from_fork+0x614/0xa30 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
=============================
[ BUG: Invalid wait context ]
7.0.0-rc7-dirty #3 Tainted: G W L
-----------------------------
kworker/3:3/212920 is trying to lock:
ffff888188296898 (ShrinkablePageRange::mm){+.+.}-{4:4}, at: <kernel::sync::lock::mutex::MutexBackend as kernel::sync::lock::Backend>::lock rust/kernel/sync/lock/mutex.rs:120 [inline]
ffff888188296898 (ShrinkablePageRange::mm){+.+.}-{4:4}, at: <kernel::sync::lock::Lock<(), kernel::sync::lock::mutex::MutexBackend>>::lock rust/kernel/sync/lock.rs:177 [inline]
ffff888188296898 (ShrinkablePageRange::mm){+.+.}-{4:4}, at: <rust_binder_main::page_range::ShrinkablePageRange as pin_init::PinnedDrop>::drop+0xe1/0x310 drivers/android/binder/page_range.rs:633
other info that might help us debug this:
context-{5:5}
3 locks held by kworker/3:3/212920:
#0: ffff888019c13948 ((wq_completion)events#2){+.+.}-{0:0}, at: process_one_work+0xfaa/0x1670 kernel/workqueue.c:3251
#1: ffffc900096a7d10 (Process::defer_work){+.+.}-{0:0}, at: process_one_work+0x85c/0x1670 kernel/workqueue.c:3252
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <kernel::sync::lock::spinlock::SpinLockBackend as kernel::sync::lock::Backend>::lock rust/kernel/sync/lock/spinlock.rs:119 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <kernel::sync::lock::Lock<rust_binder_main::process::ProcessInner, kernel::sync::lock::spinlock::SpinLockBackend>>::lock rust/kernel/sync/lock.rs:177 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <rust_binder_main::process::Process>::deferred_release drivers/android/binder/process.rs:1405 [inline]
#2: ffff888198d9f938 (Process::inner){+.+.}-{3:3}, at: <rust_binder_main::process::Process as kernel::workqueue::WorkItem>::run+0x2278/0x2fd0 drivers/android/binder/process.rs:498
stack backtrace:
CPU: 3 UID: 0 PID: 212920 Comm: kworker/3:3 Tainted: G W L 7.0.0-rc7-dirty #3 PREEMPT(full)
Tainted: [W]=WARN, [L]=SOFTLOCKUP
Hardware name: QEMU Ubuntu 24.10 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: events _RNvXs8_NtCsgzhNYVB7wSz_6kernel9workqueueINtNtNtB7_4sync3arc3ArcNtNtCsdiRiAdj6M6P_16rust_binder_main7process7ProcessEINtB5_15WorkItemPointerKy0_E3runB13_
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x6f/0xb0 lib/dump_stack.c:120
print_lock_invalid_wait_context kernel/locking/lockdep.c:4830 [inline]
check_wait_context kernel/locking/lockdep.c:4902 [inline]
__lock_acquire+0xfa4/0x2630 kernel/locking/lockdep.c:5187
lock_acquire kernel/locking/lockdep.c:5868 [inline]
lock_acquire+0x1cf/0x380 kernel/locking/lockdep.c:5825
__mutex_lock_common kernel/locking/mutex.c:614 [inline]
__mutex_lock+0x1a2/0x1b90 kernel/locking/mutex.c:776
<kernel::sync::lock::mutex::MutexBackend as kernel::sync::lock::Backend>::lock rust/kernel/sync/lock/mutex.rs:120 [inline]
<kernel::sync::lock::Lock<(), kernel::sync::lock::mutex::MutexBackend>>::lock rust/kernel/sync/lock.rs:177 [inline]
<rust_binder_main::page_range::ShrinkablePageRange as pin_init::PinnedDrop>::drop+0xe1/0x310 drivers/android/binder/page_range.rs:633
<rust_binder_main::page_range::ShrinkablePageRange as core::ops::drop::Drop>::drop drivers/android/binder/page_range.rs:127 [inline]
core::ptr::drop_in_place::<rust_binder_main::page_range::ShrinkablePageRange> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::process::Process>+0xcfd/0x1010 root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop rust/kernel/alloc/kbox.rs:676 [inline]
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::process::Process>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::process::Process> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::process::Process>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::node::Node> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::DTRWrap<rust_binder_main::node::Node>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop rust/kernel/alloc/kbox.rs:676 [inline]
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::Node>> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::Node>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::node::NodeDeath> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc> as core::ops::drop::Drop>::drop+0xdf/0x1b0 rust/kernel/alloc/kbox.rs:676
core::ptr::drop_in_place::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::alloc::kbox::Box<kernel::sync::arc::ArcInner<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>, kernel::alloc::allocator::Kmalloc>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>> as core::ops::drop::Drop>::drop rust/kernel/sync/arc.rs:476 [inline]
core::ptr::drop_in_place::<kernel::sync::arc::Arc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::ptr::drop_in_place::<kernel::list::arc::ListArc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>, 2>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:805 [inline]
core::mem::drop::<kernel::list::arc::ListArc<rust_binder_main::DTRWrap<rust_binder_main::node::NodeDeath>, 2>> root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:967 [inline]
<rust_binder_main::process::Process>::deferred_release drivers/android/binder/process.rs:1406 [inline]
<rust_binder_main::process::Process as kernel::workqueue::WorkItem>::run+0x2475/0x2fd0 drivers/android/binder/process.rs:498
process_one_work+0x8df/0x1670 kernel/workqueue.c:3276
process_scheduled_works kernel/workqueue.c:3359 [inline]
worker_thread+0x4ea/0xd60 kernel/workqueue.c:3440
kthread+0x30d/0x3f0 kernel/kthread.c:436
ret_from_fork+0x614/0xa30 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
If needed, I can share the local syzkaller reproducer attempts and additional
testing details in a follow-up.
Thanks,
syscaller
^ permalink raw reply
* Re:Re: [RFC PATCH 0/2] rust: block: add a borrowed blk-mq timeout callback
From: wenzhaoliao @ 2026-04-11 13:37 UTC (permalink / raw)
To: Andreas Hindborg
Cc: Jens Axboe, Miguel Ojeda, linux-block, rust-for-linux, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Alice Ryhl,
Trevor Gross, Danilo Krummrich, linux-kernel
In-Reply-To: <87ldetx01d.fsf@kernel.org>
Hi Greg,
Thank you so much for taking the time to review this patch and for your valuable guidance.
To be completely honest, we do not have a specific driver use case for this feature at the moment. Our overarching goal is simply to learn the kernel development process and find ways to genuinely contribute to the Rust-for-Linux project.
Recently, we submitted a patch that unfortunately duplicated existing work. In that thread, Andreas Hindborg kindly pointed us to a new task with this exact suggestion:
"If you really want to use your tokens to improve the kernel, I would suggest adding boolean parameter support to the Rust module parameter parser code."
(https://lore.kernel.org/rust-for-linux/20260216-rnull-v6-19-rc5-send-v1-0-de9a7af4b469@kernel.org/)
We took this as a good "first issue" to familiarize ourselves with the Rust subsystem, and that is the sole reason we implemented it.
Learning from you that module options are considered 1990's technology and do not fit the modern kernel model is a great lesson for us. If we misunderstood the maintainer's intention, or if pursuing this outdated mechanism goes against modern kernel design principles, we sincerely apologize for the noise and for any burden this has caused.
We are perfectly happy to drop this patch if this functionality is no longer desired in the modern kernel. We really just want to help improve the kernel, and we will try our best to sync with the maintainers to pick up tasks that are more aligned with current kernel standards (like sysfs/configfs) next time.
Best regards,
Wenzhao
发件人:Andreas Hindborg <a.hindborg@kernel.org>
发送日期:2026-04-11 18:29:34
收件人:Wenzhao Liao <wenzhaoliao@ruc.edu.cn>,Jens Axboe <axboe@kernel.dk>,Miguel Ojeda <ojeda@kernel.org>,linux-block@vger.kernel.org,rust-for-linux@vger.kernel.org
抄送人:Boqun Feng <boqun@kernel.org>,Gary Guo <gary@garyguo.net>,"Björn Roy Baron" <bjorn3_gh@protonmail.com>,Benno Lossin <lossin@kernel.org>,Alice Ryhl <aliceryhl@google.com>,Trevor Gross <tmgross@umich.edu>,Danilo Krummrich <dakr@kernel.org>,linux-kernel@vger.kernel.org
主题:Re: [RFC PATCH 0/2] rust: block: add a borrowed blk-mq timeout callback>Hi Wenzhao,
>
>"Wenzhao Liao" <wenzhaoliao@ruc.edu.cn> writes:
>
>> This small RFC series fills a missing blk-mq callback in the Rust block
>> layer. Today, Rust block drivers cannot participate in request timeout
>> handling because the Rust blk-mq vtable hardcodes `timeout: None`.
>
>I already pointed you to [1] multiple times. Please make sure to read
>that patch series. The functionality you are submitting is already
>covered by that series.
>
>If you really want to use your tokens to improve the kernel, I would
>suggest adding boolean parameter support to the Rust module parameter
>parser code.
>
>Be sure to credit your LLM according to [2]. And please do instruct your
>agents to find related discussion on the mailing list, so we avoid
>solving the same problem over and over.
>
>Best regards,
>Andreas Hindborg
>
>
>[1] https://lore.kernel.org/rust-for-linux/20260216-rnull-v6-19-rc5-send-v1-0-de9a7af4b469@kernel.org/
>[2] https://docs.kernel.org/process/coding-assistants.html
>
>
^ permalink raw reply
* Re: [PATCH v10 5/5] rust: drm: gem: Add vmap functions to shmem bindings
From: Alexandre Courbot @ 2026-04-11 13:32 UTC (permalink / raw)
To: Lyude Paul
Cc: nouveau, Gary Guo, Daniel Almeida, rust-for-linux,
Danilo Krummrich, dri-devel, Matthew Maurer, FUJITA Tomonori,
Lorenzo Stoakes, christian.koenig, Asahi Lina, Miguel Ojeda,
Andreas Hindborg, Simona Vetter, Alice Ryhl, Boqun Feng,
Sumit Semwal, Krishna Ketan Rai, linux-media, Shankari Anand,
David Airlie, Benno Lossin, Viresh Kumar, linaro-mm-sig,
Asahi Lina, Greg Kroah-Hartman, kernel
In-Reply-To: <20260409001559.622026-6-lyude@redhat.com>
Hi Lyude,
On Thu Apr 9, 2026 at 9:12 AM JST, Lyude Paul wrote:
<snip>
> @@ -288,6 +299,84 @@ pub fn owned_sg_table(&self, dev: &device::Device<Bound>) -> Result<SGTable<T>>
> // `Some(Devres<SGTableMap<T>>)`.
> Ok(SGTable(self.into()))
> }
> +
> + /// Attempt to create a vmap from the gem object, and confirm the size of said vmap.
> + fn raw_vmap(&self, min_size: usize) -> Result<*mut c_void> {
> + if self.size() < min_size {
> + return Err(ENOSPC);
> + }
> +
> + let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit();
> +
> + // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held
> + to_result(unsafe {
> + // TODO: see top of file
> + bindings::dma_resv_lock(self.raw_dma_resv(), ptr::null_mut());
> + let ret = bindings::drm_gem_shmem_vmap_locked(self.as_raw_shmem(), map.as_mut_ptr());
> + bindings::dma_resv_unlock(self.raw_dma_resv());
> + ret
> + })?;
> +
> + // SAFETY: The call to drm_gem_shmem_vunmap_locked succeeded above, so we are guaranteed
Looks like a typo: the call above is `drm_gem_shmem_vmap_locked`.
> + // that map is properly initialized.
> + let map = unsafe { map.assume_init() };
> +
> + // XXX: We don't currently support iomem allocations
> + if map.is_iomem {
> + // SAFETY:
> + // - The vmap operation above succeeded, guaranteeing that `map` points to a valid
> + // memory mapping.
> + // - We checked that this is an iomem allocation, making it safe to read vaddr_iomem
> + unsafe { self.raw_vunmap(map) };
> +
> + Err(ENOTSUPP)
> + } else {
> + // SAFETY: We checked that this is not an iomem allocation, making it safe to read vaddr
> + Ok(unsafe { map.__bindgen_anon_1.vaddr })
> + }
> + }
> +
> + /// Unmap a vmap from the gem object.
> + ///
> + /// # Safety
> + ///
> + /// - The caller promises that `map` is a valid vmap on this gem object.
> + /// - The caller promises that the memory pointed to by map will no longer be accesed through
> + /// this instance.
> + unsafe fn raw_vunmap(&self, mut map: bindings::iosys_map) {
> + let resv = self.raw_dma_resv();
> +
> + // SAFETY:
> + // - This function is safe to call with the DMA reservation lock held
> + // - Our `ARef` is proof that the underlying gem object here is initialized and thus safe to
> + // dereference.
> + unsafe {
> + // TODO: see top of file
> + bindings::dma_resv_lock(resv, ptr::null_mut());
> + bindings::drm_gem_shmem_vunmap_locked(self.as_raw_shmem(), &mut map);
> + bindings::dma_resv_unlock(resv);
> + }
> + }
> +
> + /// Creates and returns a virtual kernel memory mapping for this object.
> + #[inline]
> + pub fn vmap<const SIZE: usize>(&self) -> Result<VMapRef<'_, T, SIZE>> {
> + Ok(VMapRef {
> + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`.
> + addr: self.raw_vmap(SIZE)?,
> + owner: self,
> + })
> + }
> +
> + /// Creates and returns an owned reference to a virtual kernel memory mapping for this object.
> + #[inline]
> + pub fn owned_vmap<const SIZE: usize>(&self) -> Result<VMap<T, SIZE>> {
> + Ok(VMap {
> + // INVARIANT: `raw_vmap()` checks that the gem object is at least as large as `SIZE`.
> + addr: self.raw_vmap(SIZE)?,
> + owner: self.into(),
> + })
> + }
> }
>
> impl<T: DriverObject> Deref for Object<T> {
> @@ -386,6 +475,154 @@ unsafe impl<T: DriverObject> Send for SGTableMap<T> {}
> // it points to is guaranteed to be thread-safe.
> unsafe impl<T: DriverObject> Sync for SGTableMap<T> {}
>
> +macro_rules! impl_vmap_io_capable {
> + ($impl:ident, $ty:ty $(, $lifetime:lifetime )?) => {
How about taking a list of types as argument, so you don't need to
invoke the macro once per supported primitive?
> + impl<$( $lifetime ,)? D: DriverObject, const SIZE: usize> IoCapable<$ty>
> + for $impl<$( $lifetime ,)? D, SIZE>
> + {
> + #[inline(always)]
> + unsafe fn io_read(&self, address: usize) -> $ty {
> + let ptr = address as *mut $ty;
> +
> + // SAFETY: The safety contract of `io_read` guarantees that address is a valid
> + // address within the bounds of `Self` of at least the size of $ty, and is properly
> + // aligned.
> + unsafe { ptr::read(ptr) }
> + }
> +
> + #[inline(always)]
> + unsafe fn io_write(&self, value: $ty, address: usize) {
> + let ptr = address as *mut $ty;
> +
> + // SAFETY: The safety contract of `io_write` guarantees that address is a valid
> + // address within the bounds of `Self` of at least the size of $ty, and is properly
> + // aligned.
> + unsafe { ptr::write(ptr, value) }
> + }
> + }
> + };
> +}
> +
> +// Implement various traits common to both VMap types
> +macro_rules! impl_vmap_common {
> + ($impl:ident $(, $lifetime:lifetime )?) => {
> + impl<$( $lifetime ,)? D, const SIZE: usize> $impl<$( $lifetime ,)? D, SIZE>
The comment says "Implement various traits" but this block is not a
trait implementation.
> + where
> + D: DriverObject,
> + {
> + /// Borrows a reference to the object that owns this virtual mapping.
> + #[inline(always)]
> + pub fn owner(&self) -> &Object<D> {
> + &self.owner
> + }
> + }
> +
> + impl<$( $lifetime ,)? D, const SIZE: usize> Drop for $impl<$( $lifetime ,)? D, SIZE>
> + where
> + D: DriverObject,
> + {
> + #[inline(always)]
> + fn drop(&mut self) {
> + // SAFETY:
> + // - Our existence is proof that this map was previously created using self.owner.
> + // - Since we are in Drop, we are guaranteed that no one will access the memory
> + // through this mapping after calling this.
> + unsafe {
> + self.owner.raw_vunmap(bindings::iosys_map {
> + is_iomem: false,
> + __bindgen_anon_1: bindings::iosys_map__bindgen_ty_1 { vaddr: self.addr }
> + })
> + };
> + }
> + }
> +
> + impl<$( $lifetime ,)? D, const SIZE: usize> Io for $impl<$( $lifetime ,)? D, SIZE>
> + where
> + D: DriverObject,
> + {
> + #[inline(always)]
> + fn addr(&self) -> usize {
> + self.addr as usize
> + }
> +
> + #[inline(always)]
> + fn maxsize(&self) -> usize {
> + self.owner.size()
> + }
> + }
> +
> + impl<$( $lifetime ,)? D, const SIZE: usize> IoKnownSize for $impl<$( $lifetime ,)? D, SIZE>
> + where
> + D: DriverObject,
> + {
> + const MIN_SIZE: usize = SIZE;
> + }
> +
> + impl_vmap_io_capable!($impl, u8 $( , $lifetime )?);
> + impl_vmap_io_capable!($impl, u16 $( , $lifetime )?);
> + impl_vmap_io_capable!($impl, u32 $( , $lifetime )?);
> + #[cfg(CONFIG_64BIT)]
> + impl_vmap_io_capable!($impl, u64 $( , $lifetime )?);
> + };
> +}
> +
> +/// An owned reference to a virtual mapping for a shmem-based GEM object in kernel address space.
> +///
> +/// # Invariants
> +///
> +/// - The size of `owner` is >= SIZE.
> +/// - The memory pointed to by addr remains valid at least until this object is dropped.
> +pub struct VMap<D: DriverObject, const SIZE: usize = 0> {
> + addr: *mut c_void,
> + owner: ARef<Object<D>>,
> +}
> +
> +impl_vmap_common!(VMap);
I believe you can considerably reduce the use of macros and consolidate
the code around a single type if you define `VMap` this way:
pub struct VMap<D, R, const SIZE: usize = 0>
where
D: DriverObject,
R: Deref<Target = Object<D>>,
{
addr: *mut c_void,
owner: R,
}
Then `R` can either be `&'a Object<D>` or `ARef<Object<D>>` and you
don't need `impl_vmap_common!` anymore (`impl_vmap_io_capable!` is
probably still useful though).
The extra generic makes the type a bit more complex, but you can also
fold it into something similar to what you currently have by defining
type aliases:
pub type VMapRef<'a, D, const SIZE: usize = 0> = VMap<D, &'a Object<D>, SIZE>;
pub type VMapOwned<D, const SIZE: usize = 0> = VMap<D, ARef<Object<D>>, SIZE>;
... although I don't think that is necessary as callers will never need
to specify these generics anyway.
^ permalink raw reply
* Re: [PATCH 0/1] rust: module_param: support bool parameters
From: Greg KH @ 2026-04-11 13:20 UTC (permalink / raw)
To: Wenzhao Liao
Cc: mcgrof, petr.pavlu, da.gomez, samitolvanen, ojeda, linux-modules,
rust-for-linux, atomlin, boqun, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, dakr, linux-kernel
In-Reply-To: <20260411130254.3510128-1-wenzhaoliao@ruc.edu.cn>
On Sat, Apr 11, 2026 at 09:02:53AM -0400, Wenzhao Liao wrote:
> Sorry for the earlier noise and for our unfamiliarity with parts of the
> kernel submission process, which created extra burden for maintainers.
>
> This patch adds boolean module parameter support to the Rust `module!`
> parameter path.
>
> It implements `ModuleParam` for `bool` and wires `PARAM_OPS_BOOL` into
> the Rust module parameter machinery, so Rust-side parsing reuses the
> existing kernel `kstrtobool()` semantics through `kstrtobool_bytes()`
> instead of introducing a separate parser. A boolean parameter is also
> added to `samples/rust/rust_minimal.rs` as a small reference user and
> build-time validation point.
What driver needs this feature? Module options should be very rare
going forward as they are 1990's technology and do not fit the "modern"
kernel model at all.
thanks,
greg k-h
^ permalink raw reply
* [PATCH 1/1] rust: module_param: support bool parameters
From: Wenzhao Liao @ 2026-04-11 13:02 UTC (permalink / raw)
To: mcgrof, petr.pavlu, da.gomez, samitolvanen, ojeda, linux-modules,
rust-for-linux
Cc: atomlin, boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl,
tmgross, dakr, linux-kernel
In-Reply-To: <20260411130254.3510128-1-wenzhaoliao@ruc.edu.cn>
Add support for parsing boolean module parameters in the Rust
module! macro.
Currently, only integer types are supported by the `module_param!`
macros. This patch implements the `ModuleParam` trait for `bool`
by delegating the string parsing to the existing C implementation
via `kstrtobool_bytes()`. It also wires up `PARAM_OPS_BOOL` so that
the Rust parameter system correctly links to the C `param_ops_bool`
structure.
For demonstration and verification, a boolean parameter is added
to `samples/rust/rust_minimal.rs`.
Assisted-by: Codex:GPT-5
Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
---
rust/kernel/module_param.rs | 9 ++++++++-
rust/macros/lib.rs | 1 +
rust/macros/module.rs | 1 +
samples/rust/rust_minimal.rs | 8 ++++++++
4 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 6a8a7a875643..04ce9eda6731 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -5,7 +5,7 @@
//! C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h)
use crate::prelude::*;
-use crate::str::BStr;
+use crate::str::{kstrtobool_bytes, BStr};
use bindings;
use kernel::sync::SetOnce;
@@ -106,6 +106,12 @@ fn try_from_param_arg(arg: &BStr) -> Result<Self> {
impl_int_module_param!(isize);
impl_int_module_param!(usize);
+impl ModuleParam for bool {
+ fn try_from_param_arg(arg: &BStr) -> Result<Self> {
+ kstrtobool_bytes(arg)
+ }
+}
+
/// A wrapper for kernel parameters.
///
/// This type is instantiated by the [`module!`] macro when module parameters are
@@ -180,3 +186,4 @@ macro_rules! make_param_ops {
make_param_ops!(PARAM_OPS_U64, u64);
make_param_ops!(PARAM_OPS_ISIZE, isize);
make_param_ops!(PARAM_OPS_USIZE, usize);
+make_param_ops!(PARAM_OPS_BOOL, bool);
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 0c36194d9971..95bc3f066b49 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -52,6 +52,7 @@
/// - [`u64`]
/// - [`isize`]
/// - [`usize`]
+/// - [`bool`]
///
/// C header: [`include/linux/moduleparam.h`](srctree/include/linux/moduleparam.h)
///
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index e16298e520c7..feafa0c1623c 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -197,6 +197,7 @@ fn param_ops_path(param_type: &str) -> Path {
"u64" => parse_quote!(::kernel::module_param::PARAM_OPS_U64),
"isize" => parse_quote!(::kernel::module_param::PARAM_OPS_ISIZE),
"usize" => parse_quote!(::kernel::module_param::PARAM_OPS_USIZE),
+ "bool" => parse_quote!(::kernel::module_param::PARAM_OPS_BOOL),
t => panic!("Unsupported parameter type {}", t),
}
}
diff --git a/samples/rust/rust_minimal.rs b/samples/rust/rust_minimal.rs
index 8eb9583571d7..fedf5be1f713 100644
--- a/samples/rust/rust_minimal.rs
+++ b/samples/rust/rust_minimal.rs
@@ -15,6 +15,10 @@
default: 1,
description: "This parameter has a default of 1",
},
+ test_bool_parameter: bool {
+ default: false,
+ description: "This boolean parameter defaults to false",
+ },
},
}
@@ -30,6 +34,10 @@ fn init(_module: &'static ThisModule) -> Result<Self> {
"test_parameter: {}\n",
*module_parameters::test_parameter.value()
);
+ pr_info!(
+ "test_bool_parameter: {}\n",
+ *module_parameters::test_bool_parameter.value()
+ );
let mut numbers = KVec::new();
numbers.push(72, GFP_KERNEL)?;
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox