mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 00:28:52 +02:00 
			
		
		
		
	 93296e9d95
			
		
	
	
		93296e9d95
		
	
	
	
	
		
			
			Now that the vbios code uses a non-bound `Device` instance, store an `ARef` to it at construction time so we can use it for logging without having to carry an extra argument on every method for that sole purpose. Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com> Acked-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/r/20250808-vbios_device-v1-2-834bbbab6471@nvidia.com Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
		
			
				
	
	
		
			1158 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1158 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| 
 | |
| //! VBIOS extraction and parsing.
 | |
| 
 | |
| use crate::driver::Bar0;
 | |
| use crate::firmware::fwsec::Bcrt30Rsa3kSignature;
 | |
| use crate::firmware::FalconUCodeDescV3;
 | |
| use core::convert::TryFrom;
 | |
| use kernel::device;
 | |
| use kernel::error::Result;
 | |
| use kernel::prelude::*;
 | |
| use kernel::types::ARef;
 | |
| 
 | |
| /// 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;
 | |
| /// The bit in the last image indicator byte for the PCI Data Structure that
 | |
| /// indicates the last image. Bit 0-6 are reserved, bit 7 is last image bit.
 | |
| const LAST_IMAGE_BIT_MASK: u8 = 0x80;
 | |
| 
 | |
| // PMU lookup table entry types. Used to locate PMU table entries
 | |
| // in the Fwsec image, corresponding to falcon ucodes.
 | |
| #[expect(dead_code)]
 | |
| const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
 | |
| #[expect(dead_code)]
 | |
| const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45;
 | |
| const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85;
 | |
| 
 | |
| /// Vbios Reader for constructing the VBIOS data.
 | |
| struct VbiosIterator<'a> {
 | |
|     dev: &'a device::Device,
 | |
|     bar0: &'a Bar0,
 | |
|     /// 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: KVec<u8>,
 | |
|     /// Current offset of the [`Iterator`].
 | |
|     current_offset: usize,
 | |
|     /// Indicate whether the last image has been found.
 | |
|     last_found: bool,
 | |
| }
 | |
| 
 | |
