Skip to contentSkip to conversation

rust: DebugFS Bindings

PATCH Superseded Matthew Maurer 4 participants 17 messages Aug 19, 2025 – Aug 26, 2025

Subsystems (2)

v10 of 11 Superseded Latest is v11 →

lore
Share & tools
lore
#0 lore raw

v10 cover rust: DebugFS Bindings

This series provides safe DebugFS bindings for Rust, with sample modules using them.

Shortly after this is sent, you will see a real driver WIP using this implenting the qcom socinfo driver.

---
Changes in v10:
- Introduced Scoped to show how either a File or Dir can be bound to
data
- Remove use of use<> for MSRV compatibility
- Added Write support
- Added more complex sample driver using scoped interface
- Updated original sample driver to use writes to drive mutation
- Added FileOps<T> (only for DebugFS, not a kernel-wide abstraction)
to decrease needed unsafe for keeping vtables paired to types.
- Centralized dentry lifecycle management to entry.rs.
- Link to v9: https://lore.kernel.org/r/[email protected]

Changes in v9:
- Switched to PinInit backing instead of ForeignOwnable
- Changed sample to be a platform driver
- Exported a static property
- Demonstrated runtime mutation in platform driver (inc_counter)
- Demonstrated how driver code would interact with data structures
exported through DebugFS (Wrapper)
- Link to v8: https://lore.kernel.org/r/[email protected]

Changes in v8:
- Switched from casts to core::from_{ref, mut} in type change
- Link to v7: https://lore.kernel.org/r/[email protected]

Changes in v7:
- Rewrote entry::Entry -> Entry
- Use c_int and c_void from kernel prelude rather than core
- Removed unnecessary display_open cast
- Switched from Deref + an explicit box to ForeignOwnable for
attaching owned data.
- Made &'static and &'static mut implement ForeignOwnable
- Swapped "driver" to "module" in sample code
- Link to v6: https://lore.kernel.org/r/[email protected]

Changes in v6:
- Replaced explicit lifetimes with children keeping their parents alive.
- Added support for attaching owned data.
- Removed recomendation to only keep root handles and handles you want
to delete around.
- Refactored some code into separate files to improve clarity.
- Link to v5: https://lore.kernel.org/r/[email protected]

Changes in v5:
- Made Dir + File wrappers around Entry
- All functions return owning handles. To discard without drop, use forget. To keep a handle without drop, use ManuallyDrop.
- Fixed bugs around not(CONFIG_DEBUG_FS)
- Removed unnecessary addr_of!
- Link to v4: https://lore.kernel.org/r/[email protected]

Changes in v4:
- Remove SubDir, replace with type-level constant.
- Add lifetime to Dir to prevent subdirectories and files from outliving their parents and triggering an Oops when accessed.
- Split unsafe blocks with two calls into two blocks
- Access private field through direct pointer dereference, avoiding creation of a reference to it.
- Notably not changed - owning/non-owning handle defaults. The best read
I had from the thread was to continue with this mode, but I'm willing
to change if need be.
- Comment changes
- More comment markdown
- Remove scopes from examples
- Put as_ptr properties into a # Guarantees section.
- Link to v3: https://lore.kernel.org/r/[email protected]

Changes in v3:
- Split Dir into Dir/SubDir/File to improve API.
- Add "." to end of all comments.
- Convert INVARIANT to # Invariants on types.
- Add backticks everywhere I found variables/types in my comments.
- Promoted invariant comment to doc comment.
- Extended sample commenting to make it clearer what is happening.
- Link to v2: https://lore.kernel.org/r/[email protected]

Changes in v2:
- Drop support for builder / pinned bindings in initial series
- Remove ARef usage to abstract the dentry nature of handles
- Remove error handling to discourage users from caring whether DebugFS
is enabled.
- Support CONFIG_DEBUG_FS=n while leaving the API available
- Fixed mistaken decimal/octal mixup
- Doc/comment cleanup
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Matthew Maurer (7):
rust: debugfs: Add initial support for directories
rust: debugfs: Add support for read-only files
rust: debugfs: Add support for writable files
rust: debugfs: Add support for callback-based files
samples: rust: Add debugfs sample driver
rust: debugfs: Add support for scoped directories
samples: rust: Add scoped debugfs sample driver

 MAINTAINERS                              |   4 +
 rust/bindings/bindings_helper.h          |   1 +
 rust/kernel/debugfs.rs                   | 613 +++++++++++++++++++++++++++++++
 rust/kernel/debugfs/callback_adapters.rs | 122 ++++++
 rust/kernel/debugfs/entry.rs             | 164 +++++++++
 rust/kernel/debugfs/file_ops.rs          | 246 +++++++++++++
 rust/kernel/debugfs/traits.rs            |  97 +++++
 rust/kernel/lib.rs                       |   1 +
 samples/rust/Kconfig                     |  22 ++
 samples/rust/Makefile                    |   2 +
 samples/rust/rust_debugfs.rs             | 151 ++++++++
 samples/rust/rust_scoped_debugfs.rs      | 134 +++++++
 12 files changed, 1557 insertions(+)

---
base-commit: f3c5631f70e434e318c44001e2417d4770f06cd0
change-id: 20250428-debugfs-rust-3cd5c97eb7d1

Best regards,

Signature · 1 line
Matthew Maurer <[email protected]>
#1 lore raw

PATCH v10 1/7 rust: debugfs: Add initial support for directories

Adds a debugfs::Dir type that can be used to create and remove DebugFS directories. The Dir handle automatically cleans up the directory on Drop.

---
 MAINTAINERS                     |  2 +
 rust/bindings/bindings_helper.h |  1 +
 rust/kernel/debugfs.rs          | 82 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/entry.rs    | 61 ++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |  1 +
 5 files changed, 147 insertions(+)
MAINTAINERS +2 -0
index daf520a13bdf6a991c0160a96620f40308c29ee0..8f2dbf71ca3f8f97e4d7619375279ed11d1261b2 100644
@@ -7472,6 +7472,8 @@ F:	include/linux/kobj*
 F:	include/linux/property.h
 F:	include/linux/sysfs.h
 F:	lib/kobj*
+F:	rust/kernel/debugfs.rs
+F:	rust/kernel/debugfs/
 F:	rust/kernel/device.rs
 F:	rust/kernel/device/
 F:	rust/kernel/device_id.rs
rust/bindings/bindings_helper.h +1 -0
index 84d60635e8a9baef1f1a1b2752dc0fa044f8542f..e847820dc807fdda2d682d496a3c6361bb944c10 100644
@@ -46,6 +46,7 @@
 #include <linux/cpufreq.h>
 #include <linux/cpumask.h>
 #include <linux/cred.h>
+#include <linux/debugfs.h>
 #include <linux/device/faux.h>
 #include <linux/dma-mapping.h>
 #include <linux/errname.h>
rust/kernel/debugfs.rs new file +82 -0

82 lines not on this page · raw message

rust/kernel/debugfs/entry.rs new file +61 -0

61 lines not on this page · raw message

rust/kernel/lib.rs +1 -0
index ed53169e795c0badf548025a57f946fa18bc73e3..828620c8441566a638f31d03633fc1bf4c1bda85 100644
@@ -76,6 +76,7 @@
 pub mod cpufreq;
 pub mod cpumask;
 pub mod cred;
+pub mod debugfs;
 pub mod device;
 pub mod device_id;
 pub mod devres;
Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
maintainer
#2 lore raw

Re: [PATCH v10 1/7] rust: debugfs: Add initial support for directories

rust/kernel/debugfs.rs line 24

Quoted 3 lines · Matthew Maurer
rust/kernel/debugfs.rs new file +3 -0
+/// Owning handle to a DebugFS directory.
+///
+/// This directory will be removed when this handle has been dropped *and* all children have been

I'd say "The directory in the filesystem represented by [Dir] will be removed..." to be extra clear.

rust/kernel/debugfs.rs line 27

Quoted 3 lines
rust/kernel/debugfs.rs new file +3 -0
+/// removed.
+// We hold a reference to our parent if it exists in the `Entry` to prevent the dentry we point
+// to from being cleaned up when our parent is removed.

Please check punctuation, I think this is a bit hard to parse.

#3 lore raw

PATCH v10 2/7 rust: debugfs: Add support for read-only files

In reply to Matthew Maurer

Extends the debugfs API to support creating read-only files. This is done via the Dir::read_only_file method, which takes a data object that implements the Render trait.

The file's content is generated by the Render implementation, and the file is automatically removed when the returned File handle is dropped.

---
 rust/kernel/debugfs.rs          | 142 +++++++++++++++++++++++++++++++++++++++-
 rust/kernel/debugfs/entry.rs    |  42 ++++++++++++
 rust/kernel/debugfs/file_ops.rs | 125 +++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/traits.rs   |  28 ++++++++
 4 files changed, 336 insertions(+), 1 deletion(-)
