| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091 |
- // SPDX-License-Identifier: GPL-2.0
- //! VBIOS extraction and parsing.
- use core::convert::TryFrom;
- use kernel::{
- device,
- io::Io,
- prelude::*,
- ptr::{
- Alignable,
- Alignment, //
- },
- sync::aref::ARef,
- transmute::FromBytes,
- };
- use crate::{
- driver::Bar0,
- firmware::{
- fwsec::Bcrt30Rsa3kSignature,
- FalconUCodeDesc,
- FalconUCodeDescV2,
- FalconUCodeDescV3, //
- },
- num::FromSafeCast,
- };
- /// 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;
- /// 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),
- }
- }
- }
- // 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;
- 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 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 image = image_result?;
- dev_dbg!(
- dev,
- "Found BIOS image: size: {:#x}, type: {:?}, last: {}\n",
- image.image_size_bytes(),
- image.image_type(),
- image.is_last()
- );
- // Convert to a specific image type
- match BiosImageType::try_from(image.pcir.code_type) {
- Ok(BiosImageType::PciAt) => {
- pci_at_image = Some(PciAtBiosImage::try_from(image)?);
- }
- Ok(BiosImageType::FwSec) => {
- let fwsec = FwSecBiosBuilder {
- base: image,
- falcon_data_offset: None,
- pmu_lookup_table: None,
- falcon_ucode_offset: None,
- };
- if first_fwsec_image.is_none() {
- first_fwsec_image = Some(fwsec);
- } else {
- second_fwsec_image = Some(fwsec);
- }
- }
- _ => {
- // Ignore other image types or unknown types
- }
- }
- }
- // 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,
- }
- // SAFETY: all bit patterns are valid for `PcirStruct`.
- unsafe impl FromBytes for PcirStruct {}
- impl PcirStruct {
- fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
- let (pcir, _) = PcirStruct::from_bytes_copy_prefix(data).ok_or(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 & 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)]
- #[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,
- }
- // SAFETY: all bit patterns are valid for `BitHeader`.
- unsafe impl FromBytes for BitHeader {}
- impl BitHeader {
- fn new(data: &[u8]) -> Result<Self> {
- let (header, _) = BitHeader::from_bytes_copy_prefix(data).ok_or(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)]
- #[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 + usize::from(header.header_size);
- for i in 0..usize::from(header.token_entries) {
- let entry_offset = tokens_start + (i * usize::from(header.token_size));
- // Make sure we don't go out of bounds
- if entry_offset + usize::from(header.token_size) > 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,
- }
- // SAFETY: all bit patterns are valid for `NpdeStruct`.
- unsafe impl FromBytes for NpdeStruct {}
- impl NpdeStruct {
- fn new(dev: &device::Device, data: &[u8]) -> Option<Self> {
- let (npde, _) = NpdeStruct::from_bytes_copy_prefix(data)?;
- // 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 & 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,
- }
- #[expect(dead_code)]
- struct EfiBiosImage {
- base: BiosImage,
- // EFI-specific fields can be added here in the future.
- }
- #[expect(dead_code)]
- struct NbsiBiosImage {
- base: BiosImage,
- // NBSI-specific fields can be added here in the future.
- }
- struct FwSecBiosBuilder {
- base: BiosImage,
- /// 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: BiosImage,
- /// 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.
- #[expect(dead_code)]
- struct BiosImage {
- /// 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 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> {
- // 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 = 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 = KVec::new();
- data_copy.extend_from_slice(data, GFP_KERNEL)?;
- Ok(BiosImage {
- 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 usize::from(token.data_offset) + 4 > self.base.data.len() {
- return Err(EINVAL);
- }
- // read the 4 bytes at the offset specified in the token
- let offset = usize::from(token.data_offset);
- 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\n");
- EINVAL
- })?;
- let data_ptr = u32::from_le_bytes(bytes);
- if (usize::from_safe_cast(data_ptr)) < self.base.data.len() {
- dev_err!(self.base.dev, "Falcon data pointer out of bounds\n");
- return Err(EINVAL);
- }
- Ok(data_ptr)
- }
- }
- 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.
- #[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)?),
- })
- }
- }
- #[repr(C)]
- struct PmuLookupTableHeader {
- version: u8,
- header_len: u8,
- entry_len: u8,
- entry_count: u8,
- }
- // SAFETY: all bit patterns are valid for `PmuLookupTableHeader`.
- unsafe impl FromBytes for PmuLookupTableHeader {}
- /// 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 {
- header: PmuLookupTableHeader,
- table_data: KVec<u8>,
- }
- impl PmuLookupTable {
- fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
- let (header, _) = PmuLookupTableHeader::from_bytes_copy_prefix(data).ok_or(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 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
- };
- Ok(PmuLookupTable { header, table_data })
- }
- fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
- if idx >= self.header.entry_count {
- return Err(EINVAL);
- }
- let index = (usize::from(idx)) * usize::from(self.header.entry_len);
- 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.header.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 = usize::from_safe_cast(pci_at_image.falcon_data_ptr()?);
- 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 = usize::from_safe_cast(entry.data);
- 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 ([`FalconUCodeDesc`]).
- pub(crate) fn header(&self) -> Result<FalconUCodeDesc> {
- // Get the falcon ucode offset that was found in setup_falcon_data.
- let falcon_ucode_offset = self.falcon_ucode_offset;
- // 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;
- let data = self.base.data.get(falcon_ucode_offset..).ok_or(EINVAL)?;
- match ver {
- 2 => {
- let v2 = FalconUCodeDescV2::from_bytes_copy_prefix(data)
- .ok_or(EINVAL)?
- .0;
- Ok(FalconUCodeDesc::V2(v2))
- }
- 3 => {
- let v3 = FalconUCodeDescV3::from_bytes_copy_prefix(data)
- .ok_or(EINVAL)?
- .0;
- Ok(FalconUCodeDesc::V3(v3))
- }
- _ => {
- dev_err!(self.base.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 falcon_ucode_offset = self.falcon_ucode_offset;
- // The ucode data follows the descriptor.
- let ucode_data_offset = falcon_ucode_offset + desc.size();
- let size = usize::from_safe_cast(desc.imem_load_size() + desc.dmem_load_size());
- // 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: &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.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>(),
- sigs_count,
- )
- })
- }
- }
|