[PATCH v4 30/33] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot path

John Hubbard
2026-02-10T02:47:10Z

Add the FSP-based boot path for Hopper and Blackwell GPUs. Unlike
Turing/Ampere/Ada which use SEC2 to load the booter firmware, Hopper
and Blackwell use FSP (Firmware System Processor) with FMC firmware
to establish a Chain of Trust and boot GSP directly.

The boot() function now dispatches to either run_booter() (SEC2 path)
or run_fsp() (FSP path) based on the GPU architecture. The cmdq
commands are moved to after GSP boot, and the GSP sequencer is only
run for SEC2-based architectures.

Cc: Gary Guo <gary@garyguo.net>
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/fb.rs | 1 -
drivers/gpu/nova-core/firmware/fsp.rs | 2 -
drivers/gpu/nova-core/fsp.rs | 6 +-
drivers/gpu/nova-core/gsp/boot.rs | 158 ++++++++++++++++++++------
4 files changed, 123 insertions(+), 44 deletions(-)

diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index 7c502f15622c..a860f43ec5af 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -180,7 +180,6 @@ pub(crate) struct FbLayout {
pub(crate) heap: FbRange,
pub(crate) vf_partition_count: u8,
/// Total reserved size (heap + PMU reserved), aligned to 2MB.
- #[expect(unused)]
pub(crate) total_reserved_size: u32,
}

diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs
index edcc173c2fa6..e10954aa146a 100644
--- a/drivers/gpu/nova-core/firmware/fsp.rs
+++ b/drivers/gpu/nova-core/firmware/fsp.rs
@@ -14,7 +14,6 @@
gpu::Chipset, //
};

