[PATCH v8 6/6] rust: samples: Add debugfs sample

Matthew Maurer posted 6 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Matthew Maurer 3 months, 1 week ago
Provides an example of using the Rust DebugFS bindings.

Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
 MAINTAINERS                  |  1 +
 samples/rust/Kconfig         | 11 +++++++
 samples/rust/Makefile        |  1 +
 samples/rust/rust_debugfs.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 551e3a6a16d9051a2d58a77614c458287da2bdcc..f55c99cb2907655d109cb1fa248cdec803b5ce9a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7374,6 +7374,7 @@ F:	rust/kernel/devres.rs
 F:	rust/kernel/driver.rs
 F:	rust/kernel/faux.rs
 F:	rust/kernel/platform.rs
+F:	samples/rust/rust_debugfs.rs
 F:	samples/rust/rust_driver_platform.rs
 F:	samples/rust/rust_driver_faux.rs
 
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 7f7371a004ee0a8f67dca99c836596709a70c4fa..01101db41ae31b08a86d048cdd27da8ef9bb23a2 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -62,6 +62,17 @@ config SAMPLE_RUST_DMA
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DEBUGFS
+	tristate "DebugFS Test Module"
+	depends on DEBUG_FS
+	help
+	  This option builds the Rust DebugFS Test module sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_debugfs.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_DRIVER_PCI
 	tristate "PCI Driver"
 	depends on PCI
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index bd2faad63b4f3befe7d1ed5139fe25c7a8b6d7f6..61276222a99f8cc6d7f84c26d0533b30815ebadd 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -4,6 +4,7 @@ ccflags-y += -I$(src)				# needed for trace events
 obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE)		+= rust_misc_device.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
+obj-$(CONFIG_SAMPLE_RUST_DEBUGFS)		+= rust_debugfs.o
 obj-$(CONFIG_SAMPLE_RUST_DMA)			+= rust_dma.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6af9177c711a07764f0323e03a72d115287f1205
--- /dev/null
+++ b/samples/rust/rust_debugfs.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Sample DebugFS exporting module
+
+use core::sync::atomic::{AtomicU32, Ordering};
+use kernel::c_str;
+use kernel::debugfs::{Dir, File};
+use kernel::prelude::*;
+use kernel::sync::{new_mutex, Arc};
+
+module! {
+    type: RustDebugFs,
+    name: "rust_debugfs",
+    authors: ["Matthew Maurer"],
+    description: "Rust DebugFS usage sample",
+    license: "GPL",
+}
+
+struct RustDebugFs {
+    // As we only hold these for drop effect (to remove the directory/files) we have a leading
+    // underscore to indicate to the compiler that we don't expect to use this field directly.
+    _debugfs: Dir,
+    _subdir: Dir,
+    _file: File,
+    _file_2: File,
+}
+
+static EXAMPLE: AtomicU32 = AtomicU32::new(8);
+
+impl kernel::Module for RustDebugFs {
+    fn init(_this: &'static ThisModule) -> Result<Self> {
+        // Create a debugfs directory in the root of the filesystem called "sample_debugfs".
+        let debugfs = Dir::new(c_str!("sample_debugfs"));
+
+        // Create a subdirectory, so "sample_debugfs/subdir" now exists.
+        let sub = debugfs.subdir(c_str!("subdir"));
+
+        // Create a single file in the subdirectory called "example" that will read from the
+        // `EXAMPLE` atomic variable.
+        // We `forget` the result to avoid deleting the file at the end of the scope.
+        let file = sub.fmt_file(c_str!("example"), &EXAMPLE, &|example, f| {
+            writeln!(f, "Reading atomic: {}", example.load(Ordering::Relaxed))
+        });
+        // Now, "sample_debugfs/subdir/example" will print "Reading atomic: 8\n" when read.
+
+        // Change the value in the variable displayed by the file. This is intended to demonstrate
+        // that the module can continue to change the value used by the file.
+        EXAMPLE.store(10, Ordering::Relaxed);
+        // Now, "sample_debugfs/subdir/example" will print "Reading atomic: 10\n" when read.
+
+        // In addition to globals, we can also attach any kind of owned data. Most commonly, this
+        // will look like an `Arc<MyObject>` as those can be shared with the rest of the module.
+        let my_arc = Arc::pin_init(new_mutex!(10), GFP_KERNEL)?;
+        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+        // how to print it
+        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+            writeln!(f, "locked value: {:#010x}", *val.lock())
+        });
+
+        // Since it's an `Arc` and we cloned it, we continue to have access to `my_arc`. If this
+        // were real, we'd probably stash it in our module struct and do something with it when
+        // handling real calls.
+        *my_arc.lock() = 99;
+
+        // Save the handles we want to preserve to our module object. They will be automatically
+        // cleaned up when our module is unloaded.
+        Ok(Self {
+            _debugfs: debugfs,
+            _subdir: sub,
+            _file: file,
+            _file_2: file_2,
+        })
+    }
+}

