[PATCH RFC 3/4] samples: rust: add Rust serial device bus sample device driver

Markus Probst posted 4 patches 1 month, 2 weeks ago
[PATCH RFC 3/4] samples: rust: add Rust serial device bus sample device driver
Posted by Markus Probst 1 month, 2 weeks ago
Add a sample Rust serial device bus device driver illustrating the usage
of the platform bus abstractions.

This drivers probes through either a match of device / driver name or a
match within the OF ID table.
---
 samples/rust/Kconfig               |  10 +++
 samples/rust/Makefile              |   1 +
 samples/rust/rust_driver_serdev.rs | 175 +++++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+)

diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 3efa51bfc8ef..3b6663b4bc9b 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -161,6 +161,16 @@ config SAMPLE_RUST_DRIVER_AUXILIARY
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_SERDEV
+	tristate "Serial Device Bus Device Driver"
+	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_HOSTPROGS
 	bool "Host programs"
 	help
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index f65885d1d62b..ec5cb8065fb7 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
 
 rust_print-y := rust_print_main.o rust_print_events.o
diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs
new file mode 100644
index 000000000000..f23b38a26c32
--- /dev/null
+++ b/samples/rust/rust_driver_serdev.rs
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust Serial device bus device driver sample.
+
+use kernel::{
+    acpi,
+    device::{
+        self,
+        property::{
+            FwNodeReferenceArgs,
+            NArgs, //
+        },
+        Bound,
+        Core, //
+    },
+    of,
+    prelude::*,
+    serdev,
+    str::CString,
+    sync::aref::ARef, //
+};
+
+struct SampleDriver {
+    sdev: ARef<serdev::Device>,
+}
+
+struct Info(u32);
+
+kernel::of_device_table!(
+    OF_TABLE,
+    MODULE_OF_TABLE,
+    <SampleDriver as serdev::Driver>::IdInfo,
+    [(of::DeviceId::new(c"test,rust_driver_serdev"), Info(42))]
+);
+
+kernel::acpi_device_table!(
+    ACPI_TABLE,
+    MODULE_ACPI_TABLE,
+    <SampleDriver as serdev::Driver>::IdInfo,
+    [(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))]
+);
+
+#[vtable]
+impl serdev::Driver for SampleDriver {
+    type IdInfo = Info;
+    type InitialData = ();
+    type LateProbeData = ();
+    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, 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 let Some(info) = info {
+            dev_info!(dev, "Probed with info: '{}'.\n", info.0);
+        }
+
+        if dev.fwnode().is_some_and(|node| node.is_of_node()) {
+            Self::properties_parse(dev)?;
+        }
+
+        Ok(Self { sdev: sdev.into() })
+    }
+
+    fn configure(
+        sdev: &serdev::Device<Core>,
+        _this: Pin<&Self>,
+        _id_info: Option<&Self::IdInfo>,
+    ) -> Result {
+        dev_dbg!(
+            sdev.as_ref(),
+            "Configure Rust Serial device bus device driver sample.\n"
+        );
+
+        sdev.set_baudrate(115200);
+        sdev.set_flow_control(false);
+        sdev.set_parity(serdev::Parity::None)?;
+        Ok(())
+    }
+
+    fn late_probe(
+        sdev: &serdev::Device<Bound>,
+        _this: Pin<&Self>,
+        _initial_data: Self::InitialData,
+    ) -> impl PinInit<Self::LateProbeData, Error> {
+        dev_dbg!(
+            sdev.as_ref(),
+            "Late Probe Rust Serial device bus device driver sample.\n"
+        );
+        Ok(())
+    }
+
+    fn receive(
+        sdev: &serdev::Device<Bound>,
+        _this: Pin<&Self>,
+        _late_probe_this: Pin<&Self::LateProbeData>,
+        data: &[u8],
+    ) -> usize {
+        let _ = sdev.write_all(data, serdev::Timeout::MaxScheduleTimeout);
+        data.len()
+    }
+}
+
+impl SampleDriver {
+    fn properties_parse(dev: &device::Device) -> Result {
+        let fwnode = dev.fwnode().ok_or(ENOENT)?;
+
+        if let Ok(idx) = fwnode.property_match_string(c"compatible", c"test,rust-device") {
+            dev_info!(dev, "matched compatible string idx = {}\n", idx);
+        }
+
+        let name = c"compatible";
+        let prop = fwnode.property_read::<CString>(name).required_by(dev)?;
+        dev_info!(dev, "'{name}'='{prop:?}'\n");
+
+        let name = c"test,bool-prop";
+        let prop = fwnode.property_read_bool(c"test,bool-prop");
+        dev_info!(dev, "'{name}'='{prop}'\n");
+
+        if fwnode.property_present(c"test,u32-prop") {
+            dev_info!(dev, "'test,u32-prop' is present\n");
+        }
+
+        let name = c"test,u32-optional-prop";
+        let prop = fwnode.property_read::<u32>(name).or(0x12);
+        dev_info!(dev, "'{name}'='{prop:#x}' (default = 0x12)\n");
+
+        // A missing required property will print an error. Discard the error to
+        // prevent properties_parse from failing in that case.
+        let name = c"test,u32-required-prop";
+        let _ = fwnode.property_read::<u32>(name).required_by(dev);
+
+        let name = c"test,u32-prop";
+        let prop: u32 = fwnode.property_read(name).required_by(dev)?;
+        dev_info!(dev, "'{name}'='{prop:#x}'\n");
+
+        let name = c"test,i16-array";
+        let prop: [i16; 4] = fwnode.property_read(name).required_by(dev)?;
+        dev_info!(dev, "'{name}'='{prop:?}'\n");
+        let len = fwnode.property_count_elem::<u16>(name)?;
+        dev_info!(dev, "'{name}' length is {len}\n");
+
+        let name = c"test,i16-array";
+        let prop: KVec<i16> = fwnode.property_read_array_vec(name, 4)?.required_by(dev)?;
+        dev_info!(dev, "'{name}'='{prop:?}' (KVec)\n");
+
+        for child in fwnode.children() {
+            let name = c"test,ref-arg";
+            let nargs = NArgs::N(2);
+            let prop: FwNodeReferenceArgs = child.property_get_reference_args(name, nargs, 0)?;
+            dev_info!(dev, "'{name}'='{prop:?}'\n");
+        }
+
+        Ok(())
+    }
+}
+
+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.51.2
Re: [PATCH RFC 3/4] samples: rust: add Rust serial device bus sample device driver
Posted by Dirk Behme 1 month, 2 weeks ago
Hi Markus,

