Support creating DebugFS directories and subdirectories. Similar to the
original DebugFS API, errors are hidden.
By default, when a root directory handle leaves scope, it will be cleaned
up.
Subdirectories will by default persist until their root directory is
cleaned up, but can be converted into a root directory if a scoped
lifecycle is desired.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
MAINTAINERS | 1 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/debugfs.rs | 139 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
4 files changed, 142 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 906881b6c5cb6ff743e13b251873b89138c69a1c..a3b835e427b083a4ddd690d9e7739851f0af47ae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7271,6 +7271,7 @@ 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/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 8a2add69e5d66d1c2ebed9d2c950380e61c48842..787f928467faabd02a7f3cf041378fac856c4f89 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -13,6 +13,7 @@
#include <linux/blkdev.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..ed1aba6d700d064dbfd7e923dbcbf80b9acf5361
--- /dev/null
+++ b/rust/kernel/debugfs.rs
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+//! DebugFS Abstraction
+//!
+//! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
+
+use crate::str::CStr;
+use core::marker::PhantomData;
+
+/// Owning handle to a DebugFS entry.
+///
+/// # Invariants
+///
+/// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`.
+#[repr(transparent)]
+struct Entry<'a> {
+ #[cfg(CONFIG_DEBUG_FS)]
+ entry: *mut bindings::dentry,
+ // We need to be outlived by our parent, if they exist, but we don't actually need to be able
+ // to access the data.
+ _phantom: PhantomData<&'a Entry<'a>>,
+}
+
+// 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<'a> Entry<'a> {
+ /// 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.
+ #[cfg(CONFIG_DEBUG_FS)]
+ unsafe fn from_ptr(entry: *mut bindings::dentry) -> Self {
+ Self {
+ entry,
+ _phantom: PhantomData,
+ }
+ }
+
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ fn new() -> Self {
+ Self {
+ _phantom: PhantomData,
+ }
+ }
+
+ /// 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, NUL, or a live DebugFS directory.
+ // If this function is ever needed with `not(CONFIG_DEBUG_FS)`, hardcode it to return
+ // `ERR_PTR(ENODEV)`.
+ #[cfg(CONFIG_DEBUG_FS)]
+ 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.
+ #[cfg(CONFIG_DEBUG_FS)]
+ unsafe {
+ bindings::debugfs_remove(self.as_ptr())
+ }
+ }
+}
+
+/// Owning handle to a DebugFS directory.
+///
+/// This directory will be cleaned up when the handle leaves scope.
+pub struct Dir<'a>(Entry<'a>);
+
+impl<'a> Dir<'a> {
+ /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
+ #[cfg(CONFIG_DEBUG_FS)]
+ fn create<'b>(name: &CStr, parent: Option<&'a Dir<'b>>) -> Self {
+ let parent_ptr = match parent {
+ Some(parent) => parent.0.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` accepts null pointers to mean create at root.
+ // * If `parent` is `Some`, `parent` accepts live dentry debugfs pointers.
+ // so we can call `Self::from_ptr`.
+ let dir = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
+
+ // SAFETY: `debugfs_create_dir` either returns an error code or a legal `dentry` pointer,
+ Self(unsafe { Entry::from_ptr(dir) })
+ }
+
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ fn create<'b>(_name: &CStr, _parent: Option<&'a Dir<'b>>) -> Self {
+ Self(Entry::new())
+ }
+
+ /// 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<'b>(&'b self, name: &CStr) -> Dir<'b> {
+ 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/lib.rs b/rust/kernel/lib.rs
index c3762e80b314316b4b0cee3bfd9442f8f0510b91..86f6055b828d5f711578293d8916a517f2436977 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -45,6 +45,7 @@
#[doc(hidden)]
pub mod build_assert;
pub mod cred;
+pub mod debugfs;
pub mod device;
pub mod device_id;
pub mod devres;
--
2.49.0.967.g6a0df3ecc3-goog
On Tue May 6, 2025 at 1:51 AM CEST, Matthew Maurer wrote:
> +impl<'a> Dir<'a> {
> + /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
> + #[cfg(CONFIG_DEBUG_FS)]
> + fn create<'b>(name: &CStr, parent: Option<&'a Dir<'b>>) -> Self {
> + let parent_ptr = match parent {
> + Some(parent) => parent.0.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` accepts null pointers to mean create at root.
> + // * If `parent` is `Some`, `parent` accepts live dentry debugfs pointers.
> + // so we can call `Self::from_ptr`.
> + let dir = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
> +
> + // SAFETY: `debugfs_create_dir` either returns an error code or a legal `dentry` pointer,
> + Self(unsafe { Entry::from_ptr(dir) })
> + }
> +
> + #[cfg(not(CONFIG_DEBUG_FS))]
> + fn create<'b>(_name: &CStr, _parent: Option<&'a Dir<'b>>) -> Self {
> + Self(Entry::new())
> + }
> +
> + /// Create a DebugFS subdirectory.
I'm not familiar with debugfs, if I run `Dir::create(c"foo", None)`
twice, will both of the returned values refer to the same or different
directories? What if I give a parent?
If the answer in both cases is that they will refer to the same
directory, then I'd change the docs to mention that. So instead of
"Creates" we could say "Finds or creates" or something better.
If they refer to different files, then I am confused how that would look
like in user-land :)
> + ///
> + /// 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<'b>(&'b self, name: &CStr) -> Dir<'b> {
> + Dir::create(name, Some(self))
> + }
> +
> + /// Create a new directory in DebugFS at the root.
Ditto here.
> + ///
> + /// # 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)
> + }
I think it would make more sense for this function to return
`Dir<'static>`.
---
Cheers,
Benno
> +}
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index c3762e80b314316b4b0cee3bfd9442f8f0510b91..86f6055b828d5f711578293d8916a517f2436977 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -45,6 +45,7 @@
> #[doc(hidden)]
> pub mod build_assert;
> pub mod cred;
> +pub mod debugfs;
> pub mod device;
> pub mod device_id;
> pub mod devres;
On Wed, May 14, 2025 at 09:33:05AM +0200, Benno Lossin wrote:
> On Tue May 6, 2025 at 1:51 AM CEST, Matthew Maurer wrote:
> > +impl<'a> Dir<'a> {
> > + /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
> > + #[cfg(CONFIG_DEBUG_FS)]
> > + fn create<'b>(name: &CStr, parent: Option<&'a Dir<'b>>) -> Self {
> > + let parent_ptr = match parent {
> > + Some(parent) => parent.0.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` accepts null pointers to mean create at root.
> > + // * If `parent` is `Some`, `parent` accepts live dentry debugfs pointers.
> > + // so we can call `Self::from_ptr`.
> > + let dir = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
> > +
> > + // SAFETY: `debugfs_create_dir` either returns an error code or a legal `dentry` pointer,
> > + Self(unsafe { Entry::from_ptr(dir) })
> > + }
> > +
> > + #[cfg(not(CONFIG_DEBUG_FS))]
> > + fn create<'b>(_name: &CStr, _parent: Option<&'a Dir<'b>>) -> Self {
> > + Self(Entry::new())
> > + }
> > +
> > + /// Create a DebugFS subdirectory.
>
> I'm not familiar with debugfs, if I run `Dir::create(c"foo", None)`
> twice, will both of the returned values refer to the same or different
> directories?
You can not create a directory, or file, in the same location with the
same name. The call will fail, so don't do that :)
> What if I give a parent?
Same thing, it will fail.
> If the answer in both cases is that they will refer to the same
> directory, then I'd change the docs to mention that.
Nope, that does not happen.
> So instead of
> "Creates" we could say "Finds or creates" or something better.
Find does not happen.
> If they refer to different files, then I am confused how that would look
> like in user-land :)
Agreed, which is why that does not happen :)
thanks,
greg k-h
On Wed May 14, 2025 at 10:49 AM CEST, Greg Kroah-Hartman wrote:
> On Wed, May 14, 2025 at 09:33:05AM +0200, Benno Lossin wrote:
>> On Tue May 6, 2025 at 1:51 AM CEST, Matthew Maurer wrote:
>> > +impl<'a> Dir<'a> {
>> > + /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
>> > + #[cfg(CONFIG_DEBUG_FS)]
>> > + fn create<'b>(name: &CStr, parent: Option<&'a Dir<'b>>) -> Self {
>> > + let parent_ptr = match parent {
>> > + Some(parent) => parent.0.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` accepts null pointers to mean create at root.
>> > + // * If `parent` is `Some`, `parent` accepts live dentry debugfs pointers.
>> > + // so we can call `Self::from_ptr`.
>> > + let dir = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
>> > +
>> > + // SAFETY: `debugfs_create_dir` either returns an error code or a legal `dentry` pointer,
>> > + Self(unsafe { Entry::from_ptr(dir) })
>> > + }
>> > +
>> > + #[cfg(not(CONFIG_DEBUG_FS))]
>> > + fn create<'b>(_name: &CStr, _parent: Option<&'a Dir<'b>>) -> Self {
>> > + Self(Entry::new())
>> > + }
>> > +
>> > + /// Create a DebugFS subdirectory.
>>
>> I'm not familiar with debugfs, if I run `Dir::create(c"foo", None)`
>> twice, will both of the returned values refer to the same or different
>> directories?
>
> You can not create a directory, or file, in the same location with the
> same name. The call will fail, so don't do that :)
>
>> What if I give a parent?
>
> Same thing, it will fail.
>
>> If the answer in both cases is that they will refer to the same
>> directory, then I'd change the docs to mention that.
>
> Nope, that does not happen.
>
>> So instead of
>> "Creates" we could say "Finds or creates" or something better.
>
> Find does not happen.
>
>> If they refer to different files, then I am confused how that would look
>> like in user-land :)
>
> Agreed, which is why that does not happen :)
Ah that makes sense, thanks for explaining :)
---
Cheers,
Benno
On Mon, 2025-05-05 at 23:51 +0000, Matthew Maurer wrote:
>
> +impl<'a> Entry<'a> {
> + /// 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.
> + #[cfg(CONFIG_DEBUG_FS)]
> + unsafe fn from_ptr(entry: *mut bindings::dentry) -> Self {
> + Self {
> + entry,
> + _phantom: PhantomData,
> + }
> + }
> +
> + #[cfg(not(CONFIG_DEBUG_FS))]
> + fn new() -> Self {
> + Self {
> + _phantom: PhantomData,
> + }
> + }
I am new to Rust, so forgive me if this is a dumb question, but it looks to me that if
CONFIG_DEBUG_FS is defined, then you need to call from_ptr() to create a new Entry, but if
CONFIG_DEBUG_FS is not defined, then you need to call new() instead. Is that right? If so, is that
really idiomatic?
In the Dir implementation below, you are careful to call from_ptr() only from the CONFIG_DEBUG_FS
version of create(), and you call new() only from the !CONFIG_DEBUG_FS version of create(). So your
bases are covered as long as no driver tries to create an Entry from scratch.
But I guess that can't happen because Entry is not public, right?
> + /// 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<'b>(&'b self, name: &CStr) -> Dir<'b> {
> + 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)
> + }
Is there any real value to having two constructors, just to avoid passing None for the one time that
a root directory will be created? The C code has no problem passing NULL.
On Wed, May 7, 2025 at 11:46 AM Timur Tabi <ttabi@nvidia.com> wrote:
>
> On Mon, 2025-05-05 at 23:51 +0000, Matthew Maurer wrote:
> >
> > +impl<'a> Entry<'a> {
> > + /// 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.
> > + #[cfg(CONFIG_DEBUG_FS)]
> > + unsafe fn from_ptr(entry: *mut bindings::dentry) -> Self {
> > + Self {
> > + entry,
> > + _phantom: PhantomData,
> > + }
> > + }
> > +
> > + #[cfg(not(CONFIG_DEBUG_FS))]
> > + fn new() -> Self {
> > + Self {
> > + _phantom: PhantomData,
> > + }
> > + }
>
> I am new to Rust, so forgive me if this is a dumb question, but it looks to me that if
> CONFIG_DEBUG_FS is defined, then you need to call from_ptr() to create a new Entry, but if
> CONFIG_DEBUG_FS is not defined, then you need to call new() instead. Is that right? If so, is that
> really idiomatic?
I could make `from_ptr` take an arbitrary pointer and discard it as
well, but the callsite for `from_ptr` involves calling into the C
bindings to get a pointer back. I can do one of the following:
1. Create a stub function for the CONFIG_DEBUG_FS=n variant of those
functions (since those are in header files, so they need a special
helper) which gets compiled in, and just returns ERR_PTR(ENODEV), call
that, and pass it back in. (This leads to code bloat, though not
much.)
2. Manually call `ptr::dangling()` and pass it to the alt `from_ptr`
that ignores its argument
3. Create and call `::new`.
If I had more call-sites where I had a pointer-like object to put in
there, I'd use a `from_ptr` that discards. I used `::new` just because
it was easier.
>
> In the Dir implementation below, you are careful to call from_ptr() only from the CONFIG_DEBUG_FS
> version of create(), and you call new() only from the !CONFIG_DEBUG_FS version of create(). So your
> bases are covered as long as no driver tries to create an Entry from scratch.
>
> But I guess that can't happen because Entry is not public, right?
Correct, `Entry` is a private type.
>
> > + /// 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<'b>(&'b self, name: &CStr) -> Dir<'b> {
> > + 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)
> > + }
>
> Is there any real value to having two constructors, just to avoid passing None for the one time that
> a root directory will be created? The C code has no problem passing NULL.
Past revisions (and some of Danilo's suggestions on this revision)
required the ability to return different types when a directory was
not a subdir. In earlier versions, because subdirectories were not
automatically cleaned on drop unless opted in, where the root
directory was. In future versions, he would like me to use this to
suppress `Dir::keep` from being callable on root directories.
>
>
© 2016 - 2025 Red Hat, Inc.