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

From: John Hubbard

Date: Mon Jun 01 2026 - 23:28:17 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 352ef7683cf2..aec991afa669 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -142,7 +142,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 f41f3fea15ff..02aec5281389 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::{
@@ -31,8 +37,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 {
@@ -48,7 +131,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 fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
@@ -64,6 +147,9 @@ fn boot<'a>(

fsp.boot_fmc(dev, bar, fb_layout, &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 8c51609d0281..a4a986f89340 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -363,6 +363,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