rust/kernel/debugfs.rs +141 -1
index 893aee54b920bac80f77c2726567da76929b7244..875d433fc3608cc9ffcf022d7c00cb207016f146 100644
@@ -8,12 +8,18 @@
 // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful.
 #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))]
 
-#[cfg(CONFIG_DEBUG_FS)]
 use crate::prelude::*;
 use crate::str::CStr;
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
+use core::marker::PhantomPinned;
+use core::ops::Deref;
+
+mod traits;
+pub use traits::Render;
 
+mod file_ops;
+use file_ops::{FileOps, ReadFile};
 #[cfg(CONFIG_DEBUG_FS)]
 mod entry;
 #[cfg(CONFIG_DEBUG_FS)]
@@ -53,6 +59,31 @@ fn create(name: &CStr, parent: Option<&Dir>) -> Self {
         Self()
     }
 
+    /// Creates a DebugFS file which will own the data produced by the initializer provided in
+    /// `data`.
+    fn create_file<'a, T: Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+        file_ops: &'static FileOps<T>,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let scope = Scope::<T>::new(data, move |data| {
+            #[cfg(CONFIG_DEBUG_FS)]
+            if let Some(parent) = &self.0 {
+                // SAFETY: Because data derives from a scope, and our entry will be dropped before
+                // the data is dropped, it is guaranteed to outlive the entry we return.
+                unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }
+            } else {
+                Entry::empty()
+            }
+        });
+        try_pin_init! {
+            File {
+                scope <- scope
+            } ? E
+        }
+    }
+
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
@@ -79,4 +110,113 @@ pub fn new(name: &CStr) -> Self {
     pub fn subdir(&self, name: &CStr) -> Self {
         Dir::create(name, Some(self))
     }
+
+    /// Creates a read-only file in this directory.
+    ///
+    /// The file's contents are produced by invoking [`Render::render`] on the value initialized by
+    /// `data`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// # use kernel::prelude::*;
+    /// # let dir = Dir::new(c_str!("my_debugfs_dir"));
+    /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;
+    /// // "my_debugfs_dir/foo" now contains the number 200.
+    /// // The file is removed when `file` is dropped.
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = &<T as ReadFile<_>>::FILE_OPS;
+        self.create_file(name, data, file_ops)
+    }
+}
+
+#[pin_data]
+/// Handle to a DebugFS scope, which allows a variety of DebugFS files/directories to hang off a
+/// single structure.
+pub struct Scope<T> {
+    // This order is load-bearing for drops - `_entry` must be dropped before `data`.
+    #[cfg(CONFIG_DEBUG_FS)]
+    _entry: Entry,
+    #[pin]
+    data: T,
+    // Even if `T` is `Unpin`, we still can't allow it to be moved.
+    #[pin]
+    _pin: PhantomPinned,
+}
+
+#[pin_data]
+/// Handle to a DebugFS file, owning its backing data.
+///
+/// When dropped, the DebugFS file will be removed and the attached data will be dropped.
+pub struct File<T> {
+    #[pin]
+    scope: Scope<T>,
+}
+
+#[cfg(not(CONFIG_DEBUG_FS))]
+impl<T> Scope<T> {
+    fn new<E, TI: PinInit<T, E>, F: for<'a> FnOnce(&'a T)>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> {
+        try_pin_init! {
+            Self {
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {
+            init(&scope.data);
+            Ok(())
+        })
+    }
+}
+
+#[cfg(CONFIG_DEBUG_FS)]
+impl<T> Scope<T> {
+    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry {
+        // SAFETY: _entry is not structurally pinned
+        unsafe { &mut Pin::into_inner_unchecked(self)._entry }
+    }
+    fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry + 'b>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> + 'b
+    where
+        T: 'b,
+    {
+        try_pin_init! {
+            Self {
+                _entry: Entry::empty(),
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {
+            *scope.entry_mut() = init(&scope.data);
+            Ok(())
+        })
+    }
+}
+
+impl<T> Deref for Scope<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.data
+    }
+}
+
+impl<T> Deref for File<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.scope
+    }
 }
rust/kernel/debugfs/entry.rs +42 -0

54 lines not on this page · raw message

rust/kernel/debugfs/file_ops.rs new file +125 -0

125 lines not on this page · raw message

rust/kernel/debugfs/traits.rs new file +28 -0
new file mode 100644
index 0000000000000000000000000000000000000000..2939e18e3dda39571cd7255505e5f605f0e3d154
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+//! Traits for rendering or updating values exported to DebugFS.
+
+use crate::sync::Mutex;
+use core::fmt::{self, Debug, Formatter};
+
+/// A trait for types that can be rendered into a string.
+///
+/// This works very similarly to `Debug`, and is automatically implemented if `Debug` is
+/// implemented for a type. It is also implemented for any renderable type inside a `Mutex`.
+pub trait Render {
+    /// Formats the value using the given formatter.
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result;
+}
+
+impl<T: Render> Render for Mutex<T> {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        self.lock().render(f)
+    }
+}
+
+impl<T: Debug> Render for T {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        writeln!(f, "{self:?}")
+    }
+}
Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
maintainer
#4 lore raw

Re: [PATCH v10 2/7] rust: debugfs: Add support for read-only files

