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