Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/nova-core/vbios.rs
26444 views
1
// SPDX-License-Identifier: GPL-2.0
2
3
//! VBIOS extraction and parsing.
4
5
use crate::driver::Bar0;
6
use crate::firmware::fwsec::Bcrt30Rsa3kSignature;
7
use crate::firmware::FalconUCodeDescV3;
8
use core::convert::TryFrom;
9
use kernel::device;
10
use kernel::error::Result;
11
use kernel::pci;
12
use kernel::prelude::*;
13
14
/// The offset of the VBIOS ROM in the BAR0 space.
15
const ROM_OFFSET: usize = 0x300000;
16
/// The maximum length of the VBIOS ROM to scan into.
17
const BIOS_MAX_SCAN_LEN: usize = 0x100000;
18
/// The size to read ahead when parsing initial BIOS image headers.
19
const 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.
22
const 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)]
27
const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
28
#[expect(dead_code)]
29
const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45;
30
const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85;
31
32
/// Vbios Reader for constructing the VBIOS data.
33
struct 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
48
impl<'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
138
impl<'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
187
pub(crate) struct Vbios {
188
fwsec_image: FwSecBiosImage,
189
}
190
191
impl 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)]
259
struct 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
286
impl 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)]
349
struct 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
366
impl 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)]
396
struct 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
408
const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
409
410
impl 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)]
456
struct 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
469
impl 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)]
527
struct 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
540
impl 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.
610
macro_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
637
impl 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
682
bios_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.
692
struct PciAtBiosImage {
693
base: BiosImageBase,
694
bit_header: BitHeader,
695
bit_offset: usize,
696
}
697
698
struct EfiBiosImage {
699
base: BiosImageBase,
700
// EFI-specific fields can be added here in the future.
701
}
702
703
struct NbsiBiosImage {
704
base: BiosImageBase,
705
// NBSI-specific fields can be added here in the future.
706
}
707
708
struct 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.
727
pub(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
734
impl 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)]
759
struct 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
770
impl 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
824
impl 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
877
impl 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)]
896
struct PmuLookupTableEntry {
897
application_id: u8,
898
target_id: u8,
899
data: u32,
900
}
901
902
impl 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)]
922
struct PmuLookupTable {
923
version: u8,
924
header_len: u8,
925
entry_len: u8,
926
entry_count: u8,
927
table_data: KVec<u8>,
928
}
929
930
impl 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
997
impl 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
1078
impl 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
}
1167
1168