| // SPDX-License-Identifier: GPL-2.0 |
| |
| //! VBIOS extraction and parsing. |
| |
| use kernel::{ |
| device, |
| io::Io, |
| prelude::*, |
| ptr::{ |
| Alignable, |
| Alignment, // |
| }, |
| register, |
| sizes::SZ_4K, |
| sync::aref::ARef, |
| }; |
| |
| use crate::{ |
| driver::Bar0, |
| firmware::{ |
| fwsec::Bcrt30Rsa3kSignature, |
| FalconUCodeDesc, |
| FalconUCodeDescV2, |
| FalconUCodeDescV3, // |
| }, |
| num::FromSafeCast, |
| }; |
| |
| /// BIOS Image Type from PCI Data Structure code_type field. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| #[repr(u8)] |
| enum BiosImageType { |
| /// PC-AT compatible BIOS image (x86 legacy) |
| PciAt = 0x00, |
| /// EFI (Extensible Firmware Interface) BIOS image |
| Efi = 0x03, |
| /// NBSI (Notebook System Information) BIOS image |
| Nbsi = 0x70, |
| /// FwSec (Firmware Security) BIOS image |
| FwSec = 0xE0, |
| } |
| |
| impl TryFrom<u8> for BiosImageType { |
| type Error = Error; |
| |
| fn try_from(code: u8) -> Result<Self> { |
| match code { |
| 0x00 => Ok(Self::PciAt), |
| 0x03 => Ok(Self::Efi), |
| 0x70 => Ok(Self::Nbsi), |
| 0xE0 => Ok(Self::FwSec), |
| _ => Err(EINVAL), |
| } |
| } |
| } |
| |
| /// Vbios Reader for constructing the VBIOS data. |
| struct VbiosIterator<'a> { |
| dev: &'a device::Device, |
| bar0: Bar0<'a>, |
| /// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference |
| /// or copying into other data structures. It is the entire scanned contents of the VBIOS which |
| /// progressively extends. It is used so that we do not re-read any contents that are already |
| /// read as we use the cumulative length read so far, and re-read any gaps as we extend the |
| /// length. |
| data: KVVec<u8>, |
| /// Current offset of the [`Iterator`]. |
| current_offset: usize, |
| /// Indicate whether the last image has been found. |
| last_found: bool, |
| } |
| |
| impl<'a> VbiosIterator<'a> { |
| /// The offset of the VBIOS ROM in the BAR0 space. |
| const ROM_OFFSET: usize = 0x300000; |
| /// The maximum length of the VBIOS ROM to scan into. |
| const BIOS_MAX_SCAN_LEN: usize = 0x100000; |
| /// The size to read ahead when parsing initial BIOS image headers. |
| const BIOS_READ_AHEAD_SIZE: usize = 1024; |
| |
| /// Return the byte offset where the PCI Expansion ROM images begin in the GPU's ROM. |
| /// |
| /// The GPU's ROM may begin with an Init-from-ROM (IFR) header that precedes the PCI Expansion |
| /// ROM images (VBIOS). When present, the PROM shadow method must parse this header to determine |
| /// the offset where the PCI ROM images actually begin, and adjust all subsequent reads |
| /// accordingly. |
| /// |
| /// On most GPUs this is not needed because the IFR microcode has already applied the ROM offset |
| /// so that PROM reads transparently skip the header. On GA100, for some reason, the IFR offset |
| /// is not applied to PROM reads. Therefore, the search for the PCI expansion must skip the IFR |
| /// header, if found. |
| fn rom_offset(dev: &device::Device, bar0: Bar0<'_>) -> Result<usize> { |
| // IFR Header in VBIOS. |
| register! { |
| NV_PBUS_IFR_FMT_FIXED0(u32) @ 0x300000 { |
| 31:0 signature; |
| } |
| } |
| |
| register! { |
| NV_PBUS_IFR_FMT_FIXED1(u32) @ 0x300004 { |
| 30:16 fixed_data_size; |
| 15:8 version => u8; |
| } |
| } |
| |
| register! { |
| NV_PBUS_IFR_FMT_FIXED2(u32) @ 0x300008 { |
| 19:0 total_data_size; |
| } |
| } |
| |
| /// IFR signature. |
| const NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE: u32 = u32::from_le_bytes(*b"NVGI"); |
| /// ROM directory signature. |
| const NV_ROM_DIRECTORY_IDENTIFIER: u32 = u32::from_le_bytes(*b"RFRD"); |
| /// Offset of the NV_PMGR_ROM_ADDR_OFFSET register in IFR Extended section. |
| const IFR_SW_EXT_ROM_ADDR_OFFSET: usize = 4; |
| /// Size of Redundant Firmware Flash Status section. |
| const RFW_FLASH_STATUS_SIZE: usize = SZ_4K; |
| /// Offset in the ROM Directory of the PCI Option ROM offset. |
| const PCI_OPTION_ROM_OFFSET: usize = 8; |
| |
| let signature = bar0.read(NV_PBUS_IFR_FMT_FIXED0).signature(); |
| |
| if signature == NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE { |
| let fixed1 = bar0.read(NV_PBUS_IFR_FMT_FIXED1); |
| |
| match fixed1.version() { |
| 1 | 2 => { |
| let fixed_data_size = usize::from(fixed1.fixed_data_size()); |
| let pmgr_rom_addr_offset = fixed_data_size + IFR_SW_EXT_ROM_ADDR_OFFSET; |
| bar0.try_read32(Self::ROM_OFFSET + pmgr_rom_addr_offset) |
| .map(usize::from_safe_cast) |
| } |
| 3 => { |
| let fixed2 = bar0.read(NV_PBUS_IFR_FMT_FIXED2); |
| let total_data_size = usize::from(fixed2.total_data_size()); |
| let flash_status_offset = |
| usize::from_safe_cast(bar0.try_read32(Self::ROM_OFFSET + total_data_size)?); |
| let dir_offset = flash_status_offset + RFW_FLASH_STATUS_SIZE; |
| let dir_sig = bar0.try_read32(Self::ROM_OFFSET + dir_offset)?; |
| if dir_sig != NV_ROM_DIRECTORY_IDENTIFIER { |
| dev_err!(dev, "could not find IFR ROM directory\n"); |
| return Err(EINVAL); |
| } |
| bar0.try_read32(Self::ROM_OFFSET + dir_offset + PCI_OPTION_ROM_OFFSET) |
| .map(usize::from_safe_cast) |
| } |
| _ => { |
| dev_err!(dev, "unsupported IFR header version {}\n", fixed1.version()); |
| Err(EINVAL) |
| } |
| } |
| } else { |
| Ok(0) |
| } |
| } |
| |
| fn new(dev: &'a device::Device, bar0: Bar0<'a>) -> Result<Self> { |
| Ok(Self { |
| dev, |
| bar0, |
| data: KVVec::new(), |
| current_offset: Self::rom_offset(dev, bar0)?, |
| last_found: false, |
| }) |
| } |
| |
| /// Read bytes from the ROM at the current end of the data vector. |
| fn read_more(&mut self, len: usize) -> Result { |
| let start = self.data.len(); |
| let end = start + len; |
| |
| if end > Self::BIOS_MAX_SCAN_LEN { |
| dev_err!(self.dev, "Error: exceeded BIOS scan limit.\n"); |
| return Err(EINVAL); |
| } |
| |
| // Ensure length is a multiple of 4 for 32-bit reads |
| if len % core::mem::size_of::<u32>() != 0 { |
| dev_err!( |
| self.dev, |
| "VBIOS read length {} is not a multiple of 4\n", |
| len |
| ); |
| return Err(EINVAL); |
| } |
| |
| self.data.reserve(len, GFP_KERNEL)?; |
| // Read ROM data bytes and push directly to `data`. |
| for addr in (start..end).step_by(core::mem::size_of::<u32>()) { |
| // Read 32-bit word from the VBIOS ROM |
| let word = self.bar0.try_read32(Self::ROM_OFFSET + addr)?; |
| |
| // Convert the `u32` to a 4 byte array and push each byte. |
| word.to_ne_bytes() |
| .iter() |
| .try_for_each(|&b| self.data.push(b, GFP_KERNEL))?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// Read bytes at a specific offset, filling any gap. |
| fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result { |
| let end = offset.checked_add(len).ok_or(EINVAL)?; |
| |
| self.read_more(end.saturating_sub(self.data.len())) |
| } |
| |
| /// Read a BIOS image at a specific offset and create a [`BiosImage`] from it. |
| /// |
| /// `self.data` is extended as needed and a new [`BiosImage`] is returned. |
| /// `context` is a string describing the operation for error reporting. |
| fn read_bios_image_at_offset( |
| &mut self, |
| offset: usize, |
| len: usize, |
| context: &str, |
| ) -> Result<BiosImage> { |
| let end = offset.checked_add(len).ok_or(EINVAL)?; |
| if end > self.data.len() { |
| self.read_more_at_offset(offset, len).inspect_err(|e| { |
| dev_err!( |
| self.dev, |
| "Failed to read more at offset {:#x}: {:?}\n", |
| offset, |
| e |
| ) |
| })?; |
| } |
| |
| BiosImage::new(self.dev, &self.data[offset..end]).inspect_err(|err| { |
| dev_err!( |
| self.dev, |
| "Failed to {} at offset {:#x}: {:?}\n", |
| context, |
| offset, |
| err |
| ) |
| }) |
| } |
| } |
| |
| impl<'a> Iterator for VbiosIterator<'a> { |
| type Item = Result<BiosImage>; |
| |
| /// Iterate over all VBIOS images until the last image is detected or offset |
| /// exceeds scan limit. |
| fn next(&mut self) -> Option<Self::Item> { |
| if self.last_found { |
| return None; |
| } |
| |
| if self.current_offset >= Self::BIOS_MAX_SCAN_LEN { |
| dev_err!(self.dev, "Error: exceeded BIOS scan limit, stopping scan\n"); |
| return None; |
| } |
| |
| // Parse image headers first to get image size. |
| let image_size = match self.read_bios_image_at_offset( |
| self.current_offset, |
| Self::BIOS_READ_AHEAD_SIZE, |
| "parse initial BIOS image headers", |
| ) { |
| Ok(image) => image.image_size_bytes(), |
| Err(e) => return Some(Err(e)), |
| }; |
| |
| // Now create a new `BiosImage` with the full image data. |
| let full_image = match self.read_bios_image_at_offset( |
| self.current_offset, |
| image_size, |
| "parse full BIOS image", |
| ) { |
| Ok(image) => image, |
| Err(e) => return Some(Err(e)), |
| }; |
| |
| self.last_found = full_image.is_last(); |
| |
| // Advance to next image (aligned to 512 bytes). |
| self.current_offset += image_size; |
| self.current_offset = self.current_offset.align_up(Alignment::new::<512>())?; |
| |
| Some(Ok(full_image)) |
| } |
| } |
| |
| pub(crate) struct Vbios { |
| fwsec_image: FwSecBiosImage, |
| } |
| |
| impl Vbios { |
| /// Probe for VBIOS extraction. |
| /// |
| /// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore. |
| pub(crate) fn new(dev: &device::Device, bar0: Bar0<'_>) -> Result<Vbios> { |
| // Images to extract from iteration |
| let mut pci_at_image: Option<PciAtBiosImage> = None; |
| let mut fwsec_section: Option<KVVec<u8>> = None; |
| |
| // Parse all VBIOS images in the ROM |
| for image_result in VbiosIterator::new(dev, bar0)? { |
| let image = image_result?; |
| |
| dev_dbg!( |
| dev, |
| "Found BIOS image: size: {:#x}, type: {:?}, last: {}\n", |
| image.image_size_bytes(), |
| image.image_type(), |
| image.is_last() |
| ); |
| |
| // Once we have found the first FWSEC image, grab all data after that as the FWSEC |
| // section. This is indexed as one logical block to build the final FWSEC image. |
| if let Some(data) = fwsec_section.as_mut() { |
| data.extend_from_slice(&image.data, GFP_KERNEL)?; |
| continue; |
| } |
| |
| // Convert to a specific image type |
| match BiosImageType::try_from(image.pcir.code_type) { |
| Ok(BiosImageType::PciAt) => { |
| // Silently ignore any extra PCI-AT images. |
| if pci_at_image.is_none() { |
| pci_at_image = Some(PciAtBiosImage::try_from(image)?); |
| } |
| } |
| Ok(BiosImageType::FwSec) => fwsec_section = Some(image.data), |
| _ => { |
| // Ignore other image types or unknown types |
| } |
| } |
| } |
| |
| // Using all the images, setup the falcon data pointer in Fwsec. |
| let (Some(pci_at), Some(fwsec_section)) = (pci_at_image, fwsec_section) else { |
| dev_err!( |
| dev, |
| "Missing required images for falcon data setup, skipping\n" |
| ); |
| return Err(EINVAL); |
| }; |
| |
| let fwsec_image = FwSecBiosImage::new(dev, pci_at, fwsec_section) |
| .inspect_err(|e| dev_err!(dev, "Falcon data setup failed: {:?}\n", e))?; |
| |
| Ok(Vbios { fwsec_image }) |
| } |
| |
| pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage { |
| &self.fwsec_image |
| } |
| } |
| |
| /// PCI Data Structure as defined in PCI Firmware Specification |
| #[derive(Debug, Clone, FromBytes)] |
| #[repr(C)] |
| struct PcirStruct { |
| /// PCI Data Structure signature ("PCIR" or "NPDS") |
| signature: [u8; 4], |
| /// PCI Vendor ID (e.g., 0x10DE for NVIDIA) |
| vendor_id: u16, |
| /// PCI Device ID |
| device_id: u16, |
| /// Device List Pointer |
| device_list_ptr: u16, |
| /// PCI Data Structure Length |
| pci_data_struct_len: u16, |
| /// PCI Data Structure Revision |
| pci_data_struct_rev: u8, |
| /// Class code (3 bytes, 0x03 for display controller) |
| class_code: [u8; 3], |
| /// Size of this image in 512-byte blocks |
| image_len: u16, |
| /// Revision Level of the Vendor's ROM |
| vendor_rom_rev: u16, |
| /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI) |
| code_type: u8, |
| /// Last image indicator (0x00 = Not last image, 0x80 = Last image) |
| last_image: u8, |
| /// Maximum Run-time Image Length (units of 512 bytes) |
| max_runtime_image_len: u16, |
| } |
| |
| impl PcirStruct { |
| /// The bit in `last_image` that indicates the last image. |
| const LAST_IMAGE_BIT_MASK: u8 = 0x80; |
| |
| fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { |
| let (pcir, _) = PcirStruct::read_from_prefix(data).map_err(|_| EINVAL)?; |
| |
| // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e). |
| if &pcir.signature != b"PCIR" && &pcir.signature != b"NPDS" { |
| dev_err!( |
| dev, |
| "Invalid signature for PcirStruct: {:?}\n", |
| pcir.signature |
| ); |
| return Err(EINVAL); |
| } |
| |
| if pcir.image_len == 0 { |
| dev_err!(dev, "Invalid image length: 0\n"); |
| return Err(EINVAL); |
| } |
| |
| Ok(pcir) |
| } |
| |
| /// Check if this is the last image in the ROM. |
| fn is_last(&self) -> bool { |
| self.last_image & Self::LAST_IMAGE_BIT_MASK != 0 |
| } |
| |
| /// Calculate image size in bytes from 512-byte blocks. |
| fn image_size_bytes(&self) -> usize { |
| usize::from(self.image_len) * 512 |
| } |
| } |
| |
| /// BIOS Information Table (BIT) Header. |
| /// |
| /// This is the head of the BIT table, that is used to locate the Falcon data. The BIT table (with |
| /// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the |
| /// [`FwSecBiosImage`]. |
| #[derive(Debug, Clone, Copy, FromBytes)] |
| #[repr(C)] |
| struct BitHeader { |
| /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF) |
| id: u16, |
| /// 2h: BIT Header Signature ("BIT\0") |
| signature: [u8; 4], |
| /// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00. |
| bcd_version: u16, |
| /// 8h: Size of BIT Header (in bytes) |
| header_size: u8, |
| /// 9h: Size of BIT Tokens (in bytes) |
| token_size: u8, |
| /// 10h: Number of token entries that follow |
| token_entries: u8, |
| /// 11h: BIT Header Checksum |
| checksum: u8, |
| } |
| |
| impl BitHeader { |
| fn new(data: &[u8]) -> Result<Self> { |
| let (header, _) = BitHeader::read_from_prefix(data).map_err(|_| EINVAL)?; |
| |
| // Check header ID and signature |
| if header.id != 0xB8FF || &header.signature != b"BIT\0" { |
| return Err(EINVAL); |
| } |
| |
| Ok(header) |
| } |
| } |
| |
| /// BIT Token Entry: Records in the BIT table followed by the BIT header. |
| #[derive(Debug, Clone, Copy, FromBytes)] |
| #[repr(C)] |
| struct BitToken { |
| /// 00h: Token identifier |
| id: u8, |
| /// 01h: Version of the token data |
| data_version: u8, |
| /// 02h: Size of token data in bytes |
| data_size: u16, |
| /// 04h: Offset to the token data |
| data_offset: u16, |
| } |
| |
| impl BitToken { |
| /// BIT token ID for Falcon data. |
| const ID_FALCON_DATA: u8 = 0x70; |
| |
| /// Find a BIT token entry by BIT ID in a PciAtBiosImage |
| fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> { |
| let header = &image.bit_header; |
| let entry_size = usize::from(header.token_size); |
| |
| // Offset to the first token entry |
| let tokens_start = image.bit_offset + usize::from(header.header_size); |
| |
| for i in 0..usize::from(header.token_entries) { |
| let entry_offset = i |
| .checked_mul(entry_size) |
| .and_then(|offset| tokens_start.checked_add(offset)) |
| .ok_or(EINVAL)?; |
| let entry = image |
| .base |
| .data |
| .get(entry_offset..) |
| .and_then(|data| data.get(..entry_size)) |
| .ok_or(EINVAL)?; |
| |
| let (token, _) = BitToken::read_from_prefix(entry).map_err(|_| EINVAL)?; |
| |
| // Check if this token has the requested ID |
| if token.id == token_id { |
| return Ok(token); |
| } |
| } |
| |
| // Token not found |
| Err(ENOENT) |
| } |
| } |
| |
| /// PCI ROM Expansion Header as defined in PCI Firmware Specification. |
| /// |
| /// This header is at the beginning of every image in the set of images in the ROM. It contains a |
| /// pointer to the PCI Data Structure which describes the image. |
| #[derive(Debug, Clone, Copy, FromBytes)] |
| #[repr(C)] |
| struct PciRomHeader { |
| /// 00h: Signature (0xAA55) |
| signature: u16, |
| /// 02h: Reserved bytes for processor architecture unique data (22 bytes) |
| reserved: [u8; 22], |
| /// 18h: Pointer to PCI Data Structure (offset from start of ROM image) |
| pci_data_struct_offset: u16, |
| } |
| |
| impl PciRomHeader { |
| fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { |
| let (rom_header, _) = PciRomHeader::read_from_prefix(data) |
| .map_err(|_| EINVAL) |
| .inspect_err(|_| dev_err!(dev, "Not enough data for ROM header\n"))?; |
| |
| // Check for valid ROM signatures. |
| match rom_header.signature { |
| 0xAA55 | 0x4E56 => {} |
| _ => { |
| dev_err!(dev, "ROM signature unknown {:#x}\n", rom_header.signature); |
| return Err(EINVAL); |
| } |
| } |
| |
| Ok(rom_header) |
| } |
| } |
| |
| /// NVIDIA PCI Data Extension Structure. |
| /// |
| /// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the |
| /// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but |
| /// are needed for traversing the BIOS images. It is expected to be present in all BIOS images |
| /// except for NBSI images. |
| #[derive(Debug, Clone, FromBytes)] |
| #[repr(C)] |
| struct NpdeStruct { |
| /// 00h: Signature ("NPDE") |
| signature: [u8; 4], |
| /// 04h: NVIDIA PCI Data Extension Revision |
| npci_data_ext_rev: u16, |
| /// 06h: NVIDIA PCI Data Extension Length |
| npci_data_ext_len: u16, |
| /// 08h: Sub-image Length (in 512-byte units) |
| subimage_len: u16, |
| /// 0Ah: Last image indicator flag |
| last_image: u8, |
| } |
| |
| impl NpdeStruct { |
| /// The bit in `last_image` that indicates the last image. |
| const LAST_IMAGE_BIT_MASK: u8 = 0x80; |
| |
| fn new(dev: &device::Device, data: &[u8]) -> Option<Self> { |
| let (npde, _) = NpdeStruct::read_from_prefix(data).ok()?; |
| |
| // Signature should be "NPDE" (0x4544504E). |
| if &npde.signature != b"NPDE" { |
| dev_dbg!( |
| dev, |
| "Invalid signature for NpdeStruct: {:?}\n", |
| npde.signature |
| ); |
| return None; |
| } |
| |
| if npde.subimage_len == 0 { |
| dev_dbg!(dev, "Invalid subimage length: 0\n"); |
| return None; |
| } |
| |
| Some(npde) |
| } |
| |
| /// Check if this is the last image in the ROM. |
| fn is_last(&self) -> bool { |
| self.last_image & Self::LAST_IMAGE_BIT_MASK != 0 |
| } |
| |
| /// Calculate image size in bytes from 512-byte blocks. |
| fn image_size_bytes(&self) -> usize { |
| usize::from(self.subimage_len) * 512 |
| } |
| |
| /// Try to find NPDE in the data, the NPDE is right after the PCIR. |
| fn find_in_data( |
| dev: &device::Device, |
| data: &[u8], |
| rom_header: &PciRomHeader, |
| pcir: &PcirStruct, |
| ) -> Option<Self> { |
| // Calculate the offset where NPDE might be located |
| // NPDE should be right after the PCIR structure, aligned to 16 bytes |
| let pcir_offset = usize::from(rom_header.pci_data_struct_offset); |
| let npde_start = (pcir_offset + usize::from(pcir.pci_data_struct_len) + 0x0F) & !0x0F; |
| |
| // Check if we have enough data |
| if npde_start + core::mem::size_of::<Self>() > data.len() { |
| dev_dbg!(dev, "Not enough data for NPDE\n"); |
| return None; |
| } |
| |
| // Try to create NPDE from the data |
| NpdeStruct::new(dev, &data[npde_start..]) |
| } |
| } |
| |
| /// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain. |
| /// |
| /// It contains the BIT header and the BIT tokens. |
| struct PciAtBiosImage { |
| base: BiosImage, |
| bit_header: BitHeader, |
| bit_offset: usize, |
| } |
| |
| /// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode. |
| /// |
| /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode. |
| pub(crate) struct FwSecBiosImage { |
| /// Used for logging. |
| dev: ARef<device::Device>, |
| /// FWSEC data. |
| data: KVVec<u8>, |
| /// The offset of the Falcon ucode. |
| falcon_ucode_offset: usize, |
| } |
| |
| /// BIOS Image structure containing various headers and reference fields to all BIOS images. |
| /// |
| /// A BiosImage struct is embedded into all image types and implements common operations. |
| struct BiosImage { |
| /// PCI Data Structure |
| pcir: PcirStruct, |
| /// NVIDIA PCI Data Extension (optional) |
| npde: Option<NpdeStruct>, |
| /// Image data (includes ROM header and PCIR) |
| data: KVVec<u8>, |
| } |
| |
| impl BiosImage { |
| /// Get the image size in bytes. |
| fn image_size_bytes(&self) -> usize { |
| // Prefer NPDE image size if available |
| if let Some(ref npde) = self.npde { |
| npde.image_size_bytes() |
| } else { |
| // Otherwise, fall back to the PCIR image size |
| self.pcir.image_size_bytes() |
| } |
| } |
| |
| /// Get the BIOS image type. |
| fn image_type(&self) -> Result<BiosImageType> { |
| BiosImageType::try_from(self.pcir.code_type) |
| } |
| |
| /// Check if this is the last image. |
| fn is_last(&self) -> bool { |
| // For NBSI images, return true as they're considered the last image. |
| if self.image_type() == Ok(BiosImageType::Nbsi) { |
| return true; |
| } |
| |
| // For other image types, check the NPDE first if available |
| if let Some(ref npde) = self.npde { |
| return npde.is_last(); |
| } |
| |
| // Otherwise, fall back to checking the PCIR last_image flag |
| self.pcir.is_last() |
| } |
| |
| /// Creates a new BiosImage from raw byte data. |
| fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { |
| // Parse the ROM header. |
| let rom_header = PciRomHeader::new(dev, data)?; |
| |
| // Get the PCI Data Structure using the pointer from the ROM header. |
| let pcir_offset = usize::from(rom_header.pci_data_struct_offset); |
| let pcir_data = data |
| .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>()) |
| .ok_or(EINVAL) |
| .inspect_err(|_| { |
| dev_err!( |
| dev, |
| "PCIR offset {:#x} out of bounds (data length: {})\n", |
| pcir_offset, |
| data.len() |
| ); |
| dev_err!( |
| dev, |
| "Consider reading more data for construction of BiosImage\n" |
| ); |
| })?; |
| |
| let pcir = PcirStruct::new(dev, pcir_data) |
| .inspect_err(|e| dev_err!(dev, "Failed to create PcirStruct: {:?}\n", e))?; |
| |
| // Look for NPDE structure if this is not an NBSI image (type != 0x70). |
| let npde = NpdeStruct::find_in_data(dev, data, &rom_header, &pcir); |
| |
| // Create a copy of the data. |
| let mut data_copy = KVVec::new(); |
| data_copy.extend_from_slice(data, GFP_KERNEL)?; |
| |
| Ok(BiosImage { |
| pcir, |
| npde, |
| data: data_copy, |
| }) |
| } |
| } |
| |
| impl PciAtBiosImage { |
| /// Find a byte pattern in a slice. |
| fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> { |
| haystack |
| .windows(needle.len()) |
| .position(|window| window == needle) |
| .ok_or(EINVAL) |
| } |
| |
| /// Find the BIT header in the [`PciAtBiosImage`]. |
| fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> { |
| let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00]; |
| let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?; |
| let bit_header = BitHeader::new(&data[bit_offset..])?; |
| |
| Ok((bit_header, bit_offset)) |
| } |
| |
| /// Get a BIT token entry from the BIT table in the [`PciAtBiosImage`] |
| fn get_bit_token(&self, token_id: u8) -> Result<BitToken> { |
| BitToken::from_id(self, token_id) |
| } |
| |
| /// Find the Falcon data offset from the start of the FWSEC region. |
| /// |
| /// The BIT table contains a 4-byte pointer to the Falcon data. Testing shows this pointer |
| /// treats the PCI-AT and FWSEC images as logically contiguous even when an EFI image sits in |
| /// between them, so subtract the PCI-AT image size here to convert it to a FWSEC-relative |
| /// offset. |
| fn falcon_data_offset(&self, dev: &device::Device) -> Result<usize> { |
| let token = self.get_bit_token(BitToken::ID_FALCON_DATA)?; |
| let offset = usize::from(token.data_offset); |
| |
| // Read the 4-byte falcon data pointer at the offset specified in the token. |
| let data = &self.base.data; |
| let (ptr, _) = data |
| .get(offset..) |
| .and_then(|p| u32::read_from_prefix(p).ok()) |
| .ok_or(EINVAL)?; |
| |
| usize::from_safe_cast(ptr) |
| .checked_sub(data.len()) |
| .ok_or(EINVAL) |
| .inspect_err(|_| { |
| dev_err!(dev, "Falcon data pointer out of bounds\n"); |
| }) |
| } |
| } |
| |
| impl TryFrom<BiosImage> for PciAtBiosImage { |
| type Error = Error; |
| |
| fn try_from(base: BiosImage) -> Result<Self> { |
| let data_slice = &base.data; |
| let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?; |
| |
| Ok(PciAtBiosImage { |
| base, |
| bit_header, |
| bit_offset, |
| }) |
| } |
| } |
| |
| /// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`]. |
| /// |
| /// See the [`PmuLookupTable`] description for more information. |
| #[derive(FromBytes)] |
| #[repr(C, packed)] |
| struct PmuLookupTableEntry { |
| application_id: u8, |
| target_id: u8, |
| data: u32, |
| } |
| |
| impl PmuLookupTableEntry { |
| /// PMU lookup table application ID for firmware security license ucode. |
| #[expect(dead_code)] |
| const APPID_FIRMWARE_SEC_LIC: u8 = 0x05; |
| /// PMU lookup table application ID for debug FWSEC ucode. |
| #[expect(dead_code)] |
| const APPID_FWSEC_DBG: u8 = 0x45; |
| /// PMU lookup table application ID for production FWSEC ucode. |
| const APPID_FWSEC_PROD: u8 = 0x85; |
| } |
| |
| #[repr(C)] |
| #[derive(FromBytes)] |
| struct PmuLookupTableHeader { |
| version: u8, |
| header_len: u8, |
| entry_len: u8, |
| entry_count: u8, |
| } |
| |
| /// The [`PmuLookupTableEntry`] structure is used to find the [`PmuLookupTableEntry`] for a given |
| /// application ID. |
| /// |
| /// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to |
| /// locate the Falcon Ucode. |
| struct PmuLookupTable { |
| entries: KVVec<PmuLookupTableEntry>, |
| } |
| |
| impl PmuLookupTable { |
| fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { |
| let (header, _) = PmuLookupTableHeader::read_from_prefix(data).map_err(|_| EINVAL)?; |
| |
| let header_len = usize::from(header.header_len); |
| let entry_len = usize::from(header.entry_len); |
| let entry_count = usize::from(header.entry_count); |
| |
| let data = data |
| .get(header_len..header_len + entry_count * entry_len) |
| .ok_or(EINVAL) |
| .inspect_err(|_| { |
| dev_err!(dev, "PmuLookupTable data length less than required\n"); |
| })?; |
| |
| let mut entries = KVVec::with_capacity(entry_count, GFP_KERNEL)?; |
| for i in 0..entry_count { |
| let (entry, _) = PmuLookupTableEntry::read_from_prefix(&data[i * entry_len..]) |
| .map_err(|_| EINVAL)?; |
| entries.push(entry, GFP_KERNEL)?; |
| } |
| |
| Ok(PmuLookupTable { entries }) |
| } |
| |
| // find entry by type value |
| fn find_entry_by_type(&self, entry_type: u8) -> Result<&PmuLookupTableEntry> { |
| self.entries |
| .iter() |
| .find(|entry| entry.application_id == entry_type) |
| .ok_or(EINVAL) |
| } |
| } |
| |
| impl FwSecBiosImage { |
| /// Build the final `FwSecBiosImage` from the PCI-AT and FWSEC BIOS images. |
| fn new( |
| dev: &device::Device, |
| pci_at_image: PciAtBiosImage, |
| data: KVVec<u8>, |
| ) -> Result<FwSecBiosImage> { |
| let offset = pci_at_image.falcon_data_offset(dev)?; |
| |
| let pmu_lookup_data = data.get(offset..).ok_or(EINVAL)?; |
| let pmu_lookup_table = PmuLookupTable::new(dev, pmu_lookup_data)?; |
| |
| let entry = pmu_lookup_table |
| .find_entry_by_type(PmuLookupTableEntry::APPID_FWSEC_PROD) |
| .inspect_err(|e| { |
| dev_err!(dev, "PmuLookupTableEntry not found, error: {:?}\n", e); |
| })?; |
| |
| let falcon_ucode_offset = usize::from_safe_cast(entry.data) |
| .checked_sub(pci_at_image.base.data.len()) |
| .ok_or(EINVAL) |
| .inspect_err(|_| { |
| dev_err!(dev, "Falcon Ucode offset not in Fwsec.\n"); |
| })?; |
| |
| Ok(FwSecBiosImage { |
| dev: dev.into(), |
| data, |
| falcon_ucode_offset, |
| }) |
| } |
| |
| /// Get the FwSec header ([`FalconUCodeDesc`]). |
| pub(crate) fn header(&self) -> Result<FalconUCodeDesc> { |
| let data = self.data.get(self.falcon_ucode_offset..).ok_or(EINVAL)?; |
| |
| // Read the version byte from the header. |
| let ver = data.get(1).copied().ok_or(EINVAL)?; |
| match ver { |
| 2 => { |
| let (v2, _) = FalconUCodeDescV2::read_from_prefix(data).map_err(|_| EINVAL)?; |
| Ok(FalconUCodeDesc::V2(v2)) |
| } |
| 3 => { |
| let (v3, _) = FalconUCodeDescV3::read_from_prefix(data).map_err(|_| EINVAL)?; |
| Ok(FalconUCodeDesc::V3(v3)) |
| } |
| _ => { |
| dev_err!(self.dev, "invalid fwsec firmware version: {:?}\n", ver); |
| Err(EINVAL) |
| } |
| } |
| } |
| |
| /// Get the ucode data as a byte slice |
| pub(crate) fn ucode(&self, desc: &FalconUCodeDesc) -> Result<&[u8]> { |
| let size = usize::from_safe_cast( |
| desc.imem_load_size() |
| .checked_add(desc.dmem_load_size()) |
| .ok_or(ERANGE)?, |
| ); |
| |
| // The ucode data follows the descriptor. |
| self.data |
| .get(self.falcon_ucode_offset..) |
| .and_then(|data| data.get(desc.size()..)) |
| .and_then(|data| data.get(..size)) |
| .ok_or(ERANGE) |
| .inspect_err(|_| { |
| dev_err!( |
| self.dev, |
| "fwsec ucode data not contained within BIOS bounds\n" |
| ) |
| }) |
| } |
| |
| /// Get the signatures as a byte slice |
| pub(crate) fn sigs(&self, desc: &FalconUCodeDesc) -> Result<&[Bcrt30Rsa3kSignature]> { |
| let hdr_size = match desc { |
| FalconUCodeDesc::V2(_v2) => core::mem::size_of::<FalconUCodeDescV2>(), |
| FalconUCodeDesc::V3(_v3) => core::mem::size_of::<FalconUCodeDescV3>(), |
| }; |
| // The signatures data follows the descriptor. |
| let sigs_data_offset = self.falcon_ucode_offset + hdr_size; |
| let sigs_count = usize::from(desc.signature_count()); |
| let sigs_size = sigs_count * core::mem::size_of::<Bcrt30Rsa3kSignature>(); |
| |
| // Make sure the data is within bounds. |
| if sigs_data_offset + sigs_size > self.data.len() { |
| dev_err!( |
| self.dev, |
| "fwsec signatures data not contained within BIOS bounds\n" |
| ); |
| return Err(ERANGE); |
| } |
| |
| // SAFETY: we checked that `data + sigs_data_offset + (signature_count * |
| // sizeof::<Bcrt30Rsa3kSignature>()` is within the bounds of `data`. |
| Ok(unsafe { |
| core::slice::from_raw_parts( |
| self.data |
| .as_ptr() |
| .add(sigs_data_offset) |
| .cast::<Bcrt30Rsa3kSignature>(), |
| sigs_count, |
| ) |
| }) |
| } |
| } |