diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs index c285e57268f0..d58a3d5770d5 100644 --- a/drivers/gpu/nova-core/firmware.rs +++ b/drivers/gpu/nova-core/firmware.rs @@ -8,10 +8,8 @@ use core::ops::Deref; use kernel::{ - device, firmware, - prelude::*, - str::CString, // + prelude::*, // }; use crate::{ @@ -33,19 +31,6 @@ pub(crate) const FIRMWARE_VERSION: &str = "570.144"; -/// Requests the GPU firmware `name` suitable for `chipset`, with version `ver`. -fn request_firmware( - dev: &device::Device, - chipset: gpu::Chipset, - name: &str, - ver: &str, -) -> Result { - let chip_name = chipset.name(); - - CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{name}-{ver}.bin")) - .and_then(|path| firmware::Firmware::request(&path, dev)) -} - /// Structure used to describe some firmwares, notably FWSEC-FRTS. #[repr(C)] #[derive(Debug, Clone, FromBytes)] @@ -393,208 +378,3 @@ pub(crate) const fn create( this.0 } } - -/// Ad-hoc and temporary module to extract sections from ELF images. -/// -/// Some firmware images are currently packaged as ELF files, where sections names are used as keys -/// to specific and related bits of data. Future firmware versions are scheduled to move away from -/// that scheme before nova-core becomes stable, which means this module will eventually be -/// removed. -mod elf { - use kernel::{ - bindings, - prelude::*, - transmute::FromBytes, // - }; - - /// Trait to abstract over ELF header differences. - trait ElfHeader: FromBytes { - fn shnum(&self) -> u16; - fn shoff(&self) -> u64; - fn shstrndx(&self) -> u16; - } - - /// Trait to abstract over ELF section-header differences. - trait ElfSectionHeader: FromBytes { - fn name(&self) -> u32; - fn offset(&self) -> u64; - fn size(&self) -> u64; - } - - /// Trait describing a matching ELF header and section-header format. - trait ElfFormat { - type Header: ElfHeader; - type SectionHeader: ElfSectionHeader; - } - - /// Newtype to provide a [`FromBytes`] implementation. - #[repr(transparent)] - struct Elf64Hdr(bindings::elf64_hdr); - // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. - unsafe impl FromBytes for Elf64Hdr {} - - impl ElfHeader for Elf64Hdr { - fn shnum(&self) -> u16 { - self.0.e_shnum - } - - fn shoff(&self) -> u64 { - self.0.e_shoff - } - - fn shstrndx(&self) -> u16 { - self.0.e_shstrndx - } - } - - #[repr(transparent)] - struct Elf64SHdr(bindings::elf64_shdr); - // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. - unsafe impl FromBytes for Elf64SHdr {} - - impl ElfSectionHeader for Elf64SHdr { - fn name(&self) -> u32 { - self.0.sh_name - } - - fn offset(&self) -> u64 { - self.0.sh_offset - } - - fn size(&self) -> u64 { - self.0.sh_size - } - } - - struct Elf64Format; - - impl ElfFormat for Elf64Format { - type Header = Elf64Hdr; - type SectionHeader = Elf64SHdr; - } - - /// Newtype to provide [`FromBytes`] and [`ElfHeader`] implementations for ELF32. - #[repr(transparent)] - struct Elf32Hdr(bindings::elf32_hdr); - // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. - unsafe impl FromBytes for Elf32Hdr {} - - impl ElfHeader for Elf32Hdr { - fn shnum(&self) -> u16 { - self.0.e_shnum - } - - fn shoff(&self) -> u64 { - u64::from(self.0.e_shoff) - } - - fn shstrndx(&self) -> u16 { - self.0.e_shstrndx - } - } - - /// Newtype to provide [`FromBytes`] and [`ElfSectionHeader`] implementations for ELF32. - #[repr(transparent)] - struct Elf32SHdr(bindings::elf32_shdr); - // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability. - unsafe impl FromBytes for Elf32SHdr {} - - impl ElfSectionHeader for Elf32SHdr { - fn name(&self) -> u32 { - self.0.sh_name - } - - fn offset(&self) -> u64 { - u64::from(self.0.sh_offset) - } - - fn size(&self) -> u64 { - u64::from(self.0.sh_size) - } - } - - struct Elf32Format; - - impl ElfFormat for Elf32Format { - type Header = Elf32Hdr; - type SectionHeader = Elf32SHdr; - } - - /// Returns a NULL-terminated string from the ELF image at `offset`. - fn elf_str(elf: &[u8], offset: u64) -> Option<&str> { - let idx = usize::try_from(offset).ok()?; - let bytes = elf.get(idx..)?; - CStr::from_bytes_until_nul(bytes).ok()?.to_str().ok() - } - - fn elf_section_generic<'a, F>(elf: &'a [u8], name: &str) -> Option<&'a [u8]> - where - F: ElfFormat, - { - let hdr = F::Header::from_bytes(elf.get(0..size_of::())?)?; - - let shdr_num = usize::from(hdr.shnum()); - let shdr_start = usize::try_from(hdr.shoff()).ok()?; - let shdr_end = shdr_num - .checked_mul(size_of::()) - .and_then(|v| v.checked_add(shdr_start))?; - - // Get all the section headers as an iterator over byte chunks. - let shdr_bytes = elf.get(shdr_start..shdr_end)?; - let mut shdr_iter = shdr_bytes.chunks_exact(size_of::()); - - // Get the strings table. - let strhdr = shdr_iter - .clone() - .nth(usize::from(hdr.shstrndx())) - .and_then(F::SectionHeader::from_bytes)?; - - // Find the section which name matches `name` and return it. - shdr_iter.find_map(|sh_bytes| { - let sh = F::SectionHeader::from_bytes(sh_bytes)?; - let name_offset = strhdr.offset().checked_add(u64::from(sh.name()))?; - let section_name = elf_str(elf, name_offset)?; - - if section_name != name { - return None; - } - - let start = usize::try_from(sh.offset()).ok()?; - let end = usize::try_from(sh.size()) - .ok() - .and_then(|sz| start.checked_add(sz))?; - - elf.get(start..end) - }) - } - - /// Extract the section with name `name` from the ELF64 image `elf`. - fn elf64_section<'a>(elf: &'a [u8], name: &str) -> Option<&'a [u8]> { - elf_section_generic::(elf, name) - } - - /// Extract the section with name `name` from the ELF32 image `elf`. - fn elf32_section<'a>(elf: &'a [u8], name: &str) -> Option<&'a [u8]> { - elf_section_generic::(elf, name) - } - - /// Automatically detects ELF32 vs ELF64 based on the ELF header. - pub(super) fn elf_section<'a>(elf: &'a [u8], name: &str) -> Option<&'a [u8]> { - // ELF identification: a 4-byte magic followed by a class byte (32- vs 64-bit). - const ELFMAG: &[u8] = b"\x7fELF"; - const SELFMAG: usize = ELFMAG.len(); - const EI_CLASS: usize = 4; - const ELFCLASS32: u8 = 1; - const ELFCLASS64: u8 = 2; - - if elf.get(0..SELFMAG) != Some(ELFMAG) { - return None; - } - - match *elf.get(EI_CLASS)? { - ELFCLASS32 => elf32_section(elf, name), - ELFCLASS64 => elf64_section(elf, name), - _ => None, - } - } -} diff --git a/drivers/gpu/nova-core/firmware/fsp.rs b/drivers/gpu/nova-core/firmware/fsp.rs index 6eaf1c684b9d..5462e318410a 100644 --- a/drivers/gpu/nova-core/firmware/fsp.rs +++ b/drivers/gpu/nova-core/firmware/fsp.rs @@ -6,12 +6,14 @@ use kernel::{ device, dma::Coherent, - firmware::Firmware, prelude::*, // }; use crate::{ - firmware::elf, + firmware::tlv::{ + request_tlv, // + Tlv, + }, gpu::Chipset, // }; @@ -19,11 +21,11 @@ const FSP_HASH_SIZE: usize = 48; /// Maximum size of the FSP public key (RSA-3072), in bytes. /// -/// The FMC ELF `publickey` section may be shorter, so the remaining bytes are zero-padded. +/// The FMC `PKEY` tag may be shorter, so the remaining bytes are zero-padded. const FSP_PKEY_SIZE: usize = 384; /// Maximum size of the FSP signature (RSA-3072), in bytes. /// -/// The FMC ELF `signature` section may be shorter, so the remaining bytes are zero-padded. +/// The FMC `SIGN` tag may be shorter, so the remaining bytes are zero-padded. const FSP_SIG_SIZE: usize = 384; /// Structure to hold FMC signatures. @@ -38,64 +40,35 @@ pub(crate) struct FmcSignatures { } pub(crate) struct FspFirmware { - /// FMC firmware image data (only the "image" ELF section). + /// FMC firmware image data pub(crate) fmc_image: Coherent<[u8]>, /// FMC firmware signatures. pub(crate) fmc_sigs: KBox, } impl FspFirmware { - pub(crate) fn new( - dev: &device::Device, - chipset: Chipset, - ver: &str, - ) -> Result { - let fw = super::request_firmware(dev, chipset, "fmc", ver)?; + pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result { + let fw = request_tlv(dev, chipset, "fmc")?; + let tlv = Tlv::new(fw.data())?; + dev_dbg!(dev, "loaded fsp firmware v{}\n", tlv.get_string(b"VERS")?); - // FSP expects only the "image" section, not the entire ELF file. - let fmc_image_data = elf::elf_section(fw.data(), "image").ok_or_else(|| { - dev_err!(dev, "FMC ELF file missing 'image' section\n"); - EINVAL - })?; + let fmc_image_data = tlv.get_bytes(b"BLOB")?; let fmc_image = Coherent::from_slice(dev, fmc_image_data, GFP_KERNEL)?; Ok(Self { fmc_image, - fmc_sigs: Self::extract_fmc_signatures(&fw, dev)?, + fmc_sigs: Self::extract_fmc_signatures(&tlv, dev)?, }) } /// Extract FMC firmware signatures for Chain of Trust verification. /// - /// Extracts real cryptographic signatures from FMC ELF32 firmware sections. + /// Extracts real cryptographic signatures from FMC TLV firmware tags. /// Returns signatures in a heap-allocated structure to prevent stack overflow. - fn extract_fmc_signatures( - fmc_fw: &Firmware, - dev: &device::Device, - ) -> Result> { - let get_section = |name: &str, max_len: usize| { - elf::elf_section(fmc_fw.data(), name) - .ok_or(EINVAL) - .inspect_err(|_| dev_err!(dev, "FMC firmware missing '{}' section\n", name)) - .and_then(|section| { - if section.len() > max_len { - dev_err!( - dev, - "FMC {} section size {} > maximum {}\n", - name, - section.len(), - max_len - ); - Err(EINVAL) - } else { - Ok(section) - } - }) - }; - - let hash_section = get_section("hash", FSP_HASH_SIZE)?; - let pkey_section = get_section("publickey", FSP_PKEY_SIZE)?; - let sig_section = get_section("signature", FSP_SIG_SIZE)?; + fn extract_fmc_signatures(tlv: &Tlv<'_>, dev: &device::Device) -> Result> { + let hash_section = tlv.get_bytes(b"HASH")?; + let pkey_section = tlv.get_bytes(b"PKEY")?; + let sig_section = tlv.get_bytes(b"SIGN")?; // The hash section is a SHA-384 output: it must be exactly FSP_HASH_SIZE bytes. if hash_section.len() != FSP_HASH_SIZE { @@ -108,15 +81,36 @@ fn extract_fmc_signatures( return Err(EINVAL); } + // The key and signature sections are zero-padded to a fixed maximum, so they may be + // shorter, but must not exceed the destination buffers. + if pkey_section.len() > FSP_PKEY_SIZE { + dev_err!( + dev, + "FMC public key section size {} > maximum {}\n", + pkey_section.len(), + FSP_PKEY_SIZE + ); + return Err(EINVAL); + } + if sig_section.len() > FSP_SIG_SIZE { + dev_err!( + dev, + "FMC signature section size {} > maximum {}\n", + sig_section.len(), + FSP_SIG_SIZE + ); + return Err(EINVAL); + } + // Initialize the signatures in place to avoid building the large `FmcSignatures` on the // stack, then fill each section from the firmware. let signatures = KBox::init( pin_init::init_zeroed::().chain(|sigs| { // PANIC: src and dst lengths are both FSP_HASH_SIZE (verified above). sigs.hash384.copy_from_slice(hash_section); - // PANIC: dst is sliced to src.len(); src.len() <= FSP_PKEY_SIZE per `get_section`. + // PANIC: dst is sliced to src.len(); src.len() <= FSP_PKEY_SIZE (verified above). sigs.public_key[..pkey_section.len()].copy_from_slice(pkey_section); - // PANIC: dst is sliced to src.len(); src.len() <= FSP_SIG_SIZE per `get_section`. + // PANIC: dst is sliced to src.len(); src.len() <= FSP_SIG_SIZE (verified above). sigs.signature[..sig_section.len()].copy_from_slice(sig_section); Ok(()) }), diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs index 6263277a7614..2a4d8ff53dbe 100644 --- a/drivers/gpu/nova-core/fsp.rs +++ b/drivers/gpu/nova-core/fsp.rs @@ -32,12 +32,9 @@ Falcon, // }, fb::FbSizes, - firmware::{ - fsp::{ - FmcSignatures, - FspFirmware, // - }, - FIRMWARE_VERSION, // + firmware::fsp::{ + FmcSignatures, + FspFirmware, // }, gpu::Chipset, gsp::{ @@ -425,7 +422,7 @@ fn wait_secure_boot( const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; let falcon = Falcon::::new(dev, chipset, bar)?; - let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?; + let fsp_fw = FspFirmware::new(dev, chipset)?; read_poll_timeout( || Ok(hal.fsp_boot_status(bar)),