rust/kernel/debugfs.rs fn create(name: &CStr, parent: Option<&Dir>) -> Self { · line 67

Quoted 6 lines · Matthew Maurer
rust/kernel/debugfs.rs +6 -0
+    /// Creates a DebugFS file which will own the data produced by the initializer provided in
+    /// `data`.
+    fn create_file<'a, T: Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,

Can you please use impl PinInit<T, E> + 'a directly? It's a bit more in line with how we usually write this and I also think it makes things quite a bit more obvious compared to hiding it behind the TI generic.

Also, can you please use a where clause when the trait and lifetime bounds are getting a bit lengthy? I think for this method this is already justified.

Obviously, this applies to the whole patch (and likely subsequent ones).

Quoted 48 lines
rust/kernel/debugfs.rs +41 -0
+        file_ops: &'static FileOps<T>,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let scope = Scope::<T>::new(data, move |data| {
+            #[cfg(CONFIG_DEBUG_FS)]
+            if let Some(parent) = &self.0 {
+                // SAFETY: Because data derives from a scope, and our entry will be dropped before
+                // the data is dropped, it is guaranteed to outlive the entry we return.
+                unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }
+            } else {
+                Entry::empty()
+            }
+        });
+        try_pin_init! {
+            File {
+                scope <- scope
+            } ? E
+        }
+    }
+
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
@@ -79,4 +110,113 @@ pub fn new(name: &CStr) -> Self {
     pub fn subdir(&self, name: &CStr) -> Self {
         Dir::create(name, Some(self))
     }
+
+    /// Creates a read-only file in this directory.
+    ///
+    /// The file's contents are produced by invoking [`Render::render`] on the value initialized by
+    /// `data`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// # use kernel::prelude::*;
+    /// # let dir = Dir::new(c_str!("my_debugfs_dir"));
+    /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;
+    /// // "my_debugfs_dir/foo" now contains the number 200.
+    /// // The file is removed when `file` is dropped.
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,

rust/kernel/debugfs.rs pub fn new(name: &CStr) -> Self { · line 144

Quoted 10 lines
rust/kernel/debugfs.rs +10 -0
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = &<T as ReadFile<_>>::FILE_OPS;
+        self.create_file(name, data, file_ops)
+    }
+}
+
+#[pin_data]
+/// Handle to a DebugFS scope, which allows a variety of DebugFS files/directories to hang off a
+/// single structure.
+pub struct Scope<T> {

I think this doesn't need the Scope indirection just yet, but fine for me to add it right away.

However, please make sure to expand the documentation a bit. You probably don't want to mention where this is exactly going just yet, since technically those patches should work out standalone in case the more sophisticated scope stuff doesn't land.

But, I think you can still describe a bit more in detail that a debugfs::Scope handles the lifetime of some private data T for a debugfs::Entry.

Subsequently I would mention that this is used for file entries only.

Quoted 32 lines
rust/kernel/debugfs.rs +32 -0
+    // This order is load-bearing for drops - `_entry` must be dropped before `data`.
+    #[cfg(CONFIG_DEBUG_FS)]
+    _entry: Entry,
+    #[pin]
+    data: T,
+    // Even if `T` is `Unpin`, we still can't allow it to be moved.
+    #[pin]
+    _pin: PhantomPinned,
+}
+
+#[pin_data]
+/// Handle to a DebugFS file, owning its backing data.
+///
+/// When dropped, the DebugFS file will be removed and the attached data will be dropped.
+pub struct File<T> {
+    #[pin]
+    scope: Scope<T>,
+}
+
+#[cfg(not(CONFIG_DEBUG_FS))]
+impl<T> Scope<T> {
+    fn new<E, TI: PinInit<T, E>, F: for<'a> FnOnce(&'a T)>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> {
+        try_pin_init! {
+            Self {
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {

rust/kernel/debugfs.rs pub fn new(name: &CStr) -> Self { · line 186

Quoted 10 lines
rust/kernel/debugfs.rs +10 -0
+            init(&scope.data);
+            Ok(())
+        })
+    }
+}
+
+#[cfg(CONFIG_DEBUG_FS)]
+impl<T> Scope<T> {
+    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry {
+        // SAFETY: _entry is not structurally pinned

Please end with a period.

Review pub fn new(name: &CStr) -> Self { · line 188

Quoted 2 lines
(quoted diff) +2 -0
+        unsafe { &mut Pin::into_inner_unchecked(self)._entry }
+    }

Missing empty line.

Quoted 20 lines
rust/kernel/debugfs.rs +20 -0
+    fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry + 'b>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> + 'b
+    where
+        T: 'b,
+    {
+        try_pin_init! {
+            Self {
+                _entry: Entry::empty(),
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {
+            *scope.entry_mut() = init(&scope.data);
+            Ok(())
+        })
+    }
+}

<snip>

Quoted 5 lines
rust/kernel/debugfs/file_ops.rs new file +5 -0
+/// # Invariant
+///
+/// `FileOps<T>` will always contain an `operations` which is safe to use for a file backed
+/// off an inode which has a pointer to a `T` in its private data that is safe to convert
+/// into a reference.

rust/kernel/debugfs/file_ops.rs line 28

Quoted 10 lines
rust/kernel/debugfs/file_ops.rs new file +10 -0
+pub(super) struct FileOps<T> {
+    #[cfg(CONFIG_DEBUG_FS)]
+    operations: bindings::file_operations,
+    #[cfg(CONFIG_DEBUG_FS)]
+    mode: u16,
+    _phantom: PhantomData<T>,
+}
+
+impl<T> FileOps<T> {
+    /// # Safety

Missing empty line.

rust/kernel/debugfs/file_ops.rs line 39

Quoted 11 lines
rust/kernel/debugfs/file_ops.rs new file +11 -0
+    /// The caller asserts that the provided `operations` is safe to use for a file whose
+    /// inode has a pointer to `T` in its private data that is safe to convert into a reference.
+    const unsafe fn new(operations: bindings::file_operations, mode: u16) -> Self {
+        Self {
+            #[cfg(CONFIG_DEBUG_FS)]
+            operations,
+            #[cfg(CONFIG_DEBUG_FS)]
+            mode,
+            _phantom: PhantomData,
+        }
+    }

Same here...

rust/kernel/debugfs/file_ops.rs line 48

Quoted 9 lines
rust/kernel/debugfs/file_ops.rs new file +9 -0
+    #[cfg(CONFIG_DEBUG_FS)]
+    pub(crate) const fn mode(&self) -> u16 {
+        self.mode
+    }
+}
+
+#[cfg(CONFIG_DEBUG_FS)]
+impl<T> Deref for FileOps<T> {
+    type Target = bindings::file_operations;

...and here.

Quoted 30 lines
rust/kernel/debugfs/file_ops.rs new file +30 -0
+    fn deref(&self) -> &Self::Target {
+        &self.operations
+    }
+}
+
+struct RenderAdapter<T>(T);
+
+impl<'a, T: Render> Display for RenderAdapter<&'a T> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.0.render(f)
+    }
+}
+
+/// Implements `open` for `file_operations` via `single_open` to fill out a `seq_file`.
+///
+/// # Safety
+///
+/// * `inode`'s private pointer must point to a value of type `T` which will outlive the `inode`
+///   and will not have any unique references alias it during the call.
+/// * `file` must point to a live, not-yet-initialized file object.
+unsafe extern "C" fn render_open<T: Render + Sync>(
+    inode: *mut bindings::inode,
+    file: *mut bindings::file,
+) -> c_int {
+    // SAFETY: The caller ensures that `inode` is a valid pointer.
+    let data = unsafe { (*inode).i_private };
+    // SAFETY:
+    // * `file` is acceptable by caller precondition.
+    // * `print_act` will be called on a `seq_file` with private data set to the third argument,
+    //   so we meet its safety requirements.

rust/kernel/debugfs/file_ops.rs line 88

Quoted 10 lines
rust/kernel/debugfs/file_ops.rs new file +10 -0
+    // * The `data` pointer passed in the third argument is a valid `T` pointer that outlives
+    //   this call by caller preconditions.
+    unsafe { bindings::single_open(file, Some(render_act::<T>), data) }
+}
+
+/// Prints private data stashed in a seq_file to that seq file.
+///
+/// # Safety
+///
+/// `seq` must point to a live `seq_file` whose private data is a live pointer to a `T` which may

We usually say "valid".

Quoted 45 lines
rust/kernel/debugfs/file_ops.rs new file +37 -0
+/// not have any unique references alias it during the call.
+unsafe extern "C" fn render_act<T: Render + Sync>(
+    seq: *mut bindings::seq_file,
+    _: *mut c_void,
+) -> c_int {
+    // SAFETY: By caller precondition, this pointer is live, points to a value of type `T`, and
+    // there are not and will not be any unique references until we are done.
+    let data = unsafe { &*((*seq).private.cast::<T>()) };
+    // SAFETY: By caller precondition, `seq_file` points to a live `seq_file`, so we can lift
+    // it.
+    let seq_file = unsafe { SeqFile::from_raw(seq) };
+    seq_print!(seq_file, "{}", RenderAdapter(data));
+    0
+}
+
+// Work around lack of generic const items.
+pub(crate) trait ReadFile<T> {
+    const FILE_OPS: FileOps<T>;
+}
+
+impl<T: Render + Sync> ReadFile<T> for T {
+    const FILE_OPS: FileOps<T> = {
+        let operations = bindings::file_operations {
+            read: Some(bindings::seq_read),
+            llseek: Some(bindings::seq_lseek),
+            release: Some(bindings::single_release),
+            open: Some(render_open::<Self>),
+            // SAFETY: `file_operations` supports zeroes in all fields.
+            ..unsafe { core::mem::zeroed() }
+        };
+        // SAFETY: `operations` is all stock `seq_file` implementations except for `render_open`.
+        // `open`'s only requirement beyond what is provided to all open functions is that the
+        // inode's data pointer must point to a `T` that will outlive it, which matches the
+        // `FileOps` requirements.
+        unsafe { FileOps::new(operations, 0o400) }
+    };
+}
rust/kernel/debugfs/traits.rs new file +2 -0
new file mode 100644
index 0000000000000000000000000000000000000000..2939e18e3dda39571cd7255505e5f605f0e3d154
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.

rust/kernel/debugfs/traits.rs line 12

Quoted 10 lines
rust/kernel/debugfs/traits.rs new file +10 -0
+
+//! Traits for rendering or updating values exported to DebugFS.
+
+use crate::sync::Mutex;
+use core::fmt::{self, Debug, Formatter};
+
+/// A trait for types that can be rendered into a string.
+///
+/// This works very similarly to `Debug`, and is automatically implemented if `Debug` is
+/// implemented for a type. It is also implemented for any renderable type inside a `Mutex`.

I think the blanket impls make sense, but we should probably add a note that the contents derived via #[derive(Debug)] are not guaranteed to be stable [1].

[1] https://doc.rust-lang.org/std/fmt/trait.Debug.html#stability

rust/kernel/debugfs/traits.rs line 13

Quoted 1 line
rust/kernel/debugfs/traits.rs new file +1 -0
+pub trait Render {

Should we really call this Render? Maybe it'd be better to call it debugfs::Writer?

Yes, I know it's called in read(), but that's what the kernel does, it writes on read(). :)

Quoted 15 lines
rust/kernel/debugfs/traits.rs new file +15 -0
+    /// Formats the value using the given formatter.
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result;
+}
+
+impl<T: Render> Render for Mutex<T> {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        self.lock().render(f)
+    }
+}
+
+impl<T: Debug> Render for T {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        writeln!(f, "{self:?}")
+    }
+}
#5 lore raw

PATCH v10 3/7 rust: debugfs: Add support for writable files

In reply to Matthew Maurer

Extends the debugfs API to support creating writable files. This is done via the Dir::write_only_file and Dir::read_write_file methods, which take a data object that implements the UpdateFromSlice trait.

---
 rust/kernel/debugfs.rs          |  41 +++++++++++++-
 rust/kernel/debugfs/file_ops.rs | 115 +++++++++++++++++++++++++++++++++++++++-
 rust/kernel/debugfs/traits.rs   |  69 ++++++++++++++++++++++++
 3 files changed, 222 insertions(+), 3 deletions(-)
