[PATCH v3 16/19] nova-core: Add support for VBIOS ucode extraction for boot
Joel Fernandes
joelagnelf at nvidia.com
Mon May 19 22:59:55 UTC 2025
Hi Danilo,
On 5/14/2025 12:23 PM, Danilo Krummrich wrote:
> On Wed, May 07, 2025 at 10:52:43PM +0900, Alexandre Courbot wrote:
>> +/// PCI Data Structure as defined in PCI Firmware Specification
>> +#[derive(Debug, Clone)]
>> +#[repr(C)]
>> +struct PcirStruct {
>> + /// PCI Data Structure signature ("PCIR" or "NPDS")
>> + pub signature: [u8; 4],
>> + /// PCI Vendor ID (e.g., 0x10DE for NVIDIA)
>> + pub vendor_id: u16,
>> + /// PCI Device ID
>> + pub device_id: u16,
>> + /// Device List Pointer
>> + pub device_list_ptr: u16,
>> + /// PCI Data Structure Length
>> + pub pci_data_struct_len: u16,
>> + /// PCI Data Structure Revision
>> + pub pci_data_struct_rev: u8,
>> + /// Class code (3 bytes, 0x03 for display controller)
>> + pub class_code: [u8; 3],
>> + /// Size of this image in 512-byte blocks
>> + pub image_len: u16,
>> + /// Revision Level of the Vendor's ROM
>> + pub vendor_rom_rev: u16,
>> + /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI)
>> + pub code_type: u8,
>> + /// Last image indicator (0x00 = Not last image, 0x80 = Last image)
>> + pub last_image: u8,
>> + /// Maximum Run-time Image Length (units of 512 bytes)
>> + pub max_runtime_image_len: u16,
>> +}
>
> Here and in a couple more cases below, please don't use pub for fields of
> private structures.
Fixed thanks.
>> +
>> +impl PcirStruct {
>> + fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
>> + if data.len() < core::mem::size_of::<PcirStruct>() {
>> + dev_err!(pdev.as_ref(), "Not enough data for PcirStruct\n");
>> + return Err(EINVAL);
>> + }
>> +
>> + let mut signature = [0u8; 4];
>> + signature.copy_from_slice(&data[0..4]);
>> +
>> + // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e)
>> + if &signature != b"PCIR" && &signature != b"NPDS" {
>> + dev_err!(
>> + pdev.as_ref(),
>> + "Invalid signature for PcirStruct: {:?}\n",
>> + signature
>> + );
>> + return Err(EINVAL);
>> + }
>> +
>> + let mut class_code = [0u8; 3];
>> + class_code.copy_from_slice(&data[13..16]);
>> +
>> + Ok(PcirStruct {
>> + signature,
>> + vendor_id: u16::from_le_bytes([data[4], data[5]]),
>> + device_id: u16::from_le_bytes([data[6], data[7]]),
>> + device_list_ptr: u16::from_le_bytes([data[8], data[9]]),
>> + pci_data_struct_len: u16::from_le_bytes([data[10], data[11]]),
>> + pci_data_struct_rev: data[12],
>> + class_code,
>> + image_len: u16::from_le_bytes([data[16], data[17]]),
>> + vendor_rom_rev: u16::from_le_bytes([data[18], data[19]]),
>> + code_type: data[20],
>> + last_image: data[21],
>> + max_runtime_image_len: u16::from_le_bytes([data[22], data[23]]),
>> + })
>
> Quite some of those fields seem unused, do we still want to have them? Same for
> other structures below.
I think we discussed this in the previous posting as well. As such, I am not
keen on removing unused fields of structures part of 'standard' specifications
since I only see drawbacks of doing so:
1. Obfuscation
2. Replacement of the fields with some kind of padding so that size_of() works.
3. Goes in the opposite direction of documentation and transparency in how the
structures work.
4. Partially filling structures.
>> +
>> + /// Check if this is the last image in the ROM
>> + fn is_last(&self) -> bool {
>> + self.last_image & LAST_IMAGE_BIT_MASK != 0
>> + }
>> +
>> + /// Calculate image size in bytes
>> + fn image_size_bytes(&self) -> Result<usize> {
>> + if self.image_len > 0 {
>> + // Image size is in 512-byte blocks
>> + Ok(self.image_len as usize * 512)
>> + } else {
>> + Err(EINVAL)
>> + }
>> + }
>> +}
>> +
>> +/// BIOS Information Table (BIT) Header
>> +/// This is the head of the BIT table, that is used to locate the Falcon data.
>> +/// The BIT table (with its header) is in the PciAtBiosImage and the falcon data
>> +/// it is pointing to is in the FwSecBiosImage.
>> +#[derive(Debug, Clone, Copy)]
>> +#[expect(dead_code)]
>> +struct BitHeader {
>> + /// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
>> + pub id: u16,
>> + /// 2h: BIT Header Signature ("BIT\0")
>> + pub signature: [u8; 4],
>> + /// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00.
>> + pub bcd_version: u16,
>> + /// 8h: Size of BIT Header (in bytes)
>> + pub header_size: u8,
>> + /// 9h: Size of BIT Tokens (in bytes)
>> + pub token_size: u8,
>> + /// 10h: Number of token entries that follow
>> + pub token_entries: u8,
>> + /// 11h: BIT Header Checksum
>> + pub checksum: u8,
>> +}
>> +
>> +impl BitHeader {
>> + fn new(data: &[u8]) -> Result<Self> {
>> + if data.len() < 12 {
>> + return Err(EINVAL);
>> + }
>> +
>> + let mut signature = [0u8; 4];
>> + signature.copy_from_slice(&data[2..6]);
>> +
>> + // Check header ID and signature
>> + let id = u16::from_le_bytes([data[0], data[1]]);
>> + if id != 0xB8FF || &signature != b"BIT\0" {
>> + return Err(EINVAL);
>> + }
>> +
>> + Ok(BitHeader {
>> + id,
>> + signature,
>> + bcd_version: u16::from_le_bytes([data[6], data[7]]),
>> + header_size: data[8],
>> + token_size: data[9],
>> + token_entries: data[10],
>> + checksum: data[11],
>> + })
>> + }
>> +}
>> +
>> +/// BIT Token Entry: Records in the BIT table followed by the BIT header
>> +#[derive(Debug, Clone, Copy)]
>> +#[expect(dead_code)]
>> +struct BitToken {
>> + /// 00h: Token identifier
>> + pub id: u8,
>> + /// 01h: Version of the token data
>> + pub data_version: u8,
>> + /// 02h: Size of token data in bytes
>> + pub data_size: u16,
>> + /// 04h: Offset to the token data
>> + pub data_offset: u16,
>> +}
>> +
>> +// Define the token ID for the Falcon data
>> +pub(in crate::vbios) const BIT_TOKEN_ID_FALCON_DATA: u8 = 0x70;
>
> This can just be private.
Yep, fixed.
>> +
>> +impl BitToken {
>> + /// Find a BIT token entry by BIT ID in a PciAtBiosImage
>> + pub(in crate::vbios) fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> {
>
> Same here.
Fixed.
> <snip>
>
>> +struct PciAtBiosImage {
>> + base: BiosImageBase,
>> + bit_header: Option<BitHeader>,
>> + bit_offset: Option<usize>,
>
> Why are those Options? AFAICS, this structure is only ever created from
>
> impl TryFrom<BiosImageBase> for PciAtBiosImage
>
> and there you fail if you can't find the bit header anyways.
>
> Also BitToken::from_id fails if bit_header == None, and it doesn't seem to be
> used anywhere else.
>
> I think we should remove the Option wrapper for both.
Yes, thanks. That does simplify the code, I made the change and it works.
>
>> +/// The PmuLookupTableEntry structure is used to find the PmuLookupTableEntry
>> +/// for a given application ID. The table of entries is pointed to by the falcon
>> +/// data pointer in the BIT table, and is used to locate the Falcon Ucode.
>> +#[expect(dead_code)]
>> +struct PmuLookupTable {
>> + version: u8,
>> + header_len: u8,
>> + entry_len: u8,
>> + entry_count: u8,
>> + table_data: KVec<u8>,
>> +}
>> +
>> +impl PmuLookupTable {
>> + fn new(data: &[u8]) -> Result<Self> {
>> + if data.len() < 4 {
>> + return Err(EINVAL);
>> + }
>> +
>> + let header_len = data[1] as usize;
>> + let entry_len = data[2] as usize;
>> + let entry_count = data[3] as usize;
>> +
>> + let required_bytes = header_len + (entry_count * entry_len);
>> +
>> + if data.len() < required_bytes {
>> + return Err(EINVAL);
>> + }
>> +
>> + // Create a copy of only the table data
>> + let mut table_data = KVec::new();
>> +
>> + // "last_entry_bytes" is a debugging aid.
>> + let mut last_entry_bytes: Option<KVec<u8>> = if cfg!(debug_assertions) {
>> + Some(KVec::new())
>> + } else {
>> + None
>> + };
>> +
>> + for &byte in &data[header_len..required_bytes] {
>> + table_data.push(byte, GFP_KERNEL)?;
>
> This should just be
>
> table_data.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
>
> so you don't need the loop and potentially lots of re-allocations.
>
> Subsequently you can implement the debugging stuff as
>
> if cfg!(debug_assertions) {
> let mut last_entry_bytes = KVec::new();
>
> for &byte in &data[header_len..required_bytes] {
> // Debugging (dumps the table data to dmesg):
> last_entry_bytes.push(byte, GFP_KERNEL)?;
>
> let last_entry_bytes_len = last_entry_bytes.len();
> if last_entry_bytes_len == entry_len {
> pr_info!("Last entry bytes: {:02x?}\n", &last_entry_bytes[..]);
> last_entry_bytes = KVec::new();
> }
> }
> }
Ok, that's better, I took the opportunity to replace this code with:
(Sorry for wrapping)
// Create a copy of only the table data
let data_entries = &data[header_len..required_bytes];
let table_data = {
let mut ret = KVec::new();
ret.extend_from_slice(&data_entries, GFP_KERNEL)?;
ret
};
// Debug logging of entries (dumps the table data to dmesg)
if cfg!(debug_assertions) {
for i in 0..entry_count {
pr_info!("PMU entry: {:02x?}\n", &data_entries[i * entry_len..(i
+ 1) * entry_len]);
}
}
> In general, I feel like this patch utilizes the Option type way too much and
> often without actual need. Can you please also double check?
Yeah, sorry, I'm somewhat new to rust. :-D. I am going through all my Options now.
I will continue addressing the rest of the comments and those in the other email
and will reply soon. Thanks!
- Joel
More information about the dri-devel
mailing list