* [PATCH v7 0/6] rust: DebugFS Bindings
@ 2025-06-24 23:25 Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 1/6] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
This series provides safe DebugFS bindings for Rust, with a sample
module using them.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
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/20250618-debugfs-rust-v6-0-72cae211b133@google.com
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/20250505-debugfs-rust-v5-0-3e93ce7bb76e@google.com
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/20250502-debugfs-rust-v4-0-788a9c6c2e77@google.com
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/20250501-debugfs-rust-v3-0-850869fab672@google.com
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/20250430-debugfs-rust-v2-0-2e8d3985812b@google.com
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/20250429-debugfs-rust-v1-0-6b6e7cb7929f@google.com
---
Matthew Maurer (6):
rust: debugfs: Bind DebugFS directory creation
rust: debugfs: Bind file creation for long-lived Display
rust: types: Support &'static and &'static mut ForeignOwnable
rust: debugfs: Support arbitrary owned backing for File
rust: debugfs: Support format hooks
rust: samples: Add debugfs sample
MAINTAINERS | 3 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/debugfs.rs | 268 ++++++++++++++++++++++++++++++++++++
rust/kernel/debugfs/display_file.rs | 155 +++++++++++++++++++++
rust/kernel/debugfs/entry.rs | 66 +++++++++
rust/kernel/lib.rs | 1 +
rust/kernel/types.rs | 58 ++++++++
samples/rust/Kconfig | 11 ++
samples/rust/Makefile | 1 +
samples/rust/rust_debugfs.rs | 76 ++++++++++
10 files changed, 640 insertions(+)
---
base-commit: bd243cb5d922cc6343007a3f3918c8d1970541d2
change-id: 20250428-debugfs-rust-3cd5c97eb7d1
Best regards,
--
Matthew Maurer <mmaurer@google.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 1/6] rust: debugfs: Bind DebugFS directory creation
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 2/6] rust: debugfs: Bind file creation for long-lived Display Matthew Maurer
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Support creating DebugFS directories and subdirectories. Similar to the
original DebugFS API, errors are hidden.
Directories persist until their handle and the handles of any child
objects have been dropped.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
MAINTAINERS | 2 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/debugfs.rs | 90 +++++++++++++++++++++++++++++++++++++++++
rust/kernel/debugfs/entry.rs | 58 ++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
5 files changed, 152 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c3f7fbd0d67afe8376ea84bc17d70e9fa4b89bf6..551e3a6a16d9051a2d58a77614c458287da2bdcc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7366,6 +7366,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_id.rs
F: rust/kernel/devres.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 8cbb660e2ec218021d16e6e0144acf6f4d7cca13..655d88b8e7fe717190ccfb7b8173e84213bf8331 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -45,6 +45,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>
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2359bd11cd664fb9f7206f8fe38f758dc43d2cb8
--- /dev/null
+++ b/rust/kernel/debugfs.rs
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+//! DebugFS Abstraction
+//!
+//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
+
+#[cfg(CONFIG_DEBUG_FS)]
+use crate::prelude::GFP_KERNEL;
+use crate::str::CStr;
+#[cfg(CONFIG_DEBUG_FS)]
+use crate::sync::Arc;
+
+#[cfg(CONFIG_DEBUG_FS)]
+mod entry;
+#[cfg(CONFIG_DEBUG_FS)]
+use entry::Entry;
+
+/// Owning handle to a DebugFS directory.
+///
+/// This directory will be cleaned up when the handle and all child directory/file handles have
+/// been dropped.
+// We hold a reference to our parent if it exists to prevent the dentry we point to from being
+// cleaned up when our parent is removed.
+pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry>>);
+
+impl Dir {
+ /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
+ #[cfg(CONFIG_DEBUG_FS)]
+ fn create(name: &CStr, parent: Option<&Dir>) -> Self {
+ let parent_ptr = match parent {
+ // If the parent couldn't be allocated, just early-return
+ Some(Dir(None)) => return Self(None),
+ Some(Dir(Some(entry))) => entry.as_ptr(),
+ None => core::ptr::null_mut(),
+ };
+ // SAFETY:
+ // * `name` argument points to a NUL-terminated string that lives across the call, by
+ // invariants of `&CStr`.
+ // * If `parent` is `None`, `parent_ptr` is null to mean create at root.
+ // * If `parent` is `Some`, `parent_ptr` is a live dentry debugfs pointer.
+ let dir = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
+
+ Self(
+ // If Arc creation fails, the `Entry` will be dropped, so the directory will be cleaned
+ // up.
+ Arc::new(
+ // SAFETY: `debugfs_create_dir` either returns an error code or a legal `dentry`
+ // pointer, and the parent is the same one passed to `debugfs_create_dir`
+ unsafe { Entry::new(dir, parent.and_then(|dir| dir.0.clone())) },
+ GFP_KERNEL,
+ )
+ .ok(),
+ )
+ }
+
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ fn create(_name: &CStr, _parent: Option<&Dir>) -> Self {
+ Self()
+ }
+
+ /// Create a DebugFS subdirectory.
+ ///
+ /// Subdirectory handles cannot outlive the directory handle they were created from.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// let parent = Dir::new(c_str!("parent"));
+ /// let child = parent.subdir(c_str!("child"));
+ /// ```
+ pub fn subdir(&self, name: &CStr) -> Self {
+ Dir::create(name, Some(self))
+ }
+
+ /// Create a new directory in DebugFS at the root.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// let debugfs = Dir::new(c_str!("parent"));
+ /// ```
+ pub fn new(name: &CStr) -> Self {
+ Dir::create(name, None)
+ }
+}
diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ae0e2c4e1d58e878ebb081a71e4ac0f4a7d99b91
--- /dev/null
+++ b/rust/kernel/debugfs/entry.rs
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+use crate::sync::Arc;
+
+/// Owning handle to a DebugFS entry.
+///
+/// # Invariants
+///
+/// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`.
+pub(crate) struct Entry {
+ entry: *mut bindings::dentry,
+ _parent: Option<Arc<Entry>>,
+}
+
+// SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred
+// between threads.
+unsafe impl Send for Entry {}
+
+// SAFETY: All the native functions we re-export use interior locking, and the contents of the
+// struct are opaque to Rust.
+unsafe impl Sync for Entry {}
+
+impl Entry {
+ /// Constructs a new DebugFS [`Entry`] from the underlying pointer.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must either be an error code, `NULL`, or represent a transfer of ownership of a
+ /// live DebugFS directory. If this is a child directory or file, `'a` must be less than the
+ /// lifetime of the parent directory.
+ ///
+ /// If the dentry has a parent, it must be provided as the parent argument.
+ pub(crate) unsafe fn new(entry: *mut bindings::dentry, parent: Option<Arc<Entry>>) -> Self {
+ Self {
+ entry,
+ _parent: parent,
+ }
+ }
+
+ /// Returns the pointer representation of the DebugFS directory.
+ ///
+ /// # Guarantees
+ ///
+ /// Due to the type invariant, the value returned from this function will always be an error
+ /// code, NULL, or a live DebugFS directory.
+ pub(crate) fn as_ptr(&self) -> *mut bindings::dentry {
+ self.entry
+ }
+}
+
+impl Drop for Entry {
+ fn drop(&mut self) {
+ // SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries.
+ // `as_ptr` guarantees that the pointer is of this form.
+ unsafe { bindings::debugfs_remove(self.as_ptr()) }
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 6b4774b2b1c37f4da1866e993be6230bc6715841..43f40b1baa9717ea71e4586042e9e6979491ad37 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -66,6 +66,7 @@
pub mod cpufreq;
pub mod cpumask;
pub mod cred;
+pub mod debugfs;
pub mod device;
pub mod device_id;
pub mod devres;
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 2/6] rust: debugfs: Bind file creation for long-lived Display
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 1/6] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable Matthew Maurer
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Allows creation of files for references that live forever and lack
metadata through the `Display` implementation.
The `Display` implementation is used because `seq_printf` needs to route
through `%pA`, which in turn routes through Arguments.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/debugfs.rs | 62 +++++++++++++++++++++++++++++++++++++
rust/kernel/debugfs/display_file.rs | 61 ++++++++++++++++++++++++++++++++++++
rust/kernel/debugfs/entry.rs | 8 +++++
3 files changed, 131 insertions(+)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 2359bd11cd664fb9f7206f8fe38f758dc43d2cb8..1f20d85da56fcb89476552feefc9d97fab43cc04 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -10,7 +10,10 @@
use crate::str::CStr;
#[cfg(CONFIG_DEBUG_FS)]
use crate::sync::Arc;
+use core::fmt::Display;
+#[cfg(CONFIG_DEBUG_FS)]
+mod display_file;
#[cfg(CONFIG_DEBUG_FS)]
mod entry;
#[cfg(CONFIG_DEBUG_FS)]
@@ -59,6 +62,43 @@ fn create(_name: &CStr, _parent: Option<&Dir>) -> Self {
Self()
}
+ #[cfg(CONFIG_DEBUG_FS)]
+ fn create_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+ let Some(parent) = &self.0 else {
+ return File {
+ _entry: Entry::empty(),
+ };
+ };
+ // SAFETY:
+ // * `name` is a NUL-terminated C string, living across the call, by `CStr` invariant.
+ // * `parent` is a live `dentry` since we have a reference to it.
+ // * `vtable` is all stock `seq_file` implementations except for `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 we know because
+ // we have a static reference.
+ let ptr = unsafe {
+ bindings::debugfs_create_file_full(
+ name.as_char_ptr(),
+ 0o444,
+ parent.as_ptr(),
+ data as *const _ as *mut _,
+ core::ptr::null(),
+ &<T as display_file::DisplayFile>::VTABLE,
+ )
+ };
+
+ // SAFETY: `debugfs_create_file_full` either returns an error code or a legal
+ // dentry pointer, so `Entry::new` is safe to call here.
+ let entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
+
+ File { _entry: entry }
+ }
+
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ fn create_file<T: Display + Sized>(&self, _name: &CStr, _data: &'static T) -> File {
+ File {}
+ }
+
/// Create a DebugFS subdirectory.
///
/// Subdirectory handles cannot outlive the directory handle they were created from.
@@ -75,6 +115,22 @@ pub fn subdir(&self, name: &CStr) -> Self {
Dir::create(name, Some(self))
}
+ /// Create a file in a DebugFS directory with the provided name, and contents from invoking
+ /// [`Display::fmt`] on the provided reference.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// let dir = Dir::new(c_str!("my_debugfs_dir"));
+ /// dir.display_file(c_str!("foo"), &200);
+ /// // "my_debugfs_dir/foo" now contains the number 200.
+ /// ```
+ pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+ self.create_file(name, data)
+ }
+
/// Create a new directory in DebugFS at the root.
///
/// # Examples
@@ -88,3 +144,9 @@ pub fn new(name: &CStr) -> Self {
Dir::create(name, None)
}
}
+
+/// Handle to a DebugFS file.
+pub struct File {
+ #[cfg(CONFIG_DEBUG_FS)]
+ _entry: Entry,
+}
diff --git a/rust/kernel/debugfs/display_file.rs b/rust/kernel/debugfs/display_file.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e4b551f7092884ad12e18a32cc243d0d037931a6
--- /dev/null
+++ b/rust/kernel/debugfs/display_file.rs
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+use crate::prelude::*;
+use crate::seq_file::SeqFile;
+use crate::seq_print;
+use core::fmt::Display;
+
+/// 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 be mutated during this call.
+/// * `file` must point to a live, not-yet-initialized file object.
+pub(crate) unsafe extern "C" fn display_open<T: Display>(
+ inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> c_int {
+ // 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.
+ // * 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(display_act::<T>), (*inode).i_private) }
+}
+
+/// 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 is
+/// not being mutated.
+pub(crate) unsafe extern "C" fn display_act<T: Display>(
+ 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
+ // is not being mutated.
+ let data = unsafe { &*((*seq).private as *mut 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, "{}", data);
+ 0
+}
+
+// Work around lack of generic const items.
+pub(crate) trait DisplayFile: Display + Sized {
+ const VTABLE: bindings::file_operations = bindings::file_operations {
+ read: Some(bindings::seq_read),
+ llseek: Some(bindings::seq_lseek),
+ release: Some(bindings::single_release),
+ open: Some(display_open::<Self>),
+ // SAFETY: `file_operations` supports zeroes in all fields.
+ ..unsafe { core::mem::zeroed() }
+ };
+}
+
+impl<T: Display + Sized> DisplayFile for T {}
diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs
index ae0e2c4e1d58e878ebb081a71e4ac0f4a7d99b91..2baaf31c326c3071b92b5bc37552907fa1102246 100644
--- a/rust/kernel/debugfs/entry.rs
+++ b/rust/kernel/debugfs/entry.rs
@@ -38,6 +38,14 @@ pub(crate) unsafe fn new(entry: *mut bindings::dentry, parent: Option<Arc<Entry>
}
}
+ /// Constructs a placeholder DebugFS [`Entry`].
+ pub(crate) fn empty() -> Self {
+ Self {
+ entry: core::ptr::null_mut(),
+ _parent: None,
+ }
+ }
+
/// Returns the pointer representation of the DebugFS directory.
///
/// # Guarantees
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 1/6] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 2/6] rust: debugfs: Bind file creation for long-lived Display Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-25 8:44 ` Danilo Krummrich
2025-06-24 23:25 ` [PATCH v7 4/6] rust: debugfs: Support arbitrary owned backing for File Matthew Maurer
` (3 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
These types live forever and do not require cleanup, so they can
serve as `ForeignOwnable`.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/types.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs
index 22985b6f69820d6df8ff3aae0bf815fad36a9d92..6f9617b5b491426b1be5f3a27dc2c48ad1854da8 100644
--- a/rust/kernel/types.rs
+++ b/rust/kernel/types.rs
@@ -142,6 +142,64 @@ unsafe fn borrow<'a>(_: *mut Self::PointedTo) -> Self::Borrowed<'a> {}
unsafe fn borrow_mut<'a>(_: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {}
}
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static T {
+ type PointedTo = T;
+ type Borrowed<'a> = &'a T;
+ type BorrowedMut<'a> = &'a T;
+
+ fn into_foreign(self) -> *mut Self::PointedTo {
+ self as *const _ as _
+ }
+
+ unsafe fn from_foreign(foreign: *mut Self::PointedTo) -> Self {
+ // SAFETY: from_foreign has stricter restrictions than borrow
+ unsafe { Self::borrow(foreign) }
+ }
+
+ unsafe fn borrow<'a>(foreign: *mut Self::PointedTo) -> Self::Borrowed<'a> {
+ // SAFETY: We know the original reference lived forever, so we can convert it back
+ unsafe { &*foreign }
+ }
+
+ unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {
+ // SAFETY: borrow_mut has stricter restrictions than borrow
+ unsafe { Self::borrow(foreign) }
+ }
+}
+
+// SAFETY: The `into_foreign` function derives its pointer from a reference, so it is correctly
+// aligned.
+unsafe impl<T: 'static> ForeignOwnable for &'static mut T {
+ type PointedTo = T;
+ type Borrowed<'a> = &'a T;
+ type BorrowedMut<'a> = &'a mut T;
+
+ fn into_foreign(self) -> *mut Self::PointedTo {
+ self as *const _ as _
+ }
+
+ unsafe fn from_foreign(foreign: *mut Self::PointedTo) -> Self {
+ // SAFETY: from_foreign has stricter restrictions than `borrow_mut`
+ unsafe { Self::borrow_mut(foreign) }
+ }
+
+ unsafe fn borrow<'a>(foreign: *mut Self::PointedTo) -> Self::Borrowed<'a> {
+ // SAFETY: We know the original reference lived forever, and the requirements on the
+ // function indicate that `from_foreign` and `borrow_mut` will not happen concurrently, so
+ // we can do a shared borrow.
+ unsafe { &*foreign }
+ }
+
+ unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {
+ // SAFETY: We know the original reference lived forever, and the requirements on the
+ // function indicate that no other borrows will happen concurrently, so we can do a
+ // unique borrow.
+ unsafe { &mut *foreign }
+ }
+}
+
/// Runs a cleanup function/closure when dropped.
///
/// The [`ScopeGuard::dismiss`] function prevents the cleanup function from running.
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 4/6] rust: debugfs: Support arbitrary owned backing for File
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
` (2 preceding siblings ...)
2025-06-24 23:25 ` [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 5/6] rust: debugfs: Support format hooks Matthew Maurer
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
This allows `File`s to be backed by `ForeignOwnable` rather than just
`&'static T`. This means that dynamically allocated objects can be
attached to `File`s without needing to take extra steps to create a
pinned reference that's guaranteed to live long enough.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/debugfs.rs | 99 +++++++++++++++++++++++++++++++------
rust/kernel/debugfs/display_file.rs | 49 +++++++++++-------
2 files changed, 115 insertions(+), 33 deletions(-)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 1f20d85da56fcb89476552feefc9d97fab43cc04..929e55ee5629f6888edf29997b9ed77d274e11c8 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -5,11 +5,11 @@
//!
//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
-#[cfg(CONFIG_DEBUG_FS)]
-use crate::prelude::GFP_KERNEL;
+use crate::prelude::*;
use crate::str::CStr;
#[cfg(CONFIG_DEBUG_FS)]
use crate::sync::Arc;
+use crate::types::ForeignOwnable;
use core::fmt::Display;
#[cfg(CONFIG_DEBUG_FS)]
@@ -63,40 +63,52 @@ fn create(_name: &CStr, _parent: Option<&Dir>) -> Self {
}
#[cfg(CONFIG_DEBUG_FS)]
- fn create_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+ fn create_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D) -> File
+ where
+ for<'a> D::Borrowed<'a>: Display,
+ {
+ let mut file = File {
+ _entry: Entry::empty(),
+ _foreign: ForeignHolder::new(data),
+ };
+
let Some(parent) = &self.0 else {
- return File {
- _entry: Entry::empty(),
- };
+ return file;
};
+
// SAFETY:
// * `name` is a NUL-terminated C string, living across the call, by `CStr` invariant.
// * `parent` is a live `dentry` since we have a reference to it.
// * `vtable` is all stock `seq_file` implementations except for `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 we know because
- // we have a static reference.
+ // we have an owning `D` in the `File`, and we tear down the file during `Drop`.
let ptr = unsafe {
bindings::debugfs_create_file_full(
name.as_char_ptr(),
0o444,
parent.as_ptr(),
- data as *const _ as *mut _,
+ file._foreign.data,
core::ptr::null(),
- &<T as display_file::DisplayFile>::VTABLE,
+ &<D as display_file::DisplayFile>::VTABLE,
)
};
// SAFETY: `debugfs_create_file_full` either returns an error code or a legal
// dentry pointer, so `Entry::new` is safe to call here.
- let entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
+ file._entry = unsafe { Entry::new(ptr, Some(parent.clone())) };
- File { _entry: entry }
+ file
}
#[cfg(not(CONFIG_DEBUG_FS))]
- fn create_file<T: Display + Sized>(&self, _name: &CStr, _data: &'static T) -> File {
- File {}
+ fn create_file<D: ForeignOwnable>(&self, _name: &CStr, data: D) -> File
+ where
+ for<'a> D::Borrowed<'a>: Display,
+ {
+ File {
+ _foreign: ForeignHolder::new(data),
+ }
}
/// Create a DebugFS subdirectory.
@@ -127,7 +139,21 @@ pub fn subdir(&self, name: &CStr) -> Self {
/// dir.display_file(c_str!("foo"), &200);
/// // "my_debugfs_dir/foo" now contains the number 200.
/// ```
- pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+ ///
+ /// ```
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// # use kernel::prelude::*;
+ /// let val = KBox::new(300, GFP_KERNEL)?;
+ /// let dir = Dir::new(c_str!("my_debugfs_dir"));
+ /// dir.display_file(c_str!("foo"), val);
+ /// // "my_debugfs_dir/foo" now contains the number 300.
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn display_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D) -> File
+ where
+ for<'a> D::Borrowed<'a>: Display,
+ {
self.create_file(name, data)
}
@@ -147,6 +173,51 @@ pub fn new(name: &CStr) -> Self {
/// Handle to a DebugFS file.
pub struct File {
+ // This order is load-bearing for drops - `_entry` must be dropped before `_foreign`
#[cfg(CONFIG_DEBUG_FS)]
_entry: Entry,
+ _foreign: ForeignHolder,
+}
+
+struct ForeignHolder {
+ data: *mut c_void,
+ drop_hook: unsafe fn(*mut c_void),
+}
+
+// SAFETY: We only construct `ForeignHolder` using a pointer from a `ForeignOwnable` which
+// is also `Sync`.
+unsafe impl Sync for ForeignHolder {}
+// SAFETY: We only construct `ForeignHolder` using a pointer from a `ForeignOwnable` which
+// is also `Send`.
+unsafe impl Send for ForeignHolder {}
+
+/// Helper function to drop a `D`-typed foreign ownable from its foreign representation, useful for
+/// cases where you want the type erased.
+/// # Safety
+/// * The foreign pointer passed in must have come from `D`'s `ForeignOwnable::into_foreign`
+/// * There must be no outstanding `ForeignOwnable::borrow{,mut}`
+/// * The pointer must not have been `ForeignOwnable::from_foreign`'d
+unsafe fn drop_helper<D: ForeignOwnable>(foreign: *mut c_void) {
+ // SAFETY: By safetydocs, we meet the requirements for `from_foreign`
+ drop(unsafe { D::from_foreign(foreign as _) })
+}
+
+impl ForeignHolder {
+ fn new<D: ForeignOwnable>(data: D) -> Self {
+ Self {
+ data: data.into_foreign() as _,
+ drop_hook: drop_helper::<D>,
+ }
+ }
+}
+
+impl Drop for ForeignHolder {
+ fn drop(&mut self) {
+ // SAFETY: `drop_hook` corresponds to the original `ForeignOwnable` instance's `drop`.
+ // This is only used in the case of `File`, so the only place borrows occur is through the
+ // DebugFS file owned by `_entry`. Since `_entry` occurs earlier in the struct, it will be
+ // dropped first, so no borrows will be ongoing. We know no `from_foreign` has occurred
+ // because this pointer is not exposed anywhere that is called.
+ unsafe { (self.drop_hook)(self.data) }
+ }
}
diff --git a/rust/kernel/debugfs/display_file.rs b/rust/kernel/debugfs/display_file.rs
index e4b551f7092884ad12e18a32cc243d0d037931a6..0c2dd756fa866425d1b7771beceaa2fb43bf11e5 100644
--- a/rust/kernel/debugfs/display_file.rs
+++ b/rust/kernel/debugfs/display_file.rs
@@ -4,42 +4,48 @@
use crate::prelude::*;
use crate::seq_file::SeqFile;
use crate::seq_print;
+use crate::types::ForeignOwnable;
use core::fmt::Display;
/// 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 be mutated during this call.
+/// * `inode`'s private pointer must be the foreign representation of `D`, and no mutable borrows
+/// are outstanding.
/// * `file` must point to a live, not-yet-initialized file object.
-pub(crate) unsafe extern "C" fn display_open<T: Display>(
+pub(crate) unsafe extern "C" fn display_open<D: ForeignOwnable + Sync>(
inode: *mut bindings::inode,
file: *mut bindings::file,
-) -> c_int {
+) -> c_int
+where
+ for<'a> D::Borrowed<'a>: Display,
+{
// 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.
- // * 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(display_act::<T>), (*inode).i_private) }
+ // * The `data` pointer passed in the third argument is valid by caller preconditions.
+ unsafe { bindings::single_open(file, Some(display_act::<D>), (*inode).i_private) }
}
/// 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 is
-/// not being mutated.
-pub(crate) unsafe extern "C" fn display_act<T: Display>(
+/// `seq` must point to a live `seq_file` whose private data is the foreign representation of `D`,
+/// and no mutable borrows are outsstanding.
+pub(crate) unsafe extern "C" fn display_act<D: ForeignOwnable + 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
- // is not being mutated.
- let data = unsafe { &*((*seq).private as *mut T) };
- // SAFETY: By caller precondition, `seq_file` points to a live `seq_file`, so we can lift
+) -> c_int
+where
+ for<'a> D::Borrowed<'a>: Display,
+{
+ // SAFETY: By caller precondition, this pointer is live, has the right type, and has no mutable
+ // borrows outstanding.
+ let data = unsafe { D::borrow((*seq).private as _) };
+ // SAFETY: By caller precondition, `seq` points to a live `seq_file`, so we can lift
// it.
let seq_file = unsafe { SeqFile::from_raw(seq) };
seq_print!(seq_file, "{}", data);
@@ -47,15 +53,20 @@
}
// Work around lack of generic const items.
-pub(crate) trait DisplayFile: Display + Sized {
+pub(crate) trait DisplayFile {
+ const VTABLE: bindings::file_operations;
+}
+
+impl<D: ForeignOwnable + Sync> DisplayFile for D
+where
+ for<'a> D::Borrowed<'a>: Display,
+{
const VTABLE: bindings::file_operations = bindings::file_operations {
read: Some(bindings::seq_read),
llseek: Some(bindings::seq_lseek),
release: Some(bindings::single_release),
- open: Some(display_open::<Self>),
+ open: Some(display_open::<D>),
// SAFETY: `file_operations` supports zeroes in all fields.
..unsafe { core::mem::zeroed() }
};
}
-
-impl<T: Display + Sized> DisplayFile for T {}
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 5/6] rust: debugfs: Support format hooks
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
` (3 preceding siblings ...)
2025-06-24 23:25 ` [PATCH v7 4/6] rust: debugfs: Support arbitrary owned backing for File Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 6/6] rust: samples: Add debugfs sample Matthew Maurer
2025-06-25 6:22 ` [PATCH v7 0/6] rust: DebugFS Bindings Dirk Behme
6 siblings, 0 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Rather than always using Display, allow hooking arbitrary functions to
arbitrary files. Display technically has the expressiveness to do this,
but requires a new type be declared for every different way to render
things, which can be very clumsy.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/debugfs.rs | 45 ++++++++++++++++++++
rust/kernel/debugfs/display_file.rs | 85 ++++++++++++++++++++++++++++++++++++-
2 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 929e55ee5629f6888edf29997b9ed77d274e11c8..d74b599e8534536b10502e6db8c6f3197f7ab4a5 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -10,7 +10,9 @@
#[cfg(CONFIG_DEBUG_FS)]
use crate::sync::Arc;
use crate::types::ForeignOwnable;
+use core::fmt;
use core::fmt::Display;
+use core::ops::Deref;
#[cfg(CONFIG_DEBUG_FS)]
mod display_file;
@@ -157,6 +159,49 @@ pub fn display_file<D: ForeignOwnable + Send + Sync>(&self, name: &CStr, data: D
self.create_file(name, data)
}
+ /// Create a file in a DebugFS directory with the provided name, and contents from invoking `f`
+ /// on the provided reference.
+ ///
+ /// `f` must be a function item or a non-capturing closure, or this will fail to compile.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use core::sync::atomic::{AtomicU32, Ordering};
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// let dir = Dir::new(c_str!("foo"));
+ /// static MY_ATOMIC: AtomicU32 = AtomicU32::new(3);
+ /// let file = dir.fmt_file(c_str!("bar"), &MY_ATOMIC, &|val, f| {
+ /// let out = val.load(Ordering::Relaxed);
+ /// writeln!(f, "{out:#010x}")
+ /// });
+ /// MY_ATOMIC.store(10, Ordering::Relaxed);
+ /// ```
+ pub fn fmt_file<
+ D: ForeignOwnable + Send + Sync,
+ T,
+ F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+ >(
+ &self,
+ name: &CStr,
+ data: D,
+ f: &'static F,
+ ) -> File
+ where
+ for<'a> D::Borrowed<'a>: Deref<Target = T>,
+ {
+ #[cfg(CONFIG_DEBUG_FS)]
+ let data_adapted = display_file::FormatAdapter::new(data, f);
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ let data_adapted = {
+ // Mark used
+ let (_, _) = (data, f);
+ &0
+ };
+ self.display_file(name, data_adapted)
+ }
+
/// Create a new directory in DebugFS at the root.
///
/// # Examples
diff --git a/rust/kernel/debugfs/display_file.rs b/rust/kernel/debugfs/display_file.rs
index 0c2dd756fa866425d1b7771beceaa2fb43bf11e5..b38675a90e1b2e359fb54afd91062b26d1c32ba2 100644
--- a/rust/kernel/debugfs/display_file.rs
+++ b/rust/kernel/debugfs/display_file.rs
@@ -5,7 +5,9 @@
use crate::seq_file::SeqFile;
use crate::seq_print;
use crate::types::ForeignOwnable;
-use core::fmt::Display;
+use core::fmt::{Display, Formatter, Result};
+use core::marker::PhantomData;
+use core::ops::Deref;
/// Implements `open` for `file_operations` via `single_open` to fill out a `seq_file`.
///
@@ -70,3 +72,84 @@ impl<D: ForeignOwnable + Sync> DisplayFile for D
..unsafe { core::mem::zeroed() }
};
}
+
+/// Adapter to implement `Display` via a callback with the same representation as `T`.
+///
+/// # Invariants
+///
+/// If an instance for `FormatAdapter<_, F>` is constructed, `F` is inhabited.
+#[repr(transparent)]
+pub(crate) struct FormatAdapter<D, F> {
+ inner: D,
+ _formatter: PhantomData<F>,
+}
+
+impl<D, F> FormatAdapter<D, F> {
+ pub(crate) fn new(inner: D, _f: &'static F) -> Self {
+ // INVARIANT: We were passed a reference to F, so it is inhabited.
+ FormatAdapter {
+ inner,
+ _formatter: PhantomData,
+ }
+ }
+}
+
+pub(crate) struct BorrowedAdapter<'a, D: ForeignOwnable, F> {
+ borrowed: D::Borrowed<'a>,
+ _formatter: PhantomData<F>,
+}
+
+// SAFETY: We delegate to D's implementation of `ForeignOwnable`, so `into_foreign` produced aligned
+// pointers.
+unsafe impl<D: ForeignOwnable, F> ForeignOwnable for FormatAdapter<D, F> {
+ type PointedTo = D::PointedTo;
+ type Borrowed<'a> = BorrowedAdapter<'a, D, F>;
+ type BorrowedMut<'a> = Self::Borrowed<'a>;
+ fn into_foreign(self) -> *mut Self::PointedTo {
+ self.inner.into_foreign()
+ }
+ unsafe fn from_foreign(foreign: *mut Self::PointedTo) -> Self {
+ Self {
+ // SAFETY: `into_foreign` is delegated, so a delegated `from_foreign` is safe.
+ inner: unsafe { D::from_foreign(foreign) },
+ _formatter: PhantomData,
+ }
+ }
+ unsafe fn borrow<'a>(foreign: *mut Self::PointedTo) -> Self::Borrowed<'a> {
+ BorrowedAdapter {
+ // SAFETY: `into_foreign` is delegated, so a delegated `borrow` is safe.
+ borrowed: unsafe { D::borrow(foreign) },
+ _formatter: PhantomData,
+ }
+ }
+ unsafe fn borrow_mut<'a>(foreign: *mut Self::PointedTo) -> Self::BorrowedMut<'a> {
+ // SAFETY: `borrow_mut` has stricter requirements than `borrow`
+ unsafe { Self::borrow(foreign) }
+ }
+}
+
+impl<'a, D: ForeignOwnable<Borrowed<'a>: Deref<Target = T>>, T, F> Display
+ for BorrowedAdapter<'a, D, F>
+where
+ F: Fn(&T, &mut Formatter<'_>) -> Result + 'static,
+{
+ fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
+ // SAFETY: FormatAdapter<_, F> can only be constructed if F is inhabited
+ let f: &F = unsafe { materialize_zst_fmt() };
+ f(&self.borrowed, fmt)
+ }
+}
+
+/// For types with a unique value, produce a static reference to it.
+///
+/// # Safety
+///
+/// The caller asserts that F is inhabited
+unsafe fn materialize_zst_fmt<F>() -> &'static F {
+ const { assert!(core::mem::size_of::<F>() == 0) };
+ let zst_dangle: core::ptr::NonNull<F> = core::ptr::NonNull::dangling();
+ // SAFETY: While the pointer is dangling, it is a dangling pointer to a ZST, based on the
+ // assertion above. The type is also inhabited, by the caller's assertion. This means
+ // we can materialize it.
+ unsafe { zst_dangle.as_ref() }
+}
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 6/6] rust: samples: Add debugfs sample
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
` (4 preceding siblings ...)
2025-06-24 23:25 ` [PATCH v7 5/6] rust: debugfs: Support format hooks Matthew Maurer
@ 2025-06-24 23:25 ` Matthew Maurer
2025-06-25 6:22 ` [PATCH v7 0/6] rust: DebugFS Bindings Dirk Behme
6 siblings, 0 replies; 10+ messages in thread
From: Matthew Maurer @ 2025-06-24 23:25 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Provides an example of using the Rust DebugFS bindings.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
MAINTAINERS | 1 +
samples/rust/Kconfig | 11 +++++++
samples/rust/Makefile | 1 +
samples/rust/rust_debugfs.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 89 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 551e3a6a16d9051a2d58a77614c458287da2bdcc..f55c99cb2907655d109cb1fa248cdec803b5ce9a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7374,6 +7374,7 @@ F: rust/kernel/devres.rs
F: rust/kernel/driver.rs
F: rust/kernel/faux.rs
F: rust/kernel/platform.rs
+F: samples/rust/rust_debugfs.rs
F: samples/rust/rust_driver_platform.rs
F: samples/rust/rust_driver_faux.rs
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 7f7371a004ee0a8f67dca99c836596709a70c4fa..01101db41ae31b08a86d048cdd27da8ef9bb23a2 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -62,6 +62,17 @@ config SAMPLE_RUST_DMA
If unsure, say N.
+config SAMPLE_RUST_DEBUGFS
+ tristate "DebugFS Test Module"
+ depends on DEBUG_FS
+ help
+ This option builds the Rust DebugFS Test module sample.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_debugfs.
+
+ If unsure, say N.
+
config SAMPLE_RUST_DRIVER_PCI
tristate "PCI Driver"
depends on PCI
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index bd2faad63b4f3befe7d1ed5139fe25c7a8b6d7f6..61276222a99f8cc6d7f84c26d0533b30815ebadd 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -4,6 +4,7 @@ ccflags-y += -I$(src) # needed for trace events
obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o
obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE) += rust_misc_device.o
obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o
+obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) += rust_debugfs.o
obj-$(CONFIG_SAMPLE_RUST_DMA) += rust_dma.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o
diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6af9177c711a07764f0323e03a72d115287f1205
--- /dev/null
+++ b/samples/rust/rust_debugfs.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Sample DebugFS exporting module
+
+use core::sync::atomic::{AtomicU32, Ordering};
+use kernel::c_str;
+use kernel::debugfs::{Dir, File};
+use kernel::prelude::*;
+use kernel::sync::{new_mutex, Arc};
+
+module! {
+ type: RustDebugFs,
+ name: "rust_debugfs",
+ authors: ["Matthew Maurer"],
+ description: "Rust DebugFS usage sample",
+ license: "GPL",
+}
+
+struct RustDebugFs {
+ // As we only hold these for drop effect (to remove the directory/files) we have a leading
+ // underscore to indicate to the compiler that we don't expect to use this field directly.
+ _debugfs: Dir,
+ _subdir: Dir,
+ _file: File,
+ _file_2: File,
+}
+
+static EXAMPLE: AtomicU32 = AtomicU32::new(8);
+
+impl kernel::Module for RustDebugFs {
+ fn init(_this: &'static ThisModule) -> Result<Self> {
+ // Create a debugfs directory in the root of the filesystem called "sample_debugfs".
+ let debugfs = Dir::new(c_str!("sample_debugfs"));
+
+ // Create a subdirectory, so "sample_debugfs/subdir" now exists.
+ let sub = debugfs.subdir(c_str!("subdir"));
+
+ // Create a single file in the subdirectory called "example" that will read from the
+ // `EXAMPLE` atomic variable.
+ // We `forget` the result to avoid deleting the file at the end of the scope.
+ let file = sub.fmt_file(c_str!("example"), &EXAMPLE, &|example, f| {
+ writeln!(f, "Reading atomic: {}", example.load(Ordering::Relaxed))
+ });
+ // Now, "sample_debugfs/subdir/example" will print "Reading atomic: 8\n" when read.
+
+ // Change the value in the variable displayed by the file. This is intended to demonstrate
+ // that the module can continue to change the value used by the file.
+ EXAMPLE.store(10, Ordering::Relaxed);
+ // Now, "sample_debugfs/subdir/example" will print "Reading atomic: 10\n" when read.
+
+ // In addition to globals, we can also attach any kind of owned data. Most commonly, this
+ // will look like an `Arc<MyObject>` as those can be shared with the rest of the module.
+ let my_arc = Arc::pin_init(new_mutex!(10), GFP_KERNEL)?;
+ // An `Arc<Mutex<usize>>` doesn't implement display, so let's give explicit instructions on
+ // how to print it
+ let file_2 = sub.fmt_file(c_str!("arc_backed"), my_arc.clone(), &|val, f| {
+ writeln!(f, "locked value: {:#010x}", *val.lock())
+ });
+
+ // Since it's an `Arc` and we cloned it, we continue to have access to `my_arc`. If this
+ // were real, we'd probably stash it in our module struct and do something with it when
+ // handling real calls.
+ *my_arc.lock() = 99;
+
+ // Save the handles we want to preserve to our module object. They will be automatically
+ // cleaned up when our module is unloaded.
+ Ok(Self {
+ _debugfs: debugfs,
+ _subdir: sub,
+ _file: file,
+ _file_2: file_2,
+ })
+ }
+}
--
2.50.0.714.g196bf9f422-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 0/6] rust: DebugFS Bindings
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
` (5 preceding siblings ...)
2025-06-24 23:25 ` [PATCH v7 6/6] rust: samples: Add debugfs sample Matthew Maurer
@ 2025-06-25 6:22 ` Dirk Behme
6 siblings, 0 replies; 10+ messages in thread
From: Dirk Behme @ 2025-06-25 6:22 UTC (permalink / raw)
To: Matthew Maurer, Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin
Cc: linux-kernel, rust-for-linux
On 25/06/2025 01:25, Matthew Maurer wrote:
> This series provides safe DebugFS bindings for Rust, with a sample
> module using them.
>
> Signed-off-by: Matthew Maurer <mmaurer@google.com>
Same testing done as on v6:
Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Thanks!
Dirk
> ---
> 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/20250618-debugfs-rust-v6-0-72cae211b133@google.com
>
> 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/20250505-debugfs-rust-v5-0-3e93ce7bb76e@google.com
>
> 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/20250502-debugfs-rust-v4-0-788a9c6c2e77@google.com
>
> 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/20250501-debugfs-rust-v3-0-850869fab672@google.com
>
> 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/20250430-debugfs-rust-v2-0-2e8d3985812b@google.com
>
> 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/20250429-debugfs-rust-v1-0-6b6e7cb7929f@google.com
>
> ---
> Matthew Maurer (6):
> rust: debugfs: Bind DebugFS directory creation
> rust: debugfs: Bind file creation for long-lived Display
> rust: types: Support &'static and &'static mut ForeignOwnable
> rust: debugfs: Support arbitrary owned backing for File
> rust: debugfs: Support format hooks
> rust: samples: Add debugfs sample
>
> MAINTAINERS | 3 +
> rust/bindings/bindings_helper.h | 1 +
> rust/kernel/debugfs.rs | 268 ++++++++++++++++++++++++++++++++++++
> rust/kernel/debugfs/display_file.rs | 155 +++++++++++++++++++++
> rust/kernel/debugfs/entry.rs | 66 +++++++++
> rust/kernel/lib.rs | 1 +
> rust/kernel/types.rs | 58 ++++++++
> samples/rust/Kconfig | 11 ++
> samples/rust/Makefile | 1 +
> samples/rust/rust_debugfs.rs | 76 ++++++++++
> 10 files changed, 640 insertions(+)
> ---
> base-commit: bd243cb5d922cc6343007a3f3918c8d1970541d2
> change-id: 20250428-debugfs-rust-3cd5c97eb7d1
>
> Best regards,
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable
2025-06-24 23:25 ` [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable Matthew Maurer
@ 2025-06-25 8:44 ` Danilo Krummrich
2025-06-25 8:46 ` Danilo Krummrich
0 siblings, 1 reply; 10+ messages in thread
From: Danilo Krummrich @ 2025-06-25 8:44 UTC (permalink / raw)
To: Matthew Maurer
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Greg Kroah-Hartman, Rafael J. Wysocki, Sami Tolvanen, Timur Tabi,
Benno Lossin, linux-kernel, rust-for-linux
On Tue, Jun 24, 2025 at 11:25:22PM +0000, Matthew Maurer wrote:
> + fn into_foreign(self) -> *mut Self::PointedTo {
> + self as *const _ as _
Please prefer
core::ptr::from_ref(self).cast_mut()
instead.
> + }
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable
2025-06-25 8:44 ` Danilo Krummrich
@ 2025-06-25 8:46 ` Danilo Krummrich
0 siblings, 0 replies; 10+ messages in thread
From: Danilo Krummrich @ 2025-06-25 8:46 UTC (permalink / raw)
To: Matthew Maurer
Cc: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Greg Kroah-Hartman, Rafael J. Wysocki, Sami Tolvanen, Timur Tabi,
Benno Lossin, linux-kernel, rust-for-linux
On Wed, Jun 25, 2025 at 10:44:27AM +0200, Danilo Krummrich wrote:
> On Tue, Jun 24, 2025 at 11:25:22PM +0000, Matthew Maurer wrote:
> > + fn into_foreign(self) -> *mut Self::PointedTo {
> > + self as *const _ as _
>
> Please prefer
>
> core::ptr::from_ref(self).cast_mut()
>
> instead.
I think in the second impl it can just be:
core::ptr::from_mut(self)
>
> > + }
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-06-25 8:46 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-24 23:25 [PATCH v7 0/6] rust: DebugFS Bindings Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 1/6] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 2/6] rust: debugfs: Bind file creation for long-lived Display Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 3/6] rust: types: Support &'static and &'static mut ForeignOwnable Matthew Maurer
2025-06-25 8:44 ` Danilo Krummrich
2025-06-25 8:46 ` Danilo Krummrich
2025-06-24 23:25 ` [PATCH v7 4/6] rust: debugfs: Support arbitrary owned backing for File Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 5/6] rust: debugfs: Support format hooks Matthew Maurer
2025-06-24 23:25 ` [PATCH v7 6/6] rust: samples: Add debugfs sample Matthew Maurer
2025-06-25 6:22 ` [PATCH v7 0/6] rust: DebugFS Bindings Dirk Behme
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).