rust/kernel/debugfs.rs +39 -2
index 875d433fc3608cc9ffcf022d7c00cb207016f146..62bc2b1d4e5a4b21441a09e03bff74c32c6781d2 100644
@@ -16,10 +16,10 @@
 use core::ops::Deref;
 
 mod traits;
-pub use traits::Render;
+pub use traits::{Render, UpdateFromSlice};
 
 mod file_ops;
-use file_ops::{FileOps, ReadFile};
+use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
 #[cfg(CONFIG_DEBUG_FS)]
 mod entry;
 #[cfg(CONFIG_DEBUG_FS)]
@@ -136,6 +136,43 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<
         let file_ops = &<T as ReadFile<_>>::FILE_OPS;
         self.create_file(name, data, file_ops)
     }
+
+    /// Creates a read-write file in this directory.
+    ///
+    /// Reading the file uses the [`Render`] implementation.
+    /// Writing to the file uses the [`UpdateFromSlice`] implementation.
+    pub fn read_write_file<
+        'a,
+        T: Render + UpdateFromSlice + Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;
+        self.create_file(name, data, file_ops)
+    }
+
+    /// Creates a write-only file in this directory.
+    ///
+    /// The file owns its backing data. Writing to the file uses the [`UpdateFromSlice`]
+    /// implementation.
+    ///
+    /// The file is removed when the returned [`File`] is dropped.
+    pub fn write_only_file<
+        'a,
+        T: UpdateFromSlice + Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        self.create_file(name, data, &T::FILE_OPS)
+    }
 }
 
 #[pin_data]
rust/kernel/debugfs/file_ops.rs +114 -1

127 lines not on this page · raw message

rust/kernel/debugfs/traits.rs +69 -0

80 lines not on this page · raw message

Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
maintainer
#6 lore raw

Re: [PATCH v10 3/7] rust: debugfs: Add support for writable files

rust/kernel/debugfs.rs pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit< · line 148

Quoted 9 lines · Matthew Maurer
rust/kernel/debugfs.rs +9 -0
+    /// Creates a read-write file in this directory.
+    ///
+    /// Reading the file uses the [`Render`] implementation.
+    /// Writing to the file uses the [`UpdateFromSlice`] implementation.
+    pub fn read_write_file<
+        'a,
+        T: Render + UpdateFromSlice + Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,

Same comments as in the previous patch, I think using a where clause is a bit cleaner, even though with this formatting it'd be fine too, but this is not guaranteed.

rust/kernel/debugfs.rs pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit< · line 152

Quoted 4 lines
rust/kernel/debugfs.rs +4 -0
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,

impl PinInit<T, E> + 'a

rust/kernel/debugfs.rs pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<

Quoted 7 lines
rust/kernel/debugfs.rs +6 -0
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;
+        self.create_file(name, data, file_ops)
+    }
 
+fn update<T: UpdateFromSlice + Sync>(data: &T, buf: *const c_char, count: usize) -> isize {
+    let mut reader = UserSlice::new(UserPtr::from_ptr(buf as *mut c_void), count).reader();

This naming is pretty close to what I was about to propose for the UpdateFromSlice trait. :)

Given that I proposed debugfs::Writer instead of debugfs::Render, I think we should just rename debugfs::UpdateFromSlice to debugfs::Reader.

Quoted 7 lines
rust/kernel/debugfs/file_ops.rs +7 -0
+
+    if let Err(e) = data.update_from_slice(&mut reader) {
+        return e.to_errno() as isize;
+    }
+
+    count as isize
+}

<snip>

rust/kernel/debugfs/file_ops.rs impl<T: Render + Sync> ReadFile<T> for T { · line 196

Quoted 12 lines
rust/kernel/debugfs/file_ops.rs +12 -0
+/// # Safety
+///
+/// `inode` must be a valid pointer to an `inode` struct.
+/// `file` must be a valid pointer to a `file` struct.
+unsafe extern "C" fn write_only_open(
+    inode: *mut bindings::inode,
+    file: *mut bindings::file,
+) -> c_int {
+    // SAFETY: The caller ensures that `inode` and `file` are valid pointers.
+    unsafe {
+        (*file).private_data = (*inode).i_private;
+    }

NIT: If you move the semicolon at the end of the unsafe block it goes into a single line.

Quoted 2 lines
(quoted diff) +2 -0
+    0
+}

<snip>

Quoted 5 lines
rust/kernel/debugfs/file_ops.rs +5 -0
+impl<T: UpdateFromSlice + Sync> WriteFile<T> for T {
+    const FILE_OPS: FileOps<T> = {
+        let operations = bindings::file_operations {
+            open: Some(write_only_open),
+            write: Some(write_only_write::<T>),

rust/kernel/debugfs/file_ops.rs impl<T: Render + Sync> ReadFile<T> for T { · line 236

Quoted 10 lines
rust/kernel/debugfs/file_ops.rs +10 -0
+            llseek: Some(bindings::noop_llseek),
+            // SAFETY: `file_operations` supports zeroes in all fields.
+            ..unsafe { core::mem::zeroed() }
+        };
+        // SAFETY:
+        // * `write_only_open` populates the file private data with the inode private data
+        // * `write_only_write`'s only requirement is that the private data of the file point to
+        //   a `T` and be legal to convert to a shared reference, which `write_only_open`
+        //   satisfies.
+        unsafe { FileOps::new(operations, 0o200) }

I think it'd be nice to have an abstraction for file modes, but this can be done separately.

Quoted 24 lines
(quoted diff) +2 -0
+    };
+}
rust/kernel/debugfs/traits.rs +7 -0
index 2939e18e3dda39571cd7255505e5f605f0e3d154..d64638898faaa1a6a9898c374b8c1114993376c9 100644
@@ -3,8 +3,15 @@
 
 //! Traits for rendering or updating values exported to DebugFS.
 
+use crate::prelude::*;
 use crate::sync::Mutex;
+use crate::uaccess::UserSliceReader;
 use core::fmt::{self, Debug, Formatter};
+use core::str::FromStr;
+use core::sync::atomic::{
+    AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64,
+    AtomicU8, AtomicUsize, Ordering,
+};
 
 /// A trait for types that can be rendered into a string.
 ///
@@ -26,3 +33,65 @@ fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
         writeln!(f, "{self:?}")

rust/kernel/debugfs/traits.rs fn render(&self, f: &mut Formatter<'_>) -> fmt::Result { · line 43

Quoted 10 lines
rust/kernel/debugfs/traits.rs +8 -0
     }
 }
+
+/// A trait for types that can be updated from a user slice.
+///
+/// This works similarly to `FromStr`, but operates on a `UserSliceReader` rather than a &str.
+///
+/// It is automatically implemented for all atomic integers, or any type that implements `FromStr`
+/// wrapped in a `Mutex`.
+pub trait UpdateFromSlice {

As mentioned above, I think we should name this Reader and the Render thing Writer.

rust/kernel/debugfs/traits.rs fn render(&self, f: &mut Formatter<'_>) -> fmt::Result { · line 45

Quoted 2 lines
rust/kernel/debugfs/traits.rs +2 -0
+    /// Updates the value from the given user slice.
+    fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()>;

read_from_slice()?

Quoted 55 lines
rust/kernel/debugfs/traits.rs +52 -0
+}
+
+impl<T: FromStr> UpdateFromSlice for Mutex<T> {
+    fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()> {
+        let mut buf = [0u8; 128];
+        if reader.len() > buf.len() {
+            return Err(EINVAL);
+        }
+        let n = reader.len();
+        reader.read_slice(&mut buf[..n])?;
+
+        let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
+        let val = s.trim().parse::<T>().map_err(|_| EINVAL)?;
+        *self.lock() = val;
+        Ok(())
+    }
+}
+
+macro_rules! impl_update_from_slice_for_atomic {
+    ($(($atomic_type:ty, $int_type:ty)),*) => {
+        $(
+            impl UpdateFromSlice for $atomic_type {
+                fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()> {
+                    let mut buf = [0u8; 21]; // Enough for a 64-bit number.
+                    if reader.len() > buf.len() {
+                        return Err(EINVAL);
+                    }
+                    let n = reader.len();
+                    reader.read_slice(&mut buf[..n])?;
+
+                    let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
+                    let val = s.trim().parse::<$int_type>().map_err(|_| EINVAL)?;
+                    self.store(val, Ordering::Relaxed);
+                    Ok(())
+                }
+            }
+        )*
+    };
+}
+
+impl_update_from_slice_for_atomic!(
+    (AtomicI16, i16),
+    (AtomicI32, i32),
+    (AtomicI64, i64),
+    (AtomicI8, i8),
+    (AtomicIsize, isize),
+    (AtomicU16, u16),
+    (AtomicU32, u32),
+    (AtomicU64, u64),
+    (AtomicU8, u8),
+    (AtomicUsize, usize)
+);
Signature · 2 lines
-- 
2.51.0.rc1.167.g924127e9c0-goog
#7 lore raw

PATCH v10 4/7 rust: debugfs: Add support for callback-based files

In reply to Matthew Maurer

Extends the debugfs API to support creating files with content generated and updated by callbacks. This is done via the read_callback_file, write_callback_file, and read_write_callback_file methods.

These methods allow for more flexible file definition, either because the type already has a Render or UpdateFromSlice method that doesn't do what you'd like, or because you cannot implement it (e.g. because it's a type defined in another crate or a primitive type).

---
 rust/kernel/debugfs.rs                   |  95 ++++++++++++++++++++++++
 rust/kernel/debugfs/callback_adapters.rs | 122 +++++++++++++++++++++++++++++++
 rust/kernel/debugfs/file_ops.rs          |   8 ++
 3 files changed, 225 insertions(+)
rust/kernel/debugfs.rs +95 -0
index 62bc2b1d4e5a4b21441a09e03bff74c32c6781d2..a843d01506a54d5f8626dab5223d006c9a363a91 100644
@@ -12,12 +12,16 @@
 use crate::str::CStr;
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
+use crate::uaccess::UserSliceReader;
+use core::fmt;
 use core::marker::PhantomPinned;
 use core::ops::Deref;
 
 mod traits;
 pub use traits::{Render, UpdateFromSlice};
 
+mod callback_adapters;
+use callback_adapters::{FormatAdapter, NoRender, WritableAdapter};
 mod file_ops;
 use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
 #[cfg(CONFIG_DEBUG_FS)]
@@ -137,6 +141,48 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<
         self.create_file(name, data, file_ops)
     }
 
+    /// Creates a read-only file in this directory, with contents from a callback.
+    ///
+    /// `f` must be a function item or a non-capturing closure.
+    /// This is statically asserted and not a safety requirement.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use core::sync::atomic::{AtomicU32, Ordering};
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// # use kernel::prelude::*;
+    /// # let dir = Dir::new(c_str!("foo"));
+    /// let file = KBox::pin_init(
+    ///     dir.read_callback_file(c_str!("bar"),
+    ///     AtomicU32::new(3),
+    ///     &|val, f| {
+    ///       let out = val.load(Ordering::Relaxed);
+    ///       writeln!(f, "{out:#010x}")
+    ///     }),
+    ///     GFP_KERNEL)?;
+    /// // Reading "foo/bar" will show "0x00000003".
+    /// file.store(10, Ordering::Relaxed);
+    /// // Reading "foo/bar" will now show "0x0000000a".
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn read_callback_file<
+        'a,
+        T: Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+        F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+        _f: &'static F,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt();
+        self.create_file(name, data, file_ops)
+    }
+
     /// Creates a read-write file in this directory.
     ///
     /// Reading the file uses the [`Render`] implementation.
@@ -155,6 +201,33 @@ pub fn read_write_file<
         self.create_file(name, data, file_ops)
     }
 
