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