-- 
2.50.0.727.gbf7dc18ff4-goog
Re: [PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Greg Kroah-Hartman 3 months, 1 week ago
On Fri, Jun 27, 2025 at 11:18:29PM +0000, Matthew Maurer wrote:
> +        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
> +        // how to print it
> +        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
> +            writeln!(f, "locked value: {:#010x}", *val.lock())
> +        });

While cute, is this really going to be the way to describe all "custom"
debugfs function callbacks?  No other way to point to a function itself
instead?  Look at "fun" debugfs functions like qh_lines() in
drivers/usb/host/ehci-dbg.c that is dumping tons of data out.  Putting
that inline here is going to be a bit ackward :)

So can you show an example of a "traditional" debugfs file output with
multiple lines that is dealing with a dynamically allocated device that
is associated with the module (not the static example you showed here),
as that's going to be the real way this is used, not with static
variables.

thanks,

greg k-h
Re: [PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Matthew Maurer 3 months, 1 week ago
On Tue, Jul 1, 2025 at 7:03 AM Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Fri, Jun 27, 2025 at 11:18:29PM +0000, Matthew Maurer wrote:
> > +        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
> > +        // how to print it
> > +        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
> > +            writeln!(f, "locked value: {:#010x}", *val.lock())
> > +        });
>
> While cute, is this really going to be the way to describe all "custom"
> debugfs function callbacks?  No other way to point to a function itself
> instead?  Look at "fun" debugfs functions like qh_lines() in
> drivers/usb/host/ehci-dbg.c that is dumping tons of data out.  Putting
> that inline here is going to be a bit ackward :)

Good news, function pointers are legal to pass in here as well
already, I can add that usage to make it clear.

>
> So can you show an example of a "traditional" debugfs file output with
> multiple lines that is dealing with a dynamically allocated device that
> is associated with the module (not the static example you showed here),
> as that's going to be the real way this is used, not with static
> variables.

Sure, do we want to:
* Finish creating the driver struct early in `init`, then call dynamic
`.create(&str)` or `.destroy(&str)` `.modify(&str)` type things on it
in `init` to show how it would work
* Actually wire up an input source to drive create/destroy/modify
dynamically (e.g. I could implement a miscdevice) - if you want this
one, do you have a preference on where I get my input signal from?

>
> thanks,
>
> greg k-h
Re: [PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Danilo Krummrich 3 months, 1 week ago
On Tue, Jul 01, 2025 at 10:24:04AM -0700, Matthew Maurer wrote:
> On Tue, Jul 1, 2025 at 7:03 AM Greg Kroah-Hartman
> <gregkh@linuxfoundation.org> wrote:
> >
> > On Fri, Jun 27, 2025 at 11:18:29PM +0000, Matthew Maurer wrote:
> > > +        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
> > > +        // how to print it
> > > +        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
> > > +            writeln!(f, "locked value: {:#010x}", *val.lock())
> > > +        });
> >
> > While cute, is this really going to be the way to describe all "custom"
> > debugfs function callbacks?  No other way to point to a function itself
> > instead?  Look at "fun" debugfs functions like qh_lines() in
> > drivers/usb/host/ehci-dbg.c that is dumping tons of data out.  Putting
> > that inline here is going to be a bit ackward :)
> 
> Good news, function pointers are legal to pass in here as well
> already, I can add that usage to make it clear.
> 
> >
> > So can you show an example of a "traditional" debugfs file output with
> > multiple lines that is dealing with a dynamically allocated device that
> > is associated with the module (not the static example you showed here),
> > as that's going to be the real way this is used, not with static
> > variables.
> 
> Sure, do we want to:
> * Finish creating the driver struct early in `init`, then call dynamic
> `.create(&str)` or `.destroy(&str)` `.modify(&str)` type things on it
> in `init` to show how it would work
> * Actually wire up an input source to drive create/destroy/modify
> dynamically (e.g. I could implement a miscdevice) - if you want this
> one, do you have a preference on where I get my input signal from?