+    /// Creates a read-write file in this directory, with logic from callbacks.
+    ///
+    /// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
+    ///
+    /// `f` and `w` must be function items or non-capturing closures.
+    /// This is statically asserted and not a safety requirement.
+    pub fn read_write_callback_file<
+        'a,
+        T: Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+        F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+        W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+        _f: &'static F,
+        _w: &'static W,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops =
+            <WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS
+                .adapt()
+                .adapt();
+        self.create_file(name, data, file_ops)
+    }
+
     /// Creates a write-only file in this directory.
     ///
     /// The file owns its backing data. Writing to the file uses the [`UpdateFromSlice`]
@@ -173,6 +246,28 @@ pub fn write_only_file<
     ) -> impl PinInit<File<T>, E> + 'a {
         self.create_file(name, data, &T::FILE_OPS)
     }
+
+    /// Creates a write-only file in this directory, with write logic from a callback.
+    ///
+    /// `w` must be a function item or a non-capturing closure.
+    /// This is statically asserted and not a safety requirement.
+    pub fn write_callback_file<
+        'a,
+        T: Send + Sync + 'static,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+        W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+    >(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+        _w: &'static W,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = <WritableAdapter<NoRender<T>, W> as WriteFile<_>>::FILE_OPS
+            .adapt()
+            .adapt();
+        self.create_file(name, data, file_ops)
+    }
 }
 
 #[pin_data]
rust/kernel/debugfs/callback_adapters.rs new file +122 -0

122 lines not on this page · raw message

rust/kernel/debugfs/file_ops.rs +8 -0
index 30f6a0532c7f5f4a2974edc8f1100f5485aa8da9..d11c09520d4464417003362b7468c9b9e1a2e1bf 100644
@@ -2,6 +2,7 @@
 // Copyright (C) 2025 Google LLC.
 
 use super::{Render, UpdateFromSlice};
+use crate::debugfs::callback_adapters::Adapter;
 use crate::prelude::*;
 use crate::seq_file::SeqFile;
 use crate::seq_print;
@@ -44,6 +45,13 @@ pub(crate) const fn mode(&self) -> u16 {
     }
 }
 
