[PATCH v11 19/22] gpu: nova-core: Hopper/Blackwell: add FSP Chain of Trust boot

From: John Hubbard

Date: Fri May 29 2026 - 23:20:53 EST


Build and send the Chain of Trust message to FSP, bundling the
DMA-coherent boot parameters that FSP reads at boot time.

Co-developed-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
Signed-off-by: Alexandre Courbot <acourbot@xxxxxxxxxx>
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/firmware/fsp.rs | 2 -
drivers/gpu/nova-core/fsp.rs | 142 +++++++++++++++++-
drivers/gpu/nova-core/gpu.rs | 1 -
drivers/gpu/nova-core/gsp.rs | 1 +
drivers/gpu/nova-core/gsp/fw.rs | 64 ++++++++
.../gpu/nova-core/gsp/fw/r570_144/bindings.rs | 82 ++++++++++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 26 +++-
drivers/gpu/nova-core/mctp.rs | 2 -
8 files changed, 307 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs
index dc28d0cc2d03..13e6a5b5aeae 100644
--- a/drivers/gpu/nova-core/firmware/fsp.rs
+++ b/drivers/gpu/nova-core/firmware/fsp.rs
@@ -39,10 +39,8 @@ pub(crate) struct FmcSignatures {

pub(crate) struct FspFirmware {
/// FMC firmware image data (only the "image" ELF section).
- #[expect(dead_code)]
pub(crate) fmc_image: Coherent<[u8]>,
/// FMC firmware signatures.
- #[expect(dead_code)]
pub(crate) fmc_sigs: KBox<FmcSignatures>,
}

diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 5aae8282f2f0..5878d86323b9 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -9,8 +9,14 @@

use kernel::{
device,
+ dma::Coherent,
io::poll::read_poll_timeout,
prelude::*,
+ ptr::{
+ Alignable,
+ Alignment, //
+ },
+ sizes::SZ_2M,
time::Delta,
transmute::{
AsBytes,
@@ -24,7 +30,13 @@
self,
Falcon, //
},
+ fb::FbLayout,
+ firmware::fsp::{
+ FmcSignatures,
+ FspFirmware, //
+ },
gpu::Chipset,
+ gsp::GspFmcBootParams,
mctp::{
MctpHeader,
NvdmHeader,
@@ -49,7 +61,6 @@ pub(crate) const fn new(version: u16) -> Self {
}

/// Returns the raw protocol version number for the wire format.
- #[expect(dead_code)]
pub(crate) const fn raw(self) -> u16 {
self.0
}
@@ -67,6 +78,35 @@ struct NvdmPayloadCommandResponse {
error_code: u32,
}

+/// NVDM (NVIDIA Device Management) CoT (Chain of Trust) payload, the main
+/// message body sent to FSP for Chain of Trust boot.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct NvdmPayloadCot {
+ version: u16,
+ size: u16,
+ gsp_fmc_sysmem_offset: u64,
+ frts_sysmem_offset: u64,
+ frts_sysmem_size: u32,
+ frts_vidmem_offset: u64,
+ frts_vidmem_size: u32,
+ sigs: FmcSignatures,
+ gsp_boot_args_sysmem_offset: u64,
+}
+
+/// Complete FSP message structure with MCTP and NVDM headers.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct FspMessage {
+ mctp_header: MctpHeader,
+ nvdm_header: NvdmHeader,
+ cot: NvdmPayloadCot,
+}
+
+// SAFETY: `FspMessage` is `#[repr(C, packed)]` with no padding, so all of its
+// bytes are initialized.
+unsafe impl AsBytes for FspMessage {}
+
/// Complete FSP response structure with MCTP and NVDM headers.
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -88,6 +128,47 @@ pub(crate) trait MessageToFsp: AsBytes {
const NVDM_TYPE: u32;
}

+impl MessageToFsp for FspMessage {
+ const NVDM_TYPE: u32 = NvdmType::Cot as u32;
+}
+
+/// Bundled arguments for FMC boot via FSP Chain of Trust.
+pub(crate) struct FmcBootArgs<'a> {
+ chipset: Chipset,
+ fsp_fw: &'a FspFirmware,
+ fmc_boot_params: Coherent<GspFmcBootParams>,
+ resume: bool,
+}
+
+impl<'a> FmcBootArgs<'a> {
+ /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter
+ /// structure that FSP will read.
+ pub(crate) fn new(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ fsp_fw: &'a FspFirmware,
+ wpr_meta_addr: u64,
+ libos_addr: u64,
+ resume: bool,
+ ) -> Result<Self> {
+ let init = GspFmcBootParams::new(wpr_meta_addr, libos_addr);
+
+ Ok(Self {
+ chipset,
+ fsp_fw,
+ fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?,
+ resume,
+ })
+ }
+
+ /// 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()
+ }
+}
+
/// FSP interface for Hopper/Blackwell GPUs.
pub(crate) struct Fsp;