I think the idea was to show how it works in a real driver context, e.g. a
platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a
miscdevice registered from a module, which is a rather rare use-case.

If you rebase on the latest driver-core-next, you can write a platform driver
with an ACPI ID table, which can easily probed by passing
`-acpitable file=ssdt.aml` to qemu, i.e. no need to mess with OF.
Re: [PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Matthew Maurer 3 months, 1 week ago
On Tue, Jul 1, 2025 at 10:34 AM Danilo Krummrich <dakr@kernel.org> wrote:
>
> On Tue, Jul 01, 2025 at 10:24:04AM -0700, Matthew Maurer wrote:
> > On Tue, Jul 1, 2025 at 7:03 AM Greg Kroah-Hartman
> > <gregkh@linuxfoundation.org> wrote:
> > >
> > > On Fri, Jun 27, 2025 at 11:18:29PM +0000, Matthew Maurer wrote:
> > > > +        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
> > > > +        // how to print it
> > > > +        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
> > > > +            writeln!(f, "locked value: {:#010x}", *val.lock())
> > > > +        });
> > >
> > > While cute, is this really going to be the way to describe all "custom"
> > > debugfs function callbacks?  No other way to point to a function itself
> > > instead?  Look at "fun" debugfs functions like qh_lines() in
> > > drivers/usb/host/ehci-dbg.c that is dumping tons of data out.  Putting
> > > that inline here is going to be a bit ackward :)
> >
> > Good news, function pointers are legal to pass in here as well
> > already, I can add that usage to make it clear.
> >
> > >
> > > So can you show an example of a "traditional" debugfs file output with
> > > multiple lines that is dealing with a dynamically allocated device that
> > > is associated with the module (not the static example you showed here),
> > > as that's going to be the real way this is used, not with static
> > > variables.
> >
> > Sure, do we want to:
> > * Finish creating the driver struct early in `init`, then call dynamic
> > `.create(&str)` or `.destroy(&str)` `.modify(&str)` type things on it
> > in `init` to show how it would work
> > * Actually wire up an input source to drive create/destroy/modify
> > dynamically (e.g. I could implement a miscdevice) - if you want this
> > one, do you have a preference on where I get my input signal from?
>
> I think the idea was to show how it works in a real driver context, e.g. a
> platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a
> miscdevice registered from a module, which is a rather rare use-case.
>
> If you rebase on the latest driver-core-next, you can write a platform driver
> with an ACPI ID table, which can easily probed by passing
> `-acpitable file=ssdt.aml` to qemu, i.e. no need to mess with OF.

I'm confused as to how registering as a platform driver would result
in an input source that would let me trigger the creation/destruction
of DebugFS files. I need some kind of input stream to do that. Is
there some input stream that's available to a platform driver that I'm
missing, or are you suggesting that the input stream would effectively
be the probe's `id_info` field? If I did that, wouldn't I still have a
static arrangement of DebugFS files in my driver struct?

