linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Gary Guo" <gary@garyguo.net>
To: "Vladislav Zaharov" <vladazaharova2018@gmail.com>,
	<dakr@kernel.org>, <jhubbard@nvidia.com>
Cc: <acourbot@nvidia.com>, <aliceryhl@google.com>, <ttabi@nvidia.com>,
	<gary@garyguo.net>, <nova-gpu@lists.linux.dev>,
	<dri-devel@lists.freedesktop.org>, <linux-kernel@vger.kernel.org>,
	<linux-doc@vger.kernel.org>
Subject: Re: [PATCH v3 2/3] gpu: nova-core: gsp: retain the GSP-RM log buffers after unbind
Date: Sat, 12 Sep 2026 18:55:32 +0100	[thread overview]
Message-ID: <DLDJ4S0AJ5X3.HW1TYFLP4PXT@garyguo.net> (raw)
In-Reply-To: <20260912071842.622696-3-vladazaharova2018@gmail.com>

On Sat Sep 12, 2026 at 8:18 AM BST, Vladislav Zaharov wrote:
> The GSP-RM log buffers are exposed through debugfs, but the Scope that
> owns them lives in Gsp, inside GspResources, inside the Gpu built by
> probe(). They are DMA allocations of the device and cannot outlive it,
> so the entries go away as soon as the GPU is unbound - and, more to the
> point, as soon as probe() fails, which is exactly when the log of a GSP
> that did not come up is the thing one wants to read.
>
> Add a gsp_keep_logs module parameter. When it is set, dropping the log
> buffers copies whatever the GSP wrote into memory owned by the module
> and exposes the copies until the module is unloaded. A buffer whose
> "put" pointer is still zero was never written to and is skipped.
>
> The GSP has normally been stopped by the time the buffers are dropped,
> but a boot that timed out can leave it still appending, so a DMA read
> barrier orders the read of the "put" pointer before the copy.
>
> The copies live in a "retained" directory, created during module init
> rather than on first use, which keeps the teardown path from having to
> reach for DEBUGFS_ROOT. Keeping them out of the directory used by bound
> GPUs also means a device coming back does not find its debugfs name
> taken by its own history; nouveau, which recreates the entries under the
> name of the GPU that just went away, has that problem.
>
> While at it, move the log buffer code out of gsp.rs into gsp/logbuffer.rs.
>
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Vladislav Zaharov <vladazaharova2018@gmail.com>
> ---
>  drivers/gpu/nova-core/gsp.rs           | 100 ++-------
>  drivers/gpu/nova-core/gsp/logbuffer.rs | 267 +++++++++++++++++++++++++
>  drivers/gpu/nova-core/nova_core.rs     |  32 ++-
>  3 files changed, 315 insertions(+), 84 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 25ea43f1cbe9..1a1eb7f37075 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,9 +20,13 @@
>  pub(crate) mod cmdq;
>  pub(crate) mod commands;
>  mod fw;
> +mod logbuffer;
>  mod regs;
>  mod sequencer;
>  
> +use logbuffer::LogBuffers;
> +pub(crate) use logbuffer::RetainedLogs;
> +
>  pub(crate) use fw::{
>      GspFmcBootParams,
>      GspFwWprMeta,
> @@ -77,10 +77,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)]
> @@ -101,49 +97,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> {
> @@ -165,9 +118,7 @@ pub(crate) fn new(pdev: &'gsp pci::Device<device::Bound>) -> impl PinInit<Self,
>          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)
> @@ -182,36 +133,23 @@ pub(crate) fn new(pdev: &'gsp pci::Device<device::Bound>) -> impl PinInit<Self,
>                          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,
> -                    };
> -
> -                    #[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");
> -
> -                    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
> new file mode 100644
> index 000000000000..22c47f199169
> --- /dev/null
> +++ b/drivers/gpu/nova-core/gsp/logbuffer.rs
> @@ -0,0 +1,267 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! GSP-RM log buffers, and the debugfs entries exposing them.
> +
> +use core::convert::Infallible;
> +
> +use kernel::{
> +    debugfs,
> +    device,
> +    dma::Coherent,
> +    io::{
> +        io_project,
> +        Io, //
> +    },
> +    prelude::*,
> +    sync::{
> +        aref::ARef,
> +        barrier::{
> +            dma_mb,
> +            Read, //
> +        }, //
> +    }, //
> +};
> +
> +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`.
> +    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)
> +    }
> +
> +    /// Copies the contents of this buffer into memory that does not belong to the device.
> +    ///
> +    /// A buffer the GSP never wrote to yields an empty vector, as it holds nothing worth keeping.
> +    fn snapshot(&self) -> Result<VVec<u8>> {
> +        // Offset 0 holds the "put" pointer, which the GSP advances as it appends entries. It is
> +        // still zero if nothing was ever logged, which is all that is tested here: a buffer that
> +        // was written to is copied whole, and making sense of "put" is left to the decoder.
> +        let put = io_project!(self.0, [build: ..size_of::<u64>()]).try_cast::<u64>()?;
> +        if put.read_val() == 0 {
> +            return Ok(VVec::new());
> +        }
> +
> +        // ORDERING: LOAD->LOAD ordering needed to order the "put" read before the data read. The
> +        // GSP has normally been stopped by the time this runs, but a boot that timed out can leave
> +        // it still appending.
> +        dma_mb(Read);
> +
> +        let mut snapshot = VVec::zeroed(LOG_BUFFER_SIZE, GFP_KERNEL)?;
> +        io_project!(self.0, [build: ..]).copy_to_slice(&mut snapshot);
> +
> +        Ok(snapshot)
> +    }
> +}
> +
> +/// The log buffers of a GPU, for as long as it is bound to the driver.
> +pub(super) struct LogBuffers<'a> {
> +    /// Device the buffers belong to. Also names their debugfs directory.
> +    dev: &'a device::Device<device::Bound>,
> +    /// Init log buffer.
> +    pub(super) loginit: LogBuffer<'a>,
> +    /// Interrupts log buffer.
> +    pub(super) logintr: LogBuffer<'a>,
> +    /// RM log buffer.
> +    pub(super) logrm: LogBuffer<'a>,
> +}
> +
> +impl<'a> LogBuffers<'a> {
> +    /// Allocates the three 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)?,
> +        })
> +    }
> +
> +    /// Creates an initializer exposing these buffers under a directory named after their device.
> +    pub(super) fn scope(self) -> impl PinInit<debugfs::Scope<Self>, Infallible> + 'a {
> +        let dev = self.dev;
> +
> +        #[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");
> +
> +        log_parent.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);
> +        })
> +    }
> +
> +    /// Preserves whatever the GSP logged, so it can still be read once the GPU is gone.
> +    ///
> +    /// The buffers are DMA allocations of the device and cannot outlive it, so their contents are
> +    /// copied into memory owned by the module and exposed through fresh debugfs entries. Those
> +    /// live until the module is unloaded.
> +    ///
> +    /// Does nothing if `gsp_keep_logs` was not set when the module was loaded, as there is then
> +    /// no directory to put the copies in.
> +    fn retain(&self) -> Result {
> +        // Copying is only worth it if there is somewhere to put the result, but the lock is
> +        // dropped right away: what follows allocates 64 KiB three times, and no other device
> +        // should have to wait for that.
> +        if !crate::RETAINED_LOGS.lock().is_enabled() {
> +            return Ok(());
> +        }
> +
> +        let logs = RetainedLogBuffers {
> +            dev: self.dev.into(),
> +            loginit: self.loginit.snapshot()?,
> +            logintr: self.logintr.snapshot()?,
> +            logrm: self.logrm.snapshot()?,
> +        };
> +
> +        // Nothing was ever logged, so there is nothing to keep. A copy from an earlier run of
> +        // this device is deliberately left alone: logs from a run that failed are worth more
> +        // than the silence of one that did not.
> +        if logs.loginit.is_empty() && logs.logintr.is_empty() && logs.logrm.is_empty() {
> +            return Ok(());
> +        }
> +
> +        // Take every allocation that can fail before the previous copy of this device is
> +        // dropped, so that running out of memory here cannot leave it with no logs at all.
> +        let scope = KBox::<debugfs::Scope<RetainedLogBuffers>>::new_uninit(GFP_KERNEL)?;
> +
> +        let mut retained = crate::RETAINED_LOGS.lock();
> +
> +        // The module may have been unloaded out from under us while the copies were taken.
> +        let Some(dir) = retained.dir.clone() else {
> +            return Ok(());
> +        };
> +
> +        retained.gpus.reserve(1, GFP_KERNEL)?;
> +
> +        // An earlier run of the same device may have left a copy behind, and its directory
> +        // carries the name about to be used again, so it has to go first. Nothing below can
> +        // fail, so the replacement is guaranteed to take its place.
> +        retained
> +            .gpus
> +            .retain(|gpu| gpu.dev.name() != self.dev.name());
> +
> +        let scope = scope.write_pin_init(dir.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.gpus.push(scope, GFP_KERNEL)?;
> +
> +        dev_dbg!(self.dev, "GSP-RM log buffers retained\n");
> +
> +        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 around.
> +struct RetainedLogBuffers {
> +    /// Device the buffers came from.
> +    dev: ARef<device::Device>,
> +    /// Contents of the init log buffer, empty if it was never written to.
> +    loginit: VVec<u8>,
> +    /// Contents of the interrupts log buffer, empty if it was never written to.
> +    logintr: VVec<u8>,
> +    /// Contents of the RM log buffer, empty if it was never written to.
> +    logrm: VVec<u8>,
> +}
> +
> +/// Log buffers of GPUs that are gone, and the debugfs entries exposing them.
> +///
> +/// The copies live under a `retained` directory of their own instead of next to the entries of
> +/// the GPUs that are actually bound, so that a device coming back does not find its name taken.
> +pub(crate) struct RetainedLogs {
> +    /// Parent directory of all copies. `None` unless retaining was asked for.
> +    dir: Option<debugfs::Dir>,
> +    /// One entry per GPU.
> +    gpus: KVec<Pin<KBox<debugfs::Scope<RetainedLogBuffers>>>>,
> +}
> +
> +impl RetainedLogs {
> +    /// Creates an empty set of retained log buffers, retaining disabled.
> +    pub(crate) const fn new() -> Self {
> +        Self {
> +            dir: None,
> +            gpus: KVec::new(),
> +        }
> +    }
> +
> +    /// Creates the directory the copies will live in, enabling retaining.
> +    ///
> +    /// Does nothing without `CONFIG_DEBUG_FS`, where a [`debugfs::Dir`] is a zero-sized type and
> +    /// the copies could never be read back.
> +    pub(crate) fn enable(&mut self, parent: &debugfs::Dir) {
> +        if !cfg!(CONFIG_DEBUG_FS) {
> +            return;
> +        }
> +
> +        self.dir = Some(parent.subdir(c"retained"));
> +    }
> +
> +    /// Returns whether copies are being kept.
> +    pub(crate) fn is_enabled(&self) -> bool {
> +        self.dir.is_some()
> +    }
> +
> +    /// Releases every copy and the directory holding them.
> +    pub(crate) fn clear(&mut self) {
> +        self.gpus.clear();
> +        self.dir = None;
> +    }
> +}
> diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
> index 11fe1d2858a9..557cc611f3fc 100644
> --- a/drivers/gpu/nova-core/nova_core.rs
> +++ b/drivers/gpu/nova-core/nova_core.rs
> @@ -33,11 +33,21 @@
>  // TODO: Move this into per-module data once that exists.
>  static mut DEBUGFS_ROOT: Option<debugfs::Dir> = None;
>  
> +kernel::sync::global_lock! {
> +    /// Log buffers of GPUs that are gone, kept around until the module is unloaded.
> +    // TODO: Move this into per-module data once that exists.
> +    unsafe(uninit) static RETAINED_LOGS: Mutex<gsp::RetainedLogs> = gsp::RetainedLogs::new();

One global is already too many, we don't need more.

Consider instead to put everything that needs to be shared between multiple
devices in a single struct, and smuggle the pointer to it via a single global
`static mut` in place of today's `DEBUGFS_ROOT`. Then we just need unsafe in one
place.

Something like:

    static mut DEBUGFS_DATA: Option<&'static DebugfsData> = None;

    struct DebugfsData {
        root: debugfs::Dir,
        // You can put everything here, and still have initialized during module
        // init now..
        retained_logs: ...,
    }

    struct NovaCoreModule {
        ...
        // Put this last so it's destroyed last
        debugfs_data: DebugfsData,
    }

    // Module init
    fn init(..) -> impl PinInit<Self, Error> {
        try_pin_init!(Self {
            debugfs_data <- /* construct everything here, safely */
            _: {
                DEBUGFS_DATA = Some(unsafe { &*core::ptr::from_ref(debugfs_data) });
            },
            driver <- Registration::new(MODULE_NAME, module),
        }
    }

    // Probe
    fn probe<'bound>(pdev: &'bound Device<Core<'_>>, ...) -> ... {
        // Coerce this back to `&'bound` is okay because DEBUGFS_DATA outlives
        // registration and thus outlives bound device.
        let debugfs_data: &'bound _ = unsafe { DEBUGFS_DATA.unwrap() };
    }