@@ -115,8 +196,65 @@ pub(crate) fn wait_secure_boot(dev: &device::Device, bar: &Bar0, chipset: Chipse
.map(|_| ())
}

+ /// Boots GSP FMC via FSP Chain of Trust.
+ ///
+ /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
+ /// to FSP, and waits for the response.
+ pub(crate) fn boot_fmc(
+ dev: &device::Device<device::Bound>,
+ bar: &Bar0,
+ fb_layout: &FbLayout,
+ fsp_falcon: &mut Falcon<falcon::fsp::Fsp>,
+ args: &FmcBootArgs<'_>,
+ ) -> Result {
+ dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
+
+ let fmc_addr = args.fsp_fw.fmc_image.dma_handle();
+ let fmc_boot_params_addr = args.fmc_boot_params.dma_handle();
+
+ // frts_offset is relative to FB end: FRTS_location = FB_END - frts_offset
+ let frts_offset = if !args.resume {
+ let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
+
+ frts_reserved_size
+ .align_up(Alignment::new::<SZ_2M>())
+ .ok_or(EINVAL)?
+ } else {
+ 0
+ };
+ let frts_size: u32 = if !args.resume {
+ fb_layout.frts.len().try_into()?
+ } else {
+ 0
+ };
+
+ let msg = KBox::new(
+ FspMessage {
+ mctp_header: MctpHeader::single_packet(),
+ nvdm_header: NvdmHeader::new(NvdmType::Cot),
+ cot: NvdmPayloadCot {
+ version: args.chipset.fsp_cot_version().ok_or(ENOTSUPP)?.raw(),
+ size: u16::try_from(core::mem::size_of::<NvdmPayloadCot>())
+ .map_err(|_| EINVAL)?,
+ gsp_fmc_sysmem_offset: fmc_addr,
+ frts_sysmem_offset: 0,
+ frts_sysmem_size: 0,
+ frts_vidmem_offset: frts_offset,
+ frts_vidmem_size: frts_size,
+ sigs: *args.fsp_fw.fmc_sigs,
+ gsp_boot_args_sysmem_offset: fmc_boot_params_addr,
+ },
+ },
+ GFP_KERNEL,
+ )?;
+
+ Self::send_sync_fsp(dev, bar, fsp_falcon, &*msg)?;
+
+ dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
+ Ok(())
+ }
+
/// Sends a message to FSP and waits for the response.
- #[expect(dead_code)]
fn send_sync_fsp<M>(
dev: &device::Device,
bar: &Bar0,
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 6cdface3c618..43749a62f593 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -147,7 +147,6 @@ pub(crate) fn pci_config_mirror_range(self) -> Range<u32> {
///
/// Hopper (GH100) uses version 1, Blackwell uses version 2.
/// Returns `None` for architectures that do not use FSP.
- #[expect(dead_code)]
pub(crate) const fn fsp_cot_version(self) -> Option<FspCotVersion> {
match self.arch() {
Architecture::Hopper => Some(FspCotVersion::new(1)),
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 1885cfa5cb38..69175ca3315c 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -25,6 +25,7 @@
mod sequencer;

pub(crate) use fw::{
+ GspFmcBootParams,
GspFwWprMeta,
LibosParams, //
};
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 0c54e8bf4bb3..558b37863f00 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -934,3 +934,67 @@ fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
})
}
}
+
+#[repr(u32)]
+pub(crate) enum GspDmaTarget {
+ #[expect(dead_code)]
+ LocalFb = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB,
+ CoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM,
+ NoncoherentSystem = bindings::GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM,
+}
+
+type GspAcrBootGspRmParams = bindings::GSP_ACR_BOOT_GSP_RM_PARAMS;
+
+impl GspAcrBootGspRmParams {
+ fn new(target: GspDmaTarget, wpr_meta_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let params = init!(Self {
+ target: target as u32,
+ gspRmDescSize: num::usize_into_u32::<{ size_of::<GspFwWprMeta>() }>(),
+ gspRmDescOffset: wpr_meta_addr,
+ bIsGspRmBoot: 1,
+ wprCarveoutOffset: 0,
+ wprCarveoutSize: 0,
+ __bindgen_padding_0: Default::default(),
+ });
+
+ params
+ }
+}
+
+type GspRmParams = bindings::GSP_RM_PARAMS;
+
+impl GspRmParams {
+ fn new(target: GspDmaTarget, libos_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let params = init!(Self {
+ target: target as u32,
+ bootArgsOffset: libos_addr,
+ __bindgen_padding_0: Default::default(),
+ });
+
+ params
+ }
+}
+
+pub(crate) type GspFmcBootParams = bindings::GSP_FMC_BOOT_PARAMS;
+
+// SAFETY: Padding is explicit and will not contain uninitialized data.
+unsafe impl AsBytes for GspFmcBootParams {}
+// SAFETY: This struct only contains integer types for which all bit patterns are valid.
+unsafe impl FromBytes for GspFmcBootParams {}
+
+impl GspFmcBootParams {
+ pub(crate) fn new(wpr_meta_addr: u64, libos_addr: u64) -> impl Init<Self> {
+ #[allow(non_snake_case)]
+ let init = init!(Self {
+ // Blackwell FSP obtains WPR info from other sources, so
+ // wprCarveoutOffset and wprCarveoutSize are left zero.
+ bootGspRmParams <- GspAcrBootGspRmParams::new(GspDmaTarget::CoherentSystem, wpr_meta_addr),
+ gspRmParams <- GspRmParams::new(GspDmaTarget::NoncoherentSystem, libos_addr),
+ ..Zeroable::init_zeroed()
+ });
+
+ init
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
index 1d592bd3f9ed..ea350f9b2cc4 100644
--- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
+++ b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
@@ -883,6 +883,88 @@ fn default() -> Self {
}
}
}
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB: GSP_DMA_TARGET = 0;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM: GSP_DMA_TARGET = 1;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM: GSP_DMA_TARGET = 2;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COUNT: GSP_DMA_TARGET = 3;
+pub type GSP_DMA_TARGET = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_INIT_PARAMS {
+ pub regkeys: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ACR_BOOT_GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub gspRmDescSize: u32_,
+ pub gspRmDescOffset: u64_,
+ pub wprCarveoutOffset: u64_,
+ pub wprCarveoutSize: u32_,
+ pub bIsGspRmBoot: u8_,
+ pub __bindgen_padding_0: [u8; 3usize],
+}
+impl Default for GSP_ACR_BOOT_GSP_RM_PARAMS {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub bootArgsOffset: u64_,
+}
+impl Default for GSP_RM_PARAMS {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_SPDM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub payloadBufferOffset: u64_,
+ pub payloadBufferSize: u32_,
+ pub __bindgen_padding_1: [u8; 4usize],
+}
+impl Default for GSP_SPDM_PARAMS {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_BOOT_PARAMS {
+ pub initParams: GSP_FMC_INIT_PARAMS,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub bootGspRmParams: GSP_ACR_BOOT_GSP_RM_PARAMS,
+ pub gspRmParams: GSP_RM_PARAMS,
+ pub gspSpdmParams: GSP_SPDM_PARAMS,
+}
+impl Default for GSP_FMC_BOOT_PARAMS {
+ fn default() -> Self {
+ let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
pub struct rpc_unloading_guest_driver_v1F_07 {
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index 151df05e303b..946e88481d6f 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -21,7 +21,10 @@
fsp::FspFirmware,
FIRMWARE_VERSION, //
},
- fsp::Fsp,
+ fsp::{
+ FmcBootArgs,
+ Fsp, //
+ },
gpu::Chipset,
gsp::{
boot::BootUnloadGuard,
@@ -40,20 +43,31 @@ impl GspHal for Gh100 {
/// the GSP boot internally - no manual GSP reset/boot is needed.
fn boot<'a>(
&self,
- _gsp: &'a Gsp,
+ gsp: &'a Gsp,
dev: &'a device::Device<device::Bound>,
bar: &'a Bar0,
chipset: Chipset,
- _fb_layout: &FbLayout,
- _wpr_meta: &Coherent<GspFwWprMeta>,
+ fb_layout: &FbLayout,
+ wpr_meta: &Coherent<GspFwWprMeta>,
_gsp_falcon: &'a Falcon<GspEngine>,
_sec2_falcon: &'a Falcon<Sec2>,
) -> Result<BootUnloadGuard<'a>> {
- let _fsp_falcon = Falcon::<FspEngine>::new(dev, chipset)?;
- let _fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
+ let mut fsp_falcon = Falcon::<FspEngine>::new(dev, chipset)?;
+ let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;

Fsp::wait_secure_boot(dev, bar, chipset)?;

+ let args = FmcBootArgs::new(
+ dev,
+ chipset,
+ &fsp_fw,
+ wpr_meta.dma_handle(),
+ gsp.libos.dma_handle(),
+ false,
+ )?;
+
+ Fsp::boot_fmc(dev, bar, fb_layout, &mut fsp_falcon, &args)?;
+
Err(ENOTSUPP)
}
}
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index a13146dc0cca..be3e757d05a0 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -7,8 +7,6 @@
//! Device Management) messages between the kernel driver and GPU firmware
//! processors such as FSP and GSP.

-#![expect(dead_code)]
-
use kernel::pci::Vendor;

/// NVDM message type identifiers carried over MCTP.
--
2.54.0