Skip to contentSkip to conversation

gpu: nova-core: expose the logging buffers via debugfs

PATCH Superseded Timur Tabi 4 participants 17 messages Jan 13 – Jan 15

Subsystems (6)

v4 of 11 Superseded Latest is v11 →

lore
Share & tools
lore

Timur Tabi

author
#0 lore raw

v4 cover gpu: nova-core: expose the logging buffers via debugfs

GSP-RM writes its printf message to "logging buffers", which are blocks memory allocated by the driver. The messages are encoded, so exposing the buffers as debugfs entries allows the buffers to be extracted and decoded by a special application.

When the driver loads, a /sys/kernel/debug/nova_core root entry is created. To do this, the normal module_pci_driver! macro call is replaced with an explicit initialization function, as this allows that debugfs entry to be created once for all GPUs.

Then in each GPU's initialization, a subdirectory based on the PCI BDF name is created, and the logging buffer entries are created under that.

Note: the debugfs entry has a file size of 0, because debugfs defaults a 0 size and the Rust abstractions do not adjust it for the same of the object. Nouveau makes this adjustment manually in the driver.

Changes since v3:
1. Implement a LookupDir struct instead of adding lookup methods to Dir.
2. Adds a Directory trait that is implemented by both LookupDir and Dir.
3. Creates DynParent enum to wrap both LookupDir and Dir.
4. No longer tries to "fix" Scope or scoped_dir.

Alexandre Courbot (1):
gpu: nova-core: implement BinaryWriter for LogBuffer

Timur Tabi (8):
rust: pci: add PCI device name method
gpu: nova-core: Replace module_pci_driver! with explicit module init
gpu: nova-core: use pin projection in method boot()
rust: debugfs: implement Directory trait for Dir
rust: debugfs: wrap Entry in an enum to prep for LookupDir
rust: debugfs: add LookupDir
gpu: nova-core: create debugfs root when driver loads
gpu: nova-core: create GSP-RM logging buffers debugfs entries

 drivers/gpu/nova-core/gsp.rs       |  68 +++++++++++--
 drivers/gpu/nova-core/gsp/boot.rs  |  14 +--
 drivers/gpu/nova-core/nova_core.rs |  32 ++++++-
 rust/kernel/debugfs.rs             | 148 ++++++++++++++++++++++++++++-
 rust/kernel/debugfs/entry.rs       | 100 ++++++++++++++++++-
 rust/kernel/pci.rs                 |  37 ++++++++
 6 files changed, 377 insertions(+), 22 deletions(-)

base-commit: 654826aa4a8f25cf825ad9254f37e6cb5092098f

Signature · 1 line
2.52.0
author
#1 lore raw

PATCH v4 1/9 rust: pci: add PCI device name method

Add a name() method to the PCI Device type, which returns a CStr that contains the device name, typically the BDF address.

---
 rust/kernel/pci.rs | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)
rust/kernel/pci.rs +37 -0
index 82e128431f08..125fb39f4316 100644
@@ -427,6 +427,43 @@ pub fn pci_class(&self) -> Class {
         // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
         Class::from_raw(unsafe { (*self.as_raw()).class })
     }