I could have misunderstood, but I don't think that's what Greg is
asking for - I think he wants to see how at a data structure level, I
can handle creating and destroying DebugFS files that correspond to
some kind of object being created and destroyed, rather than just
having a static list of slots in my driver struct for keeping them
alive.
Re: [PATCH v8 6/6] rust: samples: Add debugfs sample
Posted by Danilo Krummrich 3 months, 1 week ago
On Tue, Jul 01, 2025 at 11:32:42AM -0700, Matthew Maurer wrote:
> On Tue, Jul 1, 2025 at 10:34 AM Danilo Krummrich <dakr@kernel.org> wrote:
> >
> > On Tue, Jul 01, 2025 at 10:24:04AM -0700, Matthew Maurer wrote:
> > > On Tue, Jul 1, 2025 at 7:03 AM Greg Kroah-Hartman
> > > <gregkh@linuxfoundation.org> wrote:
> > > >
> > > > On Fri, Jun 27, 2025 at 11:18:29PM +0000, Matthew Maurer wrote:
> > > > > +        // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
> > > > > +        // how to print it
> > > > > +        let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
> > > > > +            writeln!(f, "locked value: {:#010x}", *val.lock())
> > > > > +        });
> > > >
> > > > While cute, is this really going to be the way to describe all "custom"
> > > > debugfs function callbacks?  No other way to point to a function itself
> > > > instead?  Look at "fun" debugfs functions like qh_lines() in
> > > > drivers/usb/host/ehci-dbg.c that is dumping tons of data out.  Putting
> > > > that inline here is going to be a bit ackward :)
> > >
> > > Good news, function pointers are legal to pass in here as well
> > > already, I can add that usage to make it clear.
> > >
> > > >
> > > > So can you show an example of a "traditional" debugfs file output with
> > > > multiple lines that is dealing with a dynamically allocated device that
> > > > is associated with the module (not the static example you showed here),
> > > > as that's going to be the real way this is used, not with static
> > > > variables.
> > >
> > > Sure, do we want to:
> > > * Finish creating the driver struct early in `init`, then call dynamic
> > > `.create(&str)` or `.destroy(&str)` `.modify(&str)` type things on it
> > > in `init` to show how it would work
> > > * Actually wire up an input source to drive create/destroy/modify
> > > dynamically (e.g. I could implement a miscdevice) - if you want this
> > > one, do you have a preference on where I get my input signal from?
> >
> > I think the idea was to show how it works in a real driver context, e.g. a
> > platform driver, just like what samples/rust/rust_driver_platform.rs does. Not a
> > miscdevice registered from a module, which is a rather rare use-case.
> >
> > If you rebase on the latest driver-core-next, you can write a platform driver
> > with an ACPI ID table, which can easily probed by passing
> > `-acpitable file=ssdt.aml` to qemu, i.e. no need to mess with OF.
> 
> I'm confused as to how registering as a platform driver would result
> in an input source that would let me trigger the creation/destruction
> of DebugFS files. I need some kind of input stream to do that. Is
> there some input stream that's available to a platform driver that I'm
> missing, or are you suggesting that the input stream would effectively
> be the probe's `id_info` field? If I did that, wouldn't I still have a
> static arrangement of DebugFS files in my driver struct?

If it's about having some dynamic input stream, creating an example with a
platform driver clearly doesn't help by itself.

But that wasn't my understanding. My understanding was that the request is to
show it in a driver context, where you won't get away with statics. :)

But that's maybe because *I* would like to focus more on this case, because it's
the common one.

> I could have misunderstood, but I don't think that's what Greg is
> asking for - I think he wants to see how at a data structure level, I
> can handle creating and destroying DebugFS files that correspond to
> some kind of object being created and destroyed, rather than just
> having a static list of slots in my driver struct for keeping them
> alive.

If that's the request, you could simply create a function that returns a
Vec with some random entries and then you just iterate over it in
Driver::probe() or Module::init()?

I don't know if that case is too interesting, since conceptually it doesn't make
a huge difference to the case where you have a single instance, i.e.

Current
-------

	struct Bar {
	   a: A,
	   b: B,
	}

	// Single.
	struct Foo {
	   bar: Arc<Bar>,
	   bar_file: File,
	}

	// Multiple.
	struct Foo {
	  bars: Vec<Arc<Bar>>
	  bar_files: Vec<File>, // contains a File for each field for every Bar
	}

Proposed (pin-init)
-------------------

	struct Bar {
	   a: File<A>,
	   b: File<B>,
	}

	// Single.
	struct Foo {
	   bar: Bar,
	}

	// Multiple.
	struct Foo {
	   bar: Vec<Bar>
	}