From: Matthew Maurer <mmaurer@google.com>
To: "Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <benno.lossin@proton.me>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Rafael J. Wysocki" <rafael@kernel.org>,
"Sami Tolvanen" <samitolvanen@google.com>,
"Timur Tabi" <ttabi@nvidia.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Matthew Maurer <mmaurer@google.com>
Subject: [PATCH v3 3/4] rust: debugfs: Support format hooks
Date: Thu, 01 May 2025 22:47:43 +0000 [thread overview]
Message-ID: <20250501-debugfs-rust-v3-3-850869fab672@google.com> (raw)
In-Reply-To: <20250501-debugfs-rust-v3-0-850869fab672@google.com>
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 | 118 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 117 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index ef69ae8f550a9fe6b0afc1c51bebaad2fc087811..9689e21cb24903e9f5a21fb93e6652adc5cafbdc 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -7,6 +7,7 @@
//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
use crate::str::CStr;
+use core::fmt;
use core::fmt::Display;
use core::mem::ManuallyDrop;
use core::ops::Deref;
@@ -133,6 +134,48 @@ fn as_ptr(&self) -> *mut bindings::dentry {
/// // "my_debugfs_dir/foo" now contains the number 200.
/// ```
pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) -> File {
+ // SAFETY: As `data` lives for the static lifetime, it outlives the file.
+ unsafe { self.display_file_raw(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<T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
+ &self,
+ name: &CStr,
+ data: &'static T,
+ f: &'static F,
+ ) -> File {
+ // SAFETY: As `data` lives for the static lifetime, it outlives the file
+ unsafe { self.fmt_file_raw(name, data, f) }
+ }
+
+ /// Creates a DebugFS file backed by the display implementation of the provided pointer.
+ ///
+ /// # Safety
+ ///
+ /// The pointee of `data` must outlive the accessibility of the `Dir` returned by this function.
+ /// This means that before `data` may become invalid, either:
+ /// * The refcount must go to zero.
+ /// * The file must be rendered inaccessible, e.g. via `debugfs_remove`.
+ unsafe fn display_file_raw<T: Display + Sized>(&self, name: &CStr, data: *const T) -> 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.
@@ -161,6 +204,32 @@ pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'static T) ->
};
File(ManuallyDrop::new(dir))
}
+
+ /// Create a file in a DebugFS directory with the provided name, and contents from invoking the
+ /// fomatter on the attached data.
+ ///
+ /// The attached function must be a ZST, and will cause a compilation error if it is not.
+ ///
+ /// # Safety
+ ///
+ /// `data` must outlive the resulting file's accessibility
+ unsafe fn fmt_file_raw<T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
+ &self,
+ name: &CStr,
+ data: &T,
+ f: &'static F,
+ ) -> File {
+ #[cfg(CONFIG_DEBUG_FS)]
+ let data_adapted = FormatAdapter::new(data, f);
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ let data_adapted = {
+ // Mark used
+ let (_, _) = (data, f);
+ &0
+ };
+ // SAFETY: data outlives the file's accessibility, so data_adapted does too
+ unsafe { self.display_file_raw(name, data_adapted) }
+ }
}
impl Drop for Dir {
@@ -228,7 +297,9 @@ pub fn remove(self) {
mod helpers {
use crate::seq_file::SeqFile;
use crate::seq_print;
- use core::fmt::Display;
+ use core::fmt;
+ use core::fmt::{Display, Formatter};
+ use core::marker::PhantomData;
/// Implements `open` for `file_operations` via `single_open` to fill out a `seq_file`.
///
@@ -283,6 +354,51 @@ pub(crate) trait DisplayFile: Display + Sized {
}
impl<T: Display + Sized> DisplayFile for T {}
+
+ /// 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<T, F> {
+ inner: T,
+ _formatter: PhantomData<F>,
+ }
+
+ impl<T, F> FormatAdapter<T, F> {
+ pub(crate) fn new<'a>(inner: &'a T, _f: &'static F) -> &'a Self {
+ // SAFETY: FormatAdapater is a repr(transparent) wrapper around T, so
+ // casting a reference is legal
+ // INVARIANT: We were passed a reference to F, so it is inhabited.
+ unsafe { core::mem::transmute(inner) }
+ }
+ }
+
+ impl<T, F> Display for FormatAdapter<T, F>
+ where
+ F: Fn(&T, &mut Formatter<'_>) -> fmt::Result + 'static,
+ {
+ fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
+ // SAFETY: FormatAdapter<_, F> can only be constructed if F is inhabited
+ let f: &F = unsafe { materialize_zst_fmt() };
+ f(&self.inner, 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() }
+ }
}
#[cfg(CONFIG_DEBUG_FS)]
--
2.49.0.906.g1f30a19c02-goog
next prev parent reply other threads:[~2025-05-01 22:47 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-01 22:47 [PATCH v3 0/4] rust: DebugFS Bindings Matthew Maurer
2025-05-01 22:47 ` [PATCH v3 1/4] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
2025-05-02 6:37 ` Danilo Krummrich
2025-05-02 7:00 ` Greg Kroah-Hartman
2025-05-02 7:05 ` Danilo Krummrich
2025-05-02 7:11 ` Greg Kroah-Hartman
2025-05-02 7:33 ` Danilo Krummrich
2025-05-02 7:39 ` Danilo Krummrich
2025-05-02 11:55 ` Greg Kroah-Hartman
2025-05-02 16:13 ` Matthew Maurer
2025-05-02 15:48 ` Matthew Maurer
2025-05-03 11:58 ` Danilo Krummrich
2025-05-02 8:12 ` Benno Lossin
2025-05-02 11:36 ` Greg Kroah-Hartman
2025-05-01 22:47 ` [PATCH v3 2/4] rust: debugfs: Bind file creation for long-lived Display Matthew Maurer
2025-05-02 6:52 ` Danilo Krummrich
2025-05-02 18:07 ` Matthew Maurer
2025-05-03 12:14 ` Danilo Krummrich
2025-05-01 22:47 ` Matthew Maurer [this message]
2025-05-01 22:47 ` [PATCH v3 4/4] rust: samples: Add debugfs sample Matthew Maurer
2025-05-02 7:01 ` Danilo Krummrich
2025-05-02 7:13 ` Greg Kroah-Hartman
2025-05-02 7:44 ` Danilo Krummrich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250501-debugfs-rust-v3-3-850869fab672@google.com \
--to=mmaurer@google.com \
--cc=a.hindborg@kernel.org \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rafael@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=samitolvanen@google.com \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.