[PATCH 15/16] gpu: nova-core: extract FWSEC from BIOS and patch it to run FWSEC-FRTS

Alexandre Courbot
2025-04-20T12:20:49Z

The FWSEC firmware needs to be extracted from the VBIOS and patched with
the desired command, as well as the right signature. Do this so we are
ready to load and run this firmware into the GSP falcon and create the
FRTS region.

Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/firmware.rs | 22 ++-
drivers/gpu/nova-core/firmware/fwsec.rs | 340 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/gpu.rs | 18 +-
3 files changed, 378 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index 58c0513d49e9a0cef36917c8e2b25c414f6fc596..010763afdd74e92a4380d739a17319e05781007f 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -8,9 +8,14 @@
use kernel::prelude::*;
use kernel::str::CString;

+use crate::dma::DmaObject;
use crate::gpu;
use crate::gpu::Chipset;

+pub(crate) mod fwsec;
+
+pub(crate) const FIRMWARE_VERSION: &'static str = "535.113.01";
+
/// Structure encapsulating the firmware blobs required for the GPU to operate.
#[expect(dead_code)]
pub(crate) struct Firmware {
@@ -69,10 +74,25 @@ pub(crate) fn size(&self) -> usize {
}
}

+/// Patch the `ucode_dma` firmware at offset `sig_base_img` with `signature`.
+fn patch_signature(ucode_dma: &mut DmaObject, signature: &[u8], sig_base_img: usize) -> Result<()> {
+ if sig_base_img + signature.len() > ucode_dma.len {
+ return Err(ERANGE);
+ }
+
+ // SAFETY: we are the only user of this object, so there cannot be any race.
+ let dst = unsafe { ucode_dma.dma.start_ptr_mut().offset(sig_base_img as isize) };
+
+ // SAFETY: `signature` and `dst` are valid, properly aligned, and do not overlap.
+ unsafe { core::ptr::copy_nonoverlapping(signature.as_ptr(), dst, signature.len()) };
+
+ Ok(())
+}
+
pub(crate) struct ModInfoBuilder<const N: usize>(firmware::ModInfoBuilder<N>);