On 20.12.25 19:44, Markus Probst wrote:
> Add a sample Rust serial device bus device driver illustrating the usage
> of the platform bus abstractions.
> 
> This drivers probes through either a match of device / driver name or a
> match within the OF ID table.
> ---
>  samples/rust/Kconfig               |  10 +++
>  samples/rust/Makefile              |   1 +
>  samples/rust/rust_driver_serdev.rs | 175 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 186 insertions(+)
...
> diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs
> new file mode 100644
> index 000000000000..f23b38a26c32
> --- /dev/null
> +++ b/samples/rust/rust_driver_serdev.rs
> @@ -0,0 +1,175 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Rust Serial device bus device driver sample.
> +
> +use kernel::{
> +    acpi,
> +    device::{
> +        self,
> +        property::{
> +            FwNodeReferenceArgs,
> +            NArgs, //
> +        },
> +        Bound,
> +        Core, //
> +    },
> +    of,
> +    prelude::*,
> +    serdev,
> +    str::CString,
> +    sync::aref::ARef, //
> +};
> +
> +struct SampleDriver {
> +    sdev: ARef<serdev::Device>,
> +}
> +
> +struct Info(u32);
> +
> +kernel::of_device_table!(
> +    OF_TABLE,
> +    MODULE_OF_TABLE,
> +    <SampleDriver as serdev::Driver>::IdInfo,
> +    [(of::DeviceId::new(c"test,rust_driver_serdev"), Info(42))]

I stopped reading here regarding the new "rust_driver_serdev" but
re-reading Rob's

https://lore.kernel.org/rust-for-linux/20241022234712.GB1848992-robh@kernel.org/

adding "test,<whatever>" should be fine as-is without any documenation.

> +);
> +
> +kernel::acpi_device_table!(
> +    ACPI_TABLE,
> +    MODULE_ACPI_TABLE,
> +    <SampleDriver as serdev::Driver>::IdInfo,
> +    [(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))]
> +);
> +
> +#[vtable]
> +impl serdev::Driver for SampleDriver {
> +    type IdInfo = Info;
> +    type InitialData = ();
> +    type LateProbeData = ();
> +    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, 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 let Some(info) = info {
> +            dev_info!(dev, "Probed with info: '{}'.\n", info.0);
> +        }

Last time I had a look to the log output from rust_driver_platform.rs
(where this is copied from?) I was slightly confused to see the
"Probed with ..." in the log but not the "Probe Rust ...". Well, I
hadn't DEBUG enabled. So I wonder if the combination of `dev_dbg!()`
and `dev_info!()` this way should be improved? At least in
rust_driver_platform.rs because we could drop that here completely? I
mean in rust_driver_platform.rs it makes sense to demonstrate how
`info` is supposed to work. But do we need that here?


> +        if dev.fwnode().is_some_and(|node| node.is_of_node()) {
> +            Self::properties_parse(dev)?;
> +        }


This is a left over from copy & paste? I mean having all this
`properties_parse()` below and calling it here does not make any sense
here? And should be dropped completely?


> +
> +        Ok(Self { sdev: sdev.into() })
> +    }
> +
> +    fn configure(
> +        sdev: &serdev::Device<Core>,
> +        _this: Pin<&Self>,
> +        _id_info: Option<&Self::IdInfo>,
> +    ) -> Result {
> +        dev_dbg!(
> +            sdev.as_ref(),
> +            "Configure Rust Serial device bus device driver sample.\n"
> +        );
> +
> +        sdev.set_baudrate(115200);
> +        sdev.set_flow_control(false);
> +        sdev.set_parity(serdev::Parity::None)?;
> +        Ok(())
> +    }
> +
> +    fn late_probe(
> +        sdev: &serdev::Device<Bound>,
> +        _this: Pin<&Self>,
> +        _initial_data: Self::InitialData,
> +    ) -> impl PinInit<Self::LateProbeData, Error> {
> +        dev_dbg!(
> +            sdev.as_ref(),
> +            "Late Probe Rust Serial device bus device driver sample.\n"
> +        );
> +        Ok(())
> +    }
> +
> +    fn receive(
> +        sdev: &serdev::Device<Bound>,
> +        _this: Pin<&Self>,
> +        _late_probe_this: Pin<&Self::LateProbeData>,
> +        data: &[u8],
> +    ) -> usize {
> +        let _ = sdev.write_all(data, serdev::Timeout::MaxScheduleTimeout);


Is it intended to have a function with the name `receive()`calling
`write()`?

> +        data.len()
> +    }
> +}
> +
> +impl SampleDriver {
> +    fn properties_parse(dev: &device::Device) -> Result {


As mentioned above I think this is a left over from copy & paste and
should be dropped?

Cheers,

Dirk
Re: [PATCH RFC 3/4] samples: rust: add Rust serial device bus sample device driver
Posted by Markus Probst 1 month, 2 weeks ago
Hi Dirk,

On Sun, 2025-12-21 at 10:11 +0100, Dirk Behme wrote:
> Hi Markus,
> 
> On 20.12.25 19:44, Markus Probst wrote:
> > Add a sample Rust serial device bus device driver illustrating the usage
> > of the platform bus abstractions.
> > 
> > This drivers probes through either a match of device / driver name or a
> > match within the OF ID table.
> > ---
> >  samples/rust/Kconfig               |  10 +++
> >  samples/rust/Makefile              |   1 +
> >  samples/rust/rust_driver_serdev.rs | 175 +++++++++++++++++++++++++++++++++++++
> >  3 files changed, 186 insertions(+)
> ...
> > diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs
> > new file mode 100644
> > index 000000000000..f23b38a26c32
> > --- /dev/null
> > +++ b/samples/rust/rust_driver_serdev.rs
> > @@ -0,0 +1,175 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +//! Rust Serial device bus device driver sample.
> > +
> > +use kernel::{
> > +    acpi,
> > +    device::{
> > +        self,
> > +        property::{
> > +            FwNodeReferenceArgs,
> > +            NArgs, //
> > +        },
> > +        Bound,
> > +        Core, //
> > +    },
> > +    of,
> > +    prelude::*,
> > +    serdev,
> > +    str::CString,
> > +    sync::aref::ARef, //
> > +};
> > +
> > +struct SampleDriver {
> > +    sdev: ARef<serdev::Device>,
> > +}
> > +
> > +struct Info(u32);
> > +
> > +kernel::of_device_table!(
> > +    OF_TABLE,
> > +    MODULE_OF_TABLE,
> > +    <SampleDriver as serdev::Driver>::IdInfo,
> > +    [(of::DeviceId::new(c"test,rust_driver_serdev"), Info(42))]
> 
> I stopped reading here regarding the new "rust_driver_serdev" but
> re-reading Rob's
> 
> https://lore.kernel.org/rust-for-linux/20241022234712.GB1848992-robh@kernel.org/
> 
> adding "test,<whatever>" should be fine as-is without any documenation.
> 
> > +);
> > +
> > +kernel::acpi_device_table!(
> > +    ACPI_TABLE,
> > +    MODULE_ACPI_TABLE,
> > +    <SampleDriver as serdev::Driver>::IdInfo,
> > +    [(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))]
> > +);
> > +
> > +#[vtable]
> > +impl serdev::Driver for SampleDriver {
> > +    type IdInfo = Info;
> > +    type InitialData = ();
> > +    type LateProbeData = ();
> > +    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, 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 let Some(info) = info {
> > +            dev_info!(dev, "Probed with info: '{}'.\n", info.0);
> > +        }
> 
> Last time I had a look to the log output from rust_driver_platform.rs
> (where this is copied from?) I was slightly confused to see the
> "Probed with ..." in the log but not the "Probe Rust ...". Well, I
> hadn't DEBUG enabled. So I wonder if the combination of `dev_dbg!()`
> and `dev_info!()` this way should be improved? At least in
> rust_driver_platform.rs because we could drop that here completely? I
> mean in rust_driver_platform.rs it makes sense to demonstrate how
> `info` is supposed to work. But do we need that here?
> 
> 
> > +        if dev.fwnode().is_some_and(|node| node.is_of_node()) {
> > +            Self::properties_parse(dev)?;
> > +        }
> 
> 
> This is a left over from copy & paste? I mean having all this
> `properties_parse()` below and calling it here does not make any sense
> here? And should be dropped completely?
It was meant to show, that this is the function in which fwnode
properties should be parsed. As I did indeed base the sample on the
platform driver sample, I left it as-is. I will minimize it down to 1
property without an extra function, given that the platform driver
sample already shows how to use properties.
> 
> 
> > +
> > +        Ok(Self { sdev: sdev.into() })
> > +    }
> > +
> > +    fn configure(
> > +        sdev: &serdev::Device<Core>,
> > +        _this: Pin<&Self>,
> > +        _id_info: Option<&Self::IdInfo>,
> > +    ) -> Result {
> > +        dev_dbg!(
> > +            sdev.as_ref(),
> > +            "Configure Rust Serial device bus device driver sample.\n"
> > +        );
> > +
> > +        sdev.set_baudrate(115200);
> > +        sdev.set_flow_control(false);
> > +        sdev.set_parity(serdev::Parity::None)?;
> > +        Ok(())
> > +    }
> > +
> > +    fn late_probe(
> > +        sdev: &serdev::Device<Bound>,
> > +        _this: Pin<&Self>,
> > +        _initial_data: Self::InitialData,
> > +    ) -> impl PinInit<Self::LateProbeData, Error> {
> > +        dev_dbg!(
> > +            sdev.as_ref(),
> > +            "Late Probe Rust Serial device bus device driver sample.\n"
> > +        );
> > +        Ok(())
> > +    }
> > +
> > +    fn receive(
> > +        sdev: &serdev::Device<Bound>,
> > +        _this: Pin<&Self>,
> > +        _late_probe_this: Pin<&Self::LateProbeData>,
> > +        data: &[u8],
> > +    ) -> usize {
> > +        let _ = sdev.write_all(data, serdev::Timeout::MaxScheduleTimeout);
> 
> 
> Is it intended to have a function with the name `receive()`calling
> `write()`?
This sample driver echos any data it receives back to the serial
device.

It shows how to receive data from the serial device and how to send
data to the serial device.

It depends on the driver, if it is necessary to call write inside
receive. The serial device may want a reply or ack from the driver
after it sent a message.

> 
> > +        data.len()
> > +    }
> > +}
> > +
> > +impl SampleDriver {
> > +    fn properties_parse(dev: &device::Device) -> Result {
> 
> 
> As mentioned above I think this is a left over from copy & paste and
> should be dropped?
> 
> Cheers,
> 
> Dirk

Thanks
- Markus Probst