[PATCH v4 32/33] gpu: nova-core: clarify the GPU firmware boot steps

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

Now that Hopper/Blackwell GSP is up and running, it's clear how to
factor out the common code and the per-architecture code, for booting
up firmware. The key is that, for Turing, Ampere, and Ada, the SEC2
firmware is used and a CPU "sequencer" must be run. For Hopper,
Blackwell and later GPUs, there is no SEC2, no sequencer, but there is
an FSP to get running instead.

This change makes that clearly visible on-screen.

Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 118 +++++++++++++++++-------------
1 file changed, 66 insertions(+), 52 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index aae5ffac262a..02eec2961b5f 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -162,7 +162,48 @@ fn run_booter(
Ok(())
}

- fn run_fsp(
+ /// Boot GSP via SEC2 booter firmware (Turing/Ampere/Ada path).
+ ///
+ /// This path uses FWSEC-FRTS to set up WPR2, then boots GSP directly,
+ /// then uses SEC2 to run the booter firmware.
+ #[allow(clippy::too_many_arguments)]
+ fn boot_via_sec2(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ chipset: Chipset,
+ gsp_falcon: &Falcon<Gsp>,
+ sec2_falcon: &Falcon<Sec2>,
+ fb_layout: &FbLayout,
+ libos: &CoherentAllocation<LibosMemoryRegionInitArgument>,
+ wpr_meta: &CoherentAllocation<GspFwWprMeta>,
+ ) -> Result {
+ // Run FWSEC-FRTS to set up the WPR2 region
+ let bios = Vbios::new(dev, bar)?;
+ Self::run_fwsec_frts(dev, gsp_falcon, bar, &bios, fb_layout)?;
+
+ // Reset and boot GSP before SEC2
+ gsp_falcon.reset(bar)?;
+ let libos_handle = libos.dma_handle();
+ let (mbox0, mbox1) = gsp_falcon.boot(
+ bar,
+ Some(libos_handle as u32),
+ Some((libos_handle >> 32) as u32),
+ )?;
+ dev_dbg!(dev, "GSP MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
+ dev_dbg!(
+ dev,
+ "Using SEC2 to load and run the booter_load firmware...\n"
+ );
+
+ // Run booter via SEC2
+ Self::run_booter(dev, bar, chipset, sec2_falcon, wpr_meta)
+ }
+
+ /// Boot GSP via FSP Chain of Trust (Hopper/Blackwell+ path).
+ ///
+ /// This path uses FSP to establish a chain of trust and boot GSP-FMC. FSP handles
+ /// the GSP boot internally - no manual GSP reset/boot is needed.
+ fn boot_via_fsp(
dev: &device::Device<device::Bound>,
bar: &Bar0,
chipset: Chipset,
@@ -311,55 +352,34 @@ pub(crate) fn boot(
sec2_falcon: &Falcon<Sec2>,
) -> Result {
let dev = pdev.as_ref();
+ let uses_sec2 = matches!(
+ chipset.arch(),
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada
+ );

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);

- 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))?;

- // 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(
+ // Architecture-specific boot path
+ if uses_sec2 {
+ Self::boot_via_sec2(
+ dev,
bar,
- Some(libos_handle as u32),
- Some((libos_handle >> 32) as u32),
+ chipset,
+ gsp_falcon,
+ sec2_falcon,
+ &fb_layout,
+ &self.libos,
+ &wpr_meta,
)?;
- 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"
- );
- }
-
- match chipset.arch() {
- Architecture::Turing | Architecture::Ampere | Architecture::Ada => {
- Self::run_booter(dev, bar, chipset, sec2_falcon, &wpr_meta)?
- }
-
- Architecture::Hopper | Architecture::Blackwell => Self::run_fsp(
+ } else {
+ Self::boot_via_fsp(
dev,
bar,
chipset,
@@ -367,9 +387,10 @@ pub(crate) fn boot(
&wpr_meta,
&self.libos,
&fb_layout,
- )?,
+ )?;
}

+ // Common post-boot initialization
gsp_falcon.write_os_version(bar, gsp_fw.bootloader.app_version);

// Poll for RISC-V to become active before running sequencer
@@ -380,29 +401,22 @@ pub(crate) fn boot(
Delta::from_secs(5),
)?;

- dev_dbg!(
- pdev,
- "RISC-V active? {}\n",
- gsp_falcon.is_riscv_active(bar),
- );
+ dev_dbg!(dev, "RISC-V active? {}\n", gsp_falcon.is_riscv_active(bar));

// Now that GSP is active, send system info and registry
self.cmdq
.send_command(bar, commands::SetSystemInfo::new(pdev, chipset))?;
self.cmdq.send_command(bar, commands::SetRegistry::new())?;

- if matches!(
- chipset.arch(),
- Architecture::Turing | Architecture::Ampere | Architecture::Ada
- ) {
+ // SEC2-based architectures need to run the GSP sequencer
+ if uses_sec2 {
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(),
+ dev: dev.into(),
bar,
};
GspSequencer::run(&mut self.cmdq, seq_params)?;
@@ -414,8 +428,8 @@ pub(crate) fn boot(
// Obtain and display basic GPU information.
let info = commands::get_gsp_info(&mut self.cmdq, bar)?;
match info.gpu_name() {
- Ok(name) => dev_info!(pdev, "GPU name: {}\n", name),
- Err(e) => dev_warn!(pdev, "GPU name unavailable: {:?}\n", e),
+ Ok(name) => dev_info!(dev, "GPU name: {}\n", name),
+ Err(e) => dev_warn!(dev, "GPU name unavailable: {:?}\n", e),
}

Ok(())
--
2.53.0