From: Timur Tabi <ttabi@nvidia.com>
To: Matthew Maurer <mmaurer@google.com>,
Danilo Krummrich <dakr@kernel.org>, Gary Guo <gary@garyguo.net>,
John Hubbard <jhubbard@nvidia.com>,
"Joel Fernandes" <joelagnelf@nvidia.com>,
Alexandre Courbot <acourbot@nvidia.com>,
<rust-for-linux@vger.kernel.org>, <nouveau@lists.freedesktop.org>
Subject: [PATCH v4 7/9] rust: debugfs: add LookupDir
Date: Tue, 13 Jan 2026 16:54:06 -0600 [thread overview]
Message-ID: <20260113225408.671252-8-ttabi@nvidia.com> (raw)
In-Reply-To: <20260113225408.671252-1-ttabi@nvidia.com>
Add `LookupDir`, a non-owning handle to an existing DebugFS directory.
Unlike `Dir`, which creates a new directory and removes it on drop,
`LookupDir` looks up an existing directory and only releases the
reference when dropped—the directory itself remains in the filesystem.
This is useful when a driver needs to add files to a directory created
by another part of the system without taking ownership of that
directory.
Signed-off-by: Timur Tabi <ttabi@nvidia.com>
---
rust/kernel/debugfs.rs | 127 +++++++++++++++++++++++++++++++++++
rust/kernel/debugfs/entry.rs | 84 +++++++++++++++++++++++
2 files changed, 211 insertions(+)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index ed740eb90fc9..2ff70d72240c 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -34,6 +34,8 @@
mod entry;
#[cfg(CONFIG_DEBUG_FS)]
use entry::Entry;
+#[cfg(CONFIG_DEBUG_FS)]
+use entry::LookupEntry;
/// Trait for DebugFS directory operations.
///
@@ -393,6 +395,131 @@ pub fn scope<'a, T: 'a, E: 'a, F>(
}
}
+/// Non-owning handle to an existing DebugFS directory.
+///
+/// Unlike [`Dir`], a [`LookupDir`] does not create a new directory. Instead, it looks up an
+/// existing directory in the DebugFS filesystem. When dropped, the directory is **not** removed
+/// from the filesystem - only the reference is released.
+///
+/// This is useful when you want to add files to an existing directory created by another part
+/// of the system without taking ownership of that directory.
+#[derive(Clone)]
+pub struct LookupDir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<LookupEntry>>);
+
+impl LookupDir {
+ /// Looks up an existing directory in DebugFS.
+ ///
+ /// If `parent` is [`None`], the lookup is performed from the root of the debugfs filesystem.
+ ///
+ /// Returns [`Some(LookupDir)`] if the directory exists, [`None`] otherwise.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::c_str;
+ /// # use kernel::debugfs::LookupDir;
+ /// // Look up a top-level directory
+ /// if let Some(dir) = LookupDir::lookup(c_str!("existing_dir"), None) {
+ /// // Use the directory...
+ /// }
+ /// // When `dir` is dropped, the directory is NOT removed.
+ /// ```
+ pub fn lookup(name: &CStr, parent: Option<&LookupDir>) -> Option<Self> {
+ #[cfg(CONFIG_DEBUG_FS)]
+ {
+ let parent_entry = match parent {
+ Some(LookupDir(None)) => return None,
+ Some(LookupDir(Some(entry))) => Some(entry.clone()),
+ None => None,
+ };
+ let entry = LookupEntry::lookup(name, parent_entry)?;
+ Some(Self(Arc::new(entry, GFP_KERNEL).ok()))
+ }
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ None
+ }
+
+ // While this function is safe, it is intentionally not public because it's a bit of a
+ // footgun.
+ //
+ // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate
+ // time, a `ScopedDir` with a `LookupDir` parent will never be deleted.
+ fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> {
+ #[cfg(CONFIG_DEBUG_FS)]
+ {
+ let parent_entry = match &self.0 {
+ None => return ScopedDir::empty(),
+ Some(entry) => entry.clone(),
+ };
+ ScopedDir {
+ entry: ManuallyDrop::new(Entry::dynamic_dir_with_lookup_parent(name, parent_entry)),
+ _phantom: PhantomData,
+ }
+ }
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ ScopedDir::empty()
+ }
+
+ /// Creates a new scope, which is a directory associated with some data `T`.
+ ///
+ /// The created directory will be a subdirectory of `self`. The `init` closure is called to
+ /// populate the directory with files and subdirectories. These files can reference the data
+ /// stored in the scope.
+ ///
+ /// The entire directory tree created within the scope will be removed when the returned
+ /// `Scope` handle is dropped.
+ ///
+ /// Note: Unlike [`Dir::scope`], this method consumes `self` because the parent entry
+ /// is kept alive by the created scope via [`DynParent`], not by the `LookupDir` handle.
+ pub fn scope<'a, T: 'a, E: 'a, F>(
+ self,
+ data: impl PinInit<T, E> + 'a,
+ name: &'a CStr,
+ init: F,
+ ) -> impl PinInit<Scope<T>, E> + 'a
+ where
+ F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a,
+ {
+ Scope::new(data, move |data| {
+ let scoped = self.scoped_dir(name);
+ init(data, &scoped);
+ scoped.into_entry()
+ })
+ }
+}
+
+impl Directory for LookupDir {
+ /// Looks up an existing directory at the DebugFS root.
+ ///
+ /// Note: Unlike [`Dir::new`], this will return an empty handle if the directory
+ /// does not exist, rather than creating it.
+ fn new(name: &CStr) -> Self {
+ Self::lookup(name, None).unwrap_or_else(|| {
+ #[cfg(CONFIG_DEBUG_FS)]
+ {
+ Self(None)
+ }
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ Self()
+ })
+ }
+
+ /// Looks up a subdirectory within this directory.
+ ///
+ /// Note: Unlike [`Dir::subdir`], this will return an empty handle if the subdirectory
+ /// does not exist, rather than creating it.
+ fn subdir(&self, name: &CStr) -> Self {
+ Self::lookup(name, Some(self)).unwrap_or_else(|| {
+ #[cfg(CONFIG_DEBUG_FS)]
+ {
+ Self(None)
+ }
+ #[cfg(not(CONFIG_DEBUG_FS))]
+ Self()
+ })
+ }
+}
+
#[pin_data]
/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry
/// without moving.
diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs
index 3152e4f96219..1e8a5cedf69d 100644
--- a/rust/kernel/debugfs/entry.rs
+++ b/rust/kernel/debugfs/entry.rs
@@ -15,6 +15,8 @@
pub(crate) enum DynParent {
/// Parent is an owned `Entry` (will be removed on drop).
Entry(Arc<Entry<'static>>),
+ /// Parent is a looked-up `LookupEntry` (will NOT be removed on drop).
+ LookupEntry(Arc<LookupEntry>),
}
/// Owning handle to a DebugFS entry.
@@ -56,6 +58,24 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self {
}
}
+ /// Creates a new directory entry with a `LookupEntry` as parent.
+ ///
+ /// The parent is kept alive via `Arc` reference counting. When this `Entry` is dropped,
+ /// the created directory will be removed, but the parent `LookupEntry` will only have
+ /// its reference count decremented.
+ pub(crate) fn dynamic_dir_with_lookup_parent(name: &CStr, parent: Arc<LookupEntry>) -> Self {
+ // SAFETY: The invariants of this function's arguments ensure the safety of this call.
+ // * `name` is a valid C string by the invariants of `&CStr`.
+ // * `parent.as_ptr()` is a pointer to a valid `dentry` by the invariants of `LookupEntry`.
+ let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent.as_ptr()) };
+
+ Entry {
+ entry,
+ _parent: Some(DynParent::LookupEntry(parent)),
+ _phantom: PhantomData,
+ }
+ }
+
/// # Safety
///
/// * `data` must outlive the returned `Entry`.
@@ -172,3 +192,67 @@ fn drop(&mut self) {
unsafe { bindings::debugfs_remove(self.as_ptr()) }
}
}
+
+/// Non-owning handle to a DebugFS entry obtained via lookup.
+///
+/// Unlike [`Entry`], dropping a [`LookupEntry`] does not remove the directory from the
+/// filesystem. It only releases the reference obtained via `debugfs_lookup`.
+///
+/// # Invariants
+///
+/// The wrapped pointer will always be `NULL` or a valid DebugFS `dentry` obtained via
+/// `debugfs_lookup`.
+pub(crate) struct LookupEntry {
+ entry: *mut bindings::dentry,
+ // If we were created with an owning parent, this is the keep-alive
+ _parent: Option<Arc<LookupEntry>>,
+}
+
+// SAFETY: [`LookupEntry`] is just a `dentry` under the hood, which the API promises can be
+// transferred between threads.
+unsafe impl Send for LookupEntry {}
+
+// SAFETY: All the C functions we call on the `dentry` pointer are threadsafe.
+unsafe impl Sync for LookupEntry {}
+
+impl LookupEntry {
+ /// Looks up an existing directory in DebugFS.
+ ///
+ /// Returns `Some(LookupEntry)` if the directory exists, `None` otherwise.
+ pub(crate) fn lookup(name: &CStr, parent: Option<Arc<Self>>) -> Option<Self> {
+ let parent_ptr = match &parent {
+ Some(entry) => entry.as_ptr(),
+ None => core::ptr::null_mut(),
+ };
+ // SAFETY: The invariants of this function's arguments ensure the safety of this call.
+ // * `name` is a valid C string by the invariants of `&CStr`.
+ // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid
+ // `dentry` by our invariant. `debugfs_lookup` handles `NULL` pointers correctly
+ // (searches from debugfs root).
+ let entry = unsafe { bindings::debugfs_lookup(name.as_char_ptr(), parent_ptr) };
+
+ if entry.is_null() {
+ None
+ } else {
+ Some(LookupEntry {
+ entry,
+ _parent: parent,
+ })
+ }
+ }
+
+ /// Returns the pointer representation of the DebugFS directory.
+ pub(crate) fn as_ptr(&self) -> *mut bindings::dentry {
+ self.entry
+ }
+}
+
+impl Drop for LookupEntry {
+ fn drop(&mut self) {
+ if !self.entry.is_null() {
+ // SAFETY: `dput` decrements the reference count on a dentry. The pointer is valid
+ // because it was obtained via `debugfs_lookup` which increments the reference count.
+ unsafe { bindings::dput(self.entry) }
+ }
+ }
+}
--
2.52.0
next prev parent reply other threads:[~2026-01-13 22:54 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-13 22:53 [PATCH v4 0/9] gpu: nova-core: expose the logging buffers via debugfs Timur Tabi
2026-01-13 22:54 ` [PATCH v4 1/9] rust: pci: add PCI device name method Timur Tabi
2026-01-13 22:54 ` [PATCH v4 2/9] gpu: nova-core: implement BinaryWriter for LogBuffer Timur Tabi
2026-01-13 22:54 ` [PATCH v4 3/9] gpu: nova-core: Replace module_pci_driver! with explicit module init Timur Tabi
2026-01-13 22:54 ` [PATCH v4 4/9] gpu: nova-core: use pin projection in method boot() Timur Tabi
2026-01-13 22:54 ` [PATCH v4 5/9] rust: debugfs: implement Directory trait for Dir Timur Tabi
2026-01-15 17:27 ` kernel test robot
2026-01-13 22:54 ` [PATCH v4 6/9] rust: debugfs: wrap Entry in an enum to prep for LookupDir Timur Tabi
2026-01-13 22:54 ` Timur Tabi [this message]
2026-01-13 22:54 ` [PATCH v4 8/9] gpu: nova-core: create debugfs root when driver loads Timur Tabi
2026-01-13 22:54 ` [PATCH v4 9/9] gpu: nova-core: create GSP-RM logging buffers debugfs entries Timur Tabi
2026-01-13 23:11 ` [PATCH v4 0/9] gpu: nova-core: expose the logging buffers via debugfs Danilo Krummrich
2026-01-13 23:26 ` Timur Tabi
2026-01-13 23:50 ` Danilo Krummrich
2026-01-13 23:59 ` Timur Tabi
2026-01-14 1:09 ` Gary Guo
2026-01-14 11:17 ` 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=20260113225408.671252-8-ttabi@nvidia.com \
--to=ttabi@nvidia.com \
--cc=acourbot@nvidia.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=joelagnelf@nvidia.com \
--cc=mmaurer@google.com \
--cc=nouveau@lists.freedesktop.org \
--cc=rust-for-linux@vger.kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox