xref: /linux/drivers/gpu/nova-core/vbios.rs (revision 220994d61cebfc04f071d69049127657c7e8191b)
16fda04e7SJoel Fernandes // SPDX-License-Identifier: GPL-2.0
26fda04e7SJoel Fernandes 
36fda04e7SJoel Fernandes //! VBIOS extraction and parsing.
46fda04e7SJoel Fernandes 
56fda04e7SJoel Fernandes use crate::driver::Bar0;
631f0feefSAlexandre Courbot use crate::firmware::fwsec::Bcrt30Rsa3kSignature;
747c4846eSJoel Fernandes use crate::firmware::FalconUCodeDescV3;
86fda04e7SJoel Fernandes use core::convert::TryFrom;
947c4846eSJoel Fernandes use kernel::device;
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> {
new(pdev: &'a pci::Device, bar0: &'a Bar0) -> Result<Self>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.
read_more(&mut self, len: usize) -> Result606fda04e7SJoel 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.
read_more_at_offset(&mut self, offset: usize, len: usize) -> Result906fda04e7SJoel 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.
read_bios_image_at_offset( &mut self, offset: usize, len: usize, context: &str, ) -> Result<BiosImage>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.
next(&mut self) -> Option<Self::Item>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;
1803606620bSAlexandre Courbot         // TODO[NUMM]: 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.
new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios>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;
19847c4846eSJoel Fernandes         let mut first_fwsec_image: Option<FwSecBiosBuilder> = None;
19947c4846eSJoel Fernandes         let mut second_fwsec_image: Option<FwSecBiosBuilder> = 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.
23347c4846eSJoel Fernandes         if let (Some(mut second), Some(first), Some(pci_at)) =
2346fda04e7SJoel Fernandes             (second_fwsec_image, first_fwsec_image, pci_at_image)
2356fda04e7SJoel Fernandes         {
23647c4846eSJoel Fernandes             second
23747c4846eSJoel Fernandes                 .setup_falcon_data(pdev, &pci_at, &first)
23847c4846eSJoel Fernandes                 .inspect_err(|e| dev_err!(pdev.as_ref(), "Falcon data setup failed: {:?}\n", e))?;
2396fda04e7SJoel Fernandes             Ok(Vbios {
24047c4846eSJoel Fernandes                 fwsec_image: second.build(pdev)?,
2416fda04e7SJoel Fernandes             })
2426fda04e7SJoel Fernandes         } else {
2436fda04e7SJoel Fernandes             dev_err!(
2446fda04e7SJoel Fernandes                 pdev.as_ref(),
2456fda04e7SJoel Fernandes                 "Missing required images for falcon data setup, skipping\n"
2466fda04e7SJoel Fernandes             );
2476fda04e7SJoel Fernandes             Err(EINVAL)
2486fda04e7SJoel Fernandes         }
2496fda04e7SJoel Fernandes     }
25047c4846eSJoel Fernandes 
fwsec_image(&self) -> &FwSecBiosImage25147c4846eSJoel Fernandes     pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage {
25247c4846eSJoel Fernandes         &self.fwsec_image
25347c4846eSJoel Fernandes     }
2546fda04e7SJoel Fernandes }
2556fda04e7SJoel Fernandes 
2566fda04e7SJoel Fernandes /// PCI Data Structure as defined in PCI Firmware Specification
2576fda04e7SJoel Fernandes #[derive(Debug, Clone)]
2586fda04e7SJoel Fernandes #[repr(C)]
2596fda04e7SJoel Fernandes struct PcirStruct {
2606fda04e7SJoel Fernandes     /// PCI Data Structure signature ("PCIR" or "NPDS")
2616fda04e7SJoel Fernandes     signature: [u8; 4],
2626fda04e7SJoel Fernandes     /// PCI Vendor ID (e.g., 0x10DE for NVIDIA)
2636fda04e7SJoel Fernandes     vendor_id: u16,
2646fda04e7SJoel Fernandes     /// PCI Device ID
2656fda04e7SJoel Fernandes     device_id: u16,
2666fda04e7SJoel Fernandes     /// Device List Pointer
2676fda04e7SJoel Fernandes     device_list_ptr: u16,
2686fda04e7SJoel Fernandes     /// PCI Data Structure Length
2696fda04e7SJoel Fernandes     pci_data_struct_len: u16,
2706fda04e7SJoel Fernandes     /// PCI Data Structure Revision
2716fda04e7SJoel Fernandes     pci_data_struct_rev: u8,
2726fda04e7SJoel Fernandes     /// Class code (3 bytes, 0x03 for display controller)
2736fda04e7SJoel Fernandes     class_code: [u8; 3],
2746fda04e7SJoel Fernandes     /// Size of this image in 512-byte blocks
2756fda04e7SJoel Fernandes     image_len: u16,
2766fda04e7SJoel Fernandes     /// Revision Level of the Vendor's ROM
2776fda04e7SJoel Fernandes     vendor_rom_rev: u16,
2786fda04e7SJoel Fernandes     /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI)
2796fda04e7SJoel Fernandes     code_type: u8,
2806fda04e7SJoel Fernandes     /// Last image indicator (0x00 = Not last image, 0x80 = Last image)
2816fda04e7SJoel Fernandes     last_image: u8,
2826fda04e7SJoel Fernandes     /// Maximum Run-time Image Length (units of 512 bytes)
2836fda04e7SJoel Fernandes     max_runtime_image_len: u16,
2846fda04e7SJoel Fernandes }
2856fda04e7SJoel Fernandes 
2866fda04e7SJoel Fernandes impl PcirStruct {
new(pdev: &pci::Device, data: &[u8]) -> Result<Self>2876fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
2886fda04e7SJoel Fernandes         if data.len() < core::mem::size_of::<PcirStruct>() {
2896fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Not enough data for PcirStruct\n");
2906fda04e7SJoel Fernandes             return Err(EINVAL);
2916fda04e7SJoel Fernandes         }
2926fda04e7SJoel Fernandes 
2936fda04e7SJoel Fernandes         let mut signature = [0u8; 4];
2946fda04e7SJoel Fernandes         signature.copy_from_slice(&data[0..4]);
2956fda04e7SJoel Fernandes 
2966fda04e7SJoel Fernandes         // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
2976fda04e7SJoel Fernandes         if &signature != b"PCIR" && &signature != b"NPDS" {
2986fda04e7SJoel Fernandes             dev_err!(
2996fda04e7SJoel Fernandes                 pdev.as_ref(),
3006fda04e7SJoel Fernandes                 "Invalid signature for PcirStruct: {:?}\n",
3016fda04e7SJoel Fernandes                 signature
3026fda04e7SJoel Fernandes             );
3036fda04e7SJoel Fernandes             return Err(EINVAL);
3046fda04e7SJoel Fernandes         }
3056fda04e7SJoel Fernandes 
3066fda04e7SJoel Fernandes         let mut class_code = [0u8; 3];
3076fda04e7SJoel Fernandes         class_code.copy_from_slice(&data[13..16]);
3086fda04e7SJoel Fernandes 
3096fda04e7SJoel Fernandes         let image_len = u16::from_le_bytes([data[16], data[17]]);
3106fda04e7SJoel Fernandes         if image_len == 0 {
3116fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Invalid image length: 0\n");
3126fda04e7SJoel Fernandes             return Err(EINVAL);
3136fda04e7SJoel Fernandes         }
3146fda04e7SJoel Fernandes 
3156fda04e7SJoel Fernandes         Ok(PcirStruct {
3166fda04e7SJoel Fernandes             signature,
3176fda04e7SJoel Fernandes             vendor_id: u16::from_le_bytes([data[4], data[5]]),
3186fda04e7SJoel Fernandes             device_id: u16::from_le_bytes([data[6], data[7]]),
3196fda04e7SJoel Fernandes             device_list_ptr: u16::from_le_bytes([data[8], data[9]]),
3206fda04e7SJoel Fernandes             pci_data_struct_len: u16::from_le_bytes([data[10], data[11]]),
3216fda04e7SJoel Fernandes             pci_data_struct_rev: data[12],
3226fda04e7SJoel Fernandes             class_code,
3236fda04e7SJoel Fernandes             image_len,
3246fda04e7SJoel Fernandes             vendor_rom_rev: u16::from_le_bytes([data[18], data[19]]),
3256fda04e7SJoel Fernandes             code_type: data[20],
3266fda04e7SJoel Fernandes             last_image: data[21],
3276fda04e7SJoel Fernandes             max_runtime_image_len: u16::from_le_bytes([data[22], data[23]]),
3286fda04e7SJoel Fernandes         })
3296fda04e7SJoel Fernandes     }
3306fda04e7SJoel Fernandes 
3316fda04e7SJoel Fernandes     /// Check if this is the last image in the ROM.
is_last(&self) -> bool3326fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
3336fda04e7SJoel Fernandes         self.last_image & LAST_IMAGE_BIT_MASK != 0
3346fda04e7SJoel Fernandes     }
3356fda04e7SJoel Fernandes 
3366fda04e7SJoel Fernandes     /// Calculate image size in bytes from 512-byte blocks.
image_size_bytes(&self) -> usize3376fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
3386fda04e7SJoel Fernandes         self.image_len as usize * 512
3396fda04e7SJoel Fernandes     }
3406fda04e7SJoel Fernandes }
3416fda04e7SJoel Fernandes 
342dc70c6aeSJoel Fernandes /// BIOS Information Table (BIT) Header.
343dc70c6aeSJoel Fernandes ///
344dc70c6aeSJoel Fernandes /// This is the head of the BIT table, that is used to locate the Falcon data. The BIT table (with
345dc70c6aeSJoel Fernandes /// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the
346dc70c6aeSJoel Fernandes /// [`FwSecBiosImage`].
347dc70c6aeSJoel Fernandes #[derive(Debug, Clone, Copy)]
348dc70c6aeSJoel Fernandes #[expect(dead_code)]
349dc70c6aeSJoel Fernandes struct BitHeader {
350dc70c6aeSJoel Fernandes     /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
351dc70c6aeSJoel Fernandes     id: u16,
352dc70c6aeSJoel Fernandes     /// 2h: BIT Header Signature ("BIT\0")
353dc70c6aeSJoel Fernandes     signature: [u8; 4],
354dc70c6aeSJoel Fernandes     /// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00.
355dc70c6aeSJoel Fernandes     bcd_version: u16,
356dc70c6aeSJoel Fernandes     /// 8h: Size of BIT Header (in bytes)
357dc70c6aeSJoel Fernandes     header_size: u8,
358dc70c6aeSJoel Fernandes     /// 9h: Size of BIT Tokens (in bytes)
359dc70c6aeSJoel Fernandes     token_size: u8,
360dc70c6aeSJoel Fernandes     /// 10h: Number of token entries that follow
361dc70c6aeSJoel Fernandes     token_entries: u8,
362dc70c6aeSJoel Fernandes     /// 11h: BIT Header Checksum
363dc70c6aeSJoel Fernandes     checksum: u8,
364dc70c6aeSJoel Fernandes }
365dc70c6aeSJoel Fernandes 
366dc70c6aeSJoel Fernandes impl BitHeader {
new(data: &[u8]) -> Result<Self>367dc70c6aeSJoel Fernandes     fn new(data: &[u8]) -> Result<Self> {
368dc70c6aeSJoel Fernandes         if data.len() < 12 {
369dc70c6aeSJoel Fernandes             return Err(EINVAL);
370dc70c6aeSJoel Fernandes         }
371dc70c6aeSJoel Fernandes 
372dc70c6aeSJoel Fernandes         let mut signature = [0u8; 4];
373dc70c6aeSJoel Fernandes         signature.copy_from_slice(&data[2..6]);
374dc70c6aeSJoel Fernandes 
375dc70c6aeSJoel Fernandes         // Check header ID and signature
376dc70c6aeSJoel Fernandes         let id = u16::from_le_bytes([data[0], data[1]]);
377dc70c6aeSJoel Fernandes         if id != 0xB8FF || &signature != b"BIT\0" {
378dc70c6aeSJoel Fernandes             return Err(EINVAL);
379dc70c6aeSJoel Fernandes         }
380dc70c6aeSJoel Fernandes 
381dc70c6aeSJoel Fernandes         Ok(BitHeader {
382dc70c6aeSJoel Fernandes             id,
383dc70c6aeSJoel Fernandes             signature,
384dc70c6aeSJoel Fernandes             bcd_version: u16::from_le_bytes([data[6], data[7]]),
385dc70c6aeSJoel Fernandes             header_size: data[8],
386dc70c6aeSJoel Fernandes             token_size: data[9],
387dc70c6aeSJoel Fernandes             token_entries: data[10],
388dc70c6aeSJoel Fernandes             checksum: data[11],
389dc70c6aeSJoel Fernandes         })
390dc70c6aeSJoel Fernandes     }
391dc70c6aeSJoel Fernandes }
392dc70c6aeSJoel Fernandes 
393dc70c6aeSJoel Fernandes /// BIT Token Entry: Records in the BIT table followed by the BIT header.
394dc70c6aeSJoel Fernandes #[derive(Debug, Clone, Copy)]
395dc70c6aeSJoel Fernandes #[expect(dead_code)]
396dc70c6aeSJoel Fernandes struct BitToken {
397dc70c6aeSJoel Fernandes     /// 00h: Token identifier
398dc70c6aeSJoel Fernandes     id: u8,
399dc70c6aeSJoel Fernandes     /// 01h: Version of the token data
400dc70c6aeSJoel Fernandes     data_version: u8,
401dc70c6aeSJoel Fernandes     /// 02h: Size of token data in bytes
402dc70c6aeSJoel Fernandes     data_size: u16,
403dc70c6aeSJoel Fernandes     /// 04h: Offset to the token data
404dc70c6aeSJoel Fernandes     data_offset: u16,
405dc70c6aeSJoel Fernandes }
406dc70c6aeSJoel Fernandes 
407dc70c6aeSJoel Fernandes // Define the token ID for the Falcon data
408dc70c6aeSJoel Fernandes const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
409dc70c6aeSJoel Fernandes 
410dc70c6aeSJoel Fernandes impl BitToken {
411dc70c6aeSJoel Fernandes     /// Find a BIT token entry by BIT ID in a PciAtBiosImage
from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self>412dc70c6aeSJoel Fernandes     fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> {
413dc70c6aeSJoel Fernandes         let header = &image.bit_header;
414dc70c6aeSJoel Fernandes 
415dc70c6aeSJoel Fernandes         // Offset to the first token entry
416dc70c6aeSJoel Fernandes         let tokens_start = image.bit_offset + header.header_size as usize;
417dc70c6aeSJoel Fernandes 
418dc70c6aeSJoel Fernandes         for i in 0..header.token_entries as usize {
419dc70c6aeSJoel Fernandes             let entry_offset = tokens_start + (i * header.token_size as usize);
420dc70c6aeSJoel Fernandes 
421dc70c6aeSJoel Fernandes             // Make sure we don't go out of bounds
422dc70c6aeSJoel Fernandes             if entry_offset + header.token_size as usize > image.base.data.len() {
423dc70c6aeSJoel Fernandes                 return Err(EINVAL);
424dc70c6aeSJoel Fernandes             }
425dc70c6aeSJoel Fernandes 
426dc70c6aeSJoel Fernandes             // Check if this token has the requested ID
427dc70c6aeSJoel Fernandes             if image.base.data[entry_offset] == token_id {
428dc70c6aeSJoel Fernandes                 return Ok(BitToken {
429dc70c6aeSJoel Fernandes                     id: image.base.data[entry_offset],
430dc70c6aeSJoel Fernandes                     data_version: image.base.data[entry_offset + 1],
431dc70c6aeSJoel Fernandes                     data_size: u16::from_le_bytes([
432dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 2],
433dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 3],
434dc70c6aeSJoel Fernandes                     ]),
435dc70c6aeSJoel Fernandes                     data_offset: u16::from_le_bytes([
436dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 4],
437dc70c6aeSJoel Fernandes                         image.base.data[entry_offset + 5],
438dc70c6aeSJoel Fernandes                     ]),
439dc70c6aeSJoel Fernandes                 });
440dc70c6aeSJoel Fernandes             }
441dc70c6aeSJoel Fernandes         }
442dc70c6aeSJoel Fernandes 
443dc70c6aeSJoel Fernandes         // Token not found
444dc70c6aeSJoel Fernandes         Err(ENOENT)
445dc70c6aeSJoel Fernandes     }
446dc70c6aeSJoel Fernandes }
447dc70c6aeSJoel Fernandes 
4486fda04e7SJoel Fernandes /// PCI ROM Expansion Header as defined in PCI Firmware Specification.
4496fda04e7SJoel Fernandes ///
4506fda04e7SJoel Fernandes /// This is header is at the beginning of every image in the set of images in the ROM. It contains
4516fda04e7SJoel Fernandes /// a pointer to the PCI Data Structure which describes the image. For "NBSI" images (NoteBook
4526fda04e7SJoel Fernandes /// System Information), the ROM header deviates from the standard and contains an offset to the
4536fda04e7SJoel Fernandes /// NBSI image however we do not yet parse that in this module and keep it for future reference.
4546fda04e7SJoel Fernandes #[derive(Debug, Clone, Copy)]
4556fda04e7SJoel Fernandes #[expect(dead_code)]
4566fda04e7SJoel Fernandes struct PciRomHeader {
4576fda04e7SJoel Fernandes     /// 00h: Signature (0xAA55)
4586fda04e7SJoel Fernandes     signature: u16,
4596fda04e7SJoel Fernandes     /// 02h: Reserved bytes for processor architecture unique data (20 bytes)
4606fda04e7SJoel Fernandes     reserved: [u8; 20],
4616fda04e7SJoel Fernandes     /// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image)
4626fda04e7SJoel Fernandes     nbsi_data_offset: Option<u16>,
4636fda04e7SJoel Fernandes     /// 18h: Pointer to PCI Data Structure (offset from start of ROM image)
4646fda04e7SJoel Fernandes     pci_data_struct_offset: u16,
4656fda04e7SJoel Fernandes     /// 1Ah: Size of block (this is NBSI-specific)
4666fda04e7SJoel Fernandes     size_of_block: Option<u32>,
4676fda04e7SJoel Fernandes }
4686fda04e7SJoel Fernandes 
4696fda04e7SJoel Fernandes impl PciRomHeader {
new(pdev: &pci::Device, data: &[u8]) -> Result<Self>4706fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
4716fda04e7SJoel Fernandes         if data.len() < 26 {
4726fda04e7SJoel Fernandes             // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock.
4736fda04e7SJoel Fernandes             return Err(EINVAL);
4746fda04e7SJoel Fernandes         }
4756fda04e7SJoel Fernandes 
4766fda04e7SJoel Fernandes         let signature = u16::from_le_bytes([data[0], data[1]]);
4776fda04e7SJoel Fernandes 
4786fda04e7SJoel Fernandes         // Check for valid ROM signatures.
4796fda04e7SJoel Fernandes         match signature {
4806fda04e7SJoel Fernandes             0xAA55 | 0xBB77 | 0x4E56 => {}
4816fda04e7SJoel Fernandes             _ => {
4826fda04e7SJoel Fernandes                 dev_err!(pdev.as_ref(), "ROM signature unknown {:#x}\n", signature);
4836fda04e7SJoel Fernandes                 return Err(EINVAL);
4846fda04e7SJoel Fernandes             }
4856fda04e7SJoel Fernandes         }
4866fda04e7SJoel Fernandes 
4876fda04e7SJoel Fernandes         // Read the pointer to the PCI Data Structure at offset 0x18.
4886fda04e7SJoel Fernandes         let pci_data_struct_ptr = u16::from_le_bytes([data[24], data[25]]);
4896fda04e7SJoel Fernandes 
4906fda04e7SJoel Fernandes         // Try to read optional fields if enough data.
4916fda04e7SJoel Fernandes         let mut size_of_block = None;
4926fda04e7SJoel Fernandes         let mut nbsi_data_offset = None;
4936fda04e7SJoel Fernandes 
4946fda04e7SJoel Fernandes         if data.len() >= 30 {
4956fda04e7SJoel Fernandes             // Read size_of_block at offset 0x1A.
4966fda04e7SJoel Fernandes             size_of_block = Some(
49743ad65ecSDanilo Krummrich                 u32::from(data[29]) << 24
49843ad65ecSDanilo Krummrich                     | u32::from(data[28]) << 16
49943ad65ecSDanilo Krummrich                     | u32::from(data[27]) << 8
50043ad65ecSDanilo Krummrich                     | u32::from(data[26]),
5016fda04e7SJoel Fernandes             );
5026fda04e7SJoel Fernandes         }
5036fda04e7SJoel Fernandes 
5046fda04e7SJoel Fernandes         // For NBSI images, try to read the nbsiDataOffset at offset 0x16.
5056fda04e7SJoel Fernandes         if data.len() >= 24 {
5066fda04e7SJoel Fernandes             nbsi_data_offset = Some(u16::from_le_bytes([data[22], data[23]]));
5076fda04e7SJoel Fernandes         }
5086fda04e7SJoel Fernandes 
5096fda04e7SJoel Fernandes         Ok(PciRomHeader {
5106fda04e7SJoel Fernandes             signature,
5116fda04e7SJoel Fernandes             reserved: [0u8; 20],
5126fda04e7SJoel Fernandes             pci_data_struct_offset: pci_data_struct_ptr,
5136fda04e7SJoel Fernandes             size_of_block,
5146fda04e7SJoel Fernandes             nbsi_data_offset,
5156fda04e7SJoel Fernandes         })
5166fda04e7SJoel Fernandes     }
5176fda04e7SJoel Fernandes }
5186fda04e7SJoel Fernandes 
5196fda04e7SJoel Fernandes /// NVIDIA PCI Data Extension Structure.
5206fda04e7SJoel Fernandes ///
5216fda04e7SJoel Fernandes /// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the
5226fda04e7SJoel Fernandes /// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but
5236fda04e7SJoel Fernandes /// are needed for traversing the BIOS images. It is expected to be present in all BIOS images
5246fda04e7SJoel Fernandes /// except for NBSI images.
5256fda04e7SJoel Fernandes #[derive(Debug, Clone)]
5266fda04e7SJoel Fernandes #[repr(C)]
5276fda04e7SJoel Fernandes struct NpdeStruct {
5286fda04e7SJoel Fernandes     /// 00h: Signature ("NPDE")
5296fda04e7SJoel Fernandes     signature: [u8; 4],
5306fda04e7SJoel Fernandes     /// 04h: NVIDIA PCI Data Extension Revision
5316fda04e7SJoel Fernandes     npci_data_ext_rev: u16,
5326fda04e7SJoel Fernandes     /// 06h: NVIDIA PCI Data Extension Length
5336fda04e7SJoel Fernandes     npci_data_ext_len: u16,
5346fda04e7SJoel Fernandes     /// 08h: Sub-image Length (in 512-byte units)
5356fda04e7SJoel Fernandes     subimage_len: u16,
5366fda04e7SJoel Fernandes     /// 0Ah: Last image indicator flag
5376fda04e7SJoel Fernandes     last_image: u8,
5386fda04e7SJoel Fernandes }
5396fda04e7SJoel Fernandes 
5406fda04e7SJoel Fernandes impl NpdeStruct {
new(pdev: &pci::Device, data: &[u8]) -> Option<Self>5416fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Option<Self> {
5426fda04e7SJoel Fernandes         if data.len() < core::mem::size_of::<Self>() {
5436fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Not enough data for NpdeStruct\n");
5446fda04e7SJoel Fernandes             return None;
5456fda04e7SJoel Fernandes         }
5466fda04e7SJoel Fernandes 
5476fda04e7SJoel Fernandes         let mut signature = [0u8; 4];
5486fda04e7SJoel Fernandes         signature.copy_from_slice(&data[0..4]);
5496fda04e7SJoel Fernandes 
5506fda04e7SJoel Fernandes         // Signature should be "NPDE" (0x4544504E).
5516fda04e7SJoel Fernandes         if &signature != b"NPDE" {
5526fda04e7SJoel Fernandes             dev_dbg!(
5536fda04e7SJoel Fernandes                 pdev.as_ref(),
5546fda04e7SJoel Fernandes                 "Invalid signature for NpdeStruct: {:?}\n",
5556fda04e7SJoel Fernandes                 signature
5566fda04e7SJoel Fernandes             );
5576fda04e7SJoel Fernandes             return None;
5586fda04e7SJoel Fernandes         }
5596fda04e7SJoel Fernandes 
5606fda04e7SJoel Fernandes         let subimage_len = u16::from_le_bytes([data[8], data[9]]);
5616fda04e7SJoel Fernandes         if subimage_len == 0 {
5626fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Invalid subimage length: 0\n");
5636fda04e7SJoel Fernandes             return None;
5646fda04e7SJoel Fernandes         }
5656fda04e7SJoel Fernandes 
5666fda04e7SJoel Fernandes         Some(NpdeStruct {
5676fda04e7SJoel Fernandes             signature,
5686fda04e7SJoel Fernandes             npci_data_ext_rev: u16::from_le_bytes([data[4], data[5]]),
5696fda04e7SJoel Fernandes             npci_data_ext_len: u16::from_le_bytes([data[6], data[7]]),
5706fda04e7SJoel Fernandes             subimage_len,
5716fda04e7SJoel Fernandes             last_image: data[10],
5726fda04e7SJoel Fernandes         })
5736fda04e7SJoel Fernandes     }
5746fda04e7SJoel Fernandes 
5756fda04e7SJoel Fernandes     /// Check if this is the last image in the ROM.
is_last(&self) -> bool5766fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
5776fda04e7SJoel Fernandes         self.last_image & LAST_IMAGE_BIT_MASK != 0
5786fda04e7SJoel Fernandes     }
5796fda04e7SJoel Fernandes 
5806fda04e7SJoel Fernandes     /// Calculate image size in bytes from 512-byte blocks.
image_size_bytes(&self) -> usize5816fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
5826fda04e7SJoel Fernandes         self.subimage_len as usize * 512
5836fda04e7SJoel Fernandes     }
5846fda04e7SJoel Fernandes 
5856fda04e7SJoel Fernandes     /// Try to find NPDE in the data, the NPDE is right after the PCIR.
find_in_data( pdev: &pci::Device, data: &[u8], rom_header: &PciRomHeader, pcir: &PcirStruct, ) -> Option<Self>5866fda04e7SJoel Fernandes     fn find_in_data(
5876fda04e7SJoel Fernandes         pdev: &pci::Device,
5886fda04e7SJoel Fernandes         data: &[u8],
5896fda04e7SJoel Fernandes         rom_header: &PciRomHeader,
5906fda04e7SJoel Fernandes         pcir: &PcirStruct,
5916fda04e7SJoel Fernandes     ) -> Option<Self> {
5926fda04e7SJoel Fernandes         // Calculate the offset where NPDE might be located
5936fda04e7SJoel Fernandes         // NPDE should be right after the PCIR structure, aligned to 16 bytes
5946fda04e7SJoel Fernandes         let pcir_offset = rom_header.pci_data_struct_offset as usize;
5956fda04e7SJoel Fernandes         let npde_start = (pcir_offset + pcir.pci_data_struct_len as usize + 0x0F) & !0x0F;
5966fda04e7SJoel Fernandes 
5976fda04e7SJoel Fernandes         // Check if we have enough data
5986fda04e7SJoel Fernandes         if npde_start + core::mem::size_of::<Self>() > data.len() {
5996fda04e7SJoel Fernandes             dev_dbg!(pdev.as_ref(), "Not enough data for NPDE\n");
6006fda04e7SJoel Fernandes             return None;
6016fda04e7SJoel Fernandes         }
6026fda04e7SJoel Fernandes 
6036fda04e7SJoel Fernandes         // Try to create NPDE from the data
6046fda04e7SJoel Fernandes         NpdeStruct::new(pdev, &data[npde_start..])
6056fda04e7SJoel Fernandes     }
6066fda04e7SJoel Fernandes }
6076fda04e7SJoel Fernandes 
6086fda04e7SJoel Fernandes // Use a macro to implement BiosImage enum and methods. This avoids having to
6096fda04e7SJoel Fernandes // repeat each enum type when implementing functions like base() in BiosImage.
6106fda04e7SJoel Fernandes macro_rules! bios_image {
6116fda04e7SJoel Fernandes     (
6126fda04e7SJoel Fernandes         $($variant:ident: $class:ident),* $(,)?
6136fda04e7SJoel Fernandes     ) => {
6146fda04e7SJoel Fernandes         // BiosImage enum with variants for each image type
6156fda04e7SJoel Fernandes         enum BiosImage {
6166fda04e7SJoel Fernandes             $($variant($class)),*
6176fda04e7SJoel Fernandes         }
6186fda04e7SJoel Fernandes 
6196fda04e7SJoel Fernandes         impl BiosImage {
6206fda04e7SJoel Fernandes             /// Get a reference to the common BIOS image data regardless of type
6216fda04e7SJoel Fernandes             fn base(&self) -> &BiosImageBase {
6226fda04e7SJoel Fernandes                 match self {
6236fda04e7SJoel Fernandes                     $(Self::$variant(img) => &img.base),*
6246fda04e7SJoel Fernandes                 }
6256fda04e7SJoel Fernandes             }
6266fda04e7SJoel Fernandes 
6276fda04e7SJoel Fernandes             /// Returns a string representing the type of BIOS image
6286fda04e7SJoel Fernandes             fn image_type_str(&self) -> &'static str {
6296fda04e7SJoel Fernandes                 match self {
6306fda04e7SJoel Fernandes                     $(Self::$variant(_) => stringify!($variant)),*
6316fda04e7SJoel Fernandes                 }
6326fda04e7SJoel Fernandes             }
6336fda04e7SJoel Fernandes         }
6346fda04e7SJoel Fernandes     }
6356fda04e7SJoel Fernandes }
6366fda04e7SJoel Fernandes 
6376fda04e7SJoel Fernandes impl BiosImage {
6386fda04e7SJoel Fernandes     /// Check if this is the last image.
is_last(&self) -> bool6396fda04e7SJoel Fernandes     fn is_last(&self) -> bool {
6406fda04e7SJoel Fernandes         let base = self.base();
6416fda04e7SJoel Fernandes 
6426fda04e7SJoel Fernandes         // For NBSI images (type == 0x70), return true as they're
6436fda04e7SJoel Fernandes         // considered the last image
6446fda04e7SJoel Fernandes         if matches!(self, Self::Nbsi(_)) {
6456fda04e7SJoel Fernandes             return true;
6466fda04e7SJoel Fernandes         }
6476fda04e7SJoel Fernandes 
6486fda04e7SJoel Fernandes         // For other image types, check the NPDE first if available
6496fda04e7SJoel Fernandes         if let Some(ref npde) = base.npde {
6506fda04e7SJoel Fernandes             return npde.is_last();
6516fda04e7SJoel Fernandes         }
6526fda04e7SJoel Fernandes 
6536fda04e7SJoel Fernandes         // Otherwise, fall back to checking the PCIR last_image flag
6546fda04e7SJoel Fernandes         base.pcir.is_last()
6556fda04e7SJoel Fernandes     }
6566fda04e7SJoel Fernandes 
6576fda04e7SJoel Fernandes     /// Get the image size in bytes.
image_size_bytes(&self) -> usize6586fda04e7SJoel Fernandes     fn image_size_bytes(&self) -> usize {
6596fda04e7SJoel Fernandes         let base = self.base();
6606fda04e7SJoel Fernandes 
6616fda04e7SJoel Fernandes         // Prefer NPDE image size if available
6626fda04e7SJoel Fernandes         if let Some(ref npde) = base.npde {
6636fda04e7SJoel Fernandes             return npde.image_size_bytes();
6646fda04e7SJoel Fernandes         }
6656fda04e7SJoel Fernandes 
6666fda04e7SJoel Fernandes         // Otherwise, fall back to the PCIR image size
6676fda04e7SJoel Fernandes         base.pcir.image_size_bytes()
6686fda04e7SJoel Fernandes     }
6696fda04e7SJoel Fernandes 
6706fda04e7SJoel Fernandes     /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which
6716fda04e7SJoel Fernandes     /// triggers the constructor of the specific BiosImage enum variant.
new(pdev: &pci::Device, data: &[u8]) -> Result<Self>6726fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
6736fda04e7SJoel Fernandes         let base = BiosImageBase::new(pdev, data)?;
6746fda04e7SJoel Fernandes         let image = base.into_image().inspect_err(|e| {
6756fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Failed to create BiosImage: {:?}\n", e);
6766fda04e7SJoel Fernandes         })?;
6776fda04e7SJoel Fernandes 
6786fda04e7SJoel Fernandes         Ok(image)
6796fda04e7SJoel Fernandes     }
6806fda04e7SJoel Fernandes }
6816fda04e7SJoel Fernandes 
6826fda04e7SJoel Fernandes bios_image! {
6836fda04e7SJoel Fernandes     PciAt: PciAtBiosImage,   // PCI-AT compatible BIOS image
6846fda04e7SJoel Fernandes     Efi: EfiBiosImage,       // EFI (Extensible Firmware Interface)
6856fda04e7SJoel Fernandes     Nbsi: NbsiBiosImage,     // NBSI (Nvidia Bios System Interface)
68647c4846eSJoel Fernandes     FwSec: FwSecBiosBuilder, // FWSEC (Firmware Security)
6876fda04e7SJoel Fernandes }
6886fda04e7SJoel Fernandes 
689dc70c6aeSJoel Fernandes /// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain.
690dc70c6aeSJoel Fernandes ///
691dc70c6aeSJoel Fernandes /// It contains the BIT header and the BIT tokens.
6926fda04e7SJoel Fernandes struct PciAtBiosImage {
6936fda04e7SJoel Fernandes     base: BiosImageBase,
694dc70c6aeSJoel Fernandes     bit_header: BitHeader,
695dc70c6aeSJoel Fernandes     bit_offset: usize,
6966fda04e7SJoel Fernandes }
6976fda04e7SJoel Fernandes 
6986fda04e7SJoel Fernandes struct EfiBiosImage {
6996fda04e7SJoel Fernandes     base: BiosImageBase,
7006fda04e7SJoel Fernandes     // EFI-specific fields can be added here in the future.
7016fda04e7SJoel Fernandes }
7026fda04e7SJoel Fernandes 
7036fda04e7SJoel Fernandes struct NbsiBiosImage {
7046fda04e7SJoel Fernandes     base: BiosImageBase,
7056fda04e7SJoel Fernandes     // NBSI-specific fields can be added here in the future.
7066fda04e7SJoel Fernandes }
7076fda04e7SJoel Fernandes 
70847c4846eSJoel Fernandes struct FwSecBiosBuilder {
7096fda04e7SJoel Fernandes     base: BiosImageBase,
71047c4846eSJoel Fernandes     /// These are temporary fields that are used during the construction of the
71147c4846eSJoel Fernandes     /// [`FwSecBiosBuilder`].
71247c4846eSJoel Fernandes     ///
71347c4846eSJoel Fernandes     /// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new
71447c4846eSJoel Fernandes     /// [`FwSecBiosImage`].
71547c4846eSJoel Fernandes     ///
71647c4846eSJoel Fernandes     /// The offset of the Falcon data from the start of Fwsec image.
71747c4846eSJoel Fernandes     falcon_data_offset: Option<usize>,
71847c4846eSJoel Fernandes     /// The [`PmuLookupTable`] starts at the offset of the falcon data pointer.
71947c4846eSJoel Fernandes     pmu_lookup_table: Option<PmuLookupTable>,
72047c4846eSJoel Fernandes     /// The offset of the Falcon ucode.
72147c4846eSJoel Fernandes     falcon_ucode_offset: Option<usize>,
72247c4846eSJoel Fernandes }
72347c4846eSJoel Fernandes 
72447c4846eSJoel Fernandes /// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode.
72547c4846eSJoel Fernandes ///
72647c4846eSJoel Fernandes /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode.
72747c4846eSJoel Fernandes pub(crate) struct FwSecBiosImage {
72847c4846eSJoel Fernandes     base: BiosImageBase,
72947c4846eSJoel Fernandes     /// The offset of the Falcon ucode.
73047c4846eSJoel Fernandes     falcon_ucode_offset: usize,
7316fda04e7SJoel Fernandes }
7326fda04e7SJoel Fernandes 
7336fda04e7SJoel Fernandes // Convert from BiosImageBase to BiosImage
7346fda04e7SJoel Fernandes impl TryFrom<BiosImageBase> for BiosImage {
7356fda04e7SJoel Fernandes     type Error = Error;
7366fda04e7SJoel Fernandes 
try_from(base: BiosImageBase) -> Result<Self>7376fda04e7SJoel Fernandes     fn try_from(base: BiosImageBase) -> Result<Self> {
7386fda04e7SJoel Fernandes         match base.pcir.code_type {
739dc70c6aeSJoel Fernandes             0x00 => Ok(BiosImage::PciAt(base.try_into()?)),
7406fda04e7SJoel Fernandes             0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })),
7416fda04e7SJoel Fernandes             0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })),
74247c4846eSJoel Fernandes             0xE0 => Ok(BiosImage::FwSec(FwSecBiosBuilder {
74347c4846eSJoel Fernandes                 base,
74447c4846eSJoel Fernandes                 falcon_data_offset: None,
74547c4846eSJoel Fernandes                 pmu_lookup_table: None,
74647c4846eSJoel Fernandes                 falcon_ucode_offset: None,
74747c4846eSJoel Fernandes             })),
7486fda04e7SJoel Fernandes             _ => Err(EINVAL),
7496fda04e7SJoel Fernandes         }
7506fda04e7SJoel Fernandes     }
7516fda04e7SJoel Fernandes }
7526fda04e7SJoel Fernandes 
7536fda04e7SJoel Fernandes /// BIOS Image structure containing various headers and reference fields to all BIOS images.
7546fda04e7SJoel Fernandes ///
7556fda04e7SJoel Fernandes /// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that
7566fda04e7SJoel Fernandes /// Rust favors composition of types over inheritance.
7576fda04e7SJoel Fernandes #[derive(Debug)]
7586fda04e7SJoel Fernandes #[expect(dead_code)]
7596fda04e7SJoel Fernandes struct BiosImageBase {
7606fda04e7SJoel Fernandes     /// PCI ROM Expansion Header
7616fda04e7SJoel Fernandes     rom_header: PciRomHeader,
7626fda04e7SJoel Fernandes     /// PCI Data Structure
7636fda04e7SJoel Fernandes     pcir: PcirStruct,
7646fda04e7SJoel Fernandes     /// NVIDIA PCI Data Extension (optional)
7656fda04e7SJoel Fernandes     npde: Option<NpdeStruct>,
7666fda04e7SJoel Fernandes     /// Image data (includes ROM header and PCIR)
7676fda04e7SJoel Fernandes     data: KVec<u8>,
7686fda04e7SJoel Fernandes }
7696fda04e7SJoel Fernandes 
7706fda04e7SJoel Fernandes impl BiosImageBase {
into_image(self) -> Result<BiosImage>7716fda04e7SJoel Fernandes     fn into_image(self) -> Result<BiosImage> {
7726fda04e7SJoel Fernandes         BiosImage::try_from(self)
7736fda04e7SJoel Fernandes     }
7746fda04e7SJoel Fernandes 
7756fda04e7SJoel Fernandes     /// Creates a new BiosImageBase from raw byte data.
new(pdev: &pci::Device, data: &[u8]) -> Result<Self>7766fda04e7SJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
7776fda04e7SJoel Fernandes         // Ensure we have enough data for the ROM header.
7786fda04e7SJoel Fernandes         if data.len() < 26 {
7796fda04e7SJoel Fernandes             dev_err!(pdev.as_ref(), "Not enough data for ROM header\n");
7806fda04e7SJoel Fernandes             return Err(EINVAL);
7816fda04e7SJoel Fernandes         }
7826fda04e7SJoel Fernandes 
7836fda04e7SJoel Fernandes         // Parse the ROM header.
7846fda04e7SJoel Fernandes         let rom_header = PciRomHeader::new(pdev, &data[0..26])
7856fda04e7SJoel Fernandes             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PciRomHeader: {:?}\n", e))?;
7866fda04e7SJoel Fernandes 
7876fda04e7SJoel Fernandes         // Get the PCI Data Structure using the pointer from the ROM header.
7886fda04e7SJoel Fernandes         let pcir_offset = rom_header.pci_data_struct_offset as usize;
7896fda04e7SJoel Fernandes         let pcir_data = data
7906fda04e7SJoel Fernandes             .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>())
7916fda04e7SJoel Fernandes             .ok_or(EINVAL)
7926fda04e7SJoel Fernandes             .inspect_err(|_| {
7936fda04e7SJoel Fernandes                 dev_err!(
7946fda04e7SJoel Fernandes                     pdev.as_ref(),
7956fda04e7SJoel Fernandes                     "PCIR offset {:#x} out of bounds (data length: {})\n",
7966fda04e7SJoel Fernandes                     pcir_offset,
7976fda04e7SJoel Fernandes                     data.len()
7986fda04e7SJoel Fernandes                 );
7996fda04e7SJoel Fernandes                 dev_err!(
8006fda04e7SJoel Fernandes                     pdev.as_ref(),
8016fda04e7SJoel Fernandes                     "Consider reading more data for construction of BiosImage\n"
8026fda04e7SJoel Fernandes                 );
8036fda04e7SJoel Fernandes             })?;
8046fda04e7SJoel Fernandes 
8056fda04e7SJoel Fernandes         let pcir = PcirStruct::new(pdev, pcir_data)
8066fda04e7SJoel Fernandes             .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PcirStruct: {:?}\n", e))?;
8076fda04e7SJoel Fernandes 
8086fda04e7SJoel Fernandes         // Look for NPDE structure if this is not an NBSI image (type != 0x70).
8096fda04e7SJoel Fernandes         let npde = NpdeStruct::find_in_data(pdev, data, &rom_header, &pcir);
8106fda04e7SJoel Fernandes 
8116fda04e7SJoel Fernandes         // Create a copy of the data.
8126fda04e7SJoel Fernandes         let mut data_copy = KVec::new();
8136fda04e7SJoel Fernandes         data_copy.extend_from_slice(data, GFP_KERNEL)?;
8146fda04e7SJoel Fernandes 
8156fda04e7SJoel Fernandes         Ok(BiosImageBase {
8166fda04e7SJoel Fernandes             rom_header,
8176fda04e7SJoel Fernandes             pcir,
8186fda04e7SJoel Fernandes             npde,
8196fda04e7SJoel Fernandes             data: data_copy,
8206fda04e7SJoel Fernandes         })
8216fda04e7SJoel Fernandes     }
8226fda04e7SJoel Fernandes }
823dc70c6aeSJoel Fernandes 
824dc70c6aeSJoel Fernandes impl PciAtBiosImage {
825dc70c6aeSJoel Fernandes     /// Find a byte pattern in a slice.
find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize>826dc70c6aeSJoel Fernandes     fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> {
827dc70c6aeSJoel Fernandes         haystack
828dc70c6aeSJoel Fernandes             .windows(needle.len())
829dc70c6aeSJoel Fernandes             .position(|window| window == needle)
830dc70c6aeSJoel Fernandes             .ok_or(EINVAL)
831dc70c6aeSJoel Fernandes     }
832dc70c6aeSJoel Fernandes 
833dc70c6aeSJoel Fernandes     /// Find the BIT header in the [`PciAtBiosImage`].
find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)>834dc70c6aeSJoel Fernandes     fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> {
835dc70c6aeSJoel Fernandes         let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00];
836dc70c6aeSJoel Fernandes         let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?;
837dc70c6aeSJoel Fernandes         let bit_header = BitHeader::new(&data[bit_offset..])?;
838dc70c6aeSJoel Fernandes 
839dc70c6aeSJoel Fernandes         Ok((bit_header, bit_offset))
840dc70c6aeSJoel Fernandes     }
841dc70c6aeSJoel Fernandes 
842dc70c6aeSJoel Fernandes     /// Get a BIT token entry from the BIT table in the [`PciAtBiosImage`]
get_bit_token(&self, token_id: u8) -> Result<BitToken>843dc70c6aeSJoel Fernandes     fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {
844dc70c6aeSJoel Fernandes         BitToken::from_id(self, token_id)
845dc70c6aeSJoel Fernandes     }
846dc70c6aeSJoel Fernandes 
847dc70c6aeSJoel Fernandes     /// Find the Falcon data pointer structure in the [`PciAtBiosImage`].
848dc70c6aeSJoel Fernandes     ///
849dc70c6aeSJoel Fernandes     /// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC
850dc70c6aeSJoel Fernandes     /// image.
falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32>851dc70c6aeSJoel Fernandes     fn falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32> {
852dc70c6aeSJoel Fernandes         let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?;
853dc70c6aeSJoel Fernandes 
854dc70c6aeSJoel Fernandes         // Make sure we don't go out of bounds
855dc70c6aeSJoel Fernandes         if token.data_offset as usize + 4 > self.base.data.len() {
856dc70c6aeSJoel Fernandes             return Err(EINVAL);
857dc70c6aeSJoel Fernandes         }
858dc70c6aeSJoel Fernandes 
859dc70c6aeSJoel Fernandes         // read the 4 bytes at the offset specified in the token
860dc70c6aeSJoel Fernandes         let offset = token.data_offset as usize;
861dc70c6aeSJoel Fernandes         let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| {
862dc70c6aeSJoel Fernandes             dev_err!(pdev.as_ref(), "Failed to convert data slice to array");
863dc70c6aeSJoel Fernandes             EINVAL
864dc70c6aeSJoel Fernandes         })?;
865dc70c6aeSJoel Fernandes 
866dc70c6aeSJoel Fernandes         let data_ptr = u32::from_le_bytes(bytes);
867dc70c6aeSJoel Fernandes 
868dc70c6aeSJoel Fernandes         if (data_ptr as usize) < self.base.data.len() {
869dc70c6aeSJoel Fernandes             dev_err!(pdev.as_ref(), "Falcon data pointer out of bounds\n");
870dc70c6aeSJoel Fernandes             return Err(EINVAL);
871dc70c6aeSJoel Fernandes         }
872dc70c6aeSJoel Fernandes 
873dc70c6aeSJoel Fernandes         Ok(data_ptr)
874dc70c6aeSJoel Fernandes     }
875dc70c6aeSJoel Fernandes }
876dc70c6aeSJoel Fernandes 
877dc70c6aeSJoel Fernandes impl TryFrom<BiosImageBase> for PciAtBiosImage {
878dc70c6aeSJoel Fernandes     type Error = Error;
879dc70c6aeSJoel Fernandes 
try_from(base: BiosImageBase) -> Result<Self>880dc70c6aeSJoel Fernandes     fn try_from(base: BiosImageBase) -> Result<Self> {
881dc70c6aeSJoel Fernandes         let data_slice = &base.data;
882dc70c6aeSJoel Fernandes         let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?;
883dc70c6aeSJoel Fernandes 
884dc70c6aeSJoel Fernandes         Ok(PciAtBiosImage {
885dc70c6aeSJoel Fernandes             base,
886dc70c6aeSJoel Fernandes             bit_header,
887dc70c6aeSJoel Fernandes             bit_offset,
888dc70c6aeSJoel Fernandes         })
889dc70c6aeSJoel Fernandes     }
890dc70c6aeSJoel Fernandes }
89147c4846eSJoel Fernandes 
89247c4846eSJoel Fernandes /// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`].
89347c4846eSJoel Fernandes ///
89447c4846eSJoel Fernandes /// See the [`PmuLookupTable`] description for more information.
89547c4846eSJoel Fernandes #[expect(dead_code)]
89647c4846eSJoel Fernandes struct PmuLookupTableEntry {
89747c4846eSJoel Fernandes     application_id: u8,
89847c4846eSJoel Fernandes     target_id: u8,
89947c4846eSJoel Fernandes     data: u32,
90047c4846eSJoel Fernandes }
90147c4846eSJoel Fernandes 
90247c4846eSJoel Fernandes impl PmuLookupTableEntry {
new(data: &[u8]) -> Result<Self>90347c4846eSJoel Fernandes     fn new(data: &[u8]) -> Result<Self> {
904*14ae91a8SRhys Lloyd         if data.len() < 6 {
90547c4846eSJoel Fernandes             return Err(EINVAL);
90647c4846eSJoel Fernandes         }
90747c4846eSJoel Fernandes 
90847c4846eSJoel Fernandes         Ok(PmuLookupTableEntry {
90947c4846eSJoel Fernandes             application_id: data[0],
91047c4846eSJoel Fernandes             target_id: data[1],
91147c4846eSJoel Fernandes             data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?),
91247c4846eSJoel Fernandes         })
91347c4846eSJoel Fernandes     }
91447c4846eSJoel Fernandes }
91547c4846eSJoel Fernandes 
91647c4846eSJoel Fernandes /// The [`PmuLookupTableEntry`] structure is used to find the [`PmuLookupTableEntry`] for a given
91747c4846eSJoel Fernandes /// application ID.
91847c4846eSJoel Fernandes ///
91947c4846eSJoel Fernandes /// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to
92047c4846eSJoel Fernandes /// locate the Falcon Ucode.
92147c4846eSJoel Fernandes #[expect(dead_code)]
92247c4846eSJoel Fernandes struct PmuLookupTable {
92347c4846eSJoel Fernandes     version: u8,
92447c4846eSJoel Fernandes     header_len: u8,
92547c4846eSJoel Fernandes     entry_len: u8,
92647c4846eSJoel Fernandes     entry_count: u8,
92747c4846eSJoel Fernandes     table_data: KVec<u8>,
92847c4846eSJoel Fernandes }
92947c4846eSJoel Fernandes 
93047c4846eSJoel Fernandes impl PmuLookupTable {
new(pdev: &pci::Device, data: &[u8]) -> Result<Self>93147c4846eSJoel Fernandes     fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
93247c4846eSJoel Fernandes         if data.len() < 4 {
93347c4846eSJoel Fernandes             return Err(EINVAL);
93447c4846eSJoel Fernandes         }
93547c4846eSJoel Fernandes 
93647c4846eSJoel Fernandes         let header_len = data[1] as usize;
93747c4846eSJoel Fernandes         let entry_len = data[2] as usize;
93847c4846eSJoel Fernandes         let entry_count = data[3] as usize;
93947c4846eSJoel Fernandes 
94047c4846eSJoel Fernandes         let required_bytes = header_len + (entry_count * entry_len);
94147c4846eSJoel Fernandes 
94247c4846eSJoel Fernandes         if data.len() < required_bytes {
94347c4846eSJoel Fernandes             dev_err!(
94447c4846eSJoel Fernandes                 pdev.as_ref(),
94547c4846eSJoel Fernandes                 "PmuLookupTable data length less than required\n"
94647c4846eSJoel Fernandes             );
94747c4846eSJoel Fernandes             return Err(EINVAL);
94847c4846eSJoel Fernandes         }
94947c4846eSJoel Fernandes 
95047c4846eSJoel Fernandes         // Create a copy of only the table data
95147c4846eSJoel Fernandes         let table_data = {
95247c4846eSJoel Fernandes             let mut ret = KVec::new();
95347c4846eSJoel Fernandes             ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
95447c4846eSJoel Fernandes             ret
95547c4846eSJoel Fernandes         };
95647c4846eSJoel Fernandes 
95747c4846eSJoel Fernandes         // Debug logging of entries (dumps the table data to dmesg)
95847c4846eSJoel Fernandes         for i in (header_len..required_bytes).step_by(entry_len) {
95947c4846eSJoel Fernandes             dev_dbg!(
96047c4846eSJoel Fernandes                 pdev.as_ref(),
96147c4846eSJoel Fernandes                 "PMU entry: {:02x?}\n",
96247c4846eSJoel Fernandes                 &data[i..][..entry_len]
96347c4846eSJoel Fernandes             );
96447c4846eSJoel Fernandes         }
96547c4846eSJoel Fernandes 
96647c4846eSJoel Fernandes         Ok(PmuLookupTable {
96747c4846eSJoel Fernandes             version: data[0],
96847c4846eSJoel Fernandes             header_len: header_len as u8,
96947c4846eSJoel Fernandes             entry_len: entry_len as u8,
97047c4846eSJoel Fernandes             entry_count: entry_count as u8,
97147c4846eSJoel Fernandes             table_data,
97247c4846eSJoel Fernandes         })
97347c4846eSJoel Fernandes     }
97447c4846eSJoel Fernandes 
lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry>97547c4846eSJoel Fernandes     fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
97647c4846eSJoel Fernandes         if idx >= self.entry_count {
97747c4846eSJoel Fernandes             return Err(EINVAL);
97847c4846eSJoel Fernandes         }
97947c4846eSJoel Fernandes 
98047c4846eSJoel Fernandes         let index = (idx as usize) * self.entry_len as usize;
98147c4846eSJoel Fernandes         PmuLookupTableEntry::new(&self.table_data[index..])
98247c4846eSJoel Fernandes     }
98347c4846eSJoel Fernandes 
98447c4846eSJoel Fernandes     // find entry by type value
find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry>98547c4846eSJoel Fernandes     fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> {
98647c4846eSJoel Fernandes         for i in 0..self.entry_count {
98747c4846eSJoel Fernandes             let entry = self.lookup_index(i)?;
98847c4846eSJoel Fernandes             if entry.application_id == entry_type {
98947c4846eSJoel Fernandes                 return Ok(entry);
99047c4846eSJoel Fernandes             }
99147c4846eSJoel Fernandes         }
99247c4846eSJoel Fernandes 
99347c4846eSJoel Fernandes         Err(EINVAL)
99447c4846eSJoel Fernandes     }
99547c4846eSJoel Fernandes }
99647c4846eSJoel Fernandes 
99747c4846eSJoel Fernandes impl FwSecBiosBuilder {
setup_falcon_data( &mut self, pdev: &pci::Device, pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result99847c4846eSJoel Fernandes     fn setup_falcon_data(
99947c4846eSJoel Fernandes         &mut self,
100047c4846eSJoel Fernandes         pdev: &pci::Device,
100147c4846eSJoel Fernandes         pci_at_image: &PciAtBiosImage,
100247c4846eSJoel Fernandes         first_fwsec: &FwSecBiosBuilder,
100347c4846eSJoel Fernandes     ) -> Result {
100447c4846eSJoel Fernandes         let mut offset = pci_at_image.falcon_data_ptr(pdev)? as usize;
100547c4846eSJoel Fernandes         let mut pmu_in_first_fwsec = false;
100647c4846eSJoel Fernandes 
100747c4846eSJoel Fernandes         // The falcon data pointer assumes that the PciAt and FWSEC images
100847c4846eSJoel Fernandes         // are contiguous in memory. However, testing shows the EFI image sits in
100947c4846eSJoel Fernandes         // between them. So calculate the offset from the end of the PciAt image
101047c4846eSJoel Fernandes         // rather than the start of it. Compensate.
101147c4846eSJoel Fernandes         offset -= pci_at_image.base.data.len();
101247c4846eSJoel Fernandes 
101347c4846eSJoel Fernandes         // The offset is now from the start of the first Fwsec image, however
101447c4846eSJoel Fernandes         // the offset points to a location in the second Fwsec image. Since
101547c4846eSJoel Fernandes         // the fwsec images are contiguous, subtract the length of the first Fwsec
101647c4846eSJoel Fernandes         // image from the offset to get the offset to the start of the second
101747c4846eSJoel Fernandes         // Fwsec image.
101847c4846eSJoel Fernandes         if offset < first_fwsec.base.data.len() {
101947c4846eSJoel Fernandes             pmu_in_first_fwsec = true;
102047c4846eSJoel Fernandes         } else {
102147c4846eSJoel Fernandes             offset -= first_fwsec.base.data.len();
102247c4846eSJoel Fernandes         }
102347c4846eSJoel Fernandes 
102447c4846eSJoel Fernandes         self.falcon_data_offset = Some(offset);
102547c4846eSJoel Fernandes 
102647c4846eSJoel Fernandes         if pmu_in_first_fwsec {
102747c4846eSJoel Fernandes             self.pmu_lookup_table =
102847c4846eSJoel Fernandes                 Some(PmuLookupTable::new(pdev, &first_fwsec.base.data[offset..])?);
102947c4846eSJoel Fernandes         } else {
103047c4846eSJoel Fernandes             self.pmu_lookup_table = Some(PmuLookupTable::new(pdev, &self.base.data[offset..])?);
103147c4846eSJoel Fernandes         }
103247c4846eSJoel Fernandes 
103347c4846eSJoel Fernandes         match self
103447c4846eSJoel Fernandes             .pmu_lookup_table
103547c4846eSJoel Fernandes             .as_ref()
103647c4846eSJoel Fernandes             .ok_or(EINVAL)?
103747c4846eSJoel Fernandes             .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD)
103847c4846eSJoel Fernandes         {
103947c4846eSJoel Fernandes             Ok(entry) => {
104047c4846eSJoel Fernandes                 let mut ucode_offset = entry.data as usize;
104147c4846eSJoel Fernandes                 ucode_offset -= pci_at_image.base.data.len();
104247c4846eSJoel Fernandes                 if ucode_offset < first_fwsec.base.data.len() {
104347c4846eSJoel Fernandes                     dev_err!(pdev.as_ref(), "Falcon Ucode offset not in second Fwsec.\n");
104447c4846eSJoel Fernandes                     return Err(EINVAL);
104547c4846eSJoel Fernandes                 }
104647c4846eSJoel Fernandes                 ucode_offset -= first_fwsec.base.data.len();
104747c4846eSJoel Fernandes                 self.falcon_ucode_offset = Some(ucode_offset);
104847c4846eSJoel Fernandes             }
104947c4846eSJoel Fernandes             Err(e) => {
105047c4846eSJoel Fernandes                 dev_err!(
105147c4846eSJoel Fernandes                     pdev.as_ref(),
105247c4846eSJoel Fernandes                     "PmuLookupTableEntry not found, error: {:?}\n",
105347c4846eSJoel Fernandes                     e
105447c4846eSJoel Fernandes                 );
105547c4846eSJoel Fernandes                 return Err(EINVAL);
105647c4846eSJoel Fernandes             }
105747c4846eSJoel Fernandes         }
105847c4846eSJoel Fernandes         Ok(())
105947c4846eSJoel Fernandes     }
106047c4846eSJoel Fernandes 
106147c4846eSJoel Fernandes     /// Build the final FwSecBiosImage from this builder
build(self, pdev: &pci::Device) -> Result<FwSecBiosImage>106247c4846eSJoel Fernandes     fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> {
106347c4846eSJoel Fernandes         let ret = FwSecBiosImage {
106447c4846eSJoel Fernandes             base: self.base,
106547c4846eSJoel Fernandes             falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?,
106647c4846eSJoel Fernandes         };
106747c4846eSJoel Fernandes 
106847c4846eSJoel Fernandes         if cfg!(debug_assertions) {
106947c4846eSJoel Fernandes             // Print the desc header for debugging
107047c4846eSJoel Fernandes             let desc = ret.header(pdev.as_ref())?;
107147c4846eSJoel Fernandes             dev_dbg!(pdev.as_ref(), "PmuLookupTableEntry desc: {:#?}\n", desc);
107247c4846eSJoel Fernandes         }
107347c4846eSJoel Fernandes 
107447c4846eSJoel Fernandes         Ok(ret)
107547c4846eSJoel Fernandes     }
107647c4846eSJoel Fernandes }
107747c4846eSJoel Fernandes 
107847c4846eSJoel Fernandes impl FwSecBiosImage {
107947c4846eSJoel Fernandes     /// Get the FwSec header ([`FalconUCodeDescV3`]).
header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3>108047c4846eSJoel Fernandes     pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> {
108147c4846eSJoel Fernandes         // Get the falcon ucode offset that was found in setup_falcon_data.
108247c4846eSJoel Fernandes         let falcon_ucode_offset = self.falcon_ucode_offset;
108347c4846eSJoel Fernandes 
108447c4846eSJoel Fernandes         // Make sure the offset is within the data bounds.
108547c4846eSJoel Fernandes         if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() {
108647c4846eSJoel Fernandes             dev_err!(dev, "fwsec-frts header not contained within BIOS bounds\n");
108747c4846eSJoel Fernandes             return Err(ERANGE);
108847c4846eSJoel Fernandes         }
108947c4846eSJoel Fernandes 
109047c4846eSJoel Fernandes         // Read the first 4 bytes to get the version.
109147c4846eSJoel Fernandes         let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4]
109247c4846eSJoel Fernandes             .try_into()
109347c4846eSJoel Fernandes             .map_err(|_| EINVAL)?;
109447c4846eSJoel Fernandes         let hdr = u32::from_le_bytes(hdr_bytes);
109547c4846eSJoel Fernandes         let ver = (hdr & 0xff00) >> 8;
109647c4846eSJoel Fernandes 
109747c4846eSJoel Fernandes         if ver != 3 {
109847c4846eSJoel Fernandes             dev_err!(dev, "invalid fwsec firmware version: {:?}\n", ver);
109947c4846eSJoel Fernandes             return Err(EINVAL);
110047c4846eSJoel Fernandes         }
110147c4846eSJoel Fernandes 
110247c4846eSJoel Fernandes         // Return a reference to the FalconUCodeDescV3 structure.
110347c4846eSJoel Fernandes         //
110447c4846eSJoel Fernandes         // SAFETY: We have checked that `falcon_ucode_offset + size_of::<FalconUCodeDescV3>` is
110547c4846eSJoel Fernandes         // within the bounds of `data`. Also, this data vector is from ROM, and the `data` field
110647c4846eSJoel Fernandes         // in `BiosImageBase` is immutable after construction.
110747c4846eSJoel Fernandes         Ok(unsafe {
110847c4846eSJoel Fernandes             &*(self
110947c4846eSJoel Fernandes                 .base
111047c4846eSJoel Fernandes                 .data
111147c4846eSJoel Fernandes                 .as_ptr()
111247c4846eSJoel Fernandes                 .add(falcon_ucode_offset)
111347c4846eSJoel Fernandes                 .cast::<FalconUCodeDescV3>())
111447c4846eSJoel Fernandes         })
111547c4846eSJoel Fernandes     }
111647c4846eSJoel Fernandes 
111747c4846eSJoel Fernandes     /// Get the ucode data as a byte slice
ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]>111847c4846eSJoel Fernandes     pub(crate) fn ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
111947c4846eSJoel Fernandes         let falcon_ucode_offset = self.falcon_ucode_offset;
112047c4846eSJoel Fernandes 
112147c4846eSJoel Fernandes         // The ucode data follows the descriptor.
112247c4846eSJoel Fernandes         let ucode_data_offset = falcon_ucode_offset + desc.size();
112347c4846eSJoel Fernandes         let size = (desc.imem_load_size + desc.dmem_load_size) as usize;
112447c4846eSJoel Fernandes 
112547c4846eSJoel Fernandes         // Get the data slice, checking bounds in a single operation.
112647c4846eSJoel Fernandes         self.base
112747c4846eSJoel Fernandes             .data
112847c4846eSJoel Fernandes             .get(ucode_data_offset..ucode_data_offset + size)
112947c4846eSJoel Fernandes             .ok_or(ERANGE)
113047c4846eSJoel Fernandes             .inspect_err(|_| dev_err!(dev, "fwsec ucode data not contained within BIOS bounds\n"))
113147c4846eSJoel Fernandes     }
113247c4846eSJoel Fernandes 
113347c4846eSJoel Fernandes     /// Get the signatures as a byte slice
sigs( &self, dev: &device::Device, desc: &FalconUCodeDescV3, ) -> Result<&[Bcrt30Rsa3kSignature]>113431f0feefSAlexandre Courbot     pub(crate) fn sigs(
113531f0feefSAlexandre Courbot         &self,
113631f0feefSAlexandre Courbot         dev: &device::Device,
113731f0feefSAlexandre Courbot         desc: &FalconUCodeDescV3,
113831f0feefSAlexandre Courbot     ) -> Result<&[Bcrt30Rsa3kSignature]> {
113947c4846eSJoel Fernandes         // The signatures data follows the descriptor.
114047c4846eSJoel Fernandes         let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>();
114131f0feefSAlexandre Courbot         let sigs_size =
114231f0feefSAlexandre Courbot             desc.signature_count as usize * core::mem::size_of::<Bcrt30Rsa3kSignature>();
114347c4846eSJoel Fernandes 
114447c4846eSJoel Fernandes         // Make sure the data is within bounds.
114531f0feefSAlexandre Courbot         if sigs_data_offset + sigs_size > self.base.data.len() {
114647c4846eSJoel Fernandes             dev_err!(
114747c4846eSJoel Fernandes                 dev,
114847c4846eSJoel Fernandes                 "fwsec signatures data not contained within BIOS bounds\n"
114947c4846eSJoel Fernandes             );
115047c4846eSJoel Fernandes             return Err(ERANGE);
115147c4846eSJoel Fernandes         }
115247c4846eSJoel Fernandes 
115331f0feefSAlexandre Courbot         // SAFETY: we checked that `data + sigs_data_offset + (signature_count *
115431f0feefSAlexandre Courbot         // sizeof::<Bcrt30Rsa3kSignature>()` is within the bounds of `data`.
115531f0feefSAlexandre Courbot         Ok(unsafe {
115631f0feefSAlexandre Courbot             core::slice::from_raw_parts(
115731f0feefSAlexandre Courbot                 self.base
115831f0feefSAlexandre Courbot                     .data
115931f0feefSAlexandre Courbot                     .as_ptr()
116031f0feefSAlexandre Courbot                     .add(sigs_data_offset)
116131f0feefSAlexandre Courbot                     .cast::<Bcrt30Rsa3kSignature>(),
116231f0feefSAlexandre Courbot                 desc.signature_count as usize,
116331f0feefSAlexandre Courbot             )
116431f0feefSAlexandre Courbot         })
116547c4846eSJoel Fernandes     }
116647c4846eSJoel Fernandes }
1167