| // 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<BooterFirmware> 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<FalconDmaLoadTarget>, | |
| // Load parameters for `DMEM` falcon memory. | |
| dmem_load_target: FalconDmaLoadTarget, | |
| // BROM falcon parameters. | |
| brom_params: FalconBromParams, | |
| // Device-mapped firmware image. | |
| ucode: FirmwareObject<Self, Signed>, | |
| } | |
| impl FirmwareObject<BooterFirmware, Unsigned> { | |
| fn new_booter(data: &[u8]) -> Result<Self> { | |
| 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<device::Bound>, | |
| kind: BooterKind, | |
| chipset: Chipset, | |
| falcon: &Falcon<'_, <Self as FalconFirmware>::Target>, | |
| ) -> Result<Self> { | |
| 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::<Self, _>::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<T>( | |
| &self, | |
| dev: &device::Device<device::Bound>, | |
| sec2_falcon: &Falcon<'_, Sec2>, | |
| wpr_meta: &Coherent<T>, | |
| ) -> 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<FalconDmaLoadTarget> { | |
| 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 | |
| } | |
| } | |
| } |