xref: /linux/drivers/gpu/nova-core/vbios.rs (revision dc70c6ae2441c8ab5438331b2430ec098fdd94bb)
16fda04e7SJoel Fernandes // SPDX-License-Identifier: GPL-2.0
26fda04e7SJoel Fernandes 
36fda04e7SJoel Fernandes //! VBIOS extraction and parsing.
46fda04e7SJoel Fernandes 
56fda04e7SJoel Fernandes // To be removed when all code is used.
66fda04e7SJoel Fernandes #![expect(dead_code)]
76fda04e7SJoel Fernandes 
86fda04e7SJoel Fernandes use crate::driver::Bar0;
96fda04e7SJoel Fernandes use core::convert::TryFrom;
106fda04e7SJoel Fernandes use kernel::error::Result;
116fda04e7SJoel Fernandes use kernel::pci;
126fda04e7SJoel Fernandes use kernel::prelude::*;
136fda04e7SJoel Fernandes 
146fda04e7SJoel Fernandes /// The offset of the VBIOS ROM in the BAR0 space.
156fda04e7SJoel Fernandes const ROM_OFFSET: usize = 0x300000;
166fda04e7SJoel Fernandes /// The maximum length of the VBIOS ROM to scan into.
176fda04e7SJoel Fernandes const BIOS_MAX_SCAN_LEN: usize = 0x100000;
186fda04e7SJoel Fernandes /// The size to read ahead when parsing initial BIOS image headers.
196fda04e7SJoel Fernandes const BIOS_READ_AHEAD_SIZE: usize = 1024;
206fda04e7SJoel Fernandes /// The bit in the last image indicator byte for the PCI Data Structure that
216fda04e7SJoel Fernandes /// indicates the last image. Bit 0-6 are reserved, bit 7 is last image bit.
226fda04e7SJoel Fernandes const LAST_IMAGE_BIT_MASK: u8 = 0x80;
236fda04e7SJoel Fernandes 
246fda04e7SJoel Fernandes // PMU lookup table entry types. Used to locate PMU table entries
256fda04e7SJoel Fernandes // in the Fwsec image, corresponding to falcon ucodes.
266fda04e7SJoel Fernandes #[expect(dead_code)]
276fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
286fda04e7SJoel Fernandes #[expect(dead_code)]
296fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45;
306fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85;
316fda04e7SJoel Fernandes 
326fda04e7SJoel Fernandes /// Vbios Reader for constructing the VBIOS data.
336fda04e7SJoel Fernandes struct VbiosIterator<'a> {
346fda04e7SJoel Fernandes     pdev: &'a pci::Device,
356fda04e7SJoel Fernandes     bar0: &'a Bar0,
366fda04e7SJoel Fernandes     /// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference
376fda04e7SJoel Fernandes     /// or copying into other data structures. It is the entire scanned contents of the VBIOS which
386fda04e7SJoel Fernandes     /// progressively extends. It is used so that we do not re-read any contents that are already
396fda04e7SJoel Fernandes     /// read as we use the cumulative length read so far, and re-read any gaps as we extend the
406fda04e7SJoel Fernandes     /// length.
416fda04e7SJoel Fernandes     data: KVec<u8>,
426fda04e7SJoel Fernandes     /// Current offset of the [`Iterator`].
436fda04e7SJoel Fernandes     current_offset: usize,
446fda04e7SJoel Fernandes     /// Indicate whether the last image has been found.
456fda04e7SJoel Fernandes     last_found: bool,
466fda04e7SJoel Fernandes }
476fda04e7SJoel Fernandes 
486fda04e7SJoel Fernandes impl<'a> VbiosIterator<'a> {
496fda04e7SJoel Fernandes     fn new(pdev: &'a pci::Device, bar0: &'a Bar0) -> Result<Self> {
506fda04e7SJoel Fernandes         Ok(Self {
516fda04e7SJoel Fernandes             pdev,
526fda04e7SJoel Fernandes             bar0,
536fda04e7SJoel Fernandes             data: KVec::new(),
546fda04e7SJoel Fernandes             current_offset: 0,
556fda04e7SJoel Fernandes             last_found: false,
566fda04e7SJoel Fernandes         })
576fda04e7SJoel Fernandes     }
586fda04e7SJoel Fernandes 
596fda04e7SJoel Fernandes     /// Read bytes from the ROM at the current end of the data vector.
606fda04e7SJoel Fernandes     fn read_more(&mut self, len: usize) -> Result {
616fda04e7SJoel Fernandes         let current_len = self.data.len();
626fda04e7SJoel Fernandes         let start = ROM_OFFSET + current_len;
636fda04e7SJoel Fernandes 
646fda04e7SJoel Fernandes         // Ensure length is a multiple of 4 for 32-bit reads
656fda04e7SJoel Fernandes         if len % core::mem::size_of::<u32>() != 0 {
666fda04e7SJoel Fernandes             dev_err!(
676fda04e7SJoel Fernandes                 self.pdev.as_ref(),
686fda04e7SJoel Fernandes                 "VBIOS read length {} is not a multiple of 4\n",
696fda04e7SJoel Fernandes                 len
706fda04e7SJoel Fernandes             );
716fda04e7SJoel Fernandes             return Err(EINVAL);
726fda04e7SJoel Fernandes         }
736fda04e7SJoel Fernandes 
746fda04e7SJoel Fernandes         self.data.reserve(len, GFP_KERNEL)?;
756fda04e7SJoel Fernandes         // Read ROM data bytes and push directly to `data`.
766fda04e7SJoel Fernandes         for addr in (start..start + len).step_by(core::mem::size_of::<u32>()) {
776fda04e7SJoel Fernandes             // Read 32-bit word from the VBIOS ROM
786fda04e7SJoel Fernandes             let word = self.bar0.try_read32(addr)?;
796fda04e7SJoel Fernandes 
806fda04e7SJoel Fernandes             // Convert the `u32` to a 4 byte array and push each byte.
816fda04e7SJoel Fernandes             word.to_ne_bytes()
826fda04e7SJoel Fernandes                 .iter()
836fda04e7SJoel Fernandes                 .try_for_each(|&b| self.data.push(b, GFP_KERNEL))?;
846fda04e7SJoel Fernandes         }
856fda04e7SJoel Fernandes 
866fda04e7SJoel Fernandes         Ok(())
876fda04e7SJoel Fernandes     }
886fda04e7SJoel Fernandes 
896fda04e7SJoel Fernandes     /// Read bytes at a specific offset, filling any gap.
906fda04e7SJoel Fernandes     fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result {
916fda04e7SJoel Fernandes         if offset > BIOS_MAX_SCAN_LEN {
926fda04e7SJoel Fernandes             dev_err!(self.pdev.as_ref(), "Error: exceeded BIOS scan limit.\n");
936fda04e7SJoel Fernandes             return Err(EINVAL);
946fda04e7SJoel Fernandes         }
956fda04e7SJoel Fernandes 
966fda04e7SJoel Fernandes         // If `offset` is beyond current data size, fill the gap first.
976fda04e7SJoel Fernandes         let current_len = self.data.len();
986fda04e7SJoel Fernandes         let gap_bytes = offset.saturating_sub(current_len);
996fda04e7SJoel Fernandes 
1006fda04e7SJoel Fernandes         // Now read the requested bytes at the offset.
1016fda04e7SJoel Fernandes         self.read_more(gap_bytes + len)
1026fda04e7SJoel Fernandes     }
1036fda04e7SJoel Fernandes 
1046fda04e7SJoel Fernandes     /// Read a BIOS image at a specific offset and create a [`BiosImage`] from it.
1056fda04e7SJoel Fernandes     ///
1066fda04e7SJoel Fernandes     /// `self.data` is extended as needed and a new [`BiosImage`] is returned.
1076fda04e7SJoel Fernandes     /// `context` is a string describing the operation for error reporting.
1086fda04e7SJoel Fernandes     fn read_bios_image_at_offset(
1096fda04e7SJoel Fernandes         &mut self,
1106fda04e7SJoel Fernandes         offset: usize,
1116fda04e7SJoel Fernandes         len: usize,
1126fda04e7SJoel Fernandes         context: &str,
1136fda04e7SJoel Fernandes     ) -> Result<BiosImage> {
1146fda04e7SJoel Fernandes         let data_len = self.data.len();
1156fda04e7SJoel Fernandes         if offset + len > data_len {
1166fda04e7SJoel Fernandes             self.read_more_at_offset(offset, len).inspect_err(|e| {
1176fda04e7SJoel Fernandes                 dev_err!(
1186fda04e7SJoel Fernandes                     self.pdev.as_ref(),
1196fda04e7SJoel Fernandes                     "Failed to read more at offset {:#x}: {:?}\n",
1206fda04e7SJoel Fernandes                     offset,
1216fda04e7SJoel Fernandes                     e
1226fda04e7SJoel Fernandes                 )
1236fda04e7SJoel Fernandes             })?;
1246fda04e7SJoel Fernandes         }
1256fda04e7SJoel Fernandes 
1266fda04e7SJoel Fernandes         BiosImage::new(self.pdev, &self.data[offset..offset + len]).inspect_err(|err| {
1276fda04e7SJoel Fernandes             dev_err!(
1286fda04e7SJoel Fernandes                 self.pdev.as_ref(),
1296fda04e7SJoel Fernandes                 "Failed to {} at offset {:#x}: {:?}\n",
1306fda04e7SJoel Fernandes                 context,
1316fda04e7SJoel Fernandes                 offset,
1326fda04e7SJoel Fernandes                 err
1336fda04e7SJoel Fernandes             )
1346fda04e7SJoel Fernandes         })
1356fda04e7SJoel Fernandes     }
1366fda04e7SJoel Fernandes }
1376fda04e7SJoel Fernandes 
1386fda04e7SJoel Fernandes impl<'a> Iterator for VbiosIterator<'a> {
1396fda04e7SJoel Fernandes     type Item = Result<BiosImage>;
1406fda04e7SJoel Fernandes 
1416fda04e7SJoel Fernandes     /// Iterate over all VBIOS images until the last image is detected or offset
1426fda04e7SJoel Fernandes     /// exceeds scan limit.
1436fda04e7SJoel Fernandes     fn next(&mut self) -> Option<Self::Item> {
1446fda04e7SJoel Fernandes         if self.last_found {
1456fda04e7SJoel Fernandes             return None;
1466fda04e7SJoel Fernandes         }
1476fda04e7SJoel Fernandes 
1486fda04e7SJoel Fernandes         if self.current_offset > BIOS_MAX_SCAN_LEN {
1496fda04e7SJoel Fernandes             dev_err!(
1506fda04e7SJoel Fernandes                 self.pdev.as_ref(),
1516fda04e7SJoel Fernandes                 "Error: exceeded BIOS scan limit, stopping scan\n"
1526fda04e7SJoel Fernandes             );
1536fda04e7SJoel Fernandes             return None;
1546fda04e7SJoel Fernandes         }
1556fda04e7SJoel Fernandes 
1566fda04e7SJoel Fernandes         // Parse image headers first to get image size.
1576fda04e7SJoel Fernandes         let image_size = match self.read_bios_image_at_offset(
1586fda04e7SJoel Fernandes             self.current_offset,
1596fda04e7SJoel Fernandes             BIOS_READ_AHEAD_SIZE,
1606fda04e7SJoel Fernandes             "parse initial BIOS image headers",
1616fda04e7SJoel Fernandes         ) {
1626fda04e7SJoel Fernandes             Ok(image) => image.image_size_bytes(),
1636fda04e7SJoel Fernandes             Err(e) => return Some(Err(e)),
1646fda04e7SJoel Fernandes         };
1656fda04e7SJoel Fernandes 
1666fda04e7SJoel Fernandes         // Now create a new `BiosImage` with the full image data.
1676fda04e7SJoel Fernandes         let full_image = match self.read_bios_image_at_offset(
1686fda04e7SJoel Fernandes             self.current_offset,
1696fda04e7SJoel Fernandes             image_size,
1706fda04e7SJoel Fernandes             "parse full BIOS image",
1716fda04e7SJoel Fernandes         ) {
1726fda04e7SJoel Fernandes             Ok(image) => image,
1736fda04e7SJoel Fernandes             Err(e) => return Some(Err(e)),
1746fda04e7SJoel Fernandes         };
1756fda04e7SJoel Fernandes 
1766fda04e7SJoel Fernandes         self.last_found = full_image.is_last();
1776fda04e7SJoel Fernandes 
1786fda04e7SJoel Fernandes         // Advance to next image (aligned to 512 bytes).
1796fda04e7SJoel Fernandes         self.current_offset += image_size;
1806fda04e7SJoel Fernandes         // TODO: replace with `align_up` once it lands.
1816fda04e7SJoel Fernandes         self.current_offset = self.current_offset.next_multiple_of(512);
1826fda04e7SJoel Fernandes 
1836fda04e7SJoel Fernandes         Some(Ok(full_image))
1846fda04e7SJoel Fernandes     }
1856fda04e7SJoel Fernandes }
1866fda04e7SJoel Fernandes 
1876fda04e7SJoel Fernandes pub(crate) struct Vbios {
1886fda04e7SJoel Fernandes     fwsec_image: FwSecBiosImage,
1896fda04e7SJoel Fernandes }
1906fda04e7SJoel Fernandes 
1916fda04e7SJoel Fernandes impl Vbios {
1926fda04e7SJoel Fernandes     /// Probe for VBIOS extraction.
1936fda04e7SJoel Fernandes     ///
1946fda04e7SJoel Fernandes     /// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore.
1956fda04e7SJoel Fernandes     pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> {
1966fda04e7SJoel Fernandes         // Images to extract from iteration
1976fda04e7SJoel Fernandes         let mut pci_at_image: Option<PciAtBiosImage> = None;
1986fda04e7SJoel Fernandes         let mut first_fwsec_image: Option<FwSecBiosImage> = None;
1996fda04e7SJoel Fernandes         let mut second_fwsec_image: Option<FwSecBiosImage> = None;
2006fda04e7SJoel Fernandes 
2016fda04e7SJoel Fernandes         // Parse all VBIOS images in the ROM
2026fda04e7SJoel Fernandes         for image_result in VbiosIterator::new(pdev, bar0)? {
2036fda04e7SJoel Fernandes             let full_image = image_result?;
2046fda04e7SJoel Fernandes 
2056fda04e7SJoel Fernandes             dev_dbg!(
2066fda04e7SJoel Fernandes                 pdev.as_ref(),
2076fda04e7SJoel Fernandes                 "Found BIOS image: size: {:#x}, type: {}, last: {}\n",
2086fda04e7SJoel Fernandes                 full_image.image_size_bytes(),
2096fda04e7SJoel Fernandes                 full_image.image_type_str(),
2106fda04e7SJoel Fernandes                 full_image.is_last()
2116fda04e7SJoel Fernandes             );
2126fda04e7SJoel Fernandes 
2136fda04e7SJoel Fernandes             // Get references to images we will need after the loop, in order to
2146fda04e7SJoel Fernandes             // setup the falcon data offset.
2156fda04e7SJoel Fernandes             match full_image {
2166fda04e7SJoel Fernandes                 BiosImage::PciAt(image) => {
2176fda04e7SJoel Fernandes                     pci_at_image = Some(image);
2186fda04e7SJoel Fernandes                 }
2196fda04e7SJoel Fernandes                 BiosImage::FwSec(image) => {
2206fda04e7SJoel Fernandes                     if first_fwsec_image.is_none() {
2216fda04e7SJoel Fernandes                         first_fwsec_image = Some(image);
2226fda04e7SJoel Fernandes                     } else {
2236fda04e7SJoel Fernandes                         second_fwsec_image = Some(image);
2246fda04e7SJoel Fernandes                     }
2256fda04e7SJoel Fernandes                 }
2266fda04e7SJoel Fernandes                 // For now we don't need to handle these
2276fda04e7SJoel Fernandes                 BiosImage::Efi(_image) => {}
2286fda04e7SJoel Fernandes                 BiosImage::Nbsi(_image) => {}
2296fda04e7SJoel Fernandes             }
2306fda04e7SJoel Fernandes         }
2316fda04e7SJoel Fernandes 
2326fda04e7SJoel Fernandes         // Using all the images, setup the falcon data pointer in Fwsec.
2336fda04e7SJoel Fernandes         // These are temporarily unused images and will be used in later patches.
2346fda04e7SJoel Fernandes         if let (Some(second), Some(_first), Some(_pci_at)) =
2356fda04e7SJoel Fernandes             (second_fwsec_image, first_fwsec_image, pci_at_image)
2366fda04e7SJoel Fernandes         {
2376fda04e7SJoel Fernandes             Ok(Vbios {
2386fda04e7SJoel Fernandes                 fwsec_image: second,
2396fda04e7SJoel Fernandes             })
2406fda04e7SJoel Fernandes         } else {
2416fda04e7SJoel Fernandes             dev_err!(
2426fda04e7SJoel Fernandes                 pdev.as_ref(),
2436fda04e7SJoel Fernandes                 "Missing required images for falcon data setup, skipping\n"
2446fda04e7SJoel Fernandes             );
2456fda04e7SJoel Fernandes             Err(EINVAL)
2466fda04e7SJoel Fernandes         }
2476fda04e7SJoel Fernandes     }
2486fda04e7SJoel Fernandes }
2496fda04e7SJoel Fernandes 
2506fda04e7SJoel Fernandes /// PCI Data Structure as defined in PCI Firmware Specification
2516fda04e7SJoel Fernandes #[derive(Debug, Clone)]
2526fda04e7SJoel Fernandes #[repr(C)]
2536fda04e7SJoel Fernandes struct PcirStruct {
2546fda04e7SJoel Fernandes     /// PCI Data Structure signature ("PCIR" or "NPDS")
2556fda04e7SJoel Fernandes     signature: [u8; 4],
2566fda04e7SJoel Fernandes     /// PCI Vendor ID (e.g., 0x10DE for NVIDIA)
2576fda04e7SJoel Fernandes     vendor_id: u16,
2586fda04e7SJoel Fernandes     /// PCI Device ID
2596fda04e7SJoel Fernandes     device_id: u16,
2606fda04e7SJoel Fernandes     /// Device List Pointer
2616fda04e7SJoel Fernandes     device_list_ptr: u16,
2626fda04e7SJoel Fernandes     /// PCI Data Structure Length
2636fda04e7SJoel Fernandes     pci_data_struct_len: u16,
2646fda04e7SJoel Fernandes     /// PCI Data Structure Revision
2656fda04e7SJoel Fernandes     pci_data_struct_rev: u8,
2666fda04e7SJoel Fernandes     /// Class code (3 bytes, 0x03 for display controller)
2676fda04e7SJoel Fernandes     class_code: [u8; 3],
2686fda04e7SJoel Fernandes     /// Size of this image in 512-byte blocks
2696fda04e7SJoel Fernandes     image_len: u16,
2706fda04e7SJoel Fernandes     /// Revision Level of the Vendor's ROM
2716fda04e7SJoel Fernandes     vendor_rom_rev: u16,
2726fda04e7SJoel Fernandes     /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI)
2736fda04e7SJoel Fernandes     code_type: u8,
2746fda04e7SJoel Fernandes     /// Last image indicator (0x00 = Not last image, 0x80 = Last image)
2756fda04e7SJoel Fernandes     last_image: u8,
2766fda04e7SJoel Fernandes     /// Maximum Run-time Image Length (units of 512 bytes)
2776fda04e7SJoel Fernandes     max_runtime_image_len: u16,
2786fda04e7SJoel Fernandes }
2796fda04e7SJoel Fernandes 
2806fda04e7SJoel Fernandes impl PcirStruct {
2816fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
2826fda04e7SJoel Fernandes         if data.len() < core::mem::size_of::<PcirStruct>() {
2836fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Not enough data for PcirStruct\n");
2846fda04e7SJoel Fernandes             return Err(EINVAL);
2856fda04e7SJoel Fernandes         }
2866fda04e7SJoel Fernandes 
2876fda04e7SJoel Fernandes         let mut signature = [0u8; 4];
2886fda04e7SJoel Fernandes         signature.copy_from_slice(&data[0..4]);
2896fda04e7SJoel Fernandes 
2906fda04e7SJoel Fernandes         // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
2916fda04e7SJoel Fernandes         if &signature != b"PCIR" && &signature != b"NPDS" {
2926fda04e7SJoel Fernandes             dev_err!(
2936fda04e7SJoel Fernandes                 pdev.as_ref(),
2946fda04e7SJoel Fernandes                 "Invalid signature for PcirStruct: {:?}\n",
2956fda04e7SJoel Fernandes                 signature
2966fda04e7SJoel Fernandes             );
2976fda04e7SJoel Fernandes             return Err(EINVAL);
2986fda04e7SJoel Fernandes         }
2996fda04e7SJoel Fernandes 
3006fda04e7SJoel Fernandes         let mut class_code = [0u8; 3];
3016fda04e7SJoel Fernandes         class_code.copy_from_slice(&data[13..16]);
3026fda04e7SJoel Fernandes 
3036fda04e7SJoel Fernandes         let image_len = u16::from_le_bytes([data[16], data[17]]);
3046fda04e7SJoel Fernandes         if image_len == 0 {
3056fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Invalid image length: 0\n");
3066fda04e7SJoel Fernandes             return Err(EINVAL);
3076fda04e7SJoel Fernandes         }
3086fda04e7SJoel Fernandes 
3096fda04e7SJoel Fernandes         Ok(PcirStruct {
3106fda04e7SJoel Fernandes             signature,
3116fda04e7SJoel Fernandes             vendor_id: u16::from_le_bytes([data[4], data[5]]),
3126fda04e7SJoel Fernandes             device_id: u16::from_le_bytes([data[6], data[7]]),
3136fda04e7SJoel Fernandes             device_list_ptr: u16::from_le_bytes([data[8], data[9]]),
3146fda04e7SJoel Fernandes             pci_data_struct_len: u16::from_le_bytes([data[10], data[11]]),
3156fda04e7SJoel Fernandes             pci_data_struct_rev: data[12],
3166fda04e7SJoel Fernandes             class_code,
3176fda04e7SJoel Fernandes             image_len,
3186fda04e7SJoel Fernandes             vendor_rom_rev: u16::from_le_bytes([data[18], data[19]]),
3196fda04e7SJoel Fernandes             code_type: data[20],
3206fda04e7SJoel Fernandes             last_image: data[21],
3216fda04e7SJoel Fernandes             max_runtime_image_len: u16::from_le_bytes([data[22], data[23]]),
3226fda04e7SJoel Fernandes         })
3236fda04e7SJoel Fernandes     }
3246fda04e7SJoel Fernandes 
3256fda04e7SJoel Fernandes     /// Check if this is the last image in the ROM.
3266fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
3276fda04e7SJoel Fernandes         self.last_image & LAST_IMAGE_BIT_MASK != 0
3286fda04e7SJoel Fernandes     }
3296fda04e7SJoel Fernandes 
3306fda04e7SJoel Fernandes     /// Calculate image size in bytes from 512-byte blocks.
3316fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
3326fda04e7SJoel Fernandes         self.image_len as usize * 512
3336fda04e7SJoel Fernandes     }
3346fda04e7SJoel Fernandes }
3356fda04e7SJoel Fernandes 
336*dc70c6aeSJoel Fernandes /// BIOS Information Table (BIT) Header.
337*dc70c6aeSJoel Fernandes ///
338*dc70c6aeSJoel Fernandes /// This is the head of the BIT table, that is used to locate the Falcon data. The BIT table (with
339*dc70c6aeSJoel Fernandes /// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the
340*dc70c6aeSJoel Fernandes /// [`FwSecBiosImage`].
341*dc70c6aeSJoel Fernandes #[derive(Debug, Clone, Copy)]
342*dc70c6aeSJoel Fernandes #[expect(dead_code)]
343*dc70c6aeSJoel Fernandes struct BitHeader {
344*dc70c6aeSJoel Fernandes     /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
345*dc70c6aeSJoel Fernandes     id: u16,
346*dc70c6aeSJoel Fernandes     /// 2h: BIT Header Signature ("BIT\0")
347*dc70c6aeSJoel Fernandes     signature: [u8; 4],
348*dc70c6aeSJoel Fernandes     /// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00.
349*dc70c6aeSJoel Fernandes     bcd_version: u16,
350*dc70c6aeSJoel Fernandes     /// 8h: Size of BIT Header (in bytes)
351*dc70c6aeSJoel Fernandes     header_size: u8,
352*dc70c6aeSJoel Fernandes     /// 9h: Size of BIT Tokens (in bytes)
353*dc70c6aeSJoel Fernandes     token_size: u8,
354*dc70c6aeSJoel Fernandes     /// 10h: Number of token entries that follow
355*dc70c6aeSJoel Fernandes     token_entries: u8,
356*dc70c6aeSJoel Fernandes     /// 11h: BIT Header Checksum
357*dc70c6aeSJoel Fernandes     checksum: u8,
358*dc70c6aeSJoel Fernandes }
359*dc70c6aeSJoel Fernandes 
360*dc70c6aeSJoel Fernandes impl BitHeader {
361*dc70c6aeSJoel Fernandes     fn new(data: &[u8]) -> Result<Self> {
362*dc70c6aeSJoel Fernandes         if data.len() < 12 {
363*dc70c6aeSJoel Fernandes             return Err(EINVAL);
364*dc70c6aeSJoel Fernandes         }
365*dc70c6aeSJoel Fernandes 
366*dc70c6aeSJoel Fernandes         let mut signature = [0u8; 4];
367*dc70c6aeSJoel Fernandes         signature.copy_from_slice(&data[2..6]);
368*dc70c6aeSJoel Fernandes 
369*dc70c6aeSJoel Fernandes         // Check header ID and signature
370*dc70c6aeSJoel Fernandes         let id = u16::from_le_bytes([data[0], data[1]]);
371*dc70c6aeSJoel Fernandes         if id != 0xB8FF || &signature != b"BIT\0" {
372*dc70c6aeSJoel Fernandes             return Err(EINVAL);
373*dc70c6aeSJoel Fernandes         }
374*dc70c6aeSJoel Fernandes 
375*dc70c6aeSJoel Fernandes         Ok(BitHeader {
376*dc70c6aeSJoel Fernandes             id,
377*dc70c6aeSJoel Fernandes             signature,
378*dc70c6aeSJoel Fernandes             bcd_version: u16::from_le_bytes([data[6], data[7]]),
379*dc70c6aeSJoel Fernandes             header_size: data[8],
380*dc70c6aeSJoel Fernandes             token_size: data[9],
381*dc70c6aeSJoel Fernandes             token_entries: data[10],
382*dc70c6aeSJoel Fernandes             checksum: data[11],
383*dc70c6aeSJoel Fernandes         })
384*dc70c6aeSJoel Fernandes     }
385*dc70c6aeSJoel Fernandes }
386*dc70c6aeSJoel Fernandes 
387*dc70c6aeSJoel Fernandes /// BIT Token Entry: Records in the BIT table followed by the BIT header.
388*dc70c6aeSJoel Fernandes #[derive(Debug, Clone, Copy)]
389*dc70c6aeSJoel Fernandes #[expect(dead_code)]
390*dc70c6aeSJoel Fernandes struct BitToken {
391*dc70c6aeSJoel Fernandes     /// 00h: Token identifier
392*dc70c6aeSJoel Fernandes     id: u8,
393*dc70c6aeSJoel Fernandes     /// 01h: Version of the token data
394*dc70c6aeSJoel Fernandes     data_version: u8,
395*dc70c6aeSJoel Fernandes     /// 02h: Size of token data in bytes
396*dc70c6aeSJoel Fernandes     data_size: u16,
397*dc70c6aeSJoel Fernandes     /// 04h: Offset to the token data
398*dc70c6aeSJoel Fernandes     data_offset: u16,
399*dc70c6aeSJoel Fernandes }
400*dc70c6aeSJoel Fernandes 
401*dc70c6aeSJoel Fernandes // Define the token ID for the Falcon data
402*dc70c6aeSJoel Fernandes const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
403*dc70c6aeSJoel Fernandes 
404*dc70c6aeSJoel Fernandes impl BitToken {
405*dc70c6aeSJoel Fernandes     /// Find a BIT token entry by BIT ID in a PciAtBiosImage
406*dc70c6aeSJoel Fernandes     fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> {
407*dc70c6aeSJoel Fernandes         let header = &image.bit_header;
408*dc70c6aeSJoel Fernandes 
409*dc70c6aeSJoel Fernandes         // Offset to the first token entry
410*dc70c6aeSJoel Fernandes         let tokens_start = image.bit_offset + header.header_size as usize;
411*dc70c6aeSJoel Fernandes 
412*dc70c6aeSJoel Fernandes         for i in 0..header.token_entries as usize {
413*dc70c6aeSJoel Fernandes             let entry_offset = tokens_start + (i * header.token_size as usize);
414*dc70c6aeSJoel Fernandes 
415*dc70c6aeSJoel Fernandes             // Make sure we don't go out of bounds
416*dc70c6aeSJoel Fernandes             if entry_offset + header.token_size as usize > image.base.data.len() {
417*dc70c6aeSJoel Fernandes                 return Err(EINVAL);
418*dc70c6aeSJoel Fernandes             }
419*dc70c6aeSJoel Fernandes 
420*dc70c6aeSJoel Fernandes             // Check if this token has the requested ID
421*dc70c6aeSJoel Fernandes             if image.base.data[entry_offset] == token_id {
422*dc70c6aeSJoel Fernandes                 return Ok(BitToken {
423*dc70c6aeSJoel Fernandes                     id: image.base.data[entry_offset],
424*dc70c6aeSJoel Fernandes                     data_version: image.base.data[entry_offset + 1],
425*dc70c6aeSJoel Fernandes                     data_size: u16::from_le_bytes([
426*dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 2],
427*dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 3],
428*dc70c6aeSJoel Fernandes                     ]),
429*dc70c6aeSJoel Fernandes                     data_offset: u16::from_le_bytes([
430*dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 4],
431*dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 5],
432*dc70c6aeSJoel Fernandes                     ]),
433*dc70c6aeSJoel Fernandes                 });
434*dc70c6aeSJoel Fernandes             }
435*dc70c6aeSJoel Fernandes         }
436*dc70c6aeSJoel Fernandes 
437*dc70c6aeSJoel Fernandes         // Token not found
438*dc70c6aeSJoel Fernandes         Err(ENOENT)
439*dc70c6aeSJoel Fernandes     }
440*dc70c6aeSJoel Fernandes }
441*dc70c6aeSJoel Fernandes 
4426fda04e7SJoel Fernandes /// PCI ROM Expansion Header as defined in PCI Firmware Specification.
4436fda04e7SJoel Fernandes ///
4446fda04e7SJoel Fernandes /// This is header is at the beginning of every image in the set of images in the ROM. It contains
4456fda04e7SJoel Fernandes /// a pointer to the PCI Data Structure which describes the image. For "NBSI" images (NoteBook
4466fda04e7SJoel Fernandes /// System Information), the ROM header deviates from the standard and contains an offset to the
4476fda04e7SJoel Fernandes /// NBSI image however we do not yet parse that in this module and keep it for future reference.
4486fda04e7SJoel Fernandes #[derive(Debug, Clone, Copy)]
4496fda04e7SJoel Fernandes #[expect(dead_code)]
4506fda04e7SJoel Fernandes struct PciRomHeader {
4516fda04e7SJoel Fernandes     /// 00h: Signature (0xAA55)
4526fda04e7SJoel Fernandes     signature: u16,
4536fda04e7SJoel Fernandes     /// 02h: Reserved bytes for processor architecture unique data (20 bytes)
4546fda04e7SJoel Fernandes     reserved: [u8; 20],
4556fda04e7SJoel Fernandes     /// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image)
4566fda04e7SJoel Fernandes     nbsi_data_offset: Option<u16>,
4576fda04e7SJoel Fernandes     /// 18h: Pointer to PCI Data Structure (offset from start of ROM image)
4586fda04e7SJoel Fernandes     pci_data_struct_offset: u16,
4596fda04e7SJoel Fernandes     /// 1Ah: Size of block (this is NBSI-specific)
4606fda04e7SJoel Fernandes     size_of_block: Option<u32>,
4616fda04e7SJoel Fernandes }
4626fda04e7SJoel Fernandes 
4636fda04e7SJoel Fernandes impl PciRomHeader {
4646fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
4656fda04e7SJoel Fernandes         if data.len() < 26 {
4666fda04e7SJoel Fernandes             // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock.
4676fda04e7SJoel Fernandes             return Err(EINVAL);
4686fda04e7SJoel Fernandes         }
4696fda04e7SJoel Fernandes 
4706fda04e7SJoel Fernandes         let signature = u16::from_le_bytes([data[0], data[1]]);
4716fda04e7SJoel Fernandes 
4726fda04e7SJoel Fernandes         // Check for valid ROM signatures.
4736fda04e7SJoel Fernandes         match signature {
4746fda04e7SJoel Fernandes             0xAA55 | 0xBB77 | 0x4E56 => {}
4756fda04e7SJoel Fernandes             _ => {
4766fda04e7SJoel Fernandes                 dev_err!(pdev.as_ref(), "ROM signature unknown {:#x}\n", signature);
4776fda04e7SJoel Fernandes                 return Err(EINVAL);
4786fda04e7SJoel Fernandes             }
4796fda04e7SJoel Fernandes         }
4806fda04e7SJoel Fernandes 
4816fda04e7SJoel Fernandes         // Read the pointer to the PCI Data Structure at offset 0x18.
4826fda04e7SJoel Fernandes         let pci_data_struct_ptr = u16::from_le_bytes([data[24], data[25]]);
4836fda04e7SJoel Fernandes 
4846fda04e7SJoel Fernandes         // Try to read optional fields if enough data.
4856fda04e7SJoel Fernandes         let mut size_of_block = None;
4866fda04e7SJoel Fernandes         let mut nbsi_data_offset = None;
4876fda04e7SJoel Fernandes 
4886fda04e7SJoel Fernandes         if data.len() >= 30 {
4896fda04e7SJoel Fernandes             // Read size_of_block at offset 0x1A.
4906fda04e7SJoel Fernandes             size_of_block = Some(
4916fda04e7SJoel Fernandes                 (data[29] as u32) << 24
4926fda04e7SJoel Fernandes                     | (data[28] as u32) << 16
4936fda04e7SJoel Fernandes                     | (data[27] as u32) << 8
4946fda04e7SJoel Fernandes                     | (data[26] as u32),
4956fda04e7SJoel Fernandes             );
4966fda04e7SJoel Fernandes         }
4976fda04e7SJoel Fernandes 
4986fda04e7SJoel Fernandes         // For NBSI images, try to read the nbsiDataOffset at offset 0x16.
4996fda04e7SJoel Fernandes         if data.len() >= 24 {
5006fda04e7SJoel Fernandes             nbsi_data_offset = Some(u16::from_le_bytes([data[22], data[23]]));
5016fda04e7SJoel Fernandes         }
5026fda04e7SJoel Fernandes 
5036fda04e7SJoel Fernandes         Ok(PciRomHeader {
5046fda04e7SJoel Fernandes             signature,
5056fda04e7SJoel Fernandes             reserved: [0u8; 20],
5066fda04e7SJoel Fernandes             pci_data_struct_offset: pci_data_struct_ptr,
5076fda04e7SJoel Fernandes             size_of_block,
5086fda04e7SJoel Fernandes             nbsi_data_offset,
5096fda04e7SJoel Fernandes         })
5106fda04e7SJoel Fernandes     }
5116fda04e7SJoel Fernandes }
5126fda04e7SJoel Fernandes 
5136fda04e7SJoel Fernandes /// NVIDIA PCI Data Extension Structure.
5146fda04e7SJoel Fernandes ///
5156fda04e7SJoel Fernandes /// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the
5166fda04e7SJoel Fernandes /// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but
5176fda04e7SJoel Fernandes /// are needed for traversing the BIOS images. It is expected to be present in all BIOS images
5186fda04e7SJoel Fernandes /// except for NBSI images.
5196fda04e7SJoel Fernandes #[derive(Debug, Clone)]
5206fda04e7SJoel Fernandes #[repr(C)]
5216fda04e7SJoel Fernandes struct NpdeStruct {
5226fda04e7SJoel Fernandes     /// 00h: Signature ("NPDE")
5236fda04e7SJoel Fernandes     signature: [u8; 4],
5246fda04e7SJoel Fernandes     /// 04h: NVIDIA PCI Data Extension Revision
5256fda04e7SJoel Fernandes     npci_data_ext_rev: u16,
5266fda04e7SJoel Fernandes     /// 06h: NVIDIA PCI Data Extension Length
5276fda04e7SJoel Fernandes     npci_data_ext_len: u16,
5286fda04e7SJoel Fernandes     /// 08h: Sub-image Length (in 512-byte units)
5296fda04e7SJoel Fernandes     subimage_len: u16,
5306fda04e7SJoel Fernandes     /// 0Ah: Last image indicator flag
5316fda04e7SJoel Fernandes     last_image: u8,
5326fda04e7SJoel Fernandes }
5336fda04e7SJoel Fernandes 
5346fda04e7SJoel Fernandes impl NpdeStruct {
5356fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Option<Self> {
5366fda04e7SJoel Fernandes         if data.len() < core::mem::size_of::<Self>() {
5376fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Not enough data for NpdeStruct\n");
5386fda04e7SJoel Fernandes             return None;
5396fda04e7SJoel Fernandes         }
5406fda04e7SJoel Fernandes 
5416fda04e7SJoel Fernandes         let mut signature = [0u8; 4];
5426fda04e7SJoel Fernandes         signature.copy_from_slice(&data[0..4]);
5436fda04e7SJoel Fernandes 
5446fda04e7SJoel Fernandes         // Signature should be "NPDE" (0x4544504E).
5456fda04e7SJoel Fernandes         if &signature != b"NPDE" {
5466fda04e7SJoel Fernandes             dev_dbg!(
5476fda04e7SJoel Fernandes                 pdev.as_ref(),
5486fda04e7SJoel Fernandes                 "Invalid signature for NpdeStruct: {:?}\n",
5496fda04e7SJoel Fernandes                 signature
5506fda04e7SJoel Fernandes             );
5516fda04e7SJoel Fernandes             return None;
5526fda04e7SJoel Fernandes         }
5536fda04e7SJoel Fernandes 
5546fda04e7SJoel Fernandes         let subimage_len = u16::from_le_bytes([data[8], data[9]]);
5556fda04e7SJoel Fernandes         if subimage_len == 0 {
5566fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Invalid subimage length: 0\n");
5576fda04e7SJoel Fernandes             return None;
5586fda04e7SJoel Fernandes         }
5596fda04e7SJoel Fernandes 
5606fda04e7SJoel Fernandes         Some(NpdeStruct {
5616fda04e7SJoel Fernandes             signature,
5626fda04e7SJoel Fernandes             npci_data_ext_rev: u16::from_le_bytes([data[4], data[5]]),
5636fda04e7SJoel Fernandes             npci_data_ext_len: u16::from_le_bytes([data[6], data[7]]),
5646fda04e7SJoel Fernandes             subimage_len,
5656fda04e7SJoel Fernandes             last_image: data[10],
5666fda04e7SJoel Fernandes         })
5676fda04e7SJoel Fernandes     }
5686fda04e7SJoel Fernandes 
5696fda04e7SJoel Fernandes     /// Check if this is the last image in the ROM.
5706fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
5716fda04e7SJoel Fernandes         self.last_image & LAST_IMAGE_BIT_MASK != 0
5726fda04e7SJoel Fernandes     }
5736fda04e7SJoel Fernandes 
5746fda04e7SJoel Fernandes     /// Calculate image size in bytes from 512-byte blocks.
5756fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
5766fda04e7SJoel Fernandes         self.subimage_len as usize * 512
5776fda04e7SJoel Fernandes     }
5786fda04e7SJoel Fernandes 
5796fda04e7SJoel Fernandes     /// Try to find NPDE in the data, the NPDE is right after the PCIR.
5806fda04e7SJoel Fernandes     fn find_in_data(
5816fda04e7SJoel Fernandes         pdev: &pci::Device,
5826fda04e7SJoel Fernandes         data: &[u8],
5836fda04e7SJoel Fernandes         rom_header: &PciRomHeader,
5846fda04e7SJoel Fernandes         pcir: &PcirStruct,
5856fda04e7SJoel Fernandes     ) -> Option<Self> {
5866fda04e7SJoel Fernandes         // Calculate the offset where NPDE might be located
5876fda04e7SJoel Fernandes         // NPDE should be right after the PCIR structure, aligned to 16 bytes
5886fda04e7SJoel Fernandes         let pcir_offset = rom_header.pci_data_struct_offset as usize;
5896fda04e7SJoel Fernandes         let npde_start = (pcir_offset + pcir.pci_data_struct_len as usize + 0x0F) & !0x0F;
5906fda04e7SJoel Fernandes 
5916fda04e7SJoel Fernandes         // Check if we have enough data
5926fda04e7SJoel Fernandes         if npde_start + core::mem::size_of::<Self>() > data.len() {
5936fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Not enough data for NPDE\n");
5946fda04e7SJoel Fernandes             return None;
5956fda04e7SJoel Fernandes         }
5966fda04e7SJoel Fernandes 
5976fda04e7SJoel Fernandes         // Try to create NPDE from the data
5986fda04e7SJoel Fernandes         NpdeStruct::new(pdev, &data[npde_start..])
5996fda04e7SJoel Fernandes     }
6006fda04e7SJoel Fernandes }
6016fda04e7SJoel Fernandes 
6026fda04e7SJoel Fernandes // Use a macro to implement BiosImage enum and methods. This avoids having to
6036fda04e7SJoel Fernandes // repeat each enum type when implementing functions like base() in BiosImage.
6046fda04e7SJoel Fernandes macro_rules! bios_image {
6056fda04e7SJoel Fernandes     (
6066fda04e7SJoel Fernandes         $($variant:ident: $class:ident),* $(,)?
6076fda04e7SJoel Fernandes     ) => {
6086fda04e7SJoel Fernandes         // BiosImage enum with variants for each image type
6096fda04e7SJoel Fernandes         enum BiosImage {
6106fda04e7SJoel Fernandes             $($variant($class)),*
6116fda04e7SJoel Fernandes         }
6126fda04e7SJoel Fernandes 
6136fda04e7SJoel Fernandes         impl BiosImage {
6146fda04e7SJoel Fernandes             /// Get a reference to the common BIOS image data regardless of type
6156fda04e7SJoel Fernandes             fn base(&self) -> &BiosImageBase {
6166fda04e7SJoel Fernandes                 match self {
6176fda04e7SJoel Fernandes                     $(Self::$variant(img) => &img.base),*
6186fda04e7SJoel Fernandes                 }
6196fda04e7SJoel Fernandes             }
6206fda04e7SJoel Fernandes 
6216fda04e7SJoel Fernandes             /// Returns a string representing the type of BIOS image
6226fda04e7SJoel Fernandes             fn image_type_str(&self) -> &'static str {
6236fda04e7SJoel Fernandes                 match self {
6246fda04e7SJoel Fernandes                     $(Self::$variant(_) => stringify!($variant)),*
6256fda04e7SJoel Fernandes                 }
6266fda04e7SJoel Fernandes             }
6276fda04e7SJoel Fernandes         }
6286fda04e7SJoel Fernandes     }
6296fda04e7SJoel Fernandes }
6306fda04e7SJoel Fernandes 
6316fda04e7SJoel Fernandes impl BiosImage {
6326fda04e7SJoel Fernandes     /// Check if this is the last image.
6336fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
6346fda04e7SJoel Fernandes         let base = self.base();
6356fda04e7SJoel Fernandes 
6366fda04e7SJoel Fernandes         // For NBSI images (type == 0x70), return true as they're
6376fda04e7SJoel Fernandes         // considered the last image
6386fda04e7SJoel Fernandes         if matches!(self, Self::Nbsi(_)) {
6396fda04e7SJoel Fernandes             return true;
6406fda04e7SJoel Fernandes         }
6416fda04e7SJoel Fernandes 
6426fda04e7SJoel Fernandes         // For other image types, check the NPDE first if available
6436fda04e7SJoel Fernandes         if let Some(ref npde) = base.npde {
6446fda04e7SJoel Fernandes             return npde.is_last();
6456fda04e7SJoel Fernandes         }
6466fda04e7SJoel Fernandes 
6476fda04e7SJoel Fernandes         // Otherwise, fall back to checking the PCIR last_image flag
6486fda04e7SJoel Fernandes         base.pcir.is_last()
6496fda04e7SJoel Fernandes     }
6506fda04e7SJoel Fernandes 
6516fda04e7SJoel Fernandes     /// Get the image size in bytes.
6526fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
6536fda04e7SJoel Fernandes         let base = self.base();
6546fda04e7SJoel Fernandes 
6556fda04e7SJoel Fernandes         // Prefer NPDE image size if available
6566fda04e7SJoel Fernandes         if let Some(ref npde) = base.npde {
6576fda04e7SJoel Fernandes             return npde.image_size_bytes();
6586fda04e7SJoel Fernandes         }
6596fda04e7SJoel Fernandes 
6606fda04e7SJoel Fernandes         // Otherwise, fall back to the PCIR image size
6616fda04e7SJoel Fernandes         base.pcir.image_size_bytes()
6626fda04e7SJoel Fernandes     }
6636fda04e7SJoel Fernandes 
6646fda04e7SJoel Fernandes     /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which
6656fda04e7SJoel Fernandes     /// triggers the constructor of the specific BiosImage enum variant.
6666fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
6676fda04e7SJoel Fernandes         let base = BiosImageBase::new(pdev, data)?;
6686fda04e7SJoel Fernandes         let image = base.into_image().inspect_err(|e| {
6696fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Failed to create BiosImage: {:?}\n", e);
6706fda04e7SJoel Fernandes         })?;
6716fda04e7SJoel Fernandes 
6726fda04e7SJoel Fernandes         Ok(image)
6736fda04e7SJoel Fernandes     }
6746fda04e7SJoel Fernandes }
6756fda04e7SJoel Fernandes 
6766fda04e7SJoel Fernandes bios_image! {
6776fda04e7SJoel Fernandes     PciAt: PciAtBiosImage,   // PCI-AT compatible BIOS image
6786fda04e7SJoel Fernandes     Efi: EfiBiosImage,       // EFI (Extensible Firmware Interface)
6796fda04e7SJoel Fernandes     Nbsi: NbsiBiosImage,     // NBSI (Nvidia Bios System Interface)
6806fda04e7SJoel Fernandes     FwSec: FwSecBiosImage,   // FWSEC (Firmware Security)
6816fda04e7SJoel Fernandes }
6826fda04e7SJoel Fernandes 
683*dc70c6aeSJoel Fernandes /// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain.
684*dc70c6aeSJoel Fernandes ///
685*dc70c6aeSJoel Fernandes /// It contains the BIT header and the BIT tokens.
6866fda04e7SJoel Fernandes struct PciAtBiosImage {
6876fda04e7SJoel Fernandes     base: BiosImageBase,
688*dc70c6aeSJoel Fernandes     bit_header: BitHeader,
689*dc70c6aeSJoel Fernandes     bit_offset: usize,
6906fda04e7SJoel Fernandes }
6916fda04e7SJoel Fernandes 
6926fda04e7SJoel Fernandes struct EfiBiosImage {
6936fda04e7SJoel Fernandes     base: BiosImageBase,
6946fda04e7SJoel Fernandes     // EFI-specific fields can be added here in the future.
6956fda04e7SJoel Fernandes }
6966fda04e7SJoel Fernandes 
6976fda04e7SJoel Fernandes struct NbsiBiosImage {
6986fda04e7SJoel Fernandes     base: BiosImageBase,
6996fda04e7SJoel Fernandes     // NBSI-specific fields can be added here in the future.
7006fda04e7SJoel Fernandes }
7016fda04e7SJoel Fernandes 
7026fda04e7SJoel Fernandes struct FwSecBiosImage {
7036fda04e7SJoel Fernandes     base: BiosImageBase,
7046fda04e7SJoel Fernandes     // FWSEC-specific fields can be added here in the future.
7056fda04e7SJoel Fernandes }
7066fda04e7SJoel Fernandes 
7076fda04e7SJoel Fernandes // Convert from BiosImageBase to BiosImage
7086fda04e7SJoel Fernandes impl TryFrom<BiosImageBase> for BiosImage {
7096fda04e7SJoel Fernandes     type Error = Error;
7106fda04e7SJoel Fernandes 
7116fda04e7SJoel Fernandes     fn try_from(base: BiosImageBase) -> Result<Self> {
7126fda04e7SJoel Fernandes         match base.pcir.code_type {
713*dc70c6aeSJoel Fernandes             0x00 => Ok(BiosImage::PciAt(base.try_into()?)),
7146fda04e7SJoel Fernandes             0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })),
7156fda04e7SJoel Fernandes             0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })),
7166fda04e7SJoel Fernandes             0xE0 => Ok(BiosImage::FwSec(FwSecBiosImage { base })),
7176fda04e7SJoel Fernandes             _ => Err(EINVAL),
7186fda04e7SJoel Fernandes         }
7196fda04e7SJoel Fernandes     }
7206fda04e7SJoel Fernandes }
7216fda04e7SJoel Fernandes 
7226fda04e7SJoel Fernandes /// BIOS Image structure containing various headers and reference fields to all BIOS images.
7236fda04e7SJoel Fernandes ///
7246fda04e7SJoel Fernandes /// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that
7256fda04e7SJoel Fernandes /// Rust favors composition of types over inheritance.
7266fda04e7SJoel Fernandes #[derive(Debug)]
7276fda04e7SJoel Fernandes #[expect(dead_code)]
7286fda04e7SJoel Fernandes struct BiosImageBase {
7296fda04e7SJoel Fernandes     /// PCI ROM Expansion Header
7306fda04e7SJoel Fernandes     rom_header: PciRomHeader,
7316fda04e7SJoel Fernandes     /// PCI Data Structure
7326fda04e7SJoel Fernandes     pcir: PcirStruct,
7336fda04e7SJoel Fernandes     /// NVIDIA PCI Data Extension (optional)
7346fda04e7SJoel Fernandes     npde: Option<NpdeStruct>,
7356fda04e7SJoel Fernandes     /// Image data (includes ROM header and PCIR)
7366fda04e7SJoel Fernandes     data: KVec<u8>,
7376fda04e7SJoel Fernandes }
7386fda04e7SJoel Fernandes 
7396fda04e7SJoel Fernandes impl BiosImageBase {
7406fda04e7SJoel Fernandes     fn into_image(self) -> Result<BiosImage> {
7416fda04e7SJoel Fernandes         BiosImage::try_from(self)
7426fda04e7SJoel Fernandes     }
7436fda04e7SJoel Fernandes 
7446fda04e7SJoel Fernandes     /// Creates a new BiosImageBase from raw byte data.
7456fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
7466fda04e7SJoel Fernandes         // Ensure we have enough data for the ROM header.
7476fda04e7SJoel Fernandes         if data.len() < 26 {
7486fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Not enough data for ROM header\n");
7496fda04e7SJoel Fernandes             return Err(EINVAL);
7506fda04e7SJoel Fernandes         }
7516fda04e7SJoel Fernandes 
7526fda04e7SJoel Fernandes         // Parse the ROM header.
7536fda04e7SJoel Fernandes         let rom_header = PciRomHeader::new(pdev, &data[0..26])
7546fda04e7SJoel Fernandes             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PciRomHeader: {:?}\n", e))?;
7556fda04e7SJoel Fernandes 
7566fda04e7SJoel Fernandes         // Get the PCI Data Structure using the pointer from the ROM header.
7576fda04e7SJoel Fernandes         let pcir_offset = rom_header.pci_data_struct_offset as usize;
7586fda04e7SJoel Fernandes         let pcir_data = data
7596fda04e7SJoel Fernandes             .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>())
7606fda04e7SJoel Fernandes             .ok_or(EINVAL)
7616fda04e7SJoel Fernandes             .inspect_err(|_| {
7626fda04e7SJoel Fernandes                 dev_err!(
7636fda04e7SJoel Fernandes                     pdev.as_ref(),
7646fda04e7SJoel Fernandes                     "PCIR offset {:#x} out of bounds (data length: {})\n",
7656fda04e7SJoel Fernandes                     pcir_offset,
7666fda04e7SJoel Fernandes                     data.len()
7676fda04e7SJoel Fernandes                 );
7686fda04e7SJoel Fernandes                 dev_err!(
7696fda04e7SJoel Fernandes                     pdev.as_ref(),
7706fda04e7SJoel Fernandes                     "Consider reading more data for construction of BiosImage\n"
7716fda04e7SJoel Fernandes                 );
7726fda04e7SJoel Fernandes             })?;
7736fda04e7SJoel Fernandes 
7746fda04e7SJoel Fernandes         let pcir = PcirStruct::new(pdev, pcir_data)
7756fda04e7SJoel Fernandes             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PcirStruct: {:?}\n", e))?;
7766fda04e7SJoel Fernandes 
7776fda04e7SJoel Fernandes         // Look for NPDE structure if this is not an NBSI image (type != 0x70).
7786fda04e7SJoel Fernandes         let npde = NpdeStruct::find_in_data(pdev, data, &rom_header, &pcir);
7796fda04e7SJoel Fernandes 
7806fda04e7SJoel Fernandes         // Create a copy of the data.
7816fda04e7SJoel Fernandes         let mut data_copy = KVec::new();
7826fda04e7SJoel Fernandes         data_copy.extend_from_slice(data, GFP_KERNEL)?;
7836fda04e7SJoel Fernandes 
7846fda04e7SJoel Fernandes         Ok(BiosImageBase {
7856fda04e7SJoel Fernandes             rom_header,
7866fda04e7SJoel Fernandes             pcir,
7876fda04e7SJoel Fernandes             npde,
7886fda04e7SJoel Fernandes             data: data_copy,
7896fda04e7SJoel Fernandes         })
7906fda04e7SJoel Fernandes     }
7916fda04e7SJoel Fernandes }
792*dc70c6aeSJoel Fernandes 
793*dc70c6aeSJoel Fernandes impl PciAtBiosImage {
794*dc70c6aeSJoel Fernandes     /// Find a byte pattern in a slice.
795*dc70c6aeSJoel Fernandes     fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> {
796*dc70c6aeSJoel Fernandes         haystack
797*dc70c6aeSJoel Fernandes             .windows(needle.len())
798*dc70c6aeSJoel Fernandes             .position(|window| window == needle)
799*dc70c6aeSJoel Fernandes             .ok_or(EINVAL)
800*dc70c6aeSJoel Fernandes     }
801*dc70c6aeSJoel Fernandes 
802*dc70c6aeSJoel Fernandes     /// Find the BIT header in the [`PciAtBiosImage`].
803*dc70c6aeSJoel Fernandes     fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> {
804*dc70c6aeSJoel Fernandes         let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00];
805*dc70c6aeSJoel Fernandes         let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?;
806*dc70c6aeSJoel Fernandes         let bit_header = BitHeader::new(&data[bit_offset..])?;
807*dc70c6aeSJoel Fernandes 
808*dc70c6aeSJoel Fernandes         Ok((bit_header, bit_offset))
809*dc70c6aeSJoel Fernandes     }
810*dc70c6aeSJoel Fernandes 
811*dc70c6aeSJoel Fernandes     /// Get a BIT token entry from the BIT table in the [`PciAtBiosImage`]
812*dc70c6aeSJoel Fernandes     fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {
813*dc70c6aeSJoel Fernandes         BitToken::from_id(self, token_id)
814*dc70c6aeSJoel Fernandes     }
815*dc70c6aeSJoel Fernandes 
816*dc70c6aeSJoel Fernandes     /// Find the Falcon data pointer structure in the [`PciAtBiosImage`].
817*dc70c6aeSJoel Fernandes     ///
818*dc70c6aeSJoel Fernandes     /// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC
819*dc70c6aeSJoel Fernandes     /// image.
820*dc70c6aeSJoel Fernandes     fn falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32> {
821*dc70c6aeSJoel Fernandes         let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?;
822*dc70c6aeSJoel Fernandes 
823*dc70c6aeSJoel Fernandes         // Make sure we don't go out of bounds
824*dc70c6aeSJoel Fernandes         if token.data_offset as usize + 4 > self.base.data.len() {
825*dc70c6aeSJoel Fernandes             return Err(EINVAL);
826*dc70c6aeSJoel Fernandes         }
827*dc70c6aeSJoel Fernandes 
828*dc70c6aeSJoel Fernandes         // read the 4 bytes at the offset specified in the token
829*dc70c6aeSJoel Fernandes         let offset = token.data_offset as usize;
830*dc70c6aeSJoel Fernandes         let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| {
831*dc70c6aeSJoel Fernandes             dev_err!(pdev.as_ref(), "Failed to convert data slice to array");
832*dc70c6aeSJoel Fernandes             EINVAL
833*dc70c6aeSJoel Fernandes         })?;
834*dc70c6aeSJoel Fernandes 
835*dc70c6aeSJoel Fernandes         let data_ptr = u32::from_le_bytes(bytes);
836*dc70c6aeSJoel Fernandes 
837*dc70c6aeSJoel Fernandes         if (data_ptr as usize) < self.base.data.len() {
838*dc70c6aeSJoel Fernandes             dev_err!(pdev.as_ref(), "Falcon data pointer out of bounds\n");
839*dc70c6aeSJoel Fernandes             return Err(EINVAL);
840*dc70c6aeSJoel Fernandes         }
841*dc70c6aeSJoel Fernandes 
842*dc70c6aeSJoel Fernandes         Ok(data_ptr)
843*dc70c6aeSJoel Fernandes     }
844*dc70c6aeSJoel Fernandes }
845*dc70c6aeSJoel Fernandes 
846*dc70c6aeSJoel Fernandes impl TryFrom<BiosImageBase> for PciAtBiosImage {
847*dc70c6aeSJoel Fernandes     type Error = Error;
848*dc70c6aeSJoel Fernandes 
849*dc70c6aeSJoel Fernandes     fn try_from(base: BiosImageBase) -> Result<Self> {
850*dc70c6aeSJoel Fernandes         let data_slice = &base.data;
851*dc70c6aeSJoel Fernandes         let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?;
852*dc70c6aeSJoel Fernandes 
853*dc70c6aeSJoel Fernandes         Ok(PciAtBiosImage {
854*dc70c6aeSJoel Fernandes             base,
855*dc70c6aeSJoel Fernandes             bit_header,
856*dc70c6aeSJoel Fernandes             bit_offset,
857*dc70c6aeSJoel Fernandes         })
858*dc70c6aeSJoel Fernandes     }
859*dc70c6aeSJoel Fernandes }
860