Best,
Gary

> +}
> +
>  /// Guard that clears `DEBUGFS_ROOT` when dropped.
>  struct DebugfsRootGuard;
>  
>  impl Drop for DebugfsRootGuard {
>      fn drop(&mut self) {
> +        // Retained log buffers own debugfs entries below `DEBUGFS_ROOT`, so they have to go away
> +        // before it does.
> +        RETAINED_LOGS.lock().clear();
> +
>          // 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 };
> @@ -58,15 +68,25 @@ impl InPlaceModule for NovaCoreModule {
>      fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
>          let dir = debugfs::Dir::new(c"nova-core");
>  
> +        // SAFETY: Module initialization runs exactly once, and before the driver is registered,
> +        // so no probe can have touched `RETAINED_LOGS` yet.
> +        unsafe { RETAINED_LOGS.init() };
> +
> +        // Creating the directory up front is what makes retaining possible without reaching for
> +        // `DEBUGFS_ROOT` later, from the teardown path of a device.
> +        if module_parameters::gsp_keep_logs.value() {
> +            RETAINED_LOGS.lock().enable(&dir);
> +        }
> +
>          // 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) };
>  
>          // Fields are initialized in the order written here, and an initializer that fails drops
>          // what it has already built, so the guard goes first: should registration fail, its drop
> -        // still takes `DEBUGFS_ROOT` down with it. Nothing would otherwise, as statics are never
> -        // dropped and the module is unloaded right away, leaving a directory behind that the
> -        // next load cannot create again.
> +        // still takes `DEBUGFS_ROOT` and the retained copies down with it. Nothing would
> +        // otherwise, as statics are never dropped and the module is unloaded right away, leaving
> +        // directories behind that the next load cannot create again.
>          try_pin_init!(Self {
>              _debugfs_guard: DebugfsRootGuard,
>              _driver <- Registration::new(MODULE_NAME, module),
> @@ -81,6 +101,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 gone",
> +        },
> +    },
>  }
>  
>  kernel::module_firmware!(firmware::ModInfoBuilder);



  reply	other threads:[~2026-09-12 17:55 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-12  7:18 [PATCH v3 0/3] gpu: nova-core: retain the GSP-RM log buffers Vladislav Zaharov
2026-09-12  7:18 ` [PATCH v3 1/3] gpu: nova-core: build the debugfs guard before registering the driver Vladislav Zaharov
2026-09-12  7:18 ` [PATCH v3 2/3] gpu: nova-core: gsp: retain the GSP-RM log buffers after unbind Vladislav Zaharov
2026-09-12 17:55   ` Gary Guo [this message]
2026-09-12  7:18 ` [PATCH v3 3/3] Documentation: nova: remove completed GSP log buffer task Vladislav Zaharov

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=DLDJ4S0AJ5X3.HW1TYFLP4PXT@garyguo.net \
    --to=gary@garyguo.net \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=dakr@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jhubbard@nvidia.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nova-gpu@lists.linux.dev \
    --cc=ttabi@nvidia.com \
    --cc=vladazaharova2018@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).