* [PATCH v7 1/4] gpu: nova-core: move the debugfs root into the module data
2026-11-13 7:53 [PATCH v7 0/4] gpu: nova-core: retain the GSP-RM log buffers Vladislav Zaharov
@ 2026-11-13 7:53 ` Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 2/4] gpu: nova-core: gsp: move the log buffers into their own file Vladislav Zaharov
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Vladislav Zaharov @ 2026-11-13 7:53 UTC (permalink / raw)
To: dakr, jhubbard
Cc: acourbot, aliceryhl, ttabi, gary, nova-gpu, dri-devel,
linux-kernel, linux-doc, vladazaharova2018
The debugfs root lives in a static that is cleared by a guard field of
the module data. The guard is built after the driver registration, so if
the registration fails, the root is never removed and the next module
load finds the name taken.
Move the root into a DebugfsData owned by the module data, and keep only
a pointer to it in the static, for devices that cannot reach the module
data yet. If the registration fails, the root is dropped with the rest
of the module data.
Fixes: 09691f5d8070 ("gpu: nova-core: create debugfs root in module init")
Signed-off-by: Vladislav Zaharov <vladazaharova2018@gmail.com>
---
drivers/gpu/nova-core/debugfs.rs | 29 ++++++++++++++
drivers/gpu/nova-core/gsp.rs | 11 +-----
drivers/gpu/nova-core/nova_core.rs | 62 ++++++++++++++++++------------
3 files changed, 69 insertions(+), 33 deletions(-)
create mode 100644 drivers/gpu/nova-core/debugfs.rs
diff --git a/drivers/gpu/nova-core/debugfs.rs b/drivers/gpu/nova-core/debugfs.rs
new file mode 100644
index 000000000000..6c6ed129f0da
--- /dev/null
+++ b/drivers/gpu/nova-core/debugfs.rs
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Debugfs data shared by all GPUs of the driver.
+
+use kernel::{
+ debugfs,
+ prelude::*, //
+};
+
+/// Debugfs data the module shares with every GPU it drives.
+#[pin_data]
+pub(crate) struct DebugfsData {
+ /// Root directory of the driver in debugfs.
+ root: debugfs::Dir,
+}
+
+impl DebugfsData {
+ /// Creates the debugfs data of the module.
+ pub(crate) fn new() -> impl PinInit<Self> {
+ pin_init!(Self {
+ root: debugfs::Dir::new(c"nova-core"),
+ })
+ }
+
+ /// Returns the root directory of the driver.
+ pub(crate) fn root(&self) -> &debugfs::Dir {
+ &self.root
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index a4b832f6aec1..79ee74a0a16f 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -200,15 +200,8 @@ pub(crate) fn new(
logrm,
};
- #[allow(static_mut_refs)]
- // SAFETY: `DEBUGFS_ROOT` is created before driver registration and cleared
- // after driver unregistration, so no probe() can race with its modification.
- //
- // PANIC: `DEBUGFS_ROOT` cannot be `None` here. It is set before driver
- // registration and cleared after driver unregistration, so it is always
- // `Some` for the entire lifetime that probe() can be called.
- let log_parent: &debugfs::Dir = unsafe { crate::DEBUGFS_ROOT.as_ref() }
- .expect("DEBUGFS_ROOT not initialized");
+ let log_parent: &debugfs::Dir =
+ crate::NovaCoreModule::debugfs_data(dev).root();
log_parent.scope(log_buffers, dev.name(), |logs, dir| {
dir.read_binary_file(c"loginit", &logs.loginit.0);
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index a8f6dc22057b..f5d08de8a212 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -3,7 +3,7 @@
//! Nova Core GPU Driver
use kernel::{
- debugfs,
+ device,
driver::Registration,
pci,
prelude::*,
@@ -11,6 +11,7 @@
};
pub mod api;
+mod debugfs;
mod driver;
mod falcon;
mod fb;
@@ -30,42 +31,55 @@
mod vbios;
mod vgpu;
-pub(crate) const MODULE_NAME: &core::ffi::CStr = <LocalModule as kernel::ModuleMetadata>::NAME;
-
-// TODO: Move this into per-module data once that exists.
-static mut DEBUGFS_ROOT: Option<debugfs::Dir> = None;
+use debugfs::DebugfsData;
-/// Guard that clears `DEBUGFS_ROOT` when dropped.
-struct DebugfsRootGuard;
+pub(crate) const MODULE_NAME: &core::ffi::CStr = <LocalModule as kernel::ModuleMetadata>::NAME;
-impl Drop for DebugfsRootGuard {
- fn drop(&mut self) {
- // SAFETY: This guard is dropped after `_driver` (due to field order),
- // so the driver is unregistered and no probe() can be running.
- unsafe { DEBUGFS_ROOT = None };
- }
-}
+/// Pointer to the [`DebugfsData`] of the module.
+///
+/// A device currently has no way to reach the data of its module, so it goes through here.
+///
+/// # Invariants
+///
+/// - Only written once before the driver is registered.
+/// - Points to a valid [`DebugfsData`] for as long as the driver is registered.
+// TODO: Drop this once devices can reach the data of their module.
+static mut DEBUGFS_DATA: *const DebugfsData = core::ptr::null();
#[pin_data]
struct NovaCoreModule {
- // Fields are dropped in declaration order, so `_driver` is dropped first,
- // then `_debugfs_guard` clears `DEBUGFS_ROOT`.
+ // Fields are dropped in declaration order. `_driver` must be dropped before `_debugfs` to
+ // uphold the invariant of `DEBUGFS_DATA` that it remains valid for as long as the driver is
+ // registered.
#[pin]
_driver: Registration<pci::Adapter<driver::NovaCoreDriver>>,
- _debugfs_guard: DebugfsRootGuard,
+ #[pin]
+ _debugfs: DebugfsData,
+}
+
+impl NovaCoreModule {
+ /// Returns the debugfs data of the module.
+ pub(crate) fn debugfs_data<'a>(_dev: &'a device::Device<device::Bound>) -> &'a DebugfsData {
+ // SAFETY: `_dev` is bound, so the driver is registered. Per the invariants of
+ // `DEBUGFS_DATA`, the pointer is thus valid and not being written to. The `'a` lifetime
+ // ensures that the returned reference cannot outlive the bound device.
+ unsafe { &*DEBUGFS_DATA }
+ }
}
impl InPlaceModule for NovaCoreModule {
fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
- let dir = debugfs::Dir::new(c"nova-core");
-
- // SAFETY: We are the only driver code running during init, so there
- // cannot be any concurrent access to `DEBUGFS_ROOT`.
- unsafe { DEBUGFS_ROOT = Some(dir) };
-
try_pin_init!(Self {
+ _debugfs <- DebugfsData::new(),
+ _: {
+ // SAFETY: The driver is not registered yet, meaning that no device can be probed
+ // and thus nothing can access `DEBUGFS_DATA` concurrently.
+ //
+ // INVARIANT: `DEBUGFS_DATA` is written before the driver is registered below and
+ // points to `_debugfs`, which is pinned and dropped after `_driver`.
+ unsafe { DEBUGFS_DATA = &*_debugfs };
+ },
_driver <- Registration::new(MODULE_NAME, module),
- _debugfs_guard: DebugfsRootGuard,
})
}
}
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v7 2/4] gpu: nova-core: gsp: move the log buffers into their own file
2026-11-13 7:53 [PATCH v7 0/4] gpu: nova-core: retain the GSP-RM log buffers Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 1/4] gpu: nova-core: move the debugfs root into the module data Vladislav Zaharov
@ 2026-11-13 7:53 ` Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 3/4] gpu: nova-core: gsp: retain the GSP-RM log buffers after unbind Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 4/4] Documentation: nova: remove completed GSP log buffer task Vladislav Zaharov
3 siblings, 0 replies; 6+ messages in thread
From: Vladislav Zaharov @ 2026-11-13 7:53 UTC (permalink / raw)
To: dakr, jhubbard
Cc: acourbot, aliceryhl, ttabi, gary, nova-gpu, dri-devel,
linux-kernel, linux-doc, vladazaharova2018
Move the GSP-RM log buffer code from gsp.rs into gsp/logbuffer.rs, ahead
of extending it. No functional change.
Signed-off-by: Vladislav Zaharov <vladazaharova2018@gmail.com>
---
drivers/gpu/nova-core/gsp.rs | 59 +++--------------------
drivers/gpu/nova-core/gsp/logbuffer.rs | 65 ++++++++++++++++++++++++++
2 files changed, 72 insertions(+), 52 deletions(-)
create mode 100644 drivers/gpu/nova-core/gsp/logbuffer.rs
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 79ee74a0a16f..43df95406d35 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -12,11 +12,7 @@
CoherentView,
DmaAddress, //
},
- io::{
- io_project,
- io_write,
- Io, //
- },
+ io::io_write,
pci,
prelude::*, //
};
@@ -24,10 +20,16 @@
pub(crate) mod cmdq;
pub(crate) mod commands;
mod fw;
+mod logbuffer;
mod nvkv;
mod regs;
mod sequencer;
+use logbuffer::{
+ LogBuffer,
+ LogBuffers, //
+};
+
pub(crate) use fw::{
GspFmcBootParams,
GspFwWprMeta,
@@ -78,10 +80,6 @@ pub(crate) fn dev(&self) -> &'gpu device::Device<device::Bound> {
}
}
-/// Number of GSP pages to use in a RM log buffer.
-const RM_LOG_BUFFER_NUM_PAGES: usize = 0x10;
-const LOG_BUFFER_SIZE: usize = RM_LOG_BUFFER_NUM_PAGES * GSP_PAGE_SIZE;
-
/// Array of page table entries, as understood by the GSP bootloader.
#[repr(C)]
#[derive(FromBytes, IntoBytes)]
@@ -102,49 +100,6 @@ fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
}
}
-/// The logging buffers are byte queues that contain encoded printf-like
-/// messages from GSP-RM. They need to be decoded by a special application
-/// that can parse the buffers.
-///
-/// The 'loginit' buffer contains logs from early GSP-RM init and
-/// exception dumps. The 'logrm' buffer contains the subsequent logs. Both are
-/// written to directly by GSP-RM and can be any multiple of GSP_PAGE_SIZE.
-///
-/// The physical address map for the log buffer is stored in the buffer
-/// itself, starting with offset 1. Offset 0 contains the "put" pointer (pp).
-/// Initially, pp is equal to 0. If the buffer has valid logging data in it,
-/// then pp points to index into the buffer where the next logging entry will
-/// be written. Therefore, the logging data is valid if:
-/// 1 <= pp < sizeof(buffer)/sizeof(u64)
-struct LogBuffer<'a>(Coherent<'a, [u8; LOG_BUFFER_SIZE]>);
-
-impl<'a> LogBuffer<'a> {
- /// Creates a new `LogBuffer` mapped on `dev`.
- fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
- let obj = Self(Coherent::zeroed(dev, GFP_KERNEL)?);
-
- let start_addr = obj.0.dma_address();
-
- let pte_view = io_project!(
- obj.0,
- [build: size_of::<u64>()..][build: ..RM_LOG_BUFFER_NUM_PAGES * size_of::<u64>()]
- )
- .try_cast::<PteArray<RM_LOG_BUFFER_NUM_PAGES>>()?;
- PteArray::init(pte_view, start_addr)?;
-
- Ok(obj)
- }
-}
-
-struct LogBuffers<'a> {
- /// Init log buffer.
- loginit: LogBuffer<'a>,
- /// Interrupts log buffer.
- logintr: LogBuffer<'a>,
- /// RM log buffer.
- logrm: LogBuffer<'a>,
-}
-
/// GSP runtime data.
#[pin_data]
pub(crate) struct Gsp<'gsp> {
diff --git a/drivers/gpu/nova-core/gsp/logbuffer.rs b/drivers/gpu/nova-core/gsp/logbuffer.rs
new file mode 100644
index 000000000000..1fb411ce6d01
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/logbuffer.rs
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! GSP-RM log buffers.
+
+use kernel::{
+ device,
+ dma::Coherent,
+ io::{
+ io_project,
+ Io, //
+ },
+ prelude::*, //
+};
+
+use crate::gsp::{
+ PteArray,
+ GSP_PAGE_SIZE, //
+};
+
+/// Number of GSP pages to use in a RM log buffer.
+const RM_LOG_BUFFER_NUM_PAGES: usize = 0x10;
+const LOG_BUFFER_SIZE: usize = RM_LOG_BUFFER_NUM_PAGES * GSP_PAGE_SIZE;
+
+/// The logging buffers are byte queues that contain encoded printf-like
+/// messages from GSP-RM. They need to be decoded by a special application
+/// that can parse the buffers.
+///
+/// The 'loginit' buffer contains logs from early GSP-RM init and
+/// exception dumps. The 'logrm' buffer contains the subsequent logs. Both are
+/// written to directly by GSP-RM and can be any multiple of GSP_PAGE_SIZE.
+///
+/// The physical address map for the log buffer is stored in the buffer
+/// itself, starting with offset 1. Offset 0 contains the "put" pointer (pp).
+/// Initially, pp is equal to 0. If the buffer has valid logging data in it,
+/// then pp points to index into the buffer where the next logging entry will
+/// be written. Therefore, the logging data is valid if:
+/// 1 <= pp < sizeof(buffer)/sizeof(u64)
+pub(super) struct LogBuffer<'a>(pub(super) Coherent<'a, [u8; LOG_BUFFER_SIZE]>);
+
+impl<'a> LogBuffer<'a> {
+ /// Creates a new `LogBuffer` mapped on `dev`.
+ pub(super) fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
+ let obj = Self(Coherent::zeroed(dev, GFP_KERNEL)?);
+
+ let start_addr = obj.0.dma_address();
+
+ let pte_view = io_project!(
+ obj.0,
+ [build: size_of::<u64>()..][build: ..RM_LOG_BUFFER_NUM_PAGES * size_of::<u64>()]
+ )
+ .try_cast::<PteArray<RM_LOG_BUFFER_NUM_PAGES>>()?;
+ PteArray::init(pte_view, start_addr)?;
+
+ Ok(obj)
+ }
+}
+
+pub(super) struct LogBuffers<'a> {
+ /// Init log buffer.
+ pub(super) loginit: LogBuffer<'a>,
+ /// Interrupts log buffer.
+ pub(super) logintr: LogBuffer<'a>,
+ /// RM log buffer.
+ pub(super) logrm: LogBuffer<'a>,
+}
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v7 3/4] gpu: nova-core: gsp: retain the GSP-RM log buffers after unbind
2026-11-13 7:53 [PATCH v7 0/4] gpu: nova-core: retain the GSP-RM log buffers Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 1/4] gpu: nova-core: move the debugfs root into the module data Vladislav Zaharov
2026-11-13 7:53 ` [PATCH v7 2/4] gpu: nova-core: gsp: move the log buffers into their own file Vladislav Zaharov
@ 2026-11-13 7:53 ` Vladislav Zaharov
2026-11-13 20:34 ` Danilo Krummrich
2026-11-13 7:53 ` [PATCH v7 4/4] Documentation: nova: remove completed GSP log buffer task Vladislav Zaharov
3 siblings, 1 reply; 6+ messages in thread
From: Vladislav Zaharov @ 2026-11-13 7:53 UTC (permalink / raw)
To: dakr, jhubbard
Cc: acourbot, aliceryhl, ttabi, gary, nova-gpu, dri-devel,
linux-kernel, linux-doc, vladazaharova2018
The GSP-RM log buffers are DMA allocations of the device, so their
debugfs files go away when the GPU is unbound, including when probe()
fails. That is when the logs are most needed.
Add a gsp_keep_logs module parameter. When it is set, the buffers are
copied into module memory when they are dropped, and the copies are
exposed in the same files until the GPU is bound again or the module is
unloaded.
Signed-off-by: Vladislav Zaharov <vladazaharova2018@gmail.com>
---
drivers/gpu/nova-core/debugfs.rs | 17 ++-
drivers/gpu/nova-core/gsp.rs | 42 +++----
drivers/gpu/nova-core/gsp/logbuffer.rs | 149 ++++++++++++++++++++++++-
drivers/gpu/nova-core/nova_core.rs | 6 +
4 files changed, 182 insertions(+), 32 deletions(-)
diff --git a/drivers/gpu/nova-core/debugfs.rs b/drivers/gpu/nova-core/debugfs.rs
index 6c6ed129f0da..5ba5d42a8a7b 100644
--- a/drivers/gpu/nova-core/debugfs.rs
+++ b/drivers/gpu/nova-core/debugfs.rs
@@ -4,12 +4,21 @@
use kernel::{
debugfs,
- prelude::*, //
+ new_mutex,
+ prelude::*,
+ sync::Mutex, //
};
+use crate::gsp::RetainedLogs;
+
/// Debugfs data the module shares with every GPU it drives.
#[pin_data]
pub(crate) struct DebugfsData {
+ /// Copies of the GSP-RM log buffers of GPUs that are no longer bound.
+ ///
+ /// Declared before `root`, as the copies live below it.
+ #[pin]
+ retained_logs: Mutex<RetainedLogs>,
/// Root directory of the driver in debugfs.
root: debugfs::Dir,
}
@@ -18,6 +27,7 @@ impl DebugfsData {
/// Creates the debugfs data of the module.
pub(crate) fn new() -> impl PinInit<Self> {
pin_init!(Self {
+ retained_logs <- new_mutex!(RetainedLogs::new()),
root: debugfs::Dir::new(c"nova-core"),
})
}
@@ -26,4 +36,9 @@ pub(crate) fn new() -> impl PinInit<Self> {
pub(crate) fn root(&self) -> &debugfs::Dir {
&self.root
}
+
+ /// Returns the copies of the GSP-RM log buffers of GPUs that are no longer bound.
+ pub(crate) fn retained_logs(&self) -> &Mutex<RetainedLogs> {
+ &self.retained_logs
+ }
}
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 43df95406d35..43945f5c09c9 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -25,10 +25,8 @@
mod regs;
mod sequencer;
-use logbuffer::{
- LogBuffer,
- LogBuffers, //
-};
+use logbuffer::LogBuffers;
+pub(crate) use logbuffer::RetainedLogs;
pub(crate) use fw::{
GspFmcBootParams,
@@ -124,9 +122,7 @@ pub(crate) fn new(
pin_init::pin_init_scope(move || {
let dev = pdev.as_ref();
- let loginit = LogBuffer::new(dev)?;
- let logintr = LogBuffer::new(dev)?;
- let logrm = LogBuffer::new(dev)?;
+ let log_buffers = LogBuffers::new(dev)?;
// Initialise the logging structures. The OpenRM equivalents are in:
// _kgspInitLibosLoggingStructures (allocates memory for buffers)
@@ -141,29 +137,23 @@ pub(crate) fn new(
GFP_KERNEL,
)?;
- libos.init_at(0, LibosMemoryRegionInitArgument::new("LOGINIT", &loginit.0))?;
- libos.init_at(1, LibosMemoryRegionInitArgument::new("LOGINTR", &logintr.0))?;
- libos.init_at(2, LibosMemoryRegionInitArgument::new("LOGRM", &logrm.0))?;
+ libos.init_at(
+ 0,
+ LibosMemoryRegionInitArgument::new("LOGINIT", &log_buffers.loginit.0),
+ )?;
+ libos.init_at(
+ 1,
+ LibosMemoryRegionInitArgument::new("LOGINTR", &log_buffers.logintr.0),
+ )?;
+ libos.init_at(
+ 2,
+ LibosMemoryRegionInitArgument::new("LOGRM", &log_buffers.logrm.0),
+ )?;
libos.init_at(3, LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
libos.into()
},
- logs <- {
- let log_buffers = LogBuffers {
- loginit,
- logintr,
- logrm,
- };
-
- let log_parent: &debugfs::Dir =
- crate::NovaCoreModule::debugfs_data(dev).root();
-
- log_parent.scope(log_buffers, dev.name(), |logs, dir| {
- dir.read_binary_file(c"loginit", &logs.loginit.0);
- dir.read_binary_file(c"logintr", &logs.logintr.0);
- dir.read_binary_file(c"logrm", &logs.logrm.0);
- })
- },
+ logs <- log_buffers.scope(),
}))
})
}
diff --git a/drivers/gpu/nova-core/gsp/logbuffer.rs b/drivers/gpu/nova-core/gsp/logbuffer.rs
index 1fb411ce6d01..e1a613faace5 100644
--- a/drivers/gpu/nova-core/gsp/logbuffer.rs
+++ b/drivers/gpu/nova-core/gsp/logbuffer.rs
@@ -2,19 +2,30 @@
//! GSP-RM log buffers.
+use core::convert::Infallible;
+
use kernel::{
+ debugfs,
device,
dma::Coherent,
io::{
io_project,
Io, //
},
- prelude::*, //
+ prelude::*,
+ str::CString,
+ sync::barrier::{
+ dma_mb,
+ Read, //
+ }, //
};
-use crate::gsp::{
- PteArray,
- GSP_PAGE_SIZE, //
+use crate::{
+ gsp::{
+ PteArray,
+ GSP_PAGE_SIZE, //
+ },
+ NovaCoreModule, //
};
/// Number of GSP pages to use in a RM log buffer.
@@ -39,7 +50,7 @@
impl<'a> LogBuffer<'a> {
/// Creates a new `LogBuffer` mapped on `dev`.
- pub(super) fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
+ fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
let obj = Self(Coherent::zeroed(dev, GFP_KERNEL)?);
let start_addr = obj.0.dma_address();
@@ -53,9 +64,33 @@ pub(super) fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
Ok(obj)
}
+
+ /// Copies the buffer, or returns an empty vector if the GSP never wrote to it.
+ fn snapshot(&self) -> Result<VVec<u8>> {
+ let put = io_project!(self.0, [build: ..size_of::<u64>()])
+ .try_cast::<u64>()?
+ .read_val();
+ if put == 0 {
+ return Ok(VVec::new());
+ }
+
+ // ORDERING: LOAD->LOAD ordering needed to order the "put" read before the data read, as a
+ // GSP that failed to boot may still be writing.
+ dma_mb(Read);
+
+ let mut snapshot = VVec::zeroed(LOG_BUFFER_SIZE, GFP_KERNEL)?;
+ io_project!(self.0, [build: ..]).copy_to_slice(&mut snapshot);
+
+ // The copy reads "put" again, unordered against the data, so keep the value read above.
+ snapshot[..size_of::<u64>()].copy_from_slice(&put.to_ne_bytes());
+
+ Ok(snapshot)
+ }
}
pub(super) struct LogBuffers<'a> {
+ /// Device the buffers belong to.
+ dev: &'a device::Device<device::Bound>,
/// Init log buffer.
pub(super) loginit: LogBuffer<'a>,
/// Interrupts log buffer.
@@ -63,3 +98,107 @@ pub(super) struct LogBuffers<'a> {
/// RM log buffer.
pub(super) logrm: LogBuffer<'a>,
}
+
+impl<'a> LogBuffers<'a> {
+ /// Allocates the log buffers of `dev`.
+ pub(super) fn new(dev: &'a device::Device<device::Bound>) -> Result<Self> {
+ Ok(Self {
+ dev,
+ loginit: LogBuffer::new(dev)?,
+ logintr: LogBuffer::new(dev)?,
+ logrm: LogBuffer::new(dev)?,
+ })
+ }
+
+ /// Exposes the buffers in debugfs, in a directory named after their device.
+ pub(super) fn scope(self) -> impl PinInit<debugfs::Scope<Self>, Infallible> + 'a {
+ let dev = self.dev;
+ let data = NovaCoreModule::debugfs_data(dev);
+
+ // Retained copies of an earlier run use the same directory.
+ data.retained_logs().lock().remove(dev.name());
+
+ data.root().scope(self, dev.name(), |logs, dir| {
+ dir.read_binary_file(c"loginit", &logs.loginit.0);
+ dir.read_binary_file(c"logintr", &logs.logintr.0);
+ dir.read_binary_file(c"logrm", &logs.logrm.0);
+ })
+ }
+
+ /// Exposes copies of the buffers in their place, if `gsp_keep_logs` is set.
+ fn retain(&self) -> Result {
+ if !crate::module_parameters::gsp_keep_logs.value() || !cfg!(CONFIG_DEBUG_FS) {
+ return Ok(());
+ }
+
+ let logs = RetainedLogBuffers {
+ name: CString::try_from_fmt(fmt!("{}", self.dev.name()))?,
+ loginit: self.loginit.snapshot()?,
+ logintr: self.logintr.snapshot()?,
+ logrm: self.logrm.snapshot()?,
+ };
+
+ if logs.loginit.is_empty() && logs.logintr.is_empty() && logs.logrm.is_empty() {
+ return Ok(());
+ }
+
+ // Allocate before an earlier copy is dropped, so that a failure leaves that copy in place.
+ let scope = KBox::<debugfs::Scope<RetainedLogBuffers>>::new_uninit(GFP_KERNEL)?;
+ let data = NovaCoreModule::debugfs_data(self.dev);
+ let root = data.root();
+ let mut retained = data.retained_logs().lock();
+ retained.0.reserve(1, GFP_KERNEL)?;
+
+ retained.remove(self.dev.name());
+
+ let scope = scope.write_pin_init(root.scope(logs, self.dev.name(), |logs, dir| {
+ if !logs.loginit.is_empty() {
+ dir.read_binary_file(c"loginit", &logs.loginit);
+ }
+ if !logs.logintr.is_empty() {
+ dir.read_binary_file(c"logintr", &logs.logintr);
+ }
+ if !logs.logrm.is_empty() {
+ dir.read_binary_file(c"logrm", &logs.logrm);
+ }
+ }))?;
+
+ retained.0.push(scope, GFP_KERNEL)?;
+
+ Ok(())
+ }
+}
+
+impl Drop for LogBuffers<'_> {
+ fn drop(&mut self) {
+ if let Err(e) = self.retain() {
+ dev_warn!(self.dev, "failed to retain GSP-RM log buffers: {:?}\n", e);
+ }
+ }
+}
+
+/// Copies of the log buffers of a GPU that is no longer bound.
+struct RetainedLogBuffers {
+ /// Name of the device, which also names the debugfs directory.
+ name: CString,
+ /// Init log buffer, empty if never written to.
+ loginit: VVec<u8>,
+ /// Interrupts log buffer, empty if never written to.
+ logintr: VVec<u8>,
+ /// RM log buffer, empty if never written to.
+ logrm: VVec<u8>,
+}
+
+/// Copies of the log buffers of all GPUs that are no longer bound.
+pub(crate) struct RetainedLogs(KVec<Pin<KBox<debugfs::Scope<RetainedLogBuffers>>>>);
+
+impl RetainedLogs {
+ pub(crate) const fn new() -> Self {
+ Self(KVec::new())
+ }
+
+ /// Drops the copies of the device named `name`, if any.
+ fn remove(&mut self, name: &CStr) {
+ self.0.retain(|gpu| *gpu.name != *name);
+ }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index f5d08de8a212..1671e260dfb4 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -91,6 +91,12 @@ fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
description: "Nova Core GPU driver",
license: "GPL v2",
firmware: [],
+ params: {
+ gsp_keep_logs: bool {
+ default: false,
+ description: "Keep the GSP-RM log buffers in debugfs after their GPU is unbound",
+ },
+ },
}
kernel::module_firmware!(firmware::ModInfoBuilder);
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread