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