File Index
Files and directories
Sign in
FILE

vbios.rs

File preview with syntax highlighting.

linux-6.19.14/drivers/gpu/nova-core/vbios.rs RUST
   1// SPDX-License-Identifier: GPL-2.0
   2
   3//! VBIOS extraction and parsing.
   4
   5use core::convert::TryFrom;
   6
   7use kernel::{
   8    device,
   9    prelude::*,
  10    ptr::{
  11        Alignable,
  12        Alignment, //
  13    },
  14    transmute::FromBytes,
  15    types::ARef,
  16};
  17
  18use crate::{
  19    driver::Bar0,
  20    firmware::{
  21        fwsec::Bcrt30Rsa3kSignature,
  22        FalconUCodeDescV3, //
  23    },
  24    num::FromSafeCast,
  25};
  26
  27/// The offset of the VBIOS ROM in the BAR0 space.
  28const ROM_OFFSET: usize = 0x300000;
  29/// The maximum length of the VBIOS ROM to scan into.
  30const BIOS_MAX_SCAN_LEN: usize = 0x100000;
  31/// The size to read ahead when parsing initial BIOS image headers.
  32const BIOS_READ_AHEAD_SIZE: usize = 1024;
  33/// The bit in the last image indicator byte for the PCI Data Structure that
  34/// indicates the last image. Bit 0-6 are reserved, bit 7 is last image bit.
  35const LAST_IMAGE_BIT_MASK: u8 = 0x80;
  36
  37/// BIOS Image Type from PCI Data Structure code_type field.
  38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
  39#[repr(u8)]
  40enum BiosImageType {
  41    /// PC-AT compatible BIOS image (x86 legacy)
  42    PciAt = 0x00,
  43    /// EFI (Extensible Firmware Interface) BIOS image
  44    Efi = 0x03,
  45    /// NBSI (Notebook System Information) BIOS image
  46    Nbsi = 0x70,
  47    /// FwSec (Firmware Security) BIOS image
  48    FwSec = 0xE0,
  49}
  50
  51impl TryFrom<u8> for BiosImageType {
  52    type Error = Error;
  53
  54    fn try_from(code: u8) -> Result<Self> {
  55        match code {
  56            0x00 => Ok(Self::PciAt),
  57            0x03 => Ok(Self::Efi),
  58            0x70 => Ok(Self::Nbsi),
  59            0xE0 => Ok(Self::FwSec),
  60            _ => Err(EINVAL),
  61        }
  62    }
  63}
  64
  65// PMU lookup table entry types. Used to locate PMU table entries
  66// in the Fwsec image, corresponding to falcon ucodes.
  67#[expect(dead_code)]
  68const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
  69#[expect(dead_code)]
  70const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45;
  71const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85;
  72
  73/// Vbios Reader for constructing the VBIOS data.
  74struct VbiosIterator<'a> {
  75    dev: &'a device::Device,
  76    bar0: &'a Bar0,
  77    /// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference
  78    /// or copying into other data structures. It is the entire scanned contents of the VBIOS which
  79    /// progressively extends. It is used so that we do not re-read any contents that are already
  80    /// read as we use the cumulative length read so far, and re-read any gaps as we extend the
  81    /// length.
  82    data: KVec<u8>,
  83    /// Current offset of the [`Iterator`].
  84    current_offset: usize,
  85    /// Indicate whether the last image has been found.
  86    last_found: bool,
  87}
  88
  89impl<'a> VbiosIterator<'a> {
  90    fn new(dev: &'a device::Device, bar0: &'a Bar0) -> Result<Self> {
  91        Ok(Self {
  92            dev,
  93            bar0,
  94            data: KVec::new(),
  95            current_offset: 0,
  96            last_found: false,
  97        })
  98    }
  99
 100    /// Read bytes from the ROM at the current end of the data vector.
 101    fn read_more(&mut self, len: usize) -> Result {
 102        let current_len = self.data.len();
 103        let start = ROM_OFFSET + current_len;
 104
 105        // Ensure length is a multiple of 4 for 32-bit reads
 106        if len % core::mem::size_of::<u32>() != 0 {
 107            dev_err!(
 108                self.dev,
 109                "VBIOS read length {} is not a multiple of 4\n",
 110                len
 111            );
 112            return Err(EINVAL);
 113        }
 114
 115        self.data.reserve(len, GFP_KERNEL)?;
 116        // Read ROM data bytes and push directly to `data`.
 117        for addr in (start..start + len).step_by(core::mem::size_of::<u32>()) {
 118            // Read 32-bit word from the VBIOS ROM
 119            let word = self.bar0.try_read32(addr)?;
 120
 121            // Convert the `u32` to a 4 byte array and push each byte.
 122            word.to_ne_bytes()
 123                .iter()
 124                .try_for_each(|&b| self.data.push(b, GFP_KERNEL))?;
 125        }
 126
 127        Ok(())
 128    }
 129
 130    /// Read bytes at a specific offset, filling any gap.
 131    fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result {
 132        if offset > BIOS_MAX_SCAN_LEN {
 133            dev_err!(self.dev, "Error: exceeded BIOS scan limit.\n");
 134            return Err(EINVAL);
 135        }
 136
 137        // If `offset` is beyond current data size, fill the gap first.
 138        let current_len = self.data.len();
 139        let gap_bytes = offset.saturating_sub(current_len);
 140
 141        // Now read the requested bytes at the offset.
 142        self.read_more(gap_bytes + len)
 143    }
 144
 145    /// Read a BIOS image at a specific offset and create a [`BiosImage`] from it.
 146    ///
 147    /// `self.data` is extended as needed and a new [`BiosImage`] is returned.
 148    /// `context` is a string describing the operation for error reporting.
 149    fn read_bios_image_at_offset(
 150        &mut self,
 151        offset: usize,
 152        len: usize,
 153        context: &str,
 154    ) -> Result<BiosImage> {
 155        let data_len = self.data.len();
 156        if offset + len > data_len {
 157            self.read_more_at_offset(offset, len).inspect_err(|e| {
 158                dev_err!(
 159                    self.dev,
 160                    "Failed to read more at offset {:#x}: {:?}\n",
 161                    offset,
 162                    e
 163                )
 164            })?;
 165        }
 166
 167        BiosImage::new(self.dev, &self.data[offset..offset + len]).inspect_err(|err| {
 168            dev_err!(
 169                self.dev,
 170                "Failed to {} at offset {:#x}: {:?}\n",
 171                context,
 172                offset,
 173                err
 174            )
 175        })
 176    }
 177}
 178
 179impl<'a> Iterator for VbiosIterator<'a> {
 180    type Item = Result<BiosImage>;
 181
 182    /// Iterate over all VBIOS images until the last image is detected or offset
 183    /// exceeds scan limit.
 184    fn next(&mut self) -> Option<Self::Item> {
 185        if self.last_found {
 186            return None;
 187        }
 188
 189        if self.current_offset > BIOS_MAX_SCAN_LEN {
 190            dev_err!(self.dev, "Error: exceeded BIOS scan limit, stopping scan\n");
 191            return None;
 192        }
 193
 194        // Parse image headers first to get image size.
 195        let image_size = match self.read_bios_image_at_offset(
 196            self.current_offset,
 197            BIOS_READ_AHEAD_SIZE,
 198            "parse initial BIOS image headers",
 199        ) {
 200            Ok(image) => image.image_size_bytes(),
 201            Err(e) => return Some(Err(e)),
 202        };
 203
 204        // Now create a new `BiosImage` with the full image data.
 205        let full_image = match self.read_bios_image_at_offset(
 206            self.current_offset,
 207            image_size,
 208            "parse full BIOS image",
 209        ) {
 210            Ok(image) => image,
 211            Err(e) => return Some(Err(e)),
 212        };
 213
 214        self.last_found = full_image.is_last();
 215
 216        // Advance to next image (aligned to 512 bytes).
 217        self.current_offset += image_size;
 218        self.current_offset = self.current_offset.align_up(Alignment::new::<512>())?;
 219
 220        Some(Ok(full_image))
 221    }
 222}
 223
 224pub(crate) struct Vbios {
 225    fwsec_image: FwSecBiosImage,
 226}
 227
 228impl Vbios {
 229    /// Probe for VBIOS extraction.
 230    ///
 231    /// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore.
 232    pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> {
 233        // Images to extract from iteration
 234        let mut pci_at_image: Option<PciAtBiosImage> = None;
 235        let mut first_fwsec_image: Option<FwSecBiosBuilder> = None;
 236        let mut second_fwsec_image: Option<FwSecBiosBuilder> = None;
 237
 238        // Parse all VBIOS images in the ROM
 239        for image_result in VbiosIterator::new(dev, bar0)? {
 240            let image = image_result?;
 241
 242            dev_dbg!(
 243                dev,
 244                "Found BIOS image: size: {:#x}, type: {:?}, last: {}\n",
 245                image.image_size_bytes(),
 246                image.image_type(),
 247                image.is_last()
 248            );
 249
 250            // Convert to a specific image type
 251            match BiosImageType::try_from(image.pcir.code_type) {
 252                Ok(BiosImageType::PciAt) => {
 253                    pci_at_image = Some(PciAtBiosImage::try_from(image)?);
 254                }
 255                Ok(BiosImageType::FwSec) => {
 256                    let fwsec = FwSecBiosBuilder {
 257                        base: image,
 258                        falcon_data_offset: None,
 259                        pmu_lookup_table: None,
 260                        falcon_ucode_offset: None,
 261                    };
 262                    if first_fwsec_image.is_none() {
 263                        first_fwsec_image = Some(fwsec);
 264                    } else {
 265                        second_fwsec_image = Some(fwsec);
 266                    }
 267                }
 268                _ => {
 269                    // Ignore other image types or unknown types
 270                }
 271            }
 272        }
 273
 274        // Using all the images, setup the falcon data pointer in Fwsec.
 275        if let (Some(mut second), Some(first), Some(pci_at)) =
 276            (second_fwsec_image, first_fwsec_image, pci_at_image)
 277        {
 278            second
 279                .setup_falcon_data(&pci_at, &first)
 280                .inspect_err(|e| dev_err!(dev, "Falcon data setup failed: {:?}\n", e))?;
 281            Ok(Vbios {
 282                fwsec_image: second.build()?,
 283            })
 284        } else {
 285            dev_err!(
 286                dev,
 287                "Missing required images for falcon data setup, skipping\n"
 288            );
 289            Err(EINVAL)
 290        }
 291    }
 292
 293    pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage {
 294        &self.fwsec_image
 295    }
 296}
 297
 298/// PCI Data Structure as defined in PCI Firmware Specification
 299#[derive(Debug, Clone)]
 300#[repr(C)]
 301struct PcirStruct {
 302    /// PCI Data Structure signature ("PCIR" or "NPDS")
 303    signature: [u8; 4],
 304    /// PCI Vendor ID (e.g., 0x10DE for NVIDIA)
 305    vendor_id: u16,
 306    /// PCI Device ID
 307    device_id: u16,
 308    /// Device List Pointer
 309    device_list_ptr: u16,
 310    /// PCI Data Structure Length
 311    pci_data_struct_len: u16,
 312    /// PCI Data Structure Revision
 313    pci_data_struct_rev: u8,
 314    /// Class code (3 bytes, 0x03 for display controller)
 315    class_code: [u8; 3],
 316    /// Size of this image in 512-byte blocks
 317    image_len: u16,
 318    /// Revision Level of the Vendor's ROM
 319    vendor_rom_rev: u16,
 320    /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI)
 321    code_type: u8,
 322    /// Last image indicator (0x00 = Not last image, 0x80 = Last image)
 323    last_image: u8,
 324    /// Maximum Run-time Image Length (units of 512 bytes)
 325    max_runtime_image_len: u16,
 326}
 327
 328// SAFETY: all bit patterns are valid for `PcirStruct`.
 329unsafe impl FromBytes for PcirStruct {}
 330
 331impl PcirStruct {
 332    fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 333        let (pcir, _) = PcirStruct::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
 334
 335        // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
 336        if &pcir.signature != b"PCIR" && &pcir.signature != b"NPDS" {
 337            dev_err!(
 338                dev,
 339                "Invalid signature for PcirStruct: {:?}\n",
 340                pcir.signature
 341            );
 342            return Err(EINVAL);
 343        }
 344
 345        if pcir.image_len == 0 {
 346            dev_err!(dev, "Invalid image length: 0\n");
 347            return Err(EINVAL);
 348        }
 349
 350        Ok(pcir)
 351    }
 352
 353    /// Check if this is the last image in the ROM.
 354    fn is_last(&self) -> bool {
 355        self.last_image & LAST_IMAGE_BIT_MASK != 0
 356    }
 357
 358    /// Calculate image size in bytes from 512-byte blocks.
 359    fn image_size_bytes(&self) -> usize {
 360        usize::from(self.image_len) * 512
 361    }
 362}
 363
 364/// BIOS Information Table (BIT) Header.
 365///
 366/// This is the head of the BIT table, that is used to locate the Falcon data. The BIT table (with
 367/// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the
 368/// [`FwSecBiosImage`].
 369#[derive(Debug, Clone, Copy)]
 370#[repr(C)]
 371struct BitHeader {
 372    /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
 373    id: u16,
 374    /// 2h: BIT Header Signature ("BIT\0")
 375    signature: [u8; 4],
 376    /// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00.
 377    bcd_version: u16,
 378    /// 8h: Size of BIT Header (in bytes)
 379    header_size: u8,
 380    /// 9h: Size of BIT Tokens (in bytes)
 381    token_size: u8,
 382    /// 10h: Number of token entries that follow
 383    token_entries: u8,
 384    /// 11h: BIT Header Checksum
 385    checksum: u8,
 386}
 387
 388// SAFETY: all bit patterns are valid for `BitHeader`.
 389unsafe impl FromBytes for BitHeader {}
 390
 391impl BitHeader {
 392    fn new(data: &[u8]) -> Result<Self> {
 393        let (header, _) = BitHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
 394
 395        // Check header ID and signature
 396        if header.id != 0xB8FF || &header.signature != b"BIT\0" {
 397            return Err(EINVAL);
 398        }
 399
 400        Ok(header)
 401    }
 402}
 403
 404/// BIT Token Entry: Records in the BIT table followed by the BIT header.
 405#[derive(Debug, Clone, Copy)]
 406#[expect(dead_code)]
 407struct BitToken {
 408    /// 00h: Token identifier
 409    id: u8,
 410    /// 01h: Version of the token data
 411    data_version: u8,
 412    /// 02h: Size of token data in bytes
 413    data_size: u16,
 414    /// 04h: Offset to the token data
 415    data_offset: u16,
 416}
 417
 418// Define the token ID for the Falcon data
 419const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
 420
 421impl BitToken {
 422    /// Find a BIT token entry by BIT ID in a PciAtBiosImage
 423    fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> {
 424        let header = &image.bit_header;
 425
 426        // Offset to the first token entry
 427        let tokens_start = image.bit_offset + usize::from(header.header_size);
 428
 429        for i in 0..usize::from(header.token_entries) {
 430            let entry_offset = tokens_start + (i * usize::from(header.token_size));
 431
 432            // Make sure we don't go out of bounds
 433            if entry_offset + usize::from(header.token_size) > image.base.data.len() {
 434                return Err(EINVAL);
 435            }
 436
 437            // Check if this token has the requested ID
 438            if image.base.data[entry_offset] == token_id {
 439                return Ok(BitToken {
 440                    id: image.base.data[entry_offset],
 441                    data_version: image.base.data[entry_offset + 1],
 442                    data_size: u16::from_le_bytes([
 443                        image.base.data[entry_offset + 2],
 444                        image.base.data[entry_offset + 3],
 445                    ]),
 446                    data_offset: u16::from_le_bytes([
 447                        image.base.data[entry_offset + 4],
 448                        image.base.data[entry_offset + 5],
 449                    ]),
 450                });
 451            }
 452        }
 453
 454        // Token not found
 455        Err(ENOENT)
 456    }
 457}
 458
 459/// PCI ROM Expansion Header as defined in PCI Firmware Specification.
 460///
 461/// This is header is at the beginning of every image in the set of images in the ROM. It contains
 462/// a pointer to the PCI Data Structure which describes the image. For "NBSI" images (NoteBook
 463/// System Information), the ROM header deviates from the standard and contains an offset to the
 464/// NBSI image however we do not yet parse that in this module and keep it for future reference.
 465#[derive(Debug, Clone, Copy)]
 466#[expect(dead_code)]
 467struct PciRomHeader {
 468    /// 00h: Signature (0xAA55)
 469    signature: u16,
 470    /// 02h: Reserved bytes for processor architecture unique data (20 bytes)
 471    reserved: [u8; 20],
 472    /// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image)
 473    nbsi_data_offset: Option<u16>,
 474    /// 18h: Pointer to PCI Data Structure (offset from start of ROM image)
 475    pci_data_struct_offset: u16,
 476    /// 1Ah: Size of block (this is NBSI-specific)
 477    size_of_block: Option<u32>,
 478}
 479
 480impl PciRomHeader {
 481    fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 482        if data.len() < 26 {
 483            // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock.
 484            return Err(EINVAL);
 485        }
 486
 487        let signature = u16::from_le_bytes([data[0], data[1]]);
 488
 489        // Check for valid ROM signatures.
 490        match signature {
 491            0xAA55 | 0xBB77 | 0x4E56 => {}
 492            _ => {
 493                dev_err!(dev, "ROM signature unknown {:#x}\n", signature);
 494                return Err(EINVAL);
 495            }
 496        }
 497
 498        // Read the pointer to the PCI Data Structure at offset 0x18.
 499        let pci_data_struct_ptr = u16::from_le_bytes([data[24], data[25]]);
 500
 501        // Try to read optional fields if enough data.
 502        let mut size_of_block = None;
 503        let mut nbsi_data_offset = None;
 504
 505        if data.len() >= 30 {
 506            // Read size_of_block at offset 0x1A.
 507            size_of_block = Some(
 508                u32::from(data[29]) << 24
 509                    | u32::from(data[28]) << 16
 510                    | u32::from(data[27]) << 8
 511                    | u32::from(data[26]),
 512            );
 513        }
 514
 515        // For NBSI images, try to read the nbsiDataOffset at offset 0x16.
 516        if data.len() >= 24 {
 517            nbsi_data_offset = Some(u16::from_le_bytes([data[22], data[23]]));
 518        }
 519
 520        Ok(PciRomHeader {
 521            signature,
 522            reserved: [0u8; 20],
 523            pci_data_struct_offset: pci_data_struct_ptr,
 524            size_of_block,
 525            nbsi_data_offset,
 526        })
 527    }
 528}
 529
 530/// NVIDIA PCI Data Extension Structure.
 531///
 532/// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the
 533/// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but
 534/// are needed for traversing the BIOS images. It is expected to be present in all BIOS images
 535/// except for NBSI images.
 536#[derive(Debug, Clone)]
 537#[repr(C)]
 538struct NpdeStruct {
 539    /// 00h: Signature ("NPDE")
 540    signature: [u8; 4],
 541    /// 04h: NVIDIA PCI Data Extension Revision
 542    npci_data_ext_rev: u16,
 543    /// 06h: NVIDIA PCI Data Extension Length
 544    npci_data_ext_len: u16,
 545    /// 08h: Sub-image Length (in 512-byte units)
 546    subimage_len: u16,
 547    /// 0Ah: Last image indicator flag
 548    last_image: u8,
 549}
 550
 551// SAFETY: all bit patterns are valid for `NpdeStruct`.
 552unsafe impl FromBytes for NpdeStruct {}
 553
 554impl NpdeStruct {
 555    fn new(dev: &device::Device, data: &[u8]) -> Option<Self> {
 556        let (npde, _) = NpdeStruct::from_bytes_copy_prefix(data)?;
 557
 558        // Signature should be "NPDE" (0x4544504E).
 559        if &npde.signature != b"NPDE" {
 560            dev_dbg!(
 561                dev,
 562                "Invalid signature for NpdeStruct: {:?}\n",
 563                npde.signature
 564            );
 565            return None;
 566        }
 567
 568        if npde.subimage_len == 0 {
 569            dev_dbg!(dev, "Invalid subimage length: 0\n");
 570            return None;
 571        }
 572
 573        Some(npde)
 574    }
 575
 576    /// Check if this is the last image in the ROM.
 577    fn is_last(&self) -> bool {
 578        self.last_image & LAST_IMAGE_BIT_MASK != 0
 579    }
 580
 581    /// Calculate image size in bytes from 512-byte blocks.
 582    fn image_size_bytes(&self) -> usize {
 583        usize::from(self.subimage_len) * 512
 584    }
 585
 586    /// Try to find NPDE in the data, the NPDE is right after the PCIR.
 587    fn find_in_data(
 588        dev: &device::Device,
 589        data: &[u8],
 590        rom_header: &PciRomHeader,
 591        pcir: &PcirStruct,
 592    ) -> Option<Self> {
 593        // Calculate the offset where NPDE might be located
 594        // NPDE should be right after the PCIR structure, aligned to 16 bytes
 595        let pcir_offset = usize::from(rom_header.pci_data_struct_offset);
 596        let npde_start = (pcir_offset + usize::from(pcir.pci_data_struct_len) + 0x0F) & !0x0F;
 597
 598        // Check if we have enough data
 599        if npde_start + core::mem::size_of::<Self>() > data.len() {
 600            dev_dbg!(dev, "Not enough data for NPDE\n");
 601            return None;
 602        }
 603
 604        // Try to create NPDE from the data
 605        NpdeStruct::new(dev, &data[npde_start..])
 606    }
 607}
 608
 609/// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain.
 610///
 611/// It contains the BIT header and the BIT tokens.
 612struct PciAtBiosImage {
 613    base: BiosImage,
 614    bit_header: BitHeader,
 615    bit_offset: usize,
 616}
 617
 618#[expect(dead_code)]
 619struct EfiBiosImage {
 620    base: BiosImage,
 621    // EFI-specific fields can be added here in the future.
 622}
 623
 624#[expect(dead_code)]
 625struct NbsiBiosImage {
 626    base: BiosImage,
 627    // NBSI-specific fields can be added here in the future.
 628}
 629
 630struct FwSecBiosBuilder {
 631    base: BiosImage,
 632    /// These are temporary fields that are used during the construction of the
 633    /// [`FwSecBiosBuilder`].
 634    ///
 635    /// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new
 636    /// [`FwSecBiosImage`].
 637    ///
 638    /// The offset of the Falcon data from the start of Fwsec image.
 639    falcon_data_offset: Option<usize>,
 640    /// The [`PmuLookupTable`] starts at the offset of the falcon data pointer.
 641    pmu_lookup_table: Option<PmuLookupTable>,
 642    /// The offset of the Falcon ucode.
 643    falcon_ucode_offset: Option<usize>,
 644}
 645
 646/// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode.
 647///
 648/// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode.
 649pub(crate) struct FwSecBiosImage {
 650    base: BiosImage,
 651    /// The offset of the Falcon ucode.
 652    falcon_ucode_offset: usize,
 653}
 654
 655/// BIOS Image structure containing various headers and reference fields to all BIOS images.
 656///
 657/// A BiosImage struct is embedded into all image types and implements common operations.
 658#[expect(dead_code)]
 659struct BiosImage {
 660    /// Used for logging.
 661    dev: ARef<device::Device>,
 662    /// PCI ROM Expansion Header
 663    rom_header: PciRomHeader,
 664    /// PCI Data Structure
 665    pcir: PcirStruct,
 666    /// NVIDIA PCI Data Extension (optional)
 667    npde: Option<NpdeStruct>,
 668    /// Image data (includes ROM header and PCIR)
 669    data: KVec<u8>,
 670}
 671
 672impl BiosImage {
 673    /// Get the image size in bytes.
 674    fn image_size_bytes(&self) -> usize {
 675        // Prefer NPDE image size if available
 676        if let Some(ref npde) = self.npde {
 677            npde.image_size_bytes()
 678        } else {
 679            // Otherwise, fall back to the PCIR image size
 680            self.pcir.image_size_bytes()
 681        }
 682    }
 683
 684    /// Get the BIOS image type.
 685    fn image_type(&self) -> Result<BiosImageType> {
 686        BiosImageType::try_from(self.pcir.code_type)
 687    }
 688
 689    /// Check if this is the last image.
 690    fn is_last(&self) -> bool {
 691        // For NBSI images, return true as they're considered the last image.
 692        if self.image_type() == Ok(BiosImageType::Nbsi) {
 693            return true;
 694        }
 695
 696        // For other image types, check the NPDE first if available
 697        if let Some(ref npde) = self.npde {
 698            return npde.is_last();
 699        }
 700
 701        // Otherwise, fall back to checking the PCIR last_image flag
 702        self.pcir.is_last()
 703    }
 704
 705    /// Creates a new BiosImage from raw byte data.
 706    fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 707        // Ensure we have enough data for the ROM header.
 708        if data.len() < 26 {
 709            dev_err!(dev, "Not enough data for ROM header\n");
 710            return Err(EINVAL);
 711        }
 712
 713        // Parse the ROM header.
 714        let rom_header = PciRomHeader::new(dev, &data[0..26])
 715            .inspect_err(|e| dev_err!(dev, "Failed to create PciRomHeader: {:?}\n", e))?;
 716
 717        // Get the PCI Data Structure using the pointer from the ROM header.
 718        let pcir_offset = usize::from(rom_header.pci_data_struct_offset);
 719        let pcir_data = data
 720            .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>())
 721            .ok_or(EINVAL)
 722            .inspect_err(|_| {
 723                dev_err!(
 724                    dev,
 725                    "PCIR offset {:#x} out of bounds (data length: {})\n",
 726                    pcir_offset,
 727                    data.len()
 728                );
 729                dev_err!(
 730                    dev,
 731                    "Consider reading more data for construction of BiosImage\n"
 732                );
 733            })?;
 734
 735        let pcir = PcirStruct::new(dev, pcir_data)
 736            .inspect_err(|e| dev_err!(dev, "Failed to create PcirStruct: {:?}\n", e))?;
 737
 738        // Look for NPDE structure if this is not an NBSI image (type != 0x70).
 739        let npde = NpdeStruct::find_in_data(dev, data, &rom_header, &pcir);
 740
 741        // Create a copy of the data.
 742        let mut data_copy = KVec::new();
 743        data_copy.extend_from_slice(data, GFP_KERNEL)?;
 744
 745        Ok(BiosImage {
 746            dev: dev.into(),
 747            rom_header,
 748            pcir,
 749            npde,
 750            data: data_copy,
 751        })
 752    }
 753}
 754
 755impl PciAtBiosImage {
 756    /// Find a byte pattern in a slice.
 757    fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> {
 758        haystack
 759            .windows(needle.len())
 760            .position(|window| window == needle)
 761            .ok_or(EINVAL)
 762    }
 763
 764    /// Find the BIT header in the [`PciAtBiosImage`].
 765    fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> {
 766        let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00];
 767        let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?;
 768        let bit_header = BitHeader::new(&data[bit_offset..])?;
 769
 770        Ok((bit_header, bit_offset))
 771    }
 772
 773    /// Get a BIT token entry from the BIT table in the [`PciAtBiosImage`]
 774    fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {
 775        BitToken::from_id(self, token_id)
 776    }
 777
 778    /// Find the Falcon data pointer structure in the [`PciAtBiosImage`].
 779    ///
 780    /// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC
 781    /// image.
 782    fn falcon_data_ptr(&self) -> Result<u32> {
 783        let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?;
 784
 785        // Make sure we don't go out of bounds
 786        if usize::from(token.data_offset) + 4 > self.base.data.len() {
 787            return Err(EINVAL);
 788        }
 789
 790        // read the 4 bytes at the offset specified in the token
 791        let offset = usize::from(token.data_offset);
 792        let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| {
 793            dev_err!(self.base.dev, "Failed to convert data slice to array");
 794            EINVAL
 795        })?;
 796
 797        let data_ptr = u32::from_le_bytes(bytes);
 798
 799        if (usize::from_safe_cast(data_ptr)) < self.base.data.len() {
 800            dev_err!(self.base.dev, "Falcon data pointer out of bounds\n");
 801            return Err(EINVAL);
 802        }
 803
 804        Ok(data_ptr)
 805    }
 806}
 807
 808impl TryFrom<BiosImage> for PciAtBiosImage {
 809    type Error = Error;
 810
 811    fn try_from(base: BiosImage) -> Result<Self> {
 812        let data_slice = &base.data;
 813        let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?;
 814
 815        Ok(PciAtBiosImage {
 816            base,
 817            bit_header,
 818            bit_offset,
 819        })
 820    }
 821}
 822
 823/// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`].
 824///
 825/// See the [`PmuLookupTable`] description for more information.
 826#[repr(C, packed)]
 827struct PmuLookupTableEntry {
 828    application_id: u8,
 829    target_id: u8,
 830    data: u32,
 831}
 832
 833impl PmuLookupTableEntry {
 834    fn new(data: &[u8]) -> Result<Self> {
 835        if data.len() < core::mem::size_of::<Self>() {
 836            return Err(EINVAL);
 837        }
 838
 839        Ok(PmuLookupTableEntry {
 840            application_id: data[0],
 841            target_id: data[1],
 842            data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?),
 843        })
 844    }
 845}
 846
 847#[repr(C)]
 848struct PmuLookupTableHeader {
 849    version: u8,
 850    header_len: u8,
 851    entry_len: u8,
 852    entry_count: u8,
 853}
 854
 855// SAFETY: all bit patterns are valid for `PmuLookupTableHeader`.
 856unsafe impl FromBytes for PmuLookupTableHeader {}
 857
 858/// The [`PmuLookupTableEntry`] structure is used to find the [`PmuLookupTableEntry`] for a given
 859/// application ID.
 860///
 861/// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to
 862/// locate the Falcon Ucode.
 863struct PmuLookupTable {
 864    header: PmuLookupTableHeader,
 865    table_data: KVec<u8>,
 866}
 867
 868impl PmuLookupTable {
 869    fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
 870        let (header, _) = PmuLookupTableHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
 871
 872        let header_len = usize::from(header.header_len);
 873        let entry_len = usize::from(header.entry_len);
 874        let entry_count = usize::from(header.entry_count);
 875
 876        let required_bytes = header_len + (entry_count * entry_len);
 877
 878        if data.len() < required_bytes {
 879            dev_err!(dev, "PmuLookupTable data length less than required\n");
 880            return Err(EINVAL);
 881        }
 882
 883        // Create a copy of only the table data
 884        let table_data = {
 885            let mut ret = KVec::new();
 886            ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
 887            ret
 888        };
 889
 890        // Debug logging of entries (dumps the table data to dmesg)
 891        for i in (header_len..required_bytes).step_by(entry_len) {
 892            dev_dbg!(dev, "PMU entry: {:02x?}\n", &data[i..][..entry_len]);
 893        }
 894
 895        Ok(PmuLookupTable { header, table_data })
 896    }
 897
 898    fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
 899        if idx >= self.header.entry_count {
 900            return Err(EINVAL);
 901        }
 902
 903        let index = (usize::from(idx)) * usize::from(self.header.entry_len);
 904        PmuLookupTableEntry::new(&self.table_data[index..])
 905    }
 906
 907    // find entry by type value
 908    fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> {
 909        for i in 0..self.header.entry_count {
 910            let entry = self.lookup_index(i)?;
 911            if entry.application_id == entry_type {
 912                return Ok(entry);
 913            }
 914        }
 915
 916        Err(EINVAL)
 917    }
 918}
 919
 920impl FwSecBiosBuilder {
 921    fn setup_falcon_data(
 922        &mut self,
 923        pci_at_image: &PciAtBiosImage,
 924        first_fwsec: &FwSecBiosBuilder,
 925    ) -> Result {
 926        let mut offset = usize::from_safe_cast(pci_at_image.falcon_data_ptr()?);
 927        let mut pmu_in_first_fwsec = false;
 928
 929        // The falcon data pointer assumes that the PciAt and FWSEC images
 930        // are contiguous in memory. However, testing shows the EFI image sits in
 931        // between them. So calculate the offset from the end of the PciAt image
 932        // rather than the start of it. Compensate.
 933        offset -= pci_at_image.base.data.len();
 934
 935        // The offset is now from the start of the first Fwsec image, however
 936        // the offset points to a location in the second Fwsec image. Since
 937        // the fwsec images are contiguous, subtract the length of the first Fwsec
 938        // image from the offset to get the offset to the start of the second
 939        // Fwsec image.
 940        if offset < first_fwsec.base.data.len() {
 941            pmu_in_first_fwsec = true;
 942        } else {
 943            offset -= first_fwsec.base.data.len();
 944        }
 945
 946        self.falcon_data_offset = Some(offset);
 947
 948        if pmu_in_first_fwsec {
 949            self.pmu_lookup_table = Some(PmuLookupTable::new(
 950                &self.base.dev,
 951                &first_fwsec.base.data[offset..],
 952            )?);
 953        } else {
 954            self.pmu_lookup_table = Some(PmuLookupTable::new(
 955                &self.base.dev,
 956                &self.base.data[offset..],
 957            )?);
 958        }
 959
 960        match self
 961            .pmu_lookup_table
 962            .as_ref()
 963            .ok_or(EINVAL)?
 964            .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD)
 965        {
 966            Ok(entry) => {
 967                let mut ucode_offset = usize::from_safe_cast(entry.data);
 968                ucode_offset -= pci_at_image.base.data.len();
 969                if ucode_offset < first_fwsec.base.data.len() {
 970                    dev_err!(self.base.dev, "Falcon Ucode offset not in second Fwsec.\n");
 971                    return Err(EINVAL);
 972                }
 973                ucode_offset -= first_fwsec.base.data.len();
 974                self.falcon_ucode_offset = Some(ucode_offset);
 975            }
 976            Err(e) => {
 977                dev_err!(
 978                    self.base.dev,
 979                    "PmuLookupTableEntry not found, error: {:?}\n",
 980                    e
 981                );
 982                return Err(EINVAL);
 983            }
 984        }
 985        Ok(())
 986    }
 987
 988    /// Build the final FwSecBiosImage from this builder
 989    fn build(self) -> Result<FwSecBiosImage> {
 990        let ret = FwSecBiosImage {
 991            base: self.base,
 992            falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?,
 993        };
 994
 995        if cfg!(debug_assertions) {
 996            // Print the desc header for debugging
 997            let desc = ret.header()?;
 998            dev_dbg!(ret.base.dev, "PmuLookupTableEntry desc: {:#?}\n", desc);
 999        }
1000
1001        Ok(ret)
1002    }
1003}
1004
1005impl FwSecBiosImage {
1006    /// Get the FwSec header ([`FalconUCodeDescV3`]).
1007    pub(crate) fn header(&self) -> Result<&FalconUCodeDescV3> {
1008        // Get the falcon ucode offset that was found in setup_falcon_data.
1009        let falcon_ucode_offset = self.falcon_ucode_offset;
1010
1011        // Make sure the offset is within the data bounds.
1012        if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() {
1013            dev_err!(
1014                self.base.dev,
1015                "fwsec-frts header not contained within BIOS bounds\n"
1016            );
1017            return Err(ERANGE);
1018        }
1019
1020        // Read the first 4 bytes to get the version.
1021        let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4]
1022            .try_into()
1023            .map_err(|_| EINVAL)?;
1024        let hdr = u32::from_le_bytes(hdr_bytes);
1025        let ver = (hdr & 0xff00) >> 8;
1026
1027        if ver != 3 {
1028            dev_err!(self.base.dev, "invalid fwsec firmware version: {:?}\n", ver);
1029            return Err(EINVAL);
1030        }
1031
1032        // Return a reference to the FalconUCodeDescV3 structure.
1033        //
1034        // SAFETY: We have checked that `falcon_ucode_offset + size_of::<FalconUCodeDescV3>` is
1035        // within the bounds of `data`. Also, this data vector is from ROM, and the `data` field
1036        // in `BiosImageBase` is immutable after construction.
1037        Ok(unsafe {
1038            &*(self
1039                .base
1040                .data
1041                .as_ptr()
1042                .add(falcon_ucode_offset)
1043                .cast::<FalconUCodeDescV3>())
1044        })
1045    }
1046
1047    /// Get the ucode data as a byte slice
1048    pub(crate) fn ucode(&self, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
1049        let falcon_ucode_offset = self.falcon_ucode_offset;
1050
1051        // The ucode data follows the descriptor.
1052        let ucode_data_offset = falcon_ucode_offset + desc.size();
1053        let size = usize::from_safe_cast(desc.imem_load_size + desc.dmem_load_size);
1054
1055        // Get the data slice, checking bounds in a single operation.
1056        self.base
1057            .data
1058            .get(ucode_data_offset..ucode_data_offset + size)
1059            .ok_or(ERANGE)
1060            .inspect_err(|_| {
1061                dev_err!(
1062                    self.base.dev,
1063                    "fwsec ucode data not contained within BIOS bounds\n"
1064                )
1065            })
1066    }
1067
1068    /// Get the signatures as a byte slice
1069    pub(crate) fn sigs(&self, desc: &FalconUCodeDescV3) -> Result<&[Bcrt30Rsa3kSignature]> {
1070        // The signatures data follows the descriptor.
1071        let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>();
1072        let sigs_count = usize::from(desc.signature_count);
1073        let sigs_size = sigs_count * core::mem::size_of::<Bcrt30Rsa3kSignature>();
1074
1075        // Make sure the data is within bounds.
1076        if sigs_data_offset + sigs_size > self.base.data.len() {
1077            dev_err!(
1078                self.base.dev,
1079                "fwsec signatures data not contained within BIOS bounds\n"
1080            );
1081            return Err(ERANGE);
1082        }
1083
1084        // SAFETY: we checked that `data + sigs_data_offset + (signature_count *
1085        // sizeof::<Bcrt30Rsa3kSignature>()` is within the bounds of `data`.
1086        Ok(unsafe {
1087            core::slice::from_raw_parts(
1088                self.base
1089                    .data
1090                    .as_ptr()
1091                    .add(sigs_data_offset)
1092                    .cast::<Bcrt30Rsa3kSignature>(),
1093                sigs_count,
1094            )
1095        })
1096    }
1097}
~/ Read-only file preview.