diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs index 2075b68b364a..bd47ebbb013e 100644 --- a/drivers/gpu/nova-core/firmware.rs +++ b/drivers/gpu/nova-core/firmware.rs @@ -22,10 +22,7 @@ }, gpu, gsp::boot_firmware_files, - num::{ - FromSafeCast, - IntoSafeCast, // - }, + num::IntoSafeCast, // }; pub(crate) mod booter; @@ -389,16 +386,6 @@ fn new(fw: &'a firmware::Firmware) -> Result { .map(|hdr| Self { hdr, fw }) .ok_or(EINVAL) } - - /// Returns the data payload of the firmware, or `None` if the data range is out of bounds of - /// the firmware image. - fn data(&self) -> Option<&[u8]> { - let fw_start = usize::from_safe_cast(self.hdr.data_offset); - let fw_size = usize::from_safe_cast(self.hdr.data_size); - let fw_end = fw_start.checked_add(fw_size)?; - - self.fw.get(fw_start..fw_end) - } } pub(crate) struct ModInfoBuilder(firmware::ModInfoBuilder); diff --git a/drivers/gpu/nova-core/firmware/booter.rs b/drivers/gpu/nova-core/firmware/booter.rs index acb7f4d8a532..6e7d688deadd 100644 --- a/drivers/gpu/nova-core/firmware/booter.rs +++ b/drivers/gpu/nova-core/firmware/booter.rs @@ -10,8 +10,7 @@ use kernel::{ device, dma::Coherent, - prelude::*, - transmute::FromBytes, // + prelude::*, // }; use crate::{ @@ -24,224 +23,19 @@ FalconFirmware, // }, firmware::{ - BinFirmware, + tlv::{ + request_tlv, // + Tlv, + }, FirmwareObject, FirmwareSignature, Signed, Unsigned, // }, gpu::Chipset, - num::{ - FromSafeCast, - IntoSafeCast, // - }, + num::IntoSafeCast, }; -/// Local convenience function to return a copy of `S` by reinterpreting the bytes starting at -/// `offset` in `slice`. -fn frombytes_at(slice: &[u8], offset: usize) -> Result { - let end = offset.checked_add(size_of::()).ok_or(EINVAL)?; - slice - .get(offset..end) - .and_then(S::from_bytes_copy) - .ok_or(EINVAL) -} - -/// Heavy-Secured firmware header. -/// -/// Such firmwares have an application-specific payload that needs to be patched with a given -/// signature. -#[repr(C)] -#[derive(Debug, Clone)] -struct HsHeaderV2 { - /// Offset to the start of the signatures. - sig_prod_offset: u32, - /// Size in bytes of the signatures. - sig_prod_size: u32, - /// Offset to a `u32` containing the location at which to patch the signature in the microcode - /// image. - patch_loc_offset: u32, - /// Offset to a `u32` containing the index of the signature to patch. - patch_sig_offset: u32, - /// Start offset to the signature metadata. - meta_data_offset: u32, - /// Size in bytes of the signature metadata. - meta_data_size: u32, - /// Offset to a `u32` containing the number of signatures in the signatures section. - num_sig_offset: u32, - /// Offset of the application-specific header. - header_offset: u32, - /// Size in bytes of the application-specific header. - header_size: u32, -} - -// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. -unsafe impl FromBytes for HsHeaderV2 {} - -/// Heavy-Secured Firmware image container. -/// -/// This provides convenient access to the fields of [`HsHeaderV2`] that are actually indices to -/// read from in the firmware data. -struct HsFirmwareV2<'a> { - hdr: HsHeaderV2, - fw: &'a [u8], -} - -impl<'a> HsFirmwareV2<'a> { - /// Interprets the header of `bin_fw` as a [`HsHeaderV2`] and returns an instance of - /// `HsFirmwareV2` for further parsing. - /// - /// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image. - fn new(bin_fw: &BinFirmware<'a>) -> Result { - frombytes_at::(bin_fw.fw, bin_fw.hdr.header_offset.into_safe_cast()) - .map(|hdr| Self { hdr, fw: bin_fw.fw }) - } - - /// Returns the location at which the signatures should be patched in the microcode image. - /// - /// Fails if the offset of the patch location is outside the bounds of the firmware - /// image. - fn patch_location(&self) -> Result { - frombytes_at::(self.fw, self.hdr.patch_loc_offset.into_safe_cast()) - } - - /// Returns an iterator to the signatures of the firmware. The iterator can be empty if the - /// firmware is unsigned. - /// - /// Fails if the pointed signatures are outside the bounds of the firmware image. - fn signatures_iter(&'a self) -> Result>> { - let num_sig = frombytes_at::(self.fw, self.hdr.num_sig_offset.into_safe_cast())?; - let iter = match self.hdr.sig_prod_size.checked_div(num_sig) { - // If there are no signatures, return an iterator that will yield zero elements. - None => (&[] as &[u8]).chunks_exact(1), - Some(sig_size) => { - let patch_sig = - frombytes_at::(self.fw, self.hdr.patch_sig_offset.into_safe_cast())?; - - let signatures_start = self - .hdr - .sig_prod_offset - .checked_add(patch_sig) - .map(usize::from_safe_cast) - .ok_or(EINVAL)?; - - let signatures_end = signatures_start - .checked_add(usize::from_safe_cast(self.hdr.sig_prod_size)) - .ok_or(EINVAL)?; - - self.fw - // Get signatures range. - .get(signatures_start..signatures_end) - .ok_or(EINVAL)? - .chunks_exact(sig_size.into_safe_cast()) - } - }; - - // Map the byte slices into signatures. - Ok(iter.map(BooterSignature)) - } -} - -/// Signature parameters, as defined in the firmware. -#[repr(C)] -struct HsSignatureParams { - /// Fuse version to use. - fuse_ver: u32, - /// Mask of engine IDs this firmware applies to. - engine_id_mask: u32, - /// ID of the microcode. - ucode_id: u32, -} - -// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. -unsafe impl FromBytes for HsSignatureParams {} - -impl HsSignatureParams { - /// Returns the signature parameters contained in `hs_fw`. - /// - /// Fails if the meta data parameter of `hs_fw` is outside the bounds of the firmware image, or - /// if its size doesn't match that of [`HsSignatureParams`]. - fn new(hs_fw: &HsFirmwareV2<'_>) -> Result { - let start = usize::from_safe_cast(hs_fw.hdr.meta_data_offset); - let end = start - .checked_add(hs_fw.hdr.meta_data_size.into_safe_cast()) - .ok_or(EINVAL)?; - - hs_fw - .fw - .get(start..end) - .and_then(Self::from_bytes_copy) - .ok_or(EINVAL) - } -} - -/// Header for code and data load offsets. -#[repr(C)] -#[derive(Debug, Clone)] -struct HsLoadHeaderV2 { - // Offset at which the code starts. - os_code_offset: u32, - // Total size of the code, for all apps. - os_code_size: u32, - // Offset at which the data starts. - os_data_offset: u32, - // Size of the data. - os_data_size: u32, - // Number of apps following this header. Each app is described by a [`HsLoadHeaderV2App`]. - num_apps: u32, -} - -// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. -unsafe impl FromBytes for HsLoadHeaderV2 {} - -impl HsLoadHeaderV2 { - /// Returns the load header contained in `hs_fw`. - /// - /// Fails if the header pointed at by `hs_fw` is not within the bounds of the firmware image. - fn new(hs_fw: &HsFirmwareV2<'_>) -> Result { - frombytes_at::(hs_fw.fw, hs_fw.hdr.header_offset.into_safe_cast()) - } -} - -/// Header for app code loader. -#[repr(C)] -#[derive(Debug, Clone)] -struct HsLoadHeaderV2App { - /// Offset at which to load the app code. - offset: u32, - /// Length in bytes of the app code. - len: u32, -} - -// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. -unsafe impl FromBytes for HsLoadHeaderV2App {} - -impl HsLoadHeaderV2App { - /// Returns the [`HsLoadHeaderV2App`] for app `idx` of `hs_fw`. - /// - /// Fails if `idx` is larger than the number of apps declared in `hs_fw`, or if the header is - /// not within the bounds of the firmware image. - fn new(hs_fw: &HsFirmwareV2<'_>, idx: u32) -> Result { - let load_hdr = HsLoadHeaderV2::new(hs_fw)?; - if idx >= load_hdr.num_apps { - Err(EINVAL) - } else { - frombytes_at::( - hs_fw.fw, - usize::from_safe_cast(hs_fw.hdr.header_offset) - // Skip the load header... - .checked_add(size_of::()) - // ... and jump to app header `idx`. - .and_then(|offset| { - offset - .checked_add(usize::from_safe_cast(idx).checked_mul(size_of::())?) - }) - .ok_or(EINVAL)?, - ) - } - } -} - /// 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]); @@ -291,85 +85,76 @@ pub(crate) fn new( dev: &device::Device, kind: BooterKind, chipset: Chipset, - ver: &str, falcon: &Falcon<'_, ::Target>, ) -> Result { let fw_name = match kind { BooterKind::Loader => "booter_load", BooterKind::Unloader => "booter_unload", }; - let fw = super::request_firmware(dev, chipset, fw_name, ver)?; - let bin_fw = BinFirmware::new(&fw)?; + 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")? + ); - // The binary firmware embeds a Heavy-Secured firmware. - let hs_fw = HsFirmwareV2::new(&bin_fw)?; + 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")?; - // The Heavy-Secured firmware embeds a firmware load descriptor. - let load_hdr = HsLoadHeaderV2::new(&hs_fw)?; - - // Offset in `ucode` where to patch the signature. - let patch_loc = hs_fw.patch_location()?; - - let sig_params = HsSignatureParams::new(&hs_fw)?; let brom_params = FalconBromParams { - // `load_hdr.os_data_offset` is an absolute index, but `pkc_data_offset` is from the + // `os_data_offset` is an absolute index, but `pkc_data_offset` is from the // signature patch location. - pkc_data_offset: patch_loc - .checked_sub(load_hdr.os_data_offset) - .ok_or(EINVAL)?, - engine_id_mask: u16::try_from(sig_params.engine_id_mask).map_err(|_| EINVAL)?, - ucode_id: u8::try_from(sig_params.ucode_id).map_err(|_| EINVAL)?, + 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 app0 = HsLoadHeaderV2App::new(&hs_fw, 0)?; - // Object containing the firmware microcode to be signature-patched. - let ucode = bin_fw - .data() - .ok_or(EINVAL) + let ucode = tlv + .get_bytes(b"BLOB") .and_then(FirmwareObject::::new_booter)?; - let ucode_signed = { - let mut signatures = hs_fw.signatures_iter()?.peekable(); + // 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(); - if signatures.peek().is_none() { - // If there are no signatures, then the firmware is unsigned. - ucode.no_patch_signature() - } else { - // Obtain the version from the fuse register, and extract the corresponding - // signature. - let reg_fuse_version = falcon - .signature_reg_fuse_version(brom_params.engine_id_mask, brom_params.ucode_id)?; + const FUSE_VERSION_USE_LAST_SIG: usize = 0; - // `0` means the last signature should be used. - const FUSE_VERSION_USE_LAST_SIG: u32 = 0; - let signature = match reg_fuse_version { - FUSE_VERSION_USE_LAST_SIG => signatures.last(), - // Otherwise hardware fuse version needs to be subtracted to obtain the index. - reg_fuse_version => { - let Some(idx) = sig_params.fuse_ver.checked_sub(reg_fuse_version) else { - dev_err!(dev, "invalid fuse version for Booter firmware\n"); - return Err(EINVAL); - }; - signatures.nth(idx.into_safe_cast()) - } - } - .ok_or(EINVAL)?; - - ucode.patch_signature(&signature, patch_loc.into_safe_cast())? - } + 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.offset, + app0_code_offset, Some(FalconDmaLoadTarget { src_start: 0, - dst_start: load_hdr.os_code_offset, - len: load_hdr.os_code_size, + dst_start: os_code_offset, + len: os_code_size, }), ) } else { @@ -378,15 +163,15 @@ pub(crate) fn new( Ok(Self { imem_sec_load_target: FalconDmaLoadTarget { - src_start: app0.offset, + src_start: app0_code_offset, dst_start: imem_sec_dst_start, - len: app0.len, + len: app0_code_size, }, imem_ns_load_target, dmem_load_target: FalconDmaLoadTarget { - src_start: load_hdr.os_data_offset, + src_start: os_data_offset, dst_start: 0, - len: load_hdr.os_data_size, + len: os_data_size, }, brom_params, ucode: ucode_signed, diff --git a/drivers/gpu/nova-core/firmware/tlv.rs b/drivers/gpu/nova-core/firmware/tlv.rs index 02150459c279..7b879f13a61e 100644 --- a/drivers/gpu/nova-core/firmware/tlv.rs +++ b/drivers/gpu/nova-core/firmware/tlv.rs @@ -14,7 +14,6 @@ }; /// Requests the GPU firmware TLV `name` suitable for `chipset`. -#[expect(dead_code)] pub(crate) fn request_tlv( dev: &device::Device, chipset: gpu::Chipset, @@ -131,7 +130,6 @@ pub(crate) struct Tlv<'a> { data: &'a [u8], } -#[expect(dead_code)] impl<'a> Tlv<'a> { const MAGIC: &'static [u8; 4] = b"NVFW"; diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs index e3c365cf4a68..2ae3140cb0fe 100644 --- a/drivers/gpu/nova-core/gsp/hal/tu102.rs +++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs @@ -31,8 +31,7 @@ FwsecCommand, FwsecFirmware, // }, - gsp::GspFirmware, - FIRMWARE_VERSION, // + gsp::GspFirmware, // }, gpu::Chipset, gsp::{ @@ -237,7 +236,6 @@ fn build_unload_bundle( dev, BooterKind::Unloader, chipset, - FIRMWARE_VERSION, sec2_falcon, )?, }, @@ -306,14 +304,11 @@ fn boot( "Using SEC2 to load and run the booter_load firmware...\n" ); - BooterFirmware::new( + BooterFirmware::new(dev, BooterKind::Loader, chipset, sec2_falcon)?.run( dev, - BooterKind::Loader, - chipset, - FIRMWARE_VERSION, sec2_falcon, - )? - .run(dev, sec2_falcon, &wpr_meta)?; + &wpr_meta, + )?; Ok(unload_guard.dismiss()) }