+
+    /// Returns the PCI device name.
+    ///
+    /// This returns the device name in the format "DDDD:BB:DD.F" where:
+    /// - DDDD is the PCI domain (4 hex digits)
+    /// - BB is the bus number (2 hex digits)
+    /// - DD is the device number (2 hex digits)
+    /// - F is the function number (1 hex digit)
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::{c_str, debugfs::Dir, device::Core, pci, prelude::*};
+    /// fn create_debugfs(pdev: &pci::Device<Core>) -> Result {
+    ///     let dir = Dir::new(pdev.name());
+    ///     Ok(())
+    /// }
+    /// ```
+    #[inline]
+    pub fn name(&self) -> &CStr {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct pci_dev`, which contains a `struct device dev` member.
+        unsafe {
+            let pci_dev = self.as_raw();
+            let dev = &raw const (*pci_dev).dev;
+
+            // If init_name is set, use it; otherwise use the kobject name
+            let init_name = (*dev).init_name;
+            let name_ptr = if !init_name.is_null() {
+                init_name
+            } else {
+                (*dev).kobj.name
+            };
+
+            CStr::from_char_ptr(name_ptr)
+        }
+    }
 }
 
 impl Device<device::Core> {
Signature · 1 line
2.52.0
author
#2 lore raw

PATCH v4 2/9 gpu: nova-core: implement BinaryWriter for LogBuffer

In reply to Timur Tabi

From: Alexandre Courbot <[email protected]>

LogBuffer is the entity we ultimately want to dump through debugfs. Provide a simple implementation of BinaryWriter for it, albeit it might not cut the safety requirements.

---
 drivers/gpu/nova-core/gsp.rs | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
drivers/gpu/nova-core/gsp.rs +24 -0
index 766fd9905358..9b73c96fbd3a 100644
@@ -3,6 +3,7 @@
 mod boot;
 
 use kernel::{
+    debugfs,
     device,
     dma::{
         CoherentAllocation,
@@ -117,6 +118,29 @@ pub(crate) struct Gsp {
     rmargs: CoherentAllocation<GspArgumentsCached>,
 }
 
+impl debugfs::BinaryWriter for LogBuffer {
+    fn write_to_slice(
+        &self,
+        writer: &mut kernel::uaccess::UserSliceWriter,
+        offset: &mut kernel::fs::file::Offset,
+    ) -> Result<usize> {
+        // SAFETY: This is a debug log buffer. GSP may write concurrently, so the
+        // snapshot may contain partially-written entries. This is acceptable for
+        // debugging purposes - users should be aware logs may be slightly garbled
+        // if read while GSP is actively logging.
+        let slice = unsafe { self.0.as_slice(0, self.0.count()) }?;
+
+        writer.write_slice_file(slice, offset)
+    }
+}
+
+// SAFETY: `LogBuffer` only provides shared access to the underlying `CoherentAllocation`.
+// GSP may write to the buffer concurrently regardless of CPU access, so concurrent reads
+// from multiple CPU threads do not introduce any additional races beyond what already
+// exists with the device. Reads may observe partially-written log entries, which is
+// acceptable for debug logging purposes.
+unsafe impl Sync for LogBuffer {}
+
 impl Gsp {
     // Creates an in-place initializer for a `Gsp` manager for `pdev`.
     pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
Signature · 1 line
2.52.0
author
#3 lore raw

PATCH v4 3/9 gpu: nova-core: Replace module_pci_driver! with explicit module init

In reply to Timur Tabi

Replace the module_pci_driver! macro with an explicit module initialization using the standard module! macro and InPlaceModule trait implementation. No functional change intended, with the exception that the driver now prints a message when loaded.

This change is necessary so that we can create a top-level "nova_core" debugfs entry when the driver is loaded.

---
 drivers/gpu/nova-core/nova_core.rs | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)
drivers/gpu/nova-core/nova_core.rs +23 -3
index c1121e7c64c5..d0df8db3693d 100644
@@ -2,6 +2,13 @@
 
 //! Nova Core GPU Driver
 
+use kernel::{
+    error::Error,
+    pci,
+    prelude::*,
+    InPlaceModule, //
+};
+
 #[macro_use]
 mod bitfield;
 
@@ -20,13 +27,26 @@
 
 pub(crate) const MODULE_NAME: &kernel::str::CStr = <LocalModule as kernel::ModuleMetadata>::NAME;
 
-kernel::module_pci_driver! {
-    type: driver::NovaCore,
+#[pin_data]
+struct NovaCoreModule {
+    #[pin]
+    _driver: kernel::driver::Registration<pci::Adapter<driver::NovaCore>>,
+}
+
+impl InPlaceModule for NovaCoreModule {
+    fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
+        try_pin_init!(Self {
+            _driver <- kernel::driver::Registration::new(MODULE_NAME, module),
+        })
+    }
+}
+
+module! {
+    type: NovaCoreModule,
     name: "NovaCore",
     authors: ["Danilo Krummrich"],
     description: "Nova Core GPU driver",
     license: "GPL v2",
-    firmware: [],
 }
 
 kernel::module_firmware!(firmware::ModInfoBuilder);
Signature · 1 line
2.52.0
author
#4 lore raw

PATCH v4 4/9 gpu: nova-core: use pin projection in method boot()

In reply to Timur Tabi

Struct Gsp in gsp.rs is tagged with #[pin_data], which allows any of its fields to be pinned (i.e. with #[pin]). When #[pin] is added to any field in a #[pin_data] struct, fields can no longer be directly accessed via normal field access. Instead, pin projection must be used to access those fields.

Currently, no fields are pinned, but that will change. The boot() method receives self: Pin<&mut Self>. When struct Gsp contains any pinned fields, direct field access like self.cmdq is not allowed through Pin<&mut Self>, as Pin prevents obtaining &mut Self to protect pinned data from being moved.

Use pin projection via self.as_mut().project() to access struct fields. The project() method, generated by #[pin_data], returns a projection struct providing &mut references to non-pinned fields, enabling mutable access while preserving pin invariants.

---
 drivers/gpu/nova-core/gsp/boot.rs | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/nova-core/gsp/boot.rs +8 -6
index 581b412554dc..68a8acaf8c54 100644
@@ -159,12 +159,14 @@ pub(crate) fn boot(
             CoherentAllocation::<GspFwWprMeta>::alloc_coherent(dev, 1, GFP_KERNEL | __GFP_ZERO)?;
         dma_write!(wpr_meta[0] = GspFwWprMeta::new(&gsp_fw, &fb_layout))?;
 
-        self.cmdq
+        let this = self.as_mut().project();
+
+        this.cmdq
             .send_command(bar, commands::SetSystemInfo::new(pdev))?;
-        self.cmdq.send_command(bar, commands::SetRegistry::new())?;
+        this.cmdq.send_command(bar, commands::SetRegistry::new())?;
 
         gsp_falcon.reset(bar)?;
-        let libos_handle = self.libos.dma_handle();
+        let libos_handle = this.libos.dma_handle();
         let (mbox0, mbox1) = gsp_falcon.boot(
             bar,
             Some(libos_handle as u32),
@@ -231,13 +233,13 @@ pub(crate) fn boot(
             dev: pdev.as_ref().into(),
             bar,
         };
-        GspSequencer::run(&mut self.cmdq, seq_params)?;
+        GspSequencer::run(this.cmdq, seq_params)?;
 
         // Wait until GSP is fully initialized.
-        commands::wait_gsp_init_done(&mut self.cmdq)?;
+        commands::wait_gsp_init_done(this.cmdq)?;
 
         // Obtain and display basic GPU information.
-        let info = commands::get_gsp_info(&mut self.cmdq, bar)?;
+        let info = commands::get_gsp_info(this.cmdq, bar)?;
         match info.gpu_name() {
             Ok(name) => dev_info!(pdev.as_ref(), "GPU name: {}\n", name),
             Err(e) => dev_warn!(pdev.as_ref(), "GPU name unavailable: {:?}\n", e),
Signature · 1 line
2.52.0
author
#5 lore raw

PATCH v4 5/9 rust: debugfs: implement Directory trait for Dir

In reply to Timur Tabi

Instead of having an inherent implementation for struct Dir, create a Directory trait for it. This is needed for when we add the LookupDir struct.

---
 rust/kernel/debugfs.rs | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)
rust/kernel/debugfs.rs +19 -2
index facad81e8290..ed740eb90fc9 100644
@@ -35,6 +35,18 @@
 #[cfg(CONFIG_DEBUG_FS)]
 use entry::Entry;
 
+/// Trait for DebugFS directory operations.
+///
+/// This trait abstracts the core operations that can be performed on a DebugFS directory,
+/// allowing for alternative implementations (e.g., for testing).
+pub trait Directory: Clone + Sized {
+    /// Create a new directory at the DebugFS root.
+    fn new(name: &CStr) -> Self;
+
+    /// Creates a subdirectory within this directory.
+    fn subdir(&self, name: &CStr) -> Self;
+}
+
 /// Owning handle to a DebugFS directory.
 ///
 /// The directory in the filesystem represented by [`Dir`] will be removed when handle has been
@@ -96,7 +108,9 @@ fn create_file<'a, T, E: 'a>(
             } ? E
         }
     }
+}
 
+impl Directory for Dir {
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
@@ -106,7 +120,7 @@ fn create_file<'a, T, E: 'a>(
     /// # use kernel::debugfs::Dir;
     /// let debugfs = Dir::new(c_str!("parent"));
     /// ```
-    pub fn new(name: &CStr) -> Self {
+    fn new(name: &CStr) -> Self {
         Dir::create(name, None)
     }
 
@@ -120,9 +134,12 @@ pub fn new(name: &CStr) -> Self {
     /// let parent = Dir::new(c_str!("parent"));
     /// let child = parent.subdir(c_str!("child"));
     /// ```
-    pub fn subdir(&self, name: &CStr) -> Self {
+    fn subdir(&self, name: &CStr) -> Self {
         Dir::create(name, Some(self))
     }
+}
+
+impl Dir {
 
     /// Creates a read-only file in this directory.
     ///
Signature · 1 line
2.52.0
#6 lore raw

Re: [PATCH v4 5/9] rust: debugfs: implement Directory trait for Dir

kernel test robot noticed the following build errors:

Hi Timur,

kernel test robot noticed the following build errors:

[auto build test ERROR on 654826aa4a8f25cf825ad9254f37e6cb5092098f]

url:    https://github.com/intel-lab-lkp/linux/commits/Timur-Tabi/rust-pci-add-PCI-device-name-method/20260114-065717
base:   654826aa4a8f25cf825ad9254f37e6cb5092098f
patch link:    https://lore.kernel.org/r/20260113225408.671252-6-ttabi%40nvidia.com
patch subject: [PATCH v4 5/9] rust: debugfs: implement Directory trait for Dir
config: x86_64-randconfig-122-20260114 (https://download.01.org/0day-ci/archive/20260116/[email protected]/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260116/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:4085:16
|
4085 | let dir = Dir::new(c_str!("my_debugfs_dir"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:4145:16
|
4145 | let dir = Dir::new(c_str!("my_debugfs_dir"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:4204:16
|
4204 | let dir = Dir::new(c_str!("foo"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:3973:20
|
3973 | let debugfs = Dir::new(c_str!("parent"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:4028:19
|
4028 | let parent = Dir::new(c_str!("parent"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> rust/doctests_kernel_generated.rs:11462:20
|
11462 | let dir = Dir::new(pdev.name());
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
3 + use crate::rust_doctest_kernel_alloc_allocator_rs_0::kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> samples/rust/rust_debugfs.rs:136:28
|
136 | let debugfs = Dir::new(c_str!("sample_debugfs"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
34 + use kernel::debugfs::Directory;
|
--

>> error[E0599]: no function or associated item named `new` found for struct `Dir` in the current scope

--> samples/rust/rust_debugfs_scoped.rs:134:29
|
134 | let base_dir = Dir::new(c_str!("rust_scoped_debugfs"));
| ^^^ function or associated item not found in Dir
|
= help: items from traits can only be used if the trait is in scope
help: trait Directory which provides new is implemented but not in scope; perhaps you want to import it |
9 + use kernel::debugfs::Directory;
|

Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
author
#7 lore raw

PATCH v4 6/9 rust: debugfs: wrap Entry in an enum to prep for LookupDir

In reply to Timur Tabi

To prepare the Entry struct for adding support for LookupDir, create a wrapper enum DynParent. For now, it only holds an Arc<Entry>.

---
 rust/kernel/debugfs/entry.rs | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)
rust/kernel/debugfs/entry.rs +13 -3
index 706cb7f73d6c..3152e4f96219 100644
@@ -7,6 +7,16 @@
 use crate::sync::Arc;
 use core::marker::PhantomData;
 
+/// Type-erased parent that keeps either an `Entry` or `LookupEntry` alive.
+///
+/// This allows an `Entry` to have either type of parent while maintaining proper
+/// lifetime management. The parent is kept alive via `Arc` reference counting.
+#[allow(dead_code)]
+pub(crate) enum DynParent {
+    /// Parent is an owned `Entry` (will be removed on drop).
+    Entry(Arc<Entry<'static>>),
+}
+
 /// Owning handle to a DebugFS entry.
 ///
 /// # Invariants
@@ -15,7 +25,7 @@
 pub(crate) struct Entry<'a> {
     entry: *mut bindings::dentry,
     // If we were created with an owning parent, this is the keep-alive
-    _parent: Option<Arc<Entry<'static>>>,
+    _parent: Option<DynParent>,
     // If we were created with a non-owning parent, this prevents us from outliving it
     _phantom: PhantomData<&'a ()>,
 }
@@ -41,7 +51,7 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self {
 
         Entry {
             entry,
-            _parent: parent,
+            _parent: parent.map(DynParent::Entry),
             _phantom: PhantomData,
         }
     }
@@ -74,7 +84,7 @@ pub(crate) unsafe fn dynamic_file<T>(
 
         Entry {
             entry,
-            _parent: Some(parent),
+            _parent: Some(DynParent::Entry(parent)),
             _phantom: PhantomData,
         }
     }
Signature · 1 line
2.52.0
author
#8 lore raw

PATCH v4 7/9 rust: debugfs: add LookupDir

In reply to Timur Tabi

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.

---
 rust/kernel/debugfs.rs       | 127 +++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/entry.rs |  84 +++++++++++++++++++++++
 2 files changed, 211 insertions(+)
rust/kernel/debugfs.rs +127 -0
index ed740eb90fc9..2ff70d72240c 100644
@@ -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.
rust/kernel/debugfs/entry.rs +84 -0

99 lines not on this page · raw message

Signature · 1 line
2.52.0
author
#9 lore raw

PATCH v4 8/9 gpu: nova-core: create debugfs root when driver loads

In reply to Timur Tabi

Create the 'nova_core' root debugfs entry when the driver loads. The Dir entry is part of the NovaCoreModule, so when the module unloads, the NovaCoreModule object is dropped, and that drops the _debugfs_root, which automatically deletes the debugfs file.

---
 drivers/gpu/nova-core/nova_core.rs | 6 ++++++
 1 file changed, 6 insertions(+)
drivers/gpu/nova-core/nova_core.rs +6 -0
index d0df8db3693d..c9b93ccd6092 100644
@@ -3,6 +3,7 @@
 //! Nova Core GPU Driver
 
 use kernel::{
+    debugfs::{Dir, Directory},
     error::Error,
     pci,
     prelude::*,
@@ -31,12 +32,17 @@
 struct NovaCoreModule {
     #[pin]
     _driver: kernel::driver::Registration<pci::Adapter<driver::NovaCore>>,
+    _debugfs_root: Dir,
 }
 
 impl InPlaceModule for NovaCoreModule {
     fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
+        // Create the debugfs top-level directory.  Each GPU will create a subdirectory.
+        let debugfs_root = Dir::new(kernel::c_str!("nova_core"));
+
         try_pin_init!(Self {
             _driver <- kernel::driver::Registration::new(MODULE_NAME, module),
+            _debugfs_root: debugfs_root,
         })
     }
 }
Signature · 1 line
2.52.0
author
#10 lore raw

PATCH v4 9/9 gpu: nova-core: create GSP-RM logging buffers debugfs entries

In reply to Timur Tabi

Create read-only debugfs entries for LOGINIT, LOGRM, and LOGINTR, which are the three primary printf logging buffers from GSP-RM. LOGPMU will be added at a later date, as it requires it support for its RPC message first.

This patch uses the pin_init_scope feature to create the entries. pin_init_scope solves the lifetime issue over the DEBUGFS_ROOT reference by delaying its acquisition until the time the entry is actually initialized.

---
 drivers/gpu/nova-core/gsp.rs | 46 +++++++++++++++++++++++++++++-------
 1 file changed, 37 insertions(+), 9 deletions(-)
drivers/gpu/nova-core/gsp.rs +37 -9
index 9b73c96fbd3a..dc4a009862ef 100644
@@ -3,7 +3,8 @@
 mod boot;
 
 use kernel::{
-    debugfs,
+    c_str,
+    debugfs::{self, Directory},
     device,
     dma::{
         CoherentAllocation,
@@ -101,17 +102,24 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
     }
 }
 
-/// GSP runtime data.
-#[pin_data]
-pub(crate) struct Gsp {
-    /// Libos arguments.
-    pub(crate) libos: CoherentAllocation<LibosMemoryRegionInitArgument>,
+/// Log buffers used by GSP-RM for debug logging.
+struct LogBuffers {
     /// Init log buffer.
     loginit: LogBuffer,
     /// Interrupts log buffer.
     logintr: LogBuffer,
     /// RM log buffer.
     logrm: LogBuffer,
+}
+
+/// GSP runtime data.
+#[pin_data]
+pub(crate) struct Gsp {
+    /// Libos arguments.
+    pub(crate) libos: CoherentAllocation<LibosMemoryRegionInitArgument>,
+    /// Log buffers, optionally exposed via debugfs.
+    #[pin]
+    logs: debugfs::Scope<LogBuffers>,
     /// Command queue.
     pub(crate) cmdq: Cmdq,
     /// RM arguments.
@@ -147,15 +155,17 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
         pin_init::pin_init_scope(move || {
             let dev = pdev.as_ref();
 
+            // Create log buffers before try_pin_init! so they're accessible throughout
+            let loginit = LogBuffer::new(dev)?;
+            let logintr = LogBuffer::new(dev)?;
+            let logrm = LogBuffer::new(dev)?;
+
             Ok(try_pin_init!(Self {
                 libos: CoherentAllocation::<LibosMemoryRegionInitArgument>::alloc_coherent(
                     dev,
                     GSP_PAGE_SIZE / size_of::<LibosMemoryRegionInitArgument>(),
                     GFP_KERNEL | __GFP_ZERO,
                 )?,
-                loginit: LogBuffer::new(dev)?,
-                logintr: LogBuffer::new(dev)?,
-                logrm: LogBuffer::new(dev)?,
                 cmdq: Cmdq::new(dev)?,
                 rmargs: CoherentAllocation::<GspArgumentsCached>::alloc_coherent(
                     dev,
@@ -176,6 +186,24 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
                     dma_write!(rmargs[0] = fw::GspArgumentsCached::new(cmdq))?;
                     dma_write!(libos[3] = LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
                 },
+                logs <- {
+                    let log_buffers = LogBuffers {
+                        loginit,
+                        logintr,
+                        logrm,
+                    };
+
+                    // Look up nova_core debugfs directory - only create entries if it exists.
+                    // If nova_core doesn't exist, debugfs_dir will be empty and file creation
+                    // becomes a no-op (data is still stored, just not exposed via debugfs).
+                    let debugfs_dir = debugfs::LookupDir::new(c_str!("nova_core"));
+
+                    debugfs_dir.scope(log_buffers, pdev.name(), |logs, dir| {
+                        dir.read_binary_file(c_str!("loginit"), &logs.loginit);
+                        dir.read_binary_file(c_str!("logintr"), &logs.logintr);
+                        dir.read_binary_file(c_str!("logrm"), &logs.logrm);
+                    })
+                },
             }))
         })
     }
Signature · 1 line
2.52.0
maintainer
#11 lore raw
In reply to Timur Tabi

On Tue Jan 13, 2026 at 11:53 PM CET, Timur Tabi wrote:

It seems that a lot of the feedback from the last version has not been addressed:

Quoted 2 lines

Alexandre Courbot (1):
gpu: nova-core: implement BinaryWriter for LogBuffer

https://lore.kernel.org/all/[email protected]/

Quoted 2 lines

Timur Tabi (8):
rust: pci: add PCI device name method

https://lore.kernel.org/all/[email protected]/

Quoted 1 line

gpu: nova-core: Replace module_pci_driver! with explicit module init

https://lore.kernel.org/all/[email protected]/

Quoted 4 lines

gpu: nova-core: use pin projection in method boot()
rust: debugfs: implement Directory trait for Dir
rust: debugfs: wrap Entry in an enum to prep for LookupDir
rust: debugfs: add LookupDir

https://lore.kernel.org/all/[email protected]/

Especially this one is a concern, I don't want to add this infrastructure as a workaround until we land the feature Gary works on.

As mentioned in this reply, I think that debugfs_lookup() rarely is the correct solution and more often indicates some kind of (design) issue, like it does in this case.

Before adding this, I would like to see a valid use-case elsewhere.

If we want this before Gary's work is ready, I prefer some hacky temporary workaround in nova-core to access the module field instead.

Quoted 2 lines

gpu: nova-core: create debugfs root when driver loads
gpu: nova-core: create GSP-RM logging buffers debugfs entries

author
#12 lore raw
Quoted 10 lines · Danilo Krummrich

On Tue Jan 13, 2026 at 11:53 PM CET, Timur Tabi wrote:

It seems that a lot of the feedback from the last version has not been addressed:

Quoted 2 lines

Alexandre Courbot (1):
  gpu: nova-core: implement BinaryWriter for LogBuffer

https://lore.kernel.org/all/[email protected]/

Quoted 8 lines
Quoted 2 lines

Timur Tabi (8):
  rust: pci: add PCI device name method

https://lore.kernel.org/all/[email protected]/

Quoted 1 line

  gpu: nova-core: Replace module_pci_driver! with explicit module init

https://lore.kernel.org/all/[email protected]/

Sorry, I couldn't find these messages in my inbox. I will address them in v5.

Quoted 5 lines
Quoted 4 lines

  gpu: nova-core: use pin projection in method boot()
  rust: debugfs: implement Directory trait for Dir
  rust: debugfs: wrap Entry in an enum to prep for LookupDir
  rust: debugfs: add LookupDir

Quoted 8 lines

https://lore.kernel.org/all/[email protected]/

Especially this one is a concern, I don't want to add this infrastructure as a workaround until we land the feature Gary works on.

As mentioned in this reply, I think that debugfs_lookup() rarely is the correct solution and more often indicates some kind of (design) issue, like it does in this case.

Well, I'm at a loss how to proceed, then.  I had a global variable, which is the approach that Nouveau uses, it was recommended instead that I use debugfs_lookup(). So I implemented that, but that is also rejected.

So instead of just telling me that this approach is wrong, please tell what the correct approach should be. Please note that this patchset is already pushing the limits of my R4L skills, so I will need specifics.

Quoted 1 line

Before adding this, I would like to see a valid use-case elsewhere.

I don't have one.

Quoted 2 lines

If we want this before Gary's work is ready, I prefer some hacky temporary workaround in nova-core to access the module field instead.

Danilo, you are the one who has been pushing for this code. Please note that the logging buffers can only be parsed by Nvidia employees anyway, and we can just use this patchset in house to debug GSP-RM as needed. So if you want to wait until Gary's work is ready (I don't know anything about what he's doing), please let me know now and I will just keep these patches internally.

maintainer
#13 lore raw
Quoted 3 lines · Timur Tabi
Quoted 1 line · Danilo Krummrich

On Tue Jan 13, 2026 at 11:53 PM CET, Timur Tabi wrote:

Sorry, I couldn't find these messages in my inbox. I will address them in v5.

Sounds good, thanks.

Quoted 10 lines
Quoted 10 lines
Quoted 4 lines

  gpu: nova-core: use pin projection in method boot()
  rust: debugfs: implement Directory trait for Dir
  rust: debugfs: wrap Entry in an enum to prep for LookupDir
  rust: debugfs: add LookupDir

https://lore.kernel.org/all/[email protected]/

Especially this one is a concern, I don't want to add this infrastructure as a workaround until we land the feature Gary works on.

Quoted 7 lines
Quoted 3 lines

As mentioned in this reply, I think that debugfs_lookup() rarely is the correct solution and more often indicates some kind of (design) issue, like it does in this case.

Well, I'm at a loss how to proceed, then.  I had a global variable, which is the approach that Nouveau uses, it was recommended instead that I use debugfs_lookup(). So I implemented that, but that is also rejected.

Yeah, the global is not a good approach for a final solution. But as temporary workaround it's probably fine.

Maybe Gary has an alternative idea for a temporary workaround since he's working on the proper solution to safely access module fields.

But again, I think a simple global with a FIXME comment should be fine.

Quoted 4 lines

Danilo, you are the one who has been pushing for this code. Please note that the logging buffers can only be parsed by Nvidia employees anyway, and we can just use this patchset in house to debug GSP-RM as needed. So if you want to wait until Gary's work is ready (I don't know anything about what he's doing), please let me know now and I will just keep these patches internally.

I can't remember pushing for it, but I do think it would be good to land.

author
#14 lore raw
Quoted 4 lines · Danilo Krummrich

Maybe Gary has an alternative idea for a temporary workaround since he's working on the proper solution to safely access module fields.

But again, I think a simple global with a FIXME comment should be fine.

That's what I had in my initial version.

I wish you had said something when Joel suggested it:
https://lore.freedesktop.org/nouveau/[email protected]/T/#m69a23a3db642c8cbb22efe0e24647811e8ae3f12

I spent a lot of time over the past few weeks implementing lookup support, and now you're saying it was for nothing.

What exactly is Gary's code supposed to do, anyway? "proper solution to safely access module fields" I don't know what this means.

reviewer
#15 lore raw
Quoted 8 lines · Timur Tabi
Quoted 4 lines · Danilo Krummrich

Maybe Gary has an alternative idea for a temporary workaround since he's working on the proper solution to safely access module fields.

But again, I think a simple global with a FIXME comment should be fine.

That's what I had in my initial version.

Quoted 8 lines

I wish you had said something when Joel suggested it:
https://lore.freedesktop.org/nouveau/[email protected]/T/#m69a23a3db642c8cbb22efe0e24647811e8ae3f12

I spent a lot of time over the past few weeks implementing lookup support, and now you're saying it was for nothing.

What exactly is Gary's code supposed to do, anyway? "proper solution to safely access module fields" I don't know what this means.

There're quite a few ideas being discussed here:
https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Module.2Fdriver-scoped.20data/with/564476412,
although I haven't had time to do any proper implementation work yet.

Best,
Gary

maintainer
#16 lore raw
In reply to Timur Tabi
Quoted 10 lines · Timur Tabi
Quoted 4 lines · Danilo Krummrich

Maybe Gary has an alternative idea for a temporary workaround since he's working on the proper solution to safely access module fields.

But again, I think a simple global with a FIXME comment should be fine.

That's what I had in my initial version.

I wish you had said something when Joel suggested it:
https://lore.freedesktop.org/nouveau/[email protected]/T/#m69a23a3db642c8cbb22efe0e24647811e8ae3f12

This was when I came back from being five weeks out, left with > 10k unread mails. This was known and I even offered a separate communication channel to reach out should something come up that requires my attention.

However, only two days later I did catch your subsequent series, replied [1] and mentioned everything I mentioned in this thread as well.

Quoted 2 lines

I spent a lot of time over the past few weeks implementing lookup support, and now you're saying it was for nothing.

I understand that this is frustrating, but I wouldn't say it was for nothing.

You previously mentioned that you are relatively new to Rust; glancing at the code I can imaginge that you learned a lot from working on this, which I think is pretty valuable as well.

Also, we can pick up this code at any time should someone find a valid use-case for this feature.

- Danilo

[1] https://lore.kernel.org/all/[email protected]/