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> { new(pdev: &'a pci::Device, bar0: &'a Bar0) -> Result<Self>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. read_more(&mut self, len: usize) -> Result60 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. read_more_at_offset(&mut self, offset: usize, len: usize) -> Result90 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. read_bios_image_at_offset( &mut self, offset: usize, len: usize, context: &str, ) -> Result<BiosImage>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. next(&mut self) -> Option<Self::Item>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. new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios>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 fwsec_image(&self) -> &FwSecBiosImage251 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 { new(pdev: &pci::Device, data: &[u8]) -> Result<Self>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. is_last(&self) -> bool332 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. image_size_bytes(&self) -> usize337 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 { new(data: &[u8]) -> Result<Self>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 from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self>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 { new(pdev: &pci::Device, data: &[u8]) -> Result<Self>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 { new(pdev: &pci::Device, data: &[u8]) -> Option<Self>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. is_last(&self) -> bool576 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. image_size_bytes(&self) -> usize581 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. find_in_data( pdev: &pci::Device, data: &[u8], rom_header: &PciRomHeader, pcir: &PcirStruct, ) -> Option<Self>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. is_last(&self) -> bool639 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. image_size_bytes(&self) -> usize658 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. new(pdev: &pci::Device, data: &[u8]) -> Result<Self>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 try_from(base: BiosImageBase) -> Result<Self>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 { into_image(self) -> Result<BiosImage>771 fn into_image(self) -> Result<BiosImage> { 772 BiosImage::try_from(self) 773 } 774 775 /// Creates a new BiosImageBase from raw byte data. new(pdev: &pci::Device, data: &[u8]) -> Result<Self>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. find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize>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`]. find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)>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`] get_bit_token(&self, token_id: u8) -> Result<BitToken>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. falcon_data_ptr(&self, pdev: &pci::Device) -> Result<u32>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 try_from(base: BiosImageBase) -> Result<Self>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 { new(data: &[u8]) -> Result<Self>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 { new(pdev: &pci::Device, data: &[u8]) -> Result<Self>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 lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry>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 find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry>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 { setup_falcon_data( &mut self, pdev: &pci::Device, pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result998 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 build(self, pdev: &pci::Device) -> Result<FwSecBiosImage>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`]). header(&self, dev: &device::Device) -> Result<&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 ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]>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 sigs( &self, dev: &device::Device, desc: &FalconUCodeDescV3, ) -> Result<&[Bcrt30Rsa3kSignature]>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