// 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)?; Lines 1–500 of 978. Load more
// 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)?;