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>,
"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>,
"Benno Lossin" <lossin@kernel.org>,
"Dirk Beheme" <dirk.behme@de.bosch.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
Matthew Maurer <mmaurer@google.com>
Subject: [PATCH v10 4/7] rust: debugfs: Add support for callback-based files
Date: Tue, 19 Aug 2025 22:53:39 +0000 [thread overview]
Message-ID: <20250819-debugfs-rust-v10-4-86e20f3cf3bb@google.com> (raw)
In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com>
Extends the `debugfs` API to support creating files with content
generated and updated by callbacks. This is done via the
`read_callback_file`, `write_callback_file`, and
`read_write_callback_file` methods.
These methods allow for more flexible file definition, either because
the type already has a `Render` or `UpdateFromSlice` method that doesn't
do what you'd like, or because you cannot implement it (e.g. because
it's a type defined in another crate or a primitive type).
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/debugfs.rs | 95 ++++++++++++++++++++++++
rust/kernel/debugfs/callback_adapters.rs | 122 +++++++++++++++++++++++++++++++
rust/kernel/debugfs/file_ops.rs | 8 ++
3 files changed, 225 insertions(+)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 62bc2b1d4e5a4b21441a09e03bff74c32c6781d2..a843d01506a54d5f8626dab5223d006c9a363a91 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -12,12 +12,16 @@
use crate::str::CStr;
#[cfg(CONFIG_DEBUG_FS)]
use crate::sync::Arc;
+use crate::uaccess::UserSliceReader;
+use core::fmt;
use core::marker::PhantomPinned;
use core::ops::Deref;
mod traits;
pub use traits::{Render, UpdateFromSlice};
+mod callback_adapters;
+use callback_adapters::{FormatAdapter, NoRender, WritableAdapter};
mod file_ops;
use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
#[cfg(CONFIG_DEBUG_FS)]
@@ -137,6 +141,48 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<
self.create_file(name, data, file_ops)
}
+ /// Creates a read-only file in this directory, with contents from a callback.
+ ///
+ /// `f` must be a function item or a non-capturing closure.
+ /// This is statically asserted and not a safety requirement.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use core::sync::atomic::{AtomicU32, Ordering};
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::Dir;
+ /// # use kernel::prelude::*;
+ /// # let dir = Dir::new(c_str!("foo"));
+ /// let file = KBox::pin_init(
+ /// dir.read_callback_file(c_str!("bar"),
+ /// AtomicU32::new(3),
+ /// &|val, f| {
+ /// let out = val.load(Ordering::Relaxed);
+ /// writeln!(f, "{out:#010x}")
+ /// }),
+ /// GFP_KERNEL)?;
+ /// // Reading "foo/bar" will show "0x00000003".
+ /// file.store(10, Ordering::Relaxed);
+ /// // Reading "foo/bar" will now show "0x0000000a".
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn read_callback_file<
+ 'a,
+ T: Send + Sync + 'static,
+ E: 'a,
+ TI: PinInit<T, E> + 'a,
+ F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+ >(
+ &'a self,
+ name: &'a CStr,
+ data: TI,
+ _f: &'static F,
+ ) -> impl PinInit<File<T>, E> + 'a {
+ let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt();
+ self.create_file(name, data, file_ops)
+ }
+
/// Creates a read-write file in this directory.
///
/// Reading the file uses the [`Render`] implementation.
@@ -155,6 +201,33 @@ pub fn read_write_file<
self.create_file(name, data, file_ops)
}
+ /// Creates a read-write file in this directory, with logic from callbacks.
+ ///
+ /// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
+ ///
+ /// `f` and `w` must be function items or non-capturing closures.
+ /// This is statically asserted and not a safety requirement.
+ pub fn read_write_callback_file<
+ 'a,
+ T: Send + Sync + 'static,
+ E: 'a,
+ TI: PinInit<T, E> + 'a,
+ F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
+ W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+ >(
+ &'a self,
+ name: &'a CStr,
+ data: TI,
+ _f: &'static F,
+ _w: &'static W,
+ ) -> impl PinInit<File<T>, E> + 'a {
+ let file_ops =
+ <WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS
+ .adapt()
+ .adapt();
+ self.create_file(name, data, file_ops)
+ }
+
/// Creates a write-only file in this directory.
///
/// The file owns its backing data. Writing to the file uses the [`UpdateFromSlice`]
@@ -173,6 +246,28 @@ pub fn write_only_file<
) -> impl PinInit<File<T>, E> + 'a {
self.create_file(name, data, &T::FILE_OPS)
}
+
+ /// Creates a write-only file in this directory, with write logic from a callback.
+ ///
+ /// `w` must be a function item or a non-capturing closure.
+ /// This is statically asserted and not a safety requirement.
+ pub fn write_callback_file<
+ 'a,
+ T: Send + Sync + 'static,
+ E: 'a,
+ TI: PinInit<T, E> + 'a,
+ W: Fn(&T, &mut UserSliceReader) -> Result<(), Error> + Send + Sync,
+ >(
+ &'a self,
+ name: &'a CStr,
+ data: TI,
+ _w: &'static W,
+ ) -> impl PinInit<File<T>, E> + 'a {
+ let file_ops = <WritableAdapter<NoRender<T>, W> as WriteFile<_>>::FILE_OPS
+ .adapt()
+ .adapt();
+ self.create_file(name, data, file_ops)
+ }
}
#[pin_data]
diff --git a/rust/kernel/debugfs/callback_adapters.rs b/rust/kernel/debugfs/callback_adapters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1b6001306bf4efabf144cb24aa793e07ea8ac2fb
--- /dev/null
+++ b/rust/kernel/debugfs/callback_adapters.rs
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+//! Adapters which allow the user to supply a render or update implementation as a value rather
+//! than a trait implementation. If provided, it will override the trait implementation.
+
+use super::{Render, UpdateFromSlice};
+use crate::prelude::*;
+use crate::uaccess::UserSliceReader;
+use core::fmt;
+use core::fmt::Formatter;
+use core::marker::PhantomData;
+use core::ops::Deref;
+
+/// # Safety
+///
+/// To implement this trait, it must be safe to cast a `&Self` to a `&Inner`.
+/// It is intended for use in unstacking adapters out of `FileOps` backings.
+pub(crate) unsafe trait Adapter {
+ type Inner;
+}
+
+/// Adapter to implement `UpdateFromSlice` via a callback with the same representation as `T`.
+///
+/// * Layer it on top of `RenderAdapter` if you want to add a custom callback for `render`.
+/// * Layer it on top of `NoRender` to pass through any support present on the underlying type.
+///
+/// # Invariants
+///
+/// If an instance for `WritableAdapter<_, W>` is constructed, `W` is inhabited.
+#[repr(transparent)]
+pub(crate) struct WritableAdapter<D, W> {
+ inner: D,
+ _writer: PhantomData<W>,
+}
+
+// SAFETY: Stripping off the adapter only removes constraints
+unsafe impl<D, W> Adapter for WritableAdapter<D, W> {
+ type Inner = D;
+}
+
+impl<D: Render, W> Render for WritableAdapter<D, W> {
+ fn render(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.inner.render(fmt)
+ }
+}
+
+impl<D: Deref, W> UpdateFromSlice for WritableAdapter<D, W>
+where
+ W: Fn(&D::Target, &mut UserSliceReader) -> Result + Send + Sync + 'static,
+{
+ fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result {
+ // SAFETY: WritableAdapter<_, W> can only be constructed if W is inhabited
+ let w: &W = unsafe { materialize_zst() };
+ w(self.inner.deref(), reader)
+ }
+}
+
+/// Adapter to implement `Render` 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> Deref for FormatAdapter<D, F> {
+ type Target = D;
+ fn deref(&self) -> &D {
+ &self.inner
+ }
+}
+
+impl<D, F> Render for FormatAdapter<D, F>
+where
+ F: Fn(&D, &mut Formatter<'_>) -> fmt::Result + 'static,
+{
+ fn render(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
+ // SAFETY: FormatAdapter<_, F> can only be constructed if F is inhabited
+ let f: &F = unsafe { materialize_zst() };
+ f(&self.inner, fmt)
+ }
+}
+
+// SAFETY: Stripping off the adapter only removes constraints
+unsafe impl<D, F> Adapter for FormatAdapter<D, F> {
+ type Inner = D;
+}
+
+#[repr(transparent)]
+pub(crate) struct NoRender<D> {
+ inner: D,
+}
+
+// SAFETY: Stripping off the adapter only removes constraints
+unsafe impl<D> Adapter for NoRender<D> {
+ type Inner = D;
+}
+
+impl<D> Deref for NoRender<D> {
+ type Target = D;
+ fn deref(&self) -> &D {
+ &self.inner
+ }
+}
+
+/// For types with a unique value, produce a static reference to it.
+///
+/// # Safety
+///
+/// The caller asserts that F is inhabited
+unsafe fn materialize_zst<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() }
+}
diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs
index 30f6a0532c7f5f4a2974edc8f1100f5485aa8da9..d11c09520d4464417003362b7468c9b9e1a2e1bf 100644
--- a/rust/kernel/debugfs/file_ops.rs
+++ b/rust/kernel/debugfs/file_ops.rs
@@ -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;
--
2.51.0.rc1.167.g924127e9c0-goog
next prev parent reply other threads:[~2025-08-19 22:53 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-19 22:53 [PATCH v10 0/7] rust: DebugFS Bindings Matthew Maurer
2025-08-19 22:53 ` [PATCH v10 1/7] rust: debugfs: Add initial support for directories Matthew Maurer
2025-08-26 15:39 ` Danilo Krummrich
2025-08-19 22:53 ` [PATCH v10 2/7] rust: debugfs: Add support for read-only files Matthew Maurer
2025-08-26 18:45 ` Danilo Krummrich
2025-08-19 22:53 ` [PATCH v10 3/7] rust: debugfs: Add support for writable files Matthew Maurer
2025-08-26 19:38 ` Danilo Krummrich
2025-08-19 22:53 ` Matthew Maurer [this message]
2025-08-19 22:53 ` [PATCH v10 5/7] samples: rust: Add debugfs sample driver Matthew Maurer
2025-08-20 0:34 ` Danilo Krummrich
2025-08-20 0:40 ` Matthew Maurer
2025-08-20 0:42 ` Matthew Maurer
2025-08-20 7:46 ` Benno Lossin
2025-08-19 22:53 ` [PATCH v10 6/7] rust: debugfs: Add support for scoped directories Matthew Maurer
2025-08-19 22:53 ` [PATCH v10 7/7] samples: rust: Add scoped debugfs sample driver Matthew Maurer
2025-08-19 23:14 ` [PATCH v10 0/7] rust: DebugFS Bindings Matthew Maurer
2025-08-25 11:51 ` Dirk Behme
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=20250819-debugfs-rust-v10-4-86e20f3cf3bb@google.com \
--to=mmaurer@google.com \
--cc=a.hindborg@kernel.org \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=dirk.behme@de.bosch.com \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@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.