| impl<'a> VbiosIterator<'a> {
 | |
|     fn new(dev: &'a device::Device, bar0: &'a Bar0) -> Result<Self> {
 | |
|         Ok(Self {
 | |
|             dev,
 | |
|             bar0,
 | |
|             data: KVec::new(),
 | |
|             current_offset: 0,
 | |
|             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 current_len = self.data.len();
 | |
|         let start = ROM_OFFSET + current_len;
 | |
| 
 | |
|         // 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..start + len).step_by(core::mem::size_of::<u32>()) {
 | |
|             // Read 32-bit word from the VBIOS ROM
 | |
|             let word = self.bar0.try_read32(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 {
 | |
|         if offset > BIOS_MAX_SCAN_LEN {
 | |
|             dev_err!(self.dev, "Error: exceeded BIOS scan limit.\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         // If `offset` is beyond current data size, fill the gap first.
 | |
|         let current_len = self.data.len();
 | |
|         let gap_bytes = offset.saturating_sub(current_len);
 | |
| 
 | |
|         // Now read the requested bytes at the offset.
 | |
|         self.read_more(gap_bytes + 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 data_len = self.data.len();
 | |
|         if offset + len > 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..offset + len]).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 > 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,
 | |
|             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;
 | |
|         // TODO[NUMM]: replace with `align_up` once it lands.
 | |
|         self.current_offset = self.current_offset.next_multiple_of(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 first_fwsec_image: Option<FwSecBiosBuilder> = None;
 | |
|         let mut second_fwsec_image: Option<FwSecBiosBuilder> = None;
 | |
| 
 | |
|         // Parse all VBIOS images in the ROM
 | |
|         for image_result in VbiosIterator::new(dev, bar0)? {
 | |
|             let full_image = image_result?;
 | |
| 
 | |
|             dev_dbg!(
 | |
|                 dev,
 | |
|                 "Found BIOS image: size: {:#x}, type: {}, last: {}\n",
 | |
|                 full_image.image_size_bytes(),
 | |
|                 full_image.image_type_str(),
 | |
|                 full_image.is_last()
 | |
|             );
 | |
| 
 | |
|             // Get references to images we will need after the loop, in order to
 | |
|             // setup the falcon data offset.
 | |
|             match full_image {
 | |
|                 BiosImage::PciAt(image) => {
 | |
|                     pci_at_image = Some(image);
 | |
|                 }
 | |
|                 BiosImage::FwSec(image) => {
 | |
|                     if first_fwsec_image.is_none() {
 | |
|                         first_fwsec_image = Some(image);
 | |
|                     } else {
 | |
|                         second_fwsec_image = Some(image);
 | |
|                     }
 | |
|                 }
 | |
|                 // For now we don't need to handle these
 | |
|                 BiosImage::Efi(_image) => {}
 | |
|                 BiosImage::Nbsi(_image) => {}
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Using all the images, setup the falcon data pointer in Fwsec.
 | |
|         if let (Some(mut second), Some(first), Some(pci_at)) =
 | |
|             (second_fwsec_image, first_fwsec_image, pci_at_image)
 | |
|         {
 | |
|             second
 | |
|                 .setup_falcon_data(&pci_at, &first)
 | |
|                 .inspect_err(|e| dev_err!(dev, "Falcon data setup failed: {:?}\n", e))?;
 | |
|             Ok(Vbios {
 | |
|                 fwsec_image: second.build()?,
 | |
|             })
 | |
|         } else {
 | |
|             dev_err!(
 | |
|                 dev,
 | |
|                 "Missing required images for falcon data setup, skipping\n"
 | |
|             );
 | |
|             Err(EINVAL)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage {
 | |
|         &self.fwsec_image
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// PCI Data Structure as defined in PCI Firmware Specification
 | |
| #[derive(Debug, Clone)]
 | |
| #[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 {
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 | |
|         if data.len() < core::mem::size_of::<PcirStruct>() {
 | |
|             dev_err!(dev, "Not enough data for PcirStruct\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let mut signature = [0u8; 4];
 | |
|         signature.copy_from_slice(&data[0..4]);
 | |
| 
 | |
|         // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
 | |
|         if &signature != b"PCIR" && &signature != b"NPDS" {
 | |
|             dev_err!(dev, "Invalid signature for PcirStruct: {:?}\n", signature);
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let mut class_code = [0u8; 3];
 | |
|         class_code.copy_from_slice(&data[13..16]);
 | |
| 
 | |
|         let image_len = u16::from_le_bytes([data[16], data[17]]);
 | |
|         if image_len == 0 {
 | |
|             dev_err!(dev, "Invalid image length: 0\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         Ok(PcirStruct {
 | |
|             signature,
 | |
|             vendor_id: u16::from_le_bytes([data[4], data[5]]),
 | |
|             device_id: u16::from_le_bytes([data[6], data[7]]),
 | |
|             device_list_ptr: u16::from_le_bytes([data[8], data[9]]),
 | |
|             pci_data_struct_len: u16::from_le_bytes([data[10], data[11]]),
 | |
|             pci_data_struct_rev: data[12],
 | |
|             class_code,
 | |
|             image_len,
 | |
|             vendor_rom_rev: u16::from_le_bytes([data[18], data[19]]),
 | |
|             code_type: data[20],
 | |
|             last_image: data[21],
 | |
|             max_runtime_image_len: u16::from_le_bytes([data[22], data[23]]),
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Check if this is the last image in the ROM.
 | |
|     fn is_last(&self) -> bool {
 | |
|         self.last_image & LAST_IMAGE_BIT_MASK != 0
 | |
|     }
 | |
| 
 | |
|     /// Calculate image size in bytes from 512-byte blocks.
 | |
|     fn image_size_bytes(&self) -> usize {
 | |
|         self.image_len as usize * 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)]
 | |
| #[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> {
 | |
|         if data.len() < core::mem::size_of::<Self>() {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let mut signature = [0u8; 4];
 | |
|         signature.copy_from_slice(&data[2..6]);
 | |
| 
 | |
|         // Check header ID and signature
 | |
|         let id = u16::from_le_bytes([data[0], data[1]]);
 | |
|         if id != 0xB8FF || &signature != b"BIT\0" {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         Ok(BitHeader {
 | |
|             id,
 | |
|             signature,
 | |
|             bcd_version: u16::from_le_bytes([data[6], data[7]]),
 | |
|             header_size: data[8],
 | |
|             token_size: data[9],
 | |
|             token_entries: data[10],
 | |
|             checksum: data[11],
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// BIT Token Entry: Records in the BIT table followed by the BIT header.
 | |
| #[derive(Debug, Clone, Copy)]
 | |
| #[expect(dead_code)]
 | |
| 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,
 | |
| }
 | |
| 
 | |
| // Define the token ID for the Falcon data
 | |
| const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
 | |
| 
 | |
| impl BitToken {
 | |
|     /// 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;
 | |
| 
 | |
|         // Offset to the first token entry
 | |
|         let tokens_start = image.bit_offset + header.header_size as usize;
 | |
| 
 | |
|         for i in 0..header.token_entries as usize {
 | |
|             let entry_offset = tokens_start + (i * header.token_size as usize);
 | |
| 
 | |
|             // Make sure we don't go out of bounds
 | |
|             if entry_offset + header.token_size as usize > image.base.data.len() {
 | |
|                 return Err(EINVAL);
 | |
|             }
 | |
| 
 | |
|             // Check if this token has the requested ID
 | |
|             if image.base.data[entry_offset] == token_id {
 | |
|                 return Ok(BitToken {
 | |
|                     id: image.base.data[entry_offset],
 | |
|                     data_version: image.base.data[entry_offset + 1],
 | |
|                     data_size: u16::from_le_bytes([
 | |
|                         image.base.data[entry_offset + 2],
 | |
|                         image.base.data[entry_offset + 3],
 | |
|                     ]),
 | |
|                     data_offset: u16::from_le_bytes([
 | |
|                         image.base.data[entry_offset + 4],
 | |
|                         image.base.data[entry_offset + 5],
 | |
|                     ]),
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Token not found
 | |
|         Err(ENOENT)
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// PCI ROM Expansion Header as defined in PCI Firmware Specification.
 | |
| ///
 | |
| /// This is 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. For "NBSI" images (NoteBook
 | |
| /// System Information), the ROM header deviates from the standard and contains an offset to the
 | |
| /// NBSI image however we do not yet parse that in this module and keep it for future reference.
 | |
| #[derive(Debug, Clone, Copy)]
 | |
| #[expect(dead_code)]
 | |
| struct PciRomHeader {
 | |
|     /// 00h: Signature (0xAA55)
 | |
|     signature: u16,
 | |
|     /// 02h: Reserved bytes for processor architecture unique data (20 bytes)
 | |
|     reserved: [u8; 20],
 | |
|     /// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image)
 | |
|     nbsi_data_offset: Option<u16>,
 | |
|     /// 18h: Pointer to PCI Data Structure (offset from start of ROM image)
 | |
|     pci_data_struct_offset: u16,
 | |
|     /// 1Ah: Size of block (this is NBSI-specific)
 | |
|     size_of_block: Option<u32>,
 | |
| }
 | |
| 
 | |
| impl PciRomHeader {
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 | |
|         if data.len() < 26 {
 | |
|             // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock.
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let signature = u16::from_le_bytes([data[0], data[1]]);
 | |
| 
 | |
|         // Check for valid ROM signatures.
 | |
|         match signature {
 | |
|             0xAA55 | 0xBB77 | 0x4E56 => {}
 | |
|             _ => {
 | |
|                 dev_err!(dev, "ROM signature unknown {:#x}\n", signature);
 | |
|                 return Err(EINVAL);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Read the pointer to the PCI Data Structure at offset 0x18.
 | |
|         let pci_data_struct_ptr = u16::from_le_bytes([data[24], data[25]]);
 | |
| 
 | |
|         // Try to read optional fields if enough data.
 | |
|         let mut size_of_block = None;
 | |
|         let mut nbsi_data_offset = None;
 | |
| 
 | |
|         if data.len() >= 30 {
 | |
|             // Read size_of_block at offset 0x1A.
 | |
|             size_of_block = Some(
 | |
|                 u32::from(data[29]) << 24
 | |
|                     | u32::from(data[28]) << 16
 | |
|                     | u32::from(data[27]) << 8
 | |
|                     | u32::from(data[26]),
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         // For NBSI images, try to read the nbsiDataOffset at offset 0x16.
 | |
|         if data.len() >= 24 {
 | |
|             nbsi_data_offset = Some(u16::from_le_bytes([data[22], data[23]]));
 | |
|         }
 | |
| 
 | |
|         Ok(PciRomHeader {
 | |
|             signature,
 | |
|             reserved: [0u8; 20],
 | |
|             pci_data_struct_offset: pci_data_struct_ptr,
 | |
|             size_of_block,
 | |
|             nbsi_data_offset,
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// 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)]
 | |
| #[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 {
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Option<Self> {
 | |
|         if data.len() < core::mem::size_of::<Self>() {
 | |
|             dev_dbg!(dev, "Not enough data for NpdeStruct\n");
 | |
|             return None;
 | |
|         }
 | |
| 
 | |
|         let mut signature = [0u8; 4];
 | |
|         signature.copy_from_slice(&data[0..4]);
 | |
| 
 | |
|         // Signature should be "NPDE" (0x4544504E).
 | |
|         if &signature != b"NPDE" {
 | |
|             dev_dbg!(dev, "Invalid signature for NpdeStruct: {:?}\n", signature);
 | |
|             return None;
 | |
|         }
 | |
| 
 | |
|         let subimage_len = u16::from_le_bytes([data[8], data[9]]);
 | |
|         if subimage_len == 0 {
 | |
|             dev_dbg!(dev, "Invalid subimage length: 0\n");
 | |
|             return None;
 | |
|         }
 | |
| 
 | |
|         Some(NpdeStruct {
 | |
|             signature,
 | |
|             npci_data_ext_rev: u16::from_le_bytes([data[4], data[5]]),
 | |
|             npci_data_ext_len: u16::from_le_bytes([data[6], data[7]]),
 | |
|             subimage_len,
 | |
|             last_image: data[10],
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Check if this is the last image in the ROM.
 | |
|     fn is_last(&self) -> bool {
 | |
|         self.last_image & LAST_IMAGE_BIT_MASK != 0
 | |
|     }
 | |
| 
 | |
|     /// Calculate image size in bytes from 512-byte blocks.
 | |
|     fn image_size_bytes(&self) -> usize {
 | |
|         self.subimage_len as usize * 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 = rom_header.pci_data_struct_offset as usize;
 | |
|         let npde_start = (pcir_offset + pcir.pci_data_struct_len as usize + 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..])
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Use a macro to implement BiosImage enum and methods. This avoids having to
 | |
| // repeat each enum type when implementing functions like base() in BiosImage.
 | |
| macro_rules! bios_image {
 | |
|     (
 | |
|         $($variant:ident: $class:ident),* $(,)?
 | |
|     ) => {
 | |
|         // BiosImage enum with variants for each image type
 | |
|         enum BiosImage {
 | |
|             $($variant($class)),*
 | |
|         }
 | |
| 
 | |
|         impl BiosImage {
 | |
|             /// Get a reference to the common BIOS image data regardless of type
 | |
|             fn base(&self) -> &BiosImageBase {
 | |
|                 match self {
 | |
|                     $(Self::$variant(img) => &img.base),*
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             /// Returns a string representing the type of BIOS image
 | |
|             fn image_type_str(&self) -> &'static str {
 | |
|                 match self {
 | |
|                     $(Self::$variant(_) => stringify!($variant)),*
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl BiosImage {
 | |
|     /// Check if this is the last image.
 | |
|     fn is_last(&self) -> bool {
 | |
|         let base = self.base();
 | |
| 
 | |
|         // For NBSI images (type == 0x70), return true as they're
 | |
|         // considered the last image
 | |
|         if matches!(self, Self::Nbsi(_)) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         // For other image types, check the NPDE first if available
 | |
|         if let Some(ref npde) = base.npde {
 | |
|             return npde.is_last();
 | |
|         }
 | |
| 
 | |
|         // Otherwise, fall back to checking the PCIR last_image flag
 | |
|         base.pcir.is_last()
 | |
|     }
 | |
| 
 | |
|     /// Get the image size in bytes.
 | |
|     fn image_size_bytes(&self) -> usize {
 | |
|         let base = self.base();
 | |
| 
 | |
|         // Prefer NPDE image size if available
 | |
|         if let Some(ref npde) = base.npde {
 | |
|             return npde.image_size_bytes();
 | |
|         }
 | |
| 
 | |
|         // Otherwise, fall back to the PCIR image size
 | |
|         base.pcir.image_size_bytes()
 | |
|     }
 | |
| 
 | |
|     /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which
 | |
|     /// triggers the constructor of the specific BiosImage enum variant.
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 | |
|         let base = BiosImageBase::new(dev, data)?;
 | |
|         let image = base.into_image().inspect_err(|e| {
 | |
|             dev_err!(dev, "Failed to create BiosImage: {:?}\n", e);
 | |
|         })?;
 | |
| 
 | |
|         Ok(image)
 | |
|     }
 | |
| }
 | |
| 
 | |
| bios_image! {
 | |
|     PciAt: PciAtBiosImage,   // PCI-AT compatible BIOS image
 | |
|     Efi: EfiBiosImage,       // EFI (Extensible Firmware Interface)
 | |
|     Nbsi: NbsiBiosImage,     // NBSI (Nvidia Bios System Interface)
 | |
|     FwSec: FwSecBiosBuilder, // FWSEC (Firmware Security)
 | |
| }
 | |
| 
 | |
| /// 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: BiosImageBase,
 | |
|     bit_header: BitHeader,
 | |
|     bit_offset: usize,
 | |
| }
 | |
| 
 | |
| struct EfiBiosImage {
 | |
|     base: BiosImageBase,
 | |
|     // EFI-specific fields can be added here in the future.
 | |
| }
 | |
| 
 | |
| struct NbsiBiosImage {
 | |
|     base: BiosImageBase,
 | |
|     // NBSI-specific fields can be added here in the future.
 | |
| }
 | |
| 
 | |
| struct FwSecBiosBuilder {
 | |
|     base: BiosImageBase,
 | |
|     /// These are temporary fields that are used during the construction of the
 | |
|     /// [`FwSecBiosBuilder`].
 | |
|     ///
 | |
|     /// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new
 | |
|     /// [`FwSecBiosImage`].
 | |
|     ///
 | |
|     /// The offset of the Falcon data from the start of Fwsec image.
 | |
|     falcon_data_offset: Option<usize>,
 | |
|     /// The [`PmuLookupTable`] starts at the offset of the falcon data pointer.
 | |
|     pmu_lookup_table: Option<PmuLookupTable>,
 | |
|     /// The offset of the Falcon ucode.
 | |
|     falcon_ucode_offset: Option<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 {
 | |
|     base: BiosImageBase,
 | |
|     /// The offset of the Falcon ucode.
 | |
|     falcon_ucode_offset: usize,
 | |
| }
 | |
| 
 | |
| // Convert from BiosImageBase to BiosImage
 | |
| impl TryFrom<BiosImageBase> for BiosImage {
 | |
|     type Error = Error;
 | |
| 
 | |
|     fn try_from(base: BiosImageBase) -> Result<Self> {
 | |
|         match base.pcir.code_type {
 | |
|             0x00 => Ok(BiosImage::PciAt(base.try_into()?)),
 | |
|             0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })),
 | |
|             0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })),
 | |
|             0xE0 => Ok(BiosImage::FwSec(FwSecBiosBuilder {
 | |
|                 base,
 | |
|                 falcon_data_offset: None,
 | |
|                 pmu_lookup_table: None,
 | |
|                 falcon_ucode_offset: None,
 | |
|             })),
 | |
|             _ => Err(EINVAL),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// BIOS Image structure containing various headers and reference fields to all BIOS images.
 | |
| ///
 | |
| /// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that
 | |
| /// Rust favors composition of types over inheritance.
 | |
| #[expect(dead_code)]
 | |
| struct BiosImageBase {
 | |
|     /// Used for logging.
 | |
|     dev: ARef<device::Device>,
 | |
|     /// PCI ROM Expansion Header
 | |
|     rom_header: PciRomHeader,
 | |
|     /// PCI Data Structure
 | |
|     pcir: PcirStruct,
 | |
|     /// NVIDIA PCI Data Extension (optional)
 | |
|     npde: Option<NpdeStruct>,
 | |
|     /// Image data (includes ROM header and PCIR)
 | |
|     data: KVec<u8>,
 | |
| }
 | |
| 
 | |
| impl BiosImageBase {
 | |
|     fn into_image(self) -> Result<BiosImage> {
 | |
|         BiosImage::try_from(self)
 | |
|     }
 | |
| 
 | |
|     /// Creates a new BiosImageBase from raw byte data.
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 | |
|         // Ensure we have enough data for the ROM header.
 | |
|         if data.len() < 26 {
 | |
|             dev_err!(dev, "Not enough data for ROM header\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         // Parse the ROM header.
 | |
|         let rom_header = PciRomHeader::new(dev, &data[0..26])
 | |
|             .inspect_err(|e| dev_err!(dev, "Failed to create PciRomHeader: {:?}\n", e))?;
 | |
| 
 | |
|         // Get the PCI Data Structure using the pointer from the ROM header.
 | |
|         let pcir_offset = rom_header.pci_data_struct_offset as usize;
 | |
|         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 = KVec::new();
 | |
|         data_copy.extend_from_slice(data, GFP_KERNEL)?;
 | |
| 
 | |
|         Ok(BiosImageBase {
 | |
|             dev: dev.into(),
 | |
|             rom_header,
 | |
|             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 pointer structure in the [`PciAtBiosImage`].
 | |
|     ///
 | |
|     /// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC
 | |
|     /// image.
 | |
|     fn falcon_data_ptr(&self) -> Result<u32> {
 | |
|         let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?;
 | |
| 
 | |
|         // Make sure we don't go out of bounds
 | |
|         if token.data_offset as usize + 4 > self.base.data.len() {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         // read the 4 bytes at the offset specified in the token
 | |
|         let offset = token.data_offset as usize;
 | |
|         let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| {
 | |
|             dev_err!(self.base.dev, "Failed to convert data slice to array");
 | |
|             EINVAL
 | |
|         })?;
 | |
| 
 | |
|         let data_ptr = u32::from_le_bytes(bytes);
 | |
| 
 | |
|         if (data_ptr as usize) < self.base.data.len() {
 | |
|             dev_err!(self.base.dev, "Falcon data pointer out of bounds\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         Ok(data_ptr)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl TryFrom<BiosImageBase> for PciAtBiosImage {
 | |
|     type Error = Error;
 | |
| 
 | |
|     fn try_from(base: BiosImageBase) -> 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.
 | |
| #[repr(C, packed)]
 | |
| struct PmuLookupTableEntry {
 | |
|     application_id: u8,
 | |
|     target_id: u8,
 | |
|     data: u32,
 | |
| }
 | |
| 
 | |
| impl PmuLookupTableEntry {
 | |
|     fn new(data: &[u8]) -> Result<Self> {
 | |
|         if data.len() < core::mem::size_of::<Self>() {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         Ok(PmuLookupTableEntry {
 | |
|             application_id: data[0],
 | |
|             target_id: data[1],
 | |
|             data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?),
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// 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.
 | |
| #[expect(dead_code)]
 | |
| struct PmuLookupTable {
 | |
|     version: u8,
 | |
|     header_len: u8,
 | |
|     entry_len: u8,
 | |
|     entry_count: u8,
 | |
|     table_data: KVec<u8>,
 | |
| }
 | |
| 
 | |
| impl PmuLookupTable {
 | |
|     fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 | |
|         if data.len() < 4 {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let header_len = data[1] as usize;
 | |
|         let entry_len = data[2] as usize;
 | |
|         let entry_count = data[3] as usize;
 | |
| 
 | |
|         let required_bytes = header_len + (entry_count * entry_len);
 | |
| 
 | |
|         if data.len() < required_bytes {
 | |
|             dev_err!(dev, "PmuLookupTable data length less than required\n");
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         // Create a copy of only the table data
 | |
|         let table_data = {
 | |
|             let mut ret = KVec::new();
 | |
|             ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
 | |
|             ret
 | |
|         };
 | |
| 
 | |
|         // Debug logging of entries (dumps the table data to dmesg)
 | |
|         for i in (header_len..required_bytes).step_by(entry_len) {
 | |
|             dev_dbg!(dev, "PMU entry: {:02x?}\n", &data[i..][..entry_len]);
 | |
|         }
 | |
| 
 | |
|         Ok(PmuLookupTable {
 | |
|             version: data[0],
 | |
|             header_len: header_len as u8,
 | |
|             entry_len: entry_len as u8,
 | |
|             entry_count: entry_count as u8,
 | |
|             table_data,
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
 | |
|         if idx >= self.entry_count {
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         let index = (idx as usize) * self.entry_len as usize;
 | |
|         PmuLookupTableEntry::new(&self.table_data[index..])
 | |
|     }
 | |
| 
 | |
|     // find entry by type value
 | |
|     fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> {
 | |
|         for i in 0..self.entry_count {
 | |
|             let entry = self.lookup_index(i)?;
 | |
|             if entry.application_id == entry_type {
 | |
|                 return Ok(entry);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Err(EINVAL)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl FwSecBiosBuilder {
 | |
|     fn setup_falcon_data(
 | |
|         &mut self,
 | |
|         pci_at_image: &PciAtBiosImage,
 | |
|         first_fwsec: &FwSecBiosBuilder,
 | |
|     ) -> Result {
 | |
|         let mut offset = pci_at_image.falcon_data_ptr()? as usize;
 | |
|         let mut pmu_in_first_fwsec = false;
 | |
| 
 | |
|         // The falcon data pointer assumes that the PciAt and FWSEC images
 | |
|         // are contiguous in memory. However, testing shows the EFI image sits in
 | |
|         // between them. So calculate the offset from the end of the PciAt image
 | |
|         // rather than the start of it. Compensate.
 | |
|         offset -= pci_at_image.base.data.len();
 | |
| 
 | |
|         // The offset is now from the start of the first Fwsec image, however
 | |
|         // the offset points to a location in the second Fwsec image. Since
 | |
|         // the fwsec images are contiguous, subtract the length of the first Fwsec
 | |
|         // image from the offset to get the offset to the start of the second
 | |
|         // Fwsec image.
 | |
|         if offset < first_fwsec.base.data.len() {
 | |
|             pmu_in_first_fwsec = true;
 | |
|         } else {
 | |
|             offset -= first_fwsec.base.data.len();
 | |
|         }
 | |
| 
 | |
|         self.falcon_data_offset = Some(offset);
 | |
| 
 | |
|         if pmu_in_first_fwsec {
 | |
|             self.pmu_lookup_table = Some(PmuLookupTable::new(
 | |
|                 &self.base.dev,
 | |
|                 &first_fwsec.base.data[offset..],
 | |
|             )?);
 | |
|         } else {
 | |
|             self.pmu_lookup_table = Some(PmuLookupTable::new(
 | |
|                 &self.base.dev,
 | |
|                 &self.base.data[offset..],
 | |
|             )?);
 | |
|         }
 | |
| 
 | |
|         match self
 | |
|             .pmu_lookup_table
 | |
|             .as_ref()
 | |
|             .ok_or(EINVAL)?
 | |
|             .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD)
 | |
|         {
 | |
|             Ok(entry) => {
 | |
|                 let mut ucode_offset = entry.data as usize;
 | |
|                 ucode_offset -= pci_at_image.base.data.len();
 | |
|                 if ucode_offset < first_fwsec.base.data.len() {
 | |
|                     dev_err!(self.base.dev, "Falcon Ucode offset not in second Fwsec.\n");
 | |
|                     return Err(EINVAL);
 | |
|                 }
 | |
|                 ucode_offset -= first_fwsec.base.data.len();
 | |
|                 self.falcon_ucode_offset = Some(ucode_offset);
 | |
|             }
 | |
|             Err(e) => {
 | |
|                 dev_err!(
 | |
|                     self.base.dev,
 | |
|                     "PmuLookupTableEntry not found, error: {:?}\n",
 | |
|                     e
 | |
|                 );
 | |
|                 return Err(EINVAL);
 | |
|             }
 | |
|         }
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Build the final FwSecBiosImage from this builder
 | |
|     fn build(self) -> Result<FwSecBiosImage> {
 | |
|         let ret = FwSecBiosImage {
 | |
|             base: self.base,
 | |
|             falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?,
 | |
|         };
 | |
| 
 | |
|         if cfg!(debug_assertions) {
 | |
|             // Print the desc header for debugging
 | |
|             let desc = ret.header()?;
 | |
|             dev_dbg!(ret.base.dev, "PmuLookupTableEntry desc: {:#?}\n", desc);
 | |
|         }
 | |
| 
 | |
|         Ok(ret)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl FwSecBiosImage {
 | |
|     /// Get the FwSec header ([`FalconUCodeDescV3`]).
 | |
|     pub(crate) fn header(&self) -> Result<&FalconUCodeDescV3> {
 | |
|         // Get the falcon ucode offset that was found in setup_falcon_data.
 | |
|         let falcon_ucode_offset = self.falcon_ucode_offset;
 | |
| 
 | |
|         // Make sure the offset is within the data bounds.
 | |
|         if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() {
 | |
|             dev_err!(
 | |
|                 self.base.dev,
 | |
|                 "fwsec-frts header not contained within BIOS bounds\n"
 | |
|             );
 | |
|             return Err(ERANGE);
 | |
|         }
 | |
| 
 | |
|         // Read the first 4 bytes to get the version.
 | |
|         let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4]
 | |
|             .try_into()
 | |
|             .map_err(|_| EINVAL)?;
 | |
|         let hdr = u32::from_le_bytes(hdr_bytes);
 | |
|         let ver = (hdr & 0xff00) >> 8;
 | |
| 
 | |
|         if ver != 3 {
 | |
|             dev_err!(self.base.dev, "invalid fwsec firmware version: {:?}\n", ver);
 | |
|             return Err(EINVAL);
 | |
|         }
 | |
| 
 | |
|         // Return a reference to the FalconUCodeDescV3 structure.
 | |
|         //
 | |
|         // SAFETY: We have checked that `falcon_ucode_offset + size_of::<FalconUCodeDescV3>` is
 | |
|         // within the bounds of `data`. Also, this data vector is from ROM, and the `data` field
 | |
|         // in `BiosImageBase` is immutable after construction.
 | |
|         Ok(unsafe {
 | |
|             &*(self
 | |
|                 .base
 | |
|                 .data
 | |
|                 .as_ptr()
 | |
|                 .add(falcon_ucode_offset)
 | |
|                 .cast::<FalconUCodeDescV3>())
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Get the ucode data as a byte slice
 | |
|     pub(crate) fn ucode(&self, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
 | |
|         let falcon_ucode_offset = self.falcon_ucode_offset;
 | |
| 
 | |
|         // The ucode data follows the descriptor.
 | |
|         let ucode_data_offset = falcon_ucode_offset + desc.size();
 | |
|         let size = (desc.imem_load_size + desc.dmem_load_size) as usize;
 | |
| 
 | |
|         // Get the data slice, checking bounds in a single operation.
 | |
|         self.base
 | |
|             .data
 | |
|             .get(ucode_data_offset..ucode_data_offset + size)
 | |
|             .ok_or(ERANGE)
 | |
|             .inspect_err(|_| {
 | |
|                 dev_err!(
 | |
|                     self.base.dev,
 | |
|                     "fwsec ucode data not contained within BIOS bounds\n"
 | |
|                 )
 | |
|             })
 | |
|     }
 | |
| 
 | |
|     /// Get the signatures as a byte slice
 | |
|     pub(crate) fn sigs(&self, desc: &FalconUCodeDescV3) -> Result<&[Bcrt30Rsa3kSignature]> {
 | |
|         // The signatures data follows the descriptor.
 | |
|         let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>();
 | |
|         let sigs_size =
 | |
|             desc.signature_count as usize * core::mem::size_of::<Bcrt30Rsa3kSignature>();
 | |
| 
 | |
|         // Make sure the data is within bounds.
 | |
|         if sigs_data_offset + sigs_size > self.base.data.len() {
 | |
|             dev_err!(
 | |
|                 self.base.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.base
 | |
|                     .data
 | |
|                     .as_ptr()
 | |
|                     .add(sigs_data_offset)
 | |
|                     .cast::<Bcrt30Rsa3kSignature>(),
 | |
|                 desc.signature_count as usize,
 | |
|             )
 | |
|         })
 | |
|     }
 | |
| }
 |