rust: DebugFS Bindings
Subsystems (2)
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]>
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
@@ -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
@@ -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
@@ -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
Danilo Krummrich
maintainerRe: [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.
PATCH v10 2/7 rust: debugfs: Add support for read-only files
In reply to Matthew MaurerExtends 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
@@ -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: &'aCStr , + data:TI , + file_ops: &'staticFileOps <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: &'aCStr , + 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 (&'aT )>( + 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 (&'aT ) ->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
@@ -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
Danilo Krummrich
maintainerRe: [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: &'aCStr , + 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: &'aCStr , + 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 (&'aT )>( + 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 (&'aT ) ->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 <&'aT > { +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@@ -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:?}" ) + } +}
PATCH v10 3/7 rust: debugfs: Add support for writable files
In reply to Matthew MaurerExtends 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
@@ -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: &'aCStr , + 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: &'aCStr , + 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
Danilo Krummrich
maintainerRe: [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 (bufas *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 ; + } + + countas 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@@ -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
PATCH v10 4/7 rust: debugfs: Add support for callback-based files
In reply to Matthew MaurerExtends 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
@@ -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: &'aCStr , + data:TI , + _f: &'staticF , + ) ->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: &'aCStr , + data:TI , + _f: &'staticF , + _w: &'staticW , + ) ->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: &'aCStr , + data:TI , + _w: &'staticW , + ) ->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
@@ -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
PATCH v10 5/7 samples: rust: Add debugfs sample driver
In reply to Matthew MaurerAdds 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
@@ -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
@@ -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
@@ -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
Danilo Krummrich
maintainerRe: [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::Diranddebugfs::FileAPIs to create and manage debugfs entries.Signed-off-by Matthew Maurer <[email protected]>
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
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::Diranddebugfs::FileAPIs to create and manage debugfs entries.
Quoted 6 lines
Quoted 1 line
Signed-off-by Matthew Maurer <[email protected]>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
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::Diranddebugfs::FileAPIs to create and manage debugfs entries.Quoted 6 lines
Quoted 1 line
Signed-off-by Matthew Maurer <[email protected]>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 anOptionplacement + a few ergonomic improvements topin_initwould 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 theFileAPI 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 theFileAPI.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
Benno Lossin
reviewerRe: [PATCH v10 5/7] samples: rust: Add debugfs sample driver
In reply to Matthew MaurerQuoted 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::Diranddebugfs::FileAPIs to create and manage debugfs entries.Quoted 6 lines
Quoted 1 line
Signed-off-by Matthew Maurer <[email protected]>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 anOptionplacement + 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_initwould 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
FileAPI 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.
PATCH v10 6/7 rust: debugfs: Add support for scoped directories
In reply to Matthew MaurerIntroduces 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
@@ -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 (&'dataT , &'dirScopedDir <'data, 'dir>) + 'a, + >( + &'a self, + data:TI , + name: &'aCStr , + 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 (&'aT ) ->Entry + 'b>( +fn new <'b,E : 'b,TI :PinInit <T ,E > + 'b,F :for <'a>FnOnce (&'aT ) ->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 (&'dataT , &'dirScopedDir <'data, 'dir>) + 'a, + >( + data:TI , + name: &'aCStr , + 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: &'dataT , vtable: &'staticFileOps <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: &'dataT ) { + 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: &'dataT , + _f: &'staticF , + ) { +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: &'dataT , + ) { +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: &'dataT , + _f: &'staticF , + _w: &'staticW , + ) { +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: &'dataT , + ) { +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: &'dataT , + _w: &'staticW , + ) { +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
PATCH v10 7/7 samples: rust: Add scoped debugfs sample driver
In reply to Matthew MaurerAdds 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
@@ -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
@@ -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
@@ -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
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
Signed-off-by Matthew Maurer <[email protected]>---
Changes in v10:
- Introduced Scoped to show how either a File or Dir can be bound to
data
- Remove use ofuse<>for MSRV compatibility
- Added Write support
- Added more complex sample driver using scoped interface
- Updated original sample driver to use writes to drive mutation
- AddedFileOps<T>(only for DebugFS, not a kernel-wide abstraction)
to decrease neededunsafefor keeping vtables paired to types.
- Centralizeddentrylifecycle management toentry.rs.
- Link to v9: https://lore.kernel.org/r/[email protected]Changes in v9:
- Switched toPinInitbacking instead ofForeignOwnable
- 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 tocore::from_{ref, mut}in type change
- Link to v7: https://lore.kernel.org/r/[email protected]Changes in v7:
- Rewroteentry::Entry->Entry
- Usec_intandc_voidfrom kernel prelude rather than core
- Removed unnecessarydisplay_opencast
- Switched fromDeref+ an explicit box toForeignOwnablefor
attaching owned data.
- Made&'staticand&'static mutimplementForeignOwnable
- 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, useforget. To keep a handle without drop, useManuallyDrop.
- Fixed bugs aroundnot(CONFIG_DEBUG_FS)
- Removed unnecessaryaddr_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
- Accessprivatefield 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
- Putas_ptrproperties into a# Guaranteessection.
- Link to v3: https://lore.kernel.org/r/[email protected]Changes in v3:
- SplitDirintoDir/SubDir/Fileto 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
- RemoveARefusage to abstract the dentry nature of handles
- Remove error handling to discourage users from caring whether DebugFS
is enabled.
- SupportCONFIG_DEBUG_FS=nwhile 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 driverMAINTAINERS | 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-3cd5c97eb7d1Best regards,
--
Matthew Maurer <[email protected]>
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.
Signed-off-by Matthew Maurer <[email protected]>
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