-#[expect(dead_code)]
pub(crate) struct FspFirmware {
/// FMC firmware image data (only the .image section) - submitted to hardware
pub(crate) fmc_image: DmaObject,
@@ -23,7 +22,6 @@ pub(crate) struct FspFirmware {
}

impl FspFirmware {
- #[expect(dead_code)]
pub(crate) fn new(
dev: &device::Device<device::Bound>,
chipset: Chipset,
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 35169f3bc446..5e3c15a71e2d 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -1,8 +1,5 @@
// SPDX-License-Identifier: GPL-2.0

-// TODO: remove this once the code is fully functional
-#![expect(dead_code)]
-
//! FSP (Firmware System Processor) interface for Hopper/Blackwell GPUs.
//!
//! Hopper/Blackwell use a simplified firmware boot sequence: FMC --> FSP --> GSP.
@@ -11,6 +8,7 @@

use kernel::{
device,
+ dma::CoherentAllocation,
io::poll::read_poll_timeout,
prelude::*,
ptr::{
@@ -352,8 +350,6 @@ pub(crate) fn create_fmc_boot_params(
wpr_meta_size: u32,
libos_addr: u64,
) -> Result<kernel::dma::CoherentAllocation<GspFmcBootParams>> {
- use kernel::dma::CoherentAllocation;
-
const GSP_DMA_TARGET_COHERENT_SYSTEM: u32 = 1;
const GSP_DMA_TARGET_NONCOHERENT_SYSTEM: u32 = 2;

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 1e8a7306e078..80d57a54c0c9 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -13,6 +13,7 @@
use crate::{
driver::Bar0,
falcon::{
+ fsp::Fsp as FspEngine,
gsp::Gsp,
sec2::Sec2,
Falcon,
@@ -24,6 +25,7 @@
BooterFirmware,
BooterKind, //
},
+ fsp::FspFirmware,
fwsec::{
FwsecCommand,
FwsecFirmware, //
@@ -31,9 +33,14 @@
gsp::GspFirmware,
FIRMWARE_VERSION, //
},
- gpu::Chipset,
+ fsp::Fsp,
+ gpu::{
+ Architecture,
+ Chipset, //
+ },
gsp::{
commands,
+ fw::LibosMemoryRegionInitArgument,
sequencer::{
GspSequencer,
GspSequencerParams, //
@@ -155,6 +162,55 @@ fn run_booter(
Ok(())
}

+ fn run_fsp(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ gsp_falcon: &Falcon<Gsp>,
+ wpr_meta: &CoherentAllocation<GspFwWprMeta>,
+ libos: &CoherentAllocation<LibosMemoryRegionInitArgument>,
+ fb_layout: &FbLayout,
+ ) -> Result {
+ let fsp_falcon = Falcon::<FspEngine>::new(dev, chipset)?;
+
+ Fsp::wait_secure_boot(dev, bar, chipset.arch())?;
+
+ let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
+
+ // fmc_full is a KVec<u8> for CPU-side signature extraction only.
+ // A separate buffer, fsp_fw.fmc_image, is what gets submitted to the hardware.
+ let signatures = Fsp::extract_fmc_signatures_static(dev, &fsp_fw.fmc_full)?;
+
+ // Create FMC boot parameters
+ let fmc_boot_params = Fsp::create_fmc_boot_params(
+ dev,
+ wpr_meta.dma_handle(),
+ core::mem::size_of::<GspFwWprMeta>() as u32,
+ libos.dma_handle(),
+ )?;
+
+ // Execute FSP Chain of Trust
+ // NOTE: FSP Chain of Trust handles GSP boot internally - we do NOT reset or boot GSP
+ Fsp::boot_gsp_fmc_with_signatures(
+ dev,
+ bar,
+ chipset,
+ &fsp_fw.fmc_image,
+ &fmc_boot_params,
+ u64::from(fb_layout.total_reserved_size),
+ false, // not resuming
+ &fsp_falcon,
+ &signatures,
+ )?;
+
+ // Wait for GSP lockdown to be released
+ let fmc_boot_params_addr = fmc_boot_params.dma_handle();
+ let _mbox0 =
+ Self::wait_for_gsp_lockdown_release(dev, bar, gsp_falcon, fmc_boot_params_addr)?;
+
+ Ok(())
+ }
+
/// Check if GSP lockdown has been released after FSP Chain of Trust
fn gsp_lockdown_released(
dev: &device::Device,
@@ -196,7 +252,6 @@ fn gsp_lockdown_released(
}

/// Wait for GSP lockdown to be released after FSP Chain of Trust
- #[expect(dead_code)]
fn wait_for_gsp_lockdown_release(
dev: &device::Device,
bar: &Bar0,
@@ -257,43 +312,63 @@ pub(crate) fn boot(
) -> Result {
let dev = pdev.as_ref();

- let bios = Vbios::new(dev, bar)?;
-
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, FIRMWARE_VERSION), GFP_KERNEL)?;

let fb_layout = FbLayout::new(chipset, bar, &gsp_fw)?;
dev_dbg!(dev, "{:#x?}\n", fb_layout);

- Self::run_fwsec_frts(dev, gsp_falcon, bar, &bios, &fb_layout)?;
+ if matches!(
+ chipset.arch(),
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada
+ ) {
+ let bios = Vbios::new(dev, bar)?;
+ Self::run_fwsec_frts(dev, gsp_falcon, bar, &bios, &fb_layout)?;
+ }

let wpr_meta =
CoherentAllocation::<GspFwWprMeta>::alloc_coherent(dev, 1, GFP_KERNEL | __GFP_ZERO)?;
dma_write!(wpr_meta[0] = GspFwWprMeta::new(&gsp_fw, &fb_layout))?;

- self.cmdq
- .send_command(bar, commands::SetSystemInfo::new(pdev))?;
- self.cmdq.send_command(bar, commands::SetRegistry::new())?;
+ // For SEC2-based architectures, reset GSP and boot it before SEC2
+ if matches!(
+ chipset.arch(),
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada
+ ) {
+ gsp_falcon.reset(bar)?;
+ let libos_handle = self.libos.dma_handle();
+ let (mbox0, mbox1) = gsp_falcon.boot(
+ bar,
+ Some(libos_handle as u32),
+ Some((libos_handle >> 32) as u32),
+ )?;
+ dev_dbg!(
+ pdev,
+ "GSP MBOX0: {:#x}, MBOX1: {:#x}\n",
+ mbox0,
+ mbox1
+ );

- gsp_falcon.reset(bar)?;
- let libos_handle = self.libos.dma_handle();
- let (mbox0, mbox1) = gsp_falcon.boot(
- bar,
- Some(libos_handle as u32),
- Some((libos_handle >> 32) as u32),
- )?;
- dev_dbg!(
- pdev,
- "GSP MBOX0: {:#x}, MBOX1: {:#x}\n",
- mbox0,
- mbox1
- );
+ dev_dbg!(
+ pdev,
+ "Using SEC2 to load and run the booter_load firmware...\n"
+ );
+ }

- dev_dbg!(
- pdev,
- "Using SEC2 to load and run the booter_load firmware...\n"
- );
+ match chipset.arch() {
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada => {
+ Self::run_booter(dev, bar, chipset, sec2_falcon, &wpr_meta)?
+ }

- Self::run_booter(dev, bar, chipset, sec2_falcon, &wpr_meta)?;
+ Architecture::Hopper | Architecture::Blackwell => Self::run_fsp(
+ dev,
+ bar,
+ chipset,
+ gsp_falcon,
+ &wpr_meta,
+ &self.libos,
+ &fb_layout,
+ )?,
+ }

gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);

@@ -311,16 +386,27 @@ pub(crate) fn boot(
gsp_falcon.is_riscv_active(bar),
);

- // Create and run the GSP sequencer.
- let seq_params = GspSequencerParams {
- bootloader_app_version: gsp_fw.bootloader.app_version,
- libos_dma_handle: libos_handle,
- gsp_falcon,
- sec2_falcon,
- dev: pdev.as_ref().into(),
- bar,
- };
- GspSequencer::run(&mut self.cmdq, seq_params)?;
+ // Now that GSP is active, send system info and registry
+ self.cmdq
+ .send_command(bar, commands::SetSystemInfo::new(pdev))?;
+ self.cmdq.send_command(bar, commands::SetRegistry::new())?;
+
+ if matches!(
+ chipset.arch(),
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada
+ ) {
+ let libos_handle = self.libos.dma_handle();
+ // Create and run the GSP sequencer.
+ let seq_params = GspSequencerParams {
+ bootloader_app_version: gsp_fw.bootloader.app_version,
+ libos_dma_handle: libos_handle,
+ gsp_falcon,
+ sec2_falcon,
+ dev: pdev.as_ref().into(),
+ bar,
+ };
+ GspSequencer::run(&mut self.cmdq, seq_params)?;
+ }

// Wait until GSP is fully initialized.
commands::wait_gsp_init_done(&mut self.cmdq)?;
--
2.53.0