Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/nova-core/vbios.rs
29278 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::prelude::*;
12
use kernel::ptr::{Alignable, Alignment};
13
use kernel::types::ARef;
14
15
/// The offset of the VBIOS ROM in the BAR0 space.
16
const ROM_OFFSET: usize = 0x300000;
17
/// The maximum length of the VBIOS ROM to scan into.
18
const BIOS_MAX_SCAN_LEN: usize = 0x100000;
19
/// The size to read ahead when parsing initial BIOS image headers.
20
const 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.
23
const 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)]
28
const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
29
#[expect(dead_code)]
30
const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45;
31
const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85;
32
33
/// Vbios Reader for constructing the VBIOS data.
34
struct 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
49
impl<'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
139
impl<'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
184
pub(crate) struct Vbios {
185
fwsec_image: FwSecBiosImage,
186
}
187
188
impl 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)]
256
struct 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
283
impl 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)]
342
struct 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
359
impl 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)]
389
struct 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
401
const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
402
403
impl 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)]
449
struct 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
462
impl 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)]
520
struct 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
533
impl 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.
599
macro_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
626
impl 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
671
bios_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.
681
struct PciAtBiosImage {
682
base: BiosImageBase,
683
bit_header: BitHeader,
684
bit_offset: usize,
685
}
686
687
struct EfiBiosImage {
688
base: BiosImageBase,
689
// EFI-specific fields can be added here in the future.
690
}
691
692
struct NbsiBiosImage {
693
base: BiosImageBase,
694
// NBSI-specific fields can be added here in the future.
695
}
696
697
struct 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.
716
pub(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
723
impl 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)]
747
struct 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
760
impl 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
815
impl 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
868
impl 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)]
887
struct PmuLookupTableEntry {
888
application_id: u8,
889
target_id: u8,
890
data: u32,
891
}
892
893
impl 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)]
913
struct PmuLookupTable {
914
version: u8,
915
header_len: u8,
916
entry_len: u8,
917
entry_count: u8,
918
table_data: KVec<u8>,
919
}
920
921
impl 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
981
impl 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
1066
impl 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
}
1159
1160