+impl<T: Adapter> FileOps<T> {
+    pub(super) const fn adapt(&self) -> &FileOps<T::Inner> {
+        // SAFETY: `Adapter` asserts that `T` can be legally cast to `T::Inner`.
+        unsafe { core::mem::transmute(self) }
+    }
+}
+
 #[cfg(CONFIG_DEBUG_FS)]
 impl<T> Deref for FileOps<T> {
     type Target = bindings::file_operations;
Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
#8 lore raw

PATCH v10 5/7 samples: rust: Add debugfs sample driver

In reply to Matthew Maurer

Adds a new sample driver that demonstrates the debugfs APIs.

The driver creates a directory in debugfs and populates it with a few files:
- A read-only file that displays a fwnode property.
- A read-write file that exposes an atomic counter.
- A read-write file that exposes a custom struct.

This sample serves as a basic example of how to use the debugfs::Dir and debugfs::File APIs to create and manage debugfs entries.

---
 MAINTAINERS                  |   1 +
 samples/rust/Kconfig         |  11 ++++
 samples/rust/Makefile        |   1 +
 samples/rust/rust_debugfs.rs | 151 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 164 insertions(+)
MAINTAINERS +1 -0
index 8f2dbf71ca3f8f97e4d7619375279ed11d1261b2..5b6db584a33dd7ee39de3fdd0085d2bd7b7bef0e 100644
@@ -7481,6 +7481,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
 
samples/rust/Kconfig +11 -0
index 7f7371a004ee0a8f67dca99c836596709a70c4fa..01101db41ae31b08a86d048cdd27da8ef9bb23a2 100644
@@ -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
samples/rust/Makefile +1 -0
index bd2faad63b4f3befe7d1ed5139fe25c7a8b6d7f6..61276222a99f8cc6d7f84c26d0533b30815ebadd 100644
@@ -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
samples/rust/rust_debugfs.rs new file +151 -0

151 lines not on this page · raw message

Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
maintainer
#9 lore raw

Re: [PATCH v10 5/7] samples: rust: Add debugfs sample driver

Quoted 8 lines · Matthew Maurer

Adds a new sample driver that demonstrates the debugfs APIs.

The driver creates a directory in debugfs and populates it with a few files:
- A read-only file that displays a fwnode property.
- A read-write file that exposes an atomic counter.
- A read-write file that exposes a custom struct.

Quoted 4 lines

This sample serves as a basic example of how to use the debugfs::Dir and debugfs::File APIs to create and manage debugfs entries.

This is a great example, thanks! I really like how the API turned out.

When it comes to the newly added Scope API - and I assume this does not come at a surprise - I have some concerns.

But first, thanks a lot for posting the socinfo driver in both variants, with and without the Scope API.

I had a brief look at both of those and I can see why you want this.

With the Scope thing you can indeed write things a bit more compressed (I think in the patches the differences looks quite a bit bigger than it actually is, because the scope-based one uses quite some code from the file-based one).

I think the downsides are mainly:

- The degree of complexity added for a rather specific use-case, that is also perfectly representable with the file-based API.

- It makes it convinient to expose multiple fields grouped under the same lock as separate files, which design wise we shouln't encourage for the reasons we discussed in v8.

I think for the sake of getting this series merged, which I would really love to see, I think we should focus on the file-based API first. Once we got this landed I think we can still revisit the Scope idea and have some more discussion about it.

I will have a more detailed look tomorrow (at least for the patches 1-5).

Thanks again for working on this!

- Danilo

#10 lore raw

Re: [PATCH v10 5/7] samples: rust: Add debugfs sample driver

Quoted 13 lines · Danilo Krummrich
Quoted 11 lines · Matthew Maurer

Adds a new sample driver that demonstrates the debugfs APIs.

The driver creates a directory in debugfs and populates it with a few files:
- A read-only file that displays a fwnode property.
- A read-write file that exposes an atomic counter.
- A read-write file that exposes a custom struct.

This sample serves as a basic example of how to use the debugfs::Dir and debugfs::File APIs to create and manage debugfs entries.

Quoted 6 lines
Quoted 1 line

This is a great example, thanks! I really like how the API turned out.

When it comes to the newly added Scope API - and I assume this does not come at a surprise - I have some concerns.

Yes, I expected this to be the case, but inspired by some of the comments about wanting to just create files off fields and forget about them, I wanted to take one more crack at it.

Quoted 6 lines

But first, thanks a lot for posting the socinfo driver in both variants, with and without the Scope API.

I had a brief look at both of those and I can see why you want this.

Quoted 8 lines

With the Scope thing you can indeed write things a bit more compressed (I think in the patches the differences looks quite a bit bigger than it actually is, because the scope-based one uses quite some code from the file-based one).

I think the downsides are mainly:

- The degree of complexity added for a rather specific use-case, that is also perfectly representable with the file-based API.

I don't *think* this is just for this use case - if I just wanted to improve the DebugFS use case, I'd mostly be looking at additional code for pin-init (adding an Option placement + a few ergonomic improvements to pin_init would slim off a large chunk of the code). The idea here was that a file might not always directly correspond to a field in a data structure, and the File API forces it to be one. We could decide that forcing every file to be a data structure field is a good idea, but I'm not certain it is.

Quoted 4 lines

- It makes it convinient to expose multiple fields grouped under the same lock as separate files, which design wise we shouln't encourage for the reasons we discussed in v8.

It's still pretty convenient to do this with File. I don't know how common it'll be in kernel code, but in userspace Rust, Arc<Mutex<T>> is a very common primitive. I would be unsurprised to see someone use this pattern to expose separate fields as separate files if we go with the File API.

Quoted 5 lines

I think for the sake of getting this series merged, which I would really love to see, I think we should focus on the file-based API first. Once we got this landed I think we can still revisit the Scope idea and have some more discussion about it.

This is why I put the scope API and sample as patches on the end chain of the series - it is possible to merge only the File-based API if that's what we want to do first, and consider the rest later.

Quoted 6 lines

I will have a more detailed look tomorrow (at least for the patches 1-5).

Thanks again for working on this!

- Danilo

#11 lore raw

Re: [PATCH v10 5/7] samples: rust: Add debugfs sample driver

Quoted 36 lines · Matthew Maurer
Quoted 13 lines · Danilo Krummrich
Quoted 11 lines · Matthew Maurer

Adds a new sample driver that demonstrates the debugfs APIs.

The driver creates a directory in debugfs and populates it with a few files:
- A read-only file that displays a fwnode property.
- A read-write file that exposes an atomic counter.
- A read-write file that exposes a custom struct.

This sample serves as a basic example of how to use the debugfs::Dir and debugfs::File APIs to create and manage debugfs entries.

Quoted 6 lines
Quoted 1 line

This is a great example, thanks! I really like how the API turned out.

When it comes to the newly added Scope API - and I assume this does not come at a surprise - I have some concerns.

Yes, I expected this to be the case, but inspired by some of the comments about wanting to just create files off fields and forget about them, I wanted to take one more crack at it.

Quoted 10 lines

But first, thanks a lot for posting the socinfo driver in both variants, with and without the Scope API.

I had a brief look at both of those and I can see why you want this.

With the Scope thing you can indeed write things a bit more compressed (I think in the patches the differences looks quite a bit bigger than it actually is, because the scope-based one uses quite some code from the file-based one).

Quoted 6 lines
Quoted 4 lines

I think the downsides are mainly:

- The degree of complexity added for a rather specific use-case, that is also perfectly representable with the file-based API.

I don't *think* this is just for this use case - if I just wanted to improve the DebugFS use case, I'd mostly be looking at additional code

Edit: Socinfo use case

Quoted 31 lines

for pin-init (adding an Option placement + a few ergonomic improvements to pin_init would slim off a large chunk of the code). The idea here was that a file might not always directly correspond to a field in a data structure, and the File API forces it to be one. We could decide that forcing every file to be a data structure field is a good idea, but I'm not certain it is.

Quoted 4 lines

- It makes it convinient to expose multiple fields grouped under the same lock as separate files, which design wise we shouln't encourage for the reasons we discussed in v8.

It's still pretty convenient to do this with File. I don't know how common it'll be in kernel code, but in userspace Rust, Arc<Mutex<T>> is a very common primitive. I would be unsurprised to see someone use this pattern to expose separate fields as separate files if we go with the File API.

Quoted 5 lines

I think for the sake of getting this series merged, which I would really love to see, I think we should focus on the file-based API first. Once we got this landed I think we can still revisit the Scope idea and have some more discussion about it.

This is why I put the scope API and sample as patches on the end chain of the series - it is possible to merge only the File-based API if that's what we want to do first, and consider the rest later.

Quoted 6 lines

I will have a more detailed look tomorrow (at least for the patches 1-5).

Thanks again for working on this!

- Danilo

reviewer
#12 lore raw

Re: [PATCH v10 5/7] samples: rust: Add debugfs sample driver

In reply to Matthew Maurer
Quoted 35 lines · Matthew Maurer
Quoted 13 lines · Danilo Krummrich
Quoted 11 lines · Matthew Maurer

Adds a new sample driver that demonstrates the debugfs APIs.

The driver creates a directory in debugfs and populates it with a few files:
- A read-only file that displays a fwnode property.
- A read-write file that exposes an atomic counter.
- A read-write file that exposes a custom struct.

This sample serves as a basic example of how to use the debugfs::Dir and debugfs::File APIs to create and manage debugfs entries.

Quoted 6 lines
Quoted 1 line

This is a great example, thanks! I really like how the API turned out.

When it comes to the newly added Scope API - and I assume this does not come at a surprise - I have some concerns.

Yes, I expected this to be the case, but inspired by some of the comments about wanting to just create files off fields and forget about them, I wanted to take one more crack at it.

Quoted 10 lines

But first, thanks a lot for posting the socinfo driver in both variants, with and without the Scope API.

I had a brief look at both of those and I can see why you want this.

With the Scope thing you can indeed write things a bit more compressed (I think in the patches the differences looks quite a bit bigger than it actually is, because the scope-based one uses quite some code from the file-based one).

Quoted 7 lines
Quoted 4 lines

I think the downsides are mainly:

- The degree of complexity added for a rather specific use-case, that is also perfectly representable with the file-based API.

I don't *think* this is just for this use case - if I just wanted to improve the DebugFS use case, I'd mostly be looking at additional code for pin-init (adding an Option placement + a few ergonomic

Option is currently not possible to support, since we can't set solely the discriminant (it must always be a write to the entire enum, thus requiring a move), see [1].

[1]: https://github.com/Rust-for-Linux/pin-init/issues/59

Quoted 1 line

improvements to pin_init would slim off a large chunk of the code).

I'd be interested in what kinds of improvements you need, maybe they are simple enough to just include :)

---
Cheers,
Benno

Quoted 4 lines

The idea here was that a file might not always directly correspond to a field in a data structure, and the File API forces it to be one. We could decide that forcing every file to be a data structure field is a good idea, but I'm not certain it is.

#13 lore raw

PATCH v10 6/7 rust: debugfs: Add support for scoped directories

In reply to Matthew Maurer

Introduces the concept of a ScopedDir, which allows for the creation of debugfs directories and files that are tied to the lifetime of a particular data structure. This ensures that debugfs entries do not outlive the data they refer to.

The new Dir::scope method creates a new directory that is owned by a Scope handle. All files and subdirectories created within this scope are automatically cleaned up when the Scope is dropped.

---
 rust/kernel/debugfs.rs       | 267 ++++++++++++++++++++++++++++++++++++++++++-
 rust/kernel/debugfs/entry.rs |  73 +++++++++++-
 2 files changed, 330 insertions(+), 10 deletions(-)
rust/kernel/debugfs.rs +263 -4
index a843d01506a54d5f8626dab5223d006c9a363a91..5e2b60cc1ea3eff859dbad8d7dd7a84d7c08d766 100644
@@ -14,7 +14,10 @@
 use crate::sync::Arc;
 use crate::uaccess::UserSliceReader;
 use core::fmt;
+use core::marker::PhantomData;
 use core::marker::PhantomPinned;
+#[cfg(CONFIG_DEBUG_FS)]
+use core::mem::ManuallyDrop;
 use core::ops::Deref;
 
 mod traits;
@@ -40,7 +43,7 @@
 // able to refer to us. In this case, we need to silently fail. All future child directories/files
 // will silently fail as well.
 #[derive(Clone)]
-pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry>>);
+pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry<'static>>>);
 
 impl Dir {
     /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
@@ -268,6 +271,54 @@ pub fn write_callback_file<
             .adapt();
         self.create_file(name, data, file_ops)
     }
