[PATCH v11 20/22] gpu: nova-core: Hopper/Blackwell: add GSP lockdown release polling

From: John Hubbard

Date: Fri May 29 2026 - 23:14:08 EST


On Hopper and Blackwell, FSP boots GSP with hardware lockdown enabled.
After FSP Chain of Trust completes, the driver must poll for lockdown
release before proceeding with GSP initialization. Add the register
bit and helper functions needed for this polling.

Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/fsp.rs | 1 -
drivers/gpu/nova-core/gsp/hal/gh100.rs | 90 +++++++++++++++++++++++++-
drivers/gpu/nova-core/regs.rs | 2 +
3 files changed, 90 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 5878d86323b9..15d4b01c284a 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -163,7 +163,6 @@ pub(crate) fn new(

/// DMA address of the FMC boot parameters, needed after boot for lockdown
/// release polling.
- #[expect(dead_code)]
pub(crate) fn boot_params_dma_handle(&self) -> u64 {
self.fmc_boot_params.dma_handle()
}
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index 946e88481d6f..1f333a6f57a0 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -5,7 +5,13 @@

use kernel::{
device,
- dma::Coherent, //
+ dma::Coherent,
+ io::{
+ poll::read_poll_timeout,
+ register::WithBase,
+ Io, //
+ },
+ time::Delta,
};

use crate::{
@@ -32,8 +38,85 @@
Gsp,
GspFwWprMeta, //
},
+ regs,
};

+/// GSP lockdown pattern written by firmware to mbox0 while RISC-V branch privilege
+/// lockdown is active. The low byte varies, the upper 24 bits are fixed.
+const GSP_LOCKDOWN_PATTERN: u32 = 0xbadf_4100;
+const GSP_LOCKDOWN_MASK: u32 = 0xffff_ff00;
+
+/// GSP falcon mailbox state, used to track lockdown release status.
+struct GspMbox {
+ mbox0: u32,
+ mbox1: u32,
+}
+
+impl GspMbox {
+ /// Reads both mailboxes from the GSP falcon.
+ fn read(gsp_falcon: &Falcon<GspEngine>, bar: &Bar0) -> Self {
+ Self {
+ mbox0: gsp_falcon.read_mailbox0(bar),
+ mbox1: gsp_falcon.read_mailbox1(bar),
+ }
+ }
+
+ /// Returns `true` if the lockdown pattern is present in `mbox0`.
+ fn is_locked_down(&self) -> bool {
+ (self.mbox0 & GSP_LOCKDOWN_MASK) == GSP_LOCKDOWN_PATTERN
+ }
+
+ /// Combines mailbox0 and mailbox1 into a 64-bit address.
+ fn combined_addr(&self) -> u64 {
+ (u64::from(self.mbox1) << 32) | u64::from(self.mbox0)
+ }
+
+ /// Returns `true` if GSP lockdown has been released.
+ ///
+ /// Checks the lockdown pattern, validates the boot params address,
+ /// and verifies the `HWCFG2` lockdown bit is clear.
+ fn lockdown_released(&self, bar: &Bar0, fmc_boot_params_addr: u64) -> bool {
+ if self.is_locked_down() {
+ return false;
+ }
+
+ if self.mbox0 != 0 && self.combined_addr() != fmc_boot_params_addr {
+ return true;
+ }
+
+ let hwcfg2 = bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<GspEngine>());
+ !hwcfg2.riscv_br_priv_lockdown()
+ }
+}
+
+/// Waits for GSP lockdown to be released after FSP Chain of Trust.
+fn wait_for_gsp_lockdown_release(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ gsp_falcon: &Falcon<GspEngine>,
+ fmc_boot_params_addr: u64,
+) -> Result {
+ dev_dbg!(dev, "Waiting for GSP lockdown release\n");
+
+ let mbox = read_poll_timeout(
+ || Ok(GspMbox::read(gsp_falcon, bar)),
+ |mbox| mbox.lockdown_released(bar, fmc_boot_params_addr),
+ Delta::from_millis(10),
+ Delta::from_secs(30),
+ )
+ .inspect_err(|_| {
+ dev_err!(dev, "GSP lockdown release timeout\n");
+ })?;
+
+ if mbox.mbox0 != 0 {
+ dev_err!(dev, "GSP-FMC boot failed (mbox: {:#x})\n", mbox.mbox0);
+ return Err(EIO);
+ }
+
+ dev_dbg!(dev, "GSP lockdown released\n");
+ Ok(())
+}
+
struct Gh100;

impl GspHal for Gh100 {
@@ -49,7 +132,7 @@ fn boot<'a>(
chipset: Chipset,
fb_layout: &FbLayout,
wpr_meta: &Coherent<GspFwWprMeta>,
- _gsp_falcon: &'a Falcon<GspEngine>,
+ gsp_falcon: &'a Falcon<GspEngine>,
_sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
let mut fsp_falcon = Falcon::<FspEngine>::new(dev, chipset)?;
@@ -68,6 +151,9 @@ fn boot<'a>(

Fsp::boot_fmc(dev, bar, fb_layout, &mut fsp_falcon, &args)?;

+ let fmc_boot_params_addr = args.boot_params_dma_handle();
+ wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, fmc_boot_params_addr)?;
+
Err(ENOTSUPP)
}
}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index d4067efb8772..0f48e78eebe7 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -355,6 +355,8 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) NV_PFALCON_FALCON_HWCFG2(u32) @ PFalconBase + 0x000000f4 {
/// Signal indicating that reset is completed (GA102+).
31:31 reset_ready => bool;
+ /// RISC-V branch privilege lockdown bit.
+ 13:13 riscv_br_priv_lockdown => bool;
/// Set to 0 after memory scrubbing is completed.
12:12 mem_scrubbing => bool;
10:10 riscv => bool;
--
2.54.0