978 lines · 32.2 KB · Rust
RawDownload
// 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.