+
+    // While this function is safe, it is intentionally not public because it's a bit of a
+    // footgun.
+    //
+    // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate
+    // time, a `ScopedDir` with a `Dir` parent will never be deleted.
+    fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {
+        #[cfg(CONFIG_DEBUG_FS)]
+        {
+            let parent_entry = match &self.0 {
+                None => return ScopedDir::empty(),
+                Some(entry) => entry.clone(),
+            };
+            ScopedDir {
+                entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))),
+                _phantom: PhantomData,
+            }
+        }
+        #[cfg(not(CONFIG_DEBUG_FS))]
+        ScopedDir::empty()
+    }
+
+    /// Creates a new scope, which is a directory associated with some data `T`.
+    ///
+    /// The created directory will be a subdirectory of `self`. The `init` closure is called to
+    /// populate the directory with files and subdirectories. These files can reference the data
+    /// stored in the scope.
+    ///
+    /// The entire directory tree created within the scope will be removed when the returned
+    /// `Scope` handle is dropped.
+    pub fn scope<
+        'a,
+        T: 'a,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+        F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,
+    >(
+        &'a self,
+        data: TI,
+        name: &'a CStr,
+        init: F,
+    ) -> impl PinInit<Scope<T>, E> + 'a {
+        Scope::new(data, |data| {
+            let scoped = self.scoped_dir(name);
+            init(data, &scoped);
+            scoped.into_entry()
+        })
+    }
 }
 
 #[pin_data]
@@ -276,7 +327,7 @@ pub fn write_callback_file<
 pub struct Scope<T> {
     // This order is load-bearing for drops - `_entry` must be dropped before `data`.
     #[cfg(CONFIG_DEBUG_FS)]
-    _entry: Entry,
+    _entry: Entry<'static>,
     #[pin]
     data: T,
     // Even if `T` is `Unpin`, we still can't allow it to be moved.
@@ -314,11 +365,11 @@ fn new<E, TI: PinInit<T, E>, F: for<'a> FnOnce(&'a T)>(
 
 #[cfg(CONFIG_DEBUG_FS)]
 impl<T> Scope<T> {
-    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry {
+    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> {
         // SAFETY: _entry is not structurally pinned
         unsafe { &mut Pin::into_inner_unchecked(self)._entry }
     }
-    fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry + 'b>(
+    fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b>(
         data: TI,
         init: F,
     ) -> impl PinInit<Self, E> + 'b
@@ -339,6 +390,36 @@ fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry + 'b
     }
 }
 
+impl<T> Scope<T> {
+    /// Creates a new scope, which is a directory at the root of the debugfs filesystem,
+    /// associated with some data `T`.
+    ///
+    /// The `init` closure is called to populate the directory with files and subdirectories. These
+    /// files can reference the data stored in the scope.
+    ///
+    /// The entire directory tree created within the scope will be removed when the returned
+    /// `Scope` handle is dropped.
+    pub fn dir<
+        'a,
+        E: 'a,
+        TI: PinInit<T, E> + 'a,
+        F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,
+    >(
+        data: TI,
+        name: &'a CStr,
+        init: F,
+    ) -> impl PinInit<Self, E> + 'a
+    where
+        T: 'a,
+    {
+        Scope::new(data, |data| {
+            let scoped = ScopedDir::new(name);
+            init(data, &scoped);
+            scoped.into_entry()
+        })
+    }
+}
+
 impl<T> Deref for Scope<T> {
     type Target = T;
     fn deref(&self) -> &T {
@@ -352,3 +433,181 @@ fn deref(&self) -> &T {
         &self.scope
     }
 }
+
+/// A handle to a directory which will live at most `'dir`, accessing data that will live for at
+/// least `'data`.
+///
+/// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping
+/// the `Scope` that created it.
+pub struct ScopedDir<'data, 'dir> {
+    #[cfg(CONFIG_DEBUG_FS)]
+    entry: ManuallyDrop<Entry<'dir>>,
+    _phantom: PhantomData<fn(&'data ()) -> &'dir ()>,
+}
+
+impl<'data, 'dir> ScopedDir<'data, 'dir> {
+    /// Creates a subdirectory inside this `ScopedDir`.
+    ///
+    /// The returned directory handle cannot outlive this one.
+    pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2> {
+        #[cfg(not(CONFIG_DEBUG_FS))]
+        let _ = name;
+        ScopedDir {
+            #[cfg(CONFIG_DEBUG_FS)]
+            entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))),
+            _phantom: PhantomData,
+        }
+    }
+
+    fn create_file<T: Sync>(&self, name: &CStr, data: &'data T, vtable: &'static FileOps<T>) {
+        #[cfg(CONFIG_DEBUG_FS)]
+        core::mem::forget(Entry::file(name, &self.entry, data, vtable));
+    }
+
+    /// Creates a read-only file in this directory.
+    ///
+    /// The file's contents are produced by invoking [`Render::render`]`.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn read_only_file<T: Render + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) {
+        self.create_file(name, data, &T::FILE_OPS)
+    }
+
+    /// Creates a read-only file in this directory, with contents from a callback.
+    ///
+    /// The file contents are generated by calling `f` with `data`.
+    ///
+    ///
+    /// `f` must be a function item or a non-capturing closure.
+    /// This is statically asserted and not a safety requirement.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn read_callback_file<
+        T: Send + Sync + 'static,
+        F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+    >(
+        &self,
+        name: &CStr,
+        data: &'data T,
+        _f: &'static F,
+    ) {
+        let vtable = <FormatAdapter<T, F> as ReadFile<_>>::FILE_OPS.adapt();
+        self.create_file(name, data, vtable)
+    }
+
+    /// Creates a read-write file in this directory.
+    ///
+    /// Reading the file uses the [`Render`] implementation on `data`. Writing to the file uses
+    /// the [`UpdateFromSlice`] implementation on `data`.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn read_write_file<T: Render + UpdateFromSlice + Send + Sync + 'static>(
+        &self,
+        name: &CStr,
+        data: &'data T,
+    ) {
+        let vtable = &<T as ReadWriteFile<_>>::FILE_OPS;
+        self.create_file(name, data, vtable)
+    }
+
+    /// Creates a read-write file in this directory, with logic from callbacks.
+    ///
+    /// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
+    ///
+    /// `f` and `w` must be function items or non-capturing closures.
+    /// This is statically asserted and not a safety requirement.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn read_write_callback_file<
+        T: Send + Sync + 'static,
+        F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+        W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+    >(
+        &self,
+        name: &CStr,
+        data: &'data T,
+        _f: &'static F,
+        _w: &'static W,
+    ) {
+        let vtable = <WritableAdapter<FormatAdapter<T, F>, W> as ReadWriteFile<_>>::FILE_OPS
+            .adapt()
+            .adapt();
+        self.create_file(name, data, vtable)
+    }
+
+    /// Creates a write-only file in this directory.
+    ///
+    /// Writing to the file uses the [`UpdateFromSlice`] implementation on `data`.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn write_only_file<T: UpdateFromSlice + Send + Sync + 'static>(
+        &self,
+        name: &CStr,
+        data: &'data T,
+    ) {
+        let vtable = &<T as WriteFile<_>>::FILE_OPS;
+        self.create_file(name, data, vtable)
+    }
+
+    /// Creates a write-only file in this directory, with write logic from a callback.
+    ///
+    /// Writing to the file is handled by `w`.
+    ///
+    /// `w` must be a function item or a non-capturing closure.
+    /// This is statically asserted and not a safety requirement.
+    ///
+    /// This function does not produce an owning handle to the file. The created
+    /// file is removed when the [`Scope`] that this directory belongs
+    /// to is dropped.
+    pub fn write_only_callback_file<
+        T: Send + Sync + 'static,
+        W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+    >(
+        &self,
+        name: &CStr,
+        data: &'data T,
+        _w: &'static W,
+    ) {
+        let vtable = &<WritableAdapter<NoRender<T>, W> as WriteFile<_>>::FILE_OPS
+            .adapt()
+            .adapt();
+        self.create_file(name, data, vtable)
+    }
+
+    fn empty() -> Self {
+        ScopedDir {
+            #[cfg(CONFIG_DEBUG_FS)]
+            entry: ManuallyDrop::new(Entry::empty()),
+            _phantom: PhantomData,
+        }
+    }
+    #[cfg(CONFIG_DEBUG_FS)]
+    fn into_entry(self) -> Entry<'dir> {
+        ManuallyDrop::into_inner(self.entry)
+    }
+    #[cfg(not(CONFIG_DEBUG_FS))]
+    fn into_entry(self) {}
+}
+
+impl<'data> ScopedDir<'data, 'static> {
+    // This is safe, but intentionally not exported due to footgun status. A ScopedDir with no
+    // parent will never be released by default, and needs to have its entry extracted and used
+    // somewhere.
+    fn new(name: &CStr) -> ScopedDir<'data, 'static> {
+        ScopedDir {
+            #[cfg(CONFIG_DEBUG_FS)]
+            entry: ManuallyDrop::new(Entry::dir(name, None)),
+            _phantom: PhantomData,
+        }
+    }
+}
rust/kernel/debugfs/entry.rs +67 -6