impl<const N: usize> ModInfoBuilder<N> {
- const VERSION: &'static str = "535.113.01";
+ const VERSION: &'static str = FIRMWARE_VERSION;

const fn make_entry_file(self, chipset: &str, fw: &str) -> Self {
ModInfoBuilder(
diff --git a/drivers/gpu/nova-core/firmware/fwsec.rs b/drivers/gpu/nova-core/firmware/fwsec.rs
new file mode 100644
index 0000000000000000000000000000000000000000..664319d1d31c9727bb830100641c53b5d914be5a
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/fwsec.rs
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! FWSEC is a High Secure firmware that is extracted from the BIOS and performs the first step of
+//! the GSP startup by creating the WPR2 memory region and copying critical areas of the VBIOS into
+//! it after authenticating them, ensuring they haven't been tampered with. It runs on the GSP
+//! falcon.
+//!
+//! Before being run, it needs to be patched in two areas:
+//!
+//! - The command to be run, as this firmware can perform several tasks ;
+//! - The ucode signature, so the GSP falcon can run FWSEC in HS mode.
+
+use core::alloc::Layout;
+
+use kernel::bindings;
+use kernel::device::{self, Device};
+use kernel::devres::Devres;
+use kernel::prelude::*;
+use kernel::transmute::FromBytes;
+
+use crate::dma::DmaObject;
+use crate::driver::Bar0;
+use crate::falcon::gsp::Gsp;
+use crate::falcon::{Falcon, FalconBromParams, FalconFirmware, FalconLoadTarget};
+use crate::firmware::FalconUCodeDescV3;
+use crate::vbios::Vbios;
+
+const NVFW_FALCON_APPIF_ID_DMEMMAPPER: u32 = 0x4;
+
+#[repr(C)]
+#[derive(Debug)]
+struct FalconAppifHdrV1 {
+ ver: u8,
+ hdr: u8,
+ len: u8,
+ cnt: u8,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for FalconAppifHdrV1 {}
+
+#[repr(C, packed)]
+#[derive(Debug)]
+struct FalconAppifV1 {
+ id: u32,
+ dmem_base: u32,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for FalconAppifV1 {}
+
+#[derive(Debug)]
+#[repr(C, packed)]
+struct FalconAppifDmemmapperV3 {
+ signature: u32,
+ version: u16,
+ size: u16,
+ cmd_in_buffer_offset: u32,
+ cmd_in_buffer_size: u32,
+ cmd_out_buffer_offset: u32,
+ cmd_out_buffer_size: u32,
+ nvf_img_data_buffer_offset: u32,
+ nvf_img_data_buffer_size: u32,
+ printf_buffer_hdr: u32,
+ ucode_build_time_stamp: u32,
+ ucode_signature: u32,
+ init_cmd: u32,
+ ucode_feature: u32,
+ ucode_cmd_mask0: u32,
+ ucode_cmd_mask1: u32,
+ multi_tgt_tbl: u32,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for FalconAppifDmemmapperV3 {}
+
+#[derive(Debug)]
+#[repr(C, packed)]
+struct ReadVbios {
+ ver: u32,
+ hdr: u32,
+ addr: u64,
+ size: u32,
+ flags: u32,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for ReadVbios {}
+
+#[derive(Debug)]
+#[repr(C, packed)]
+struct FrtsRegion {
+ ver: u32,
+ hdr: u32,
+ addr: u32,
+ size: u32,
+ ftype: u32,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for FrtsRegion {}
+
+const NVFW_FRTS_CMD_REGION_TYPE_FB: u32 = 2;
+
+#[repr(C, packed)]
+struct FrtsCmd {
+ read_vbios: ReadVbios,
+ frts_region: FrtsRegion,
+}
+// SAFETY: any byte sequence is valid for this struct.
+unsafe impl FromBytes for FrtsCmd {}
+
+const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS: u32 = 0x15;
+const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB: u32 = 0x19;
+
+/// Command for the [`FwsecFirmware`] to execute.
+pub(crate) enum FwsecCommand {
+ /// Asks [`FwsecFirmware`] to carve out the WPR2 area and place a verified copy of the VBIOS
+ /// image into it.
+ Frts { frts_addr: u64, frts_size: u64 },
+ /// Asks [`FwsecFirmware`] to load pre-OS apps on the PMU.
+ #[allow(dead_code)]
+ Sb,
+}
+
+/// Reinterpret the area starting from `offset` in `fw` as an instance of `T` (which must implement
+/// [`FromBytes`]) and return a reference to it.
+///
+/// # Safety
+///
+/// Callers must ensure that the region of memory returned is not written for as long as the
+/// returned reference is alive.
+unsafe fn transmute<'a, 'b, T: Sized + FromBytes>(
+ fw: &'a DmaObject,
+ offset: usize,
+) -> Result<&'b T> {
+ if offset + core::mem::size_of::<T>() > fw.len {
+ return Err(ERANGE);
+ }
+ if (fw.dma.start_ptr() as usize + offset) % core::mem::align_of::<T>() != 0 {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
+ // large enough the contains an instance of `T`, which implements `FromBytes`.
+ Ok(unsafe { &*(fw.dma.start_ptr().offset(offset as isize) as *const T) })
+}
+
+/// Reinterpret the area starting from `offset` in `fw` as a mutable instance of `T` (which must
+/// implement [`FromBytes`]) and return a reference to it.
+///
+/// # Safety
+///
+/// Callers must ensure that the region of memory returned is not read or written for as long as
+/// the returned reference is alive.
+unsafe fn transmute_mut<'a, 'b, T: Sized + FromBytes>(
+ fw: &'a mut DmaObject,
+ offset: usize,
+) -> Result<&'b mut T> {
+ if offset + core::mem::size_of::<T>() > fw.len {
+ return Err(ERANGE);
+ }
+ if (fw.dma.start_ptr_mut() as usize + offset) % core::mem::align_of::<T>() != 0 {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
+ // large enough the contains an instance of `T`, which implements `FromBytes`.
+ Ok(unsafe { &mut *(fw.dma.start_ptr_mut().offset(offset as isize) as *mut T) })
+}
+
+/// Patch the Fwsec firmware image in `fw` to run the command `cmd`.
+fn patch_command(fw: &mut DmaObject, v3_desc: &FalconUCodeDescV3, cmd: FwsecCommand) -> Result<()> {
+ let hdr_offset = (v3_desc.imem_load_size + v3_desc.interface_offset) as usize;
+ let hdr: &FalconAppifHdrV1 = unsafe { transmute(fw, hdr_offset) }?;
+
+ if hdr.ver != 1 {
+ return Err(EINVAL);
+ }
+
+ // Find the DMEM mapper section in the firmware.
+ for i in 0..hdr.cnt as usize {
+ let app: &FalconAppifV1 =
+ unsafe { transmute(fw, hdr_offset + hdr.hdr as usize + i * hdr.len as usize) }?;
+
+ if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
+ continue;
+ }
+
+ let dmem_mapper: &mut FalconAppifDmemmapperV3 =
+ unsafe { transmute_mut(fw, (v3_desc.imem_load_size + app.dmem_base) as usize) }?;
+
+ let frts_cmd: &mut FrtsCmd = unsafe {
+ transmute_mut(
+ fw,
+ (v3_desc.imem_load_size + dmem_mapper.cmd_in_buffer_offset) as usize,
+ )
+ }?;
+
+ frts_cmd.read_vbios = ReadVbios {
+ ver: 1,
+ hdr: core::mem::size_of::<ReadVbios>() as u32,
+ addr: 0,
+ size: 0,
+ flags: 2,
+ };
+
+ dmem_mapper.init_cmd = match cmd {
+ FwsecCommand::Frts {
+ frts_addr,
+ frts_size,
+ } => {
+ frts_cmd.frts_region = FrtsRegion {
+ ver: 1,
+ hdr: core::mem::size_of::<FrtsRegion>() as u32,
+ addr: (frts_addr >> 12) as u32,
+ size: (frts_size >> 12) as u32,
+ ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
+ };
+
+ NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS
+ }
+ FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
+ };
+
+ // Return early as we found and patched the DMEMMAPPER region.
+ return Ok(());
+ }
+
+ Err(ENOTSUPP)
+}
+
+/// Firmware extracted from the VBIOS and responsible for e.g. carving out the WPR2 region as the
+/// first step of the GSP bootflow.
+pub(crate) struct FwsecFirmware {
+ desc: FalconUCodeDescV3,
+ ucode: DmaObject,
+}
+
+impl FalconFirmware for FwsecFirmware {
+ type Target = Gsp;
+
+ fn dma_handle(&self) -> bindings::dma_addr_t {
+ self.ucode.dma.dma_handle()
+ }
+
+ fn imem_load(&self) -> FalconLoadTarget {
+ FalconLoadTarget {
+ src_start: 0,
+ dst_start: self.desc.imem_phys_base,
+ len: self.desc.imem_load_size,
+ }
+ }
+
+ fn dmem_load(&self) -> FalconLoadTarget {
+ FalconLoadTarget {
+ src_start: self.desc.imem_load_size,
+ dst_start: self.desc.dmem_phys_base,
+ len: Layout::from_size_align(self.desc.dmem_load_size as usize, 256)
+ // Cannot panic, as 256 is non-zero and a power of 2.
+ .unwrap()
+ .pad_to_align()
+ .size() as u32,
+ }
+ }
+
+ fn brom_params(&self) -> FalconBromParams {
+ FalconBromParams {
+ pkc_data_offset: self.desc.pkc_data_offset,
+ engine_id_mask: self.desc.engine_id_mask,
+ ucode_id: self.desc.ucode_id,
+ }
+ }
+
+ fn boot_addr(&self) -> u32 {
+ 0
+ }
+}
+
+impl FwsecFirmware {
+ /// Extract the Fwsec firmware from `bios` and patch it to run with the `cmd` command.
+ pub(crate) fn new(
+ falcon: &Falcon<Gsp>,
+ pdev: &Device<device::Bound>,
+ bar: &Devres<Bar0>,
+ bios: &Vbios,
+ cmd: FwsecCommand,
+ ) -> Result<Self> {
+ let v3_desc = bios.fwsec_header()?;
+ let ucode = bios.fwsec_ucode()?;
+
+ let mut ucode_dma = DmaObject::from_data(pdev, ucode, "fwsec-frts")?;
+ patch_command(&mut ucode_dma, v3_desc, cmd)?;
+
+ const SIG_SIZE: usize = 96 * 4;
+ let signatures = bios.fwsec_sigs()?;
+ let sig_base_img = (v3_desc.imem_load_size + v3_desc.pkc_data_offset) as usize;
+
+ if v3_desc.signature_count != 0 {
+ // Patch signature.
+ let mut sig_fuse_version = v3_desc.signature_versions as u32;
+ pr_debug!("sig_fuse_version: {}\n", sig_fuse_version);
+ let reg_fuse_version = falcon.hal.get_signature_reg_fuse_version(
+ bar,
+ v3_desc.engine_id_mask,
+ v3_desc.ucode_id,
+ )?;
+ let idx = {
+ let mut reg_fuse_version = 1 << reg_fuse_version;
+ pr_debug!("reg_fuse_version: {:#x}\n", reg_fuse_version);
+ if (reg_fuse_version & sig_fuse_version) == 0 {
+ pr_warn!(
+ "no matching signature: {:#x} {:#x}\n",
+ reg_fuse_version,
+ v3_desc.signature_versions
+ );
+ return Err(EINVAL);
+ }
+
+ let mut idx = 0;
+ while (reg_fuse_version & sig_fuse_version & 1) == 0 {
+ idx += sig_fuse_version & 1;
+ reg_fuse_version >>= 1;
+ sig_fuse_version >>= 1;
+
+ if reg_fuse_version == 0 || sig_fuse_version == 0 {
+ return Err(EINVAL);
+ }
+ }
+
+ idx
+ };
+
+ pr_debug!("patching signature with idx {}\n", idx);
+ let signature_start = idx as usize * SIG_SIZE;
+ let signature = &signatures[signature_start..signature_start + SIG_SIZE];
+ super::patch_signature(&mut ucode_dma, signature, sig_base_img)?;
+ }
+
+ Ok(FwsecFirmware {
+ desc: v3_desc.clone(),
+ ucode: ucode_dma,
+ })
+ }
+}
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index b43d1fc6bba15ffd76d564eccdb9e2afe239a3a4..5d15a99f8d1eec3c2e1f6d119eb521361733c709 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -7,6 +7,7 @@
use crate::driver::Bar0;
use crate::falcon::gsp::GspFalcon;
use crate::falcon::sec2::Sec2Falcon;
+use crate::firmware::fwsec::{FwsecCommand, FwsecFirmware};
use crate::firmware::Firmware;
use crate::gsp::fb::FbLayout;
use crate::regs;
@@ -185,7 +186,11 @@ pub(crate) fn new(
bar: Devres<Bar0>,
) -> Result<impl PinInit<Self>> {
let spec = Spec::new(&bar)?;
- let fw = Firmware::new(pdev.as_ref(), spec.chipset, "535.113.01")?;
+ let fw = Firmware::new(
+ pdev.as_ref(),
+ spec.chipset,
+ crate::firmware::FIRMWARE_VERSION,
+ )?;

dev_info!(
pdev.as_ref(),
@@ -245,6 +250,17 @@ pub(crate) fn new(
let fb_layout = FbLayout::new(spec.chipset, &bar)?;
dev_dbg!(pdev.as_ref(), "{:#x?}\n", fb_layout);

+ let _fwsec_frts = FwsecFirmware::new(
+ &gsp_falcon,
+ pdev.as_ref(),
+ &bar,
+ &bios,
+ FwsecCommand::Frts {
+ frts_addr: fb_layout.frts.start,
+ frts_size: fb_layout.frts.end - fb_layout.frts.start,
+ },
+ )?;
+
Ok(pin_init!(Self {
spec,
bar,

--
2.49.0