// SPDX-License-Identifier: GPL-2.0 // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. //! Support for loading and patching the `Booter` firmware. `Booter` is a Heavy Secured firmware //! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon //! (and optionally unload it through a separate firmware image). use core::marker::PhantomData; use kernel::{ device, dma::Coherent, prelude::*, // }; use crate::{ falcon::{ sec2::Sec2, Falcon, FalconBromParams, FalconDmaLoadTarget, FalconDmaLoadable, FalconFirmware, // }, firmware::{ tlv::{ request_tlv, // Tlv, }, FirmwareObject, FirmwareSignature, Signed, Unsigned, // }, gpu::Chipset, num::IntoSafeCast, }; /// Signature for Booter firmware. Their size is encoded into the header and not known a compile /// time, so we just wrap a byte slices on which we can implement [`FirmwareSignature`]. struct BooterSignature<'a>(&'a [u8]); impl<'a> AsRef<[u8]> for BooterSignature<'a> { fn as_ref(&self) -> &[u8] { self.0 } } impl<'a> FirmwareSignature for BooterSignature<'a> {} /// The `Booter` loader firmware, responsible for loading the GSP. pub(crate) struct BooterFirmware { // Load parameters for Secure `IMEM` falcon memory. imem_sec_load_target: FalconDmaLoadTarget, // Load parameters for Non-Secure `IMEM` falcon memory, // used only on Turing and GA100 imem_ns_load_target: Option, // Load parameters for `DMEM` falcon memory. dmem_load_target: FalconDmaLoadTarget, // BROM falcon parameters. brom_params: FalconBromParams, // Device-mapped firmware image. ucode: FirmwareObject, } impl FirmwareObject { fn new_booter(data: &[u8]) -> Result { let mut ucode = KVVec::new(); ucode.extend_from_slice(data, GFP_KERNEL)?; Ok(Self(ucode, PhantomData)) } } #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum BooterKind { Loader, Unloader, } impl BooterFirmware { /// Parses the Booter firmware contained in `fw`, and patches the correct signature so it is /// ready to be loaded and run on `falcon`. pub(crate) fn new( dev: &device::Device, kind: BooterKind, chipset: Chipset, falcon: &Falcon<'_, ::Target>, ) -> Result { let fw_name = match kind { BooterKind::Loader => "booter_load", BooterKind::Unloader => "booter_unload", }; let fw = request_tlv(dev, chipset, fw_name)?; let tlv = Tlv::new(fw.data())?; dev_dbg!( dev, "loaded {} firmware v{}\n", fw_name, tlv.get_string(b"VERS")? ); let os_data_offset = tlv.get_u32(b"DAOF")?; let os_data_size = tlv.get_u32(b"DASZ")?; let os_code_offset = tlv.get_u32(b"CDOF")?; let os_code_size = tlv.get_u32(b"CDSZ")?; let patch_loc = tlv.get_u32(b"PLOC")?; let fuse_version: usize = tlv.get_u32(b"FUSE")?.into_safe_cast(); let engine_id = tlv.get_u32(b"ENID")?; let ucode_id = tlv.get_u32(b"UCID")?; let app0_code_offset = tlv.get_u32(b"A0CO")?; let app0_code_size = tlv.get_u32(b"A0CS")?; let brom_params = FalconBromParams { // `os_data_offset` is an absolute index, but `pkc_data_offset` is from the // signature patch location. pkc_data_offset: patch_loc.checked_sub(os_data_offset).ok_or(EINVAL)?, engine_id_mask: u16::try_from(engine_id).map_err(|_| EINVAL)?, ucode_id: u8::try_from(ucode_id).map_err(|_| EINVAL)?, }; let ucode = tlv .get_bytes(b"BLOB") .and_then(FirmwareObject::::new_booter)?; // Obtain the version from the fuse register, and extract the corresponding // signature. let reg_fuse_version: usize = falcon .signature_reg_fuse_version(brom_params.engine_id_mask, brom_params.ucode_id)? .into_safe_cast(); const FUSE_VERSION_USE_LAST_SIG: usize = 0; let index = match reg_fuse_version { // `0` means the last signature should be used. FUSE_VERSION_USE_LAST_SIG => None, // Otherwise, hardware fuse version needs to be subtracted to obtain the index. _ => Some(fuse_version.checked_sub(reg_fuse_version).ok_or(EINVAL)?), }; // Extract the nth signature. Booter is always signed. let sig_chunk = tlv.get_signature(index)?; let signature = BooterSignature(sig_chunk); let ucode_signed = ucode.patch_signature(&signature, patch_loc.into_safe_cast())?; // There are two versions of Booter, one for Turing/GA100, and another for // GA102+. The extraction of the IMEM sections differs between the two // versions. Unfortunately, the file names are the same, and the headers // don't indicate the versions. The only way to differentiate is by the Chipset. let (imem_sec_dst_start, imem_ns_load_target) = if chipset <= Chipset::GA100 { ( app0_code_offset, Some(FalconDmaLoadTarget { src_start: 0, dst_start: os_code_offset, len: os_code_size, }), ) } else { (0, None) }; Ok(Self { imem_sec_load_target: FalconDmaLoadTarget { src_start: app0_code_offset, dst_start: imem_sec_dst_start, len: app0_code_size, }, imem_ns_load_target, dmem_load_target: FalconDmaLoadTarget { src_start: os_data_offset, dst_start: 0, len: os_data_size, }, brom_params, ucode: ucode_signed, }) } /// Load and run the booter firmware on SEC2. /// /// Resets SEC2, loads this firmware image, then boots with the WPR metadata /// address passed via the SEC2 mailboxes. pub(crate) fn run( &self, dev: &device::Device, sec2_falcon: &Falcon<'_, Sec2>, wpr_meta: &Coherent, ) -> Result { sec2_falcon.reset()?; sec2_falcon.load(self)?; let wpr_dma_address = wpr_meta.dma_address(); let (mbox0, mbox1) = sec2_falcon.boot( Some(wpr_dma_address as u32), Some((wpr_dma_address >> 32) as u32), )?; dev_dbg!(dev, "SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1); if mbox0 != 0 { dev_err!(dev, "Booter-load failed with error {:#x}\n", mbox0); return Err(ENODEV); } Ok(()) } } impl FalconDmaLoadable for BooterFirmware { fn as_slice(&self) -> &[u8] { self.ucode.0.as_slice() } fn imem_sec_load_params(&self) -> FalconDmaLoadTarget { self.imem_sec_load_target.clone() } fn imem_ns_load_params(&self) -> Option { self.imem_ns_load_target.clone() } fn dmem_load_params(&self) -> FalconDmaLoadTarget { self.dmem_load_target.clone() } } impl FalconFirmware for BooterFirmware { type Target = Sec2; fn brom_params(&self) -> FalconBromParams { self.brom_params.clone() } fn boot_addr(&self) -> u32 { if let Some(ns_target) = &self.imem_ns_load_target { ns_target.dst_start } else { self.imem_sec_load_target.src_start } } }