120 lines not on this page · raw message

Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
#14 lore raw

PATCH v10 7/7 samples: rust: Add scoped debugfs sample driver

In reply to Matthew Maurer

Adds a new sample driver rust_scoped_debugfs that demonstrates the use of the scoped debugfs APIs.

The driver creates a control directory with two write-only files, create and remove. Writing a name and a series of numbers to create will create a new subdirectory under a dynamic directory. This new subdirectory will contain files that expose the numbers as atomic values.

Writing a name to remove will remove the corresponding subdirectory from the dynamic directory.

This sample serves as an example of how to use the debugfs::Scope and debugfs::ScopedDir APIs to create and manage debugfs entries that are tied to the lifetime of a data structure.

---
 MAINTAINERS                         |   1 +
 samples/rust/Kconfig                |  11 +++
 samples/rust/Makefile               |   1 +
 samples/rust/rust_scoped_debugfs.rs | 134 ++++++++++++++++++++++++++++++++++++
 4 files changed, 147 insertions(+)
MAINTAINERS +1 -0
index 5b6db584a33dd7ee39de3fdd0085d2bd7b7bef0e..2cbe890085dbb6a652623b38dd0eadeeaa127a94 100644
@@ -7482,6 +7482,7 @@ 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_scoped_debugfs.rs
 F:	samples/rust/rust_driver_platform.rs
 F:	samples/rust/rust_driver_faux.rs
 
samples/rust/Kconfig +11 -0
index 01101db41ae31b08a86d048cdd27da8ef9bb23a2..3372935519d658529ee7ba25fb2c3fff6adae8c4 100644
@@ -73,6 +73,17 @@ config SAMPLE_RUST_DEBUGFS
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_SCOPED_DEBUGFS
+	tristate "Scoped DebugFS Test Module"
+	depends on DEBUG_FS
+	help
+	  This option builds the Rust Scoped DebugFS Test module sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_scoped_debugfs.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_DRIVER_PCI
 	tristate "PCI Driver"
 	depends on PCI
samples/rust/Makefile +1 -0
index 61276222a99f8cc6d7f84c26d0533b30815ebadd..de10b7f4db3996dc57be813ceb076d050ad8f65a 100644
@@ -5,6 +5,7 @@ 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_SCOPED_DEBUGFS)	+= rust_scoped_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
samples/rust/rust_scoped_debugfs.rs new file +134 -0

134 lines not on this page · raw message

Signature · 1 line
2.51.0.rc1.167.g924127e9c0-goog
#15 lore raw
In reply to Matthew Maurer
Quoted 6 lines · Matthew Maurer

This series provides safe DebugFS bindings for Rust, with sample modules using them.

Shortly after this is sent, you will see a real driver WIP using this implenting the qcom socinfo driver.

Now mailed. [1]

[1] https://lore.kernel.org/all/[email protected]/

Quoted 120 lines

---
Changes in v10:
- Introduced Scoped to show how either a File or Dir can be bound to
data
- Remove use of use<> for MSRV compatibility
- Added Write support
- Added more complex sample driver using scoped interface
- Updated original sample driver to use writes to drive mutation
- Added FileOps<T> (only for DebugFS, not a kernel-wide abstraction)
to decrease needed unsafe for keeping vtables paired to types.
- Centralized dentry lifecycle management to entry.rs.
- Link to v9: https://lore.kernel.org/r/[email protected]

Changes in v9:
- Switched to PinInit backing instead of ForeignOwnable
- Changed sample to be a platform driver
- Exported a static property
- Demonstrated runtime mutation in platform driver (inc_counter)
- Demonstrated how driver code would interact with data structures
exported through DebugFS (Wrapper)
- Link to v8: https://lore.kernel.org/r/[email protected]

Changes in v8:
- Switched from casts to core::from_{ref, mut} in type change
- Link to v7: https://lore.kernel.org/r/[email protected]

Changes in v7:
- Rewrote entry::Entry -> Entry
- Use c_int and c_void from kernel prelude rather than core
- Removed unnecessary display_open cast
- Switched from Deref + an explicit box to ForeignOwnable for
attaching owned data.
- Made &'static and &'static mut implement ForeignOwnable
- Swapped "driver" to "module" in sample code
- Link to v6: https://lore.kernel.org/r/[email protected]

Changes in v6:
- Replaced explicit lifetimes with children keeping their parents alive.
- Added support for attaching owned data.
- Removed recomendation to only keep root handles and handles you want
to delete around.
- Refactored some code into separate files to improve clarity.
- Link to v5: https://lore.kernel.org/r/[email protected]

Changes in v5:
- Made Dir + File wrappers around Entry
- All functions return owning handles. To discard without drop, use forget. To keep a handle without drop, use ManuallyDrop.
- Fixed bugs around not(CONFIG_DEBUG_FS)
- Removed unnecessary addr_of!
- Link to v4: https://lore.kernel.org/r/[email protected]

Changes in v4:
- Remove SubDir, replace with type-level constant.
- Add lifetime to Dir to prevent subdirectories and files from outliving their parents and triggering an Oops when accessed.
- Split unsafe blocks with two calls into two blocks
- Access private field through direct pointer dereference, avoiding creation of a reference to it.
- Notably not changed - owning/non-owning handle defaults. The best read
I had from the thread was to continue with this mode, but I'm willing
to change if need be.
- Comment changes
- More comment markdown
- Remove scopes from examples
- Put as_ptr properties into a # Guarantees section.
- Link to v3: https://lore.kernel.org/r/[email protected]

Changes in v3:
- Split Dir into Dir/SubDir/File to improve API.
- Add "." to end of all comments.
- Convert INVARIANT to # Invariants on types.
- Add backticks everywhere I found variables/types in my comments.
- Promoted invariant comment to doc comment.
- Extended sample commenting to make it clearer what is happening.
- Link to v2: https://lore.kernel.org/r/[email protected]

Changes in v2:
- Drop support for builder / pinned bindings in initial series
- Remove ARef usage to abstract the dentry nature of handles
- Remove error handling to discourage users from caring whether DebugFS
is enabled.
- Support CONFIG_DEBUG_FS=n while leaving the API available
- Fixed mistaken decimal/octal mixup
- Doc/comment cleanup
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Matthew Maurer (7):
rust: debugfs: Add initial support for directories
rust: debugfs: Add support for read-only files
rust: debugfs: Add support for writable files
rust: debugfs: Add support for callback-based files
samples: rust: Add debugfs sample driver
rust: debugfs: Add support for scoped directories
samples: rust: Add scoped debugfs sample driver

 MAINTAINERS                              |   4 +
 rust/bindings/bindings_helper.h          |   1 +
 rust/kernel/debugfs.rs                   | 613 +++++++++++++++++++++++++++++++
 rust/kernel/debugfs/callback_adapters.rs | 122 ++++++
 rust/kernel/debugfs/entry.rs             | 164 +++++++++
 rust/kernel/debugfs/file_ops.rs          | 246 +++++++++++++
 rust/kernel/debugfs/traits.rs            |  97 +++++
 rust/kernel/lib.rs                       |   1 +
 samples/rust/Kconfig                     |  22 ++
 samples/rust/Makefile                    |   2 +
 samples/rust/rust_debugfs.rs             | 151 ++++++++
 samples/rust/rust_scoped_debugfs.rs      | 134 +++++++
 12 files changed, 1557 insertions(+)

---
base-commit: f3c5631f70e434e318c44001e2417d4770f06cd0
change-id: 20250428-debugfs-rust-3cd5c97eb7d1

Best regards,
--
Matthew Maurer <[email protected]>

Tested-by on the v10 series
#16 lore raw
In reply to Matthew Maurer
Quoted 7 lines · Matthew Maurer

This series provides safe DebugFS bindings for Rust, with sample modules using them.

Shortly after this is sent, you will see a real driver WIP using this implenting the qcom socinfo driver.

On ARM64 on top of v6.17-rc1 I applied this series to use read_only_file() to export some u64 proprietary values.

If you like feel free to add

Thanks!

Dirk