diff options
Diffstat (limited to 'drivers/gpu/nova-core/vbios.rs')
| -rw-r--r-- | drivers/gpu/nova-core/vbios.rs | 423 |
1 files changed, 181 insertions, 242 deletions
diff --git a/drivers/gpu/nova-core/vbios.rs b/drivers/gpu/nova-core/vbios.rs index 71fbe71b84db..abf423560ff4 100644 --- a/drivers/gpu/nova-core/vbios.rs +++ b/drivers/gpu/nova-core/vbios.rs @@ -2,15 +2,27 @@ //! VBIOS extraction and parsing. -use crate::driver::Bar0; -use crate::firmware::fwsec::Bcrt30Rsa3kSignature; -use crate::firmware::FalconUCodeDescV3; use core::convert::TryFrom; -use kernel::device; -use kernel::error::Result; -use kernel::prelude::*; -use kernel::ptr::{Alignable, Alignment}; -use kernel::types::ARef; + +use kernel::{ + device, + prelude::*, + ptr::{ + Alignable, + Alignment, // + }, + transmute::FromBytes, + types::ARef, +}; + +use crate::{ + driver::Bar0, + firmware::{ + fwsec::Bcrt30Rsa3kSignature, + FalconUCodeDescV3, // + }, + num::FromSafeCast, +}; /// The offset of the VBIOS ROM in the BAR0 space. const ROM_OFFSET: usize = 0x300000; @@ -22,6 +34,34 @@ const BIOS_READ_AHEAD_SIZE: usize = 1024; /// indicates the last image. Bit 0-6 are reserved, bit 7 is last image bit. const LAST_IMAGE_BIT_MASK: u8 = 0x80; +/// BIOS Image Type from PCI Data Structure code_type field. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +enum BiosImageType { + /// PC-AT compatible BIOS image (x86 legacy) + PciAt = 0x00, + /// EFI (Extensible Firmware Interface) BIOS image + Efi = 0x03, + /// NBSI (Notebook System Information) BIOS image + Nbsi = 0x70, + /// FwSec (Firmware Security) BIOS image + FwSec = 0xE0, +} + +impl TryFrom<u8> for BiosImageType { + type Error = Error; + + fn try_from(code: u8) -> Result<Self> { + match code { + 0x00 => Ok(Self::PciAt), + 0x03 => Ok(Self::Efi), + 0x70 => Ok(Self::Nbsi), + 0xE0 => Ok(Self::FwSec), + _ => Err(EINVAL), + } + } +} + // PMU lookup table entry types. Used to locate PMU table entries // in the Fwsec image, corresponding to falcon ucodes. #[expect(dead_code)] @@ -197,32 +237,37 @@ impl Vbios { // Parse all VBIOS images in the ROM for image_result in VbiosIterator::new(dev, bar0)? { - let full_image = image_result?; + let image = image_result?; dev_dbg!( dev, - "Found BIOS image: size: {:#x}, type: {}, last: {}\n", - full_image.image_size_bytes(), - full_image.image_type_str(), - full_image.is_last() + "Found BIOS image: size: {:#x}, type: {:?}, last: {}\n", + image.image_size_bytes(), + image.image_type(), + image.is_last() ); - // Get references to images we will need after the loop, in order to - // setup the falcon data offset. - match full_image { - BiosImage::PciAt(image) => { - pci_at_image = Some(image); + // Convert to a specific image type + match BiosImageType::try_from(image.pcir.code_type) { + Ok(BiosImageType::PciAt) => { + pci_at_image = Some(PciAtBiosImage::try_from(image)?); } - BiosImage::FwSec(image) => { + Ok(BiosImageType::FwSec) => { + let fwsec = FwSecBiosBuilder { + base: image, + falcon_data_offset: None, + pmu_lookup_table: None, + falcon_ucode_offset: None, + }; if first_fwsec_image.is_none() { - first_fwsec_image = Some(image); + first_fwsec_image = Some(fwsec); } else { - second_fwsec_image = Some(image); + second_fwsec_image = Some(fwsec); } } - // For now we don't need to handle these - BiosImage::Efi(_image) => {} - BiosImage::Nbsi(_image) => {} + _ => { + // Ignore other image types or unknown types + } } } @@ -280,45 +325,29 @@ struct PcirStruct { max_runtime_image_len: u16, } +// SAFETY: all bit patterns are valid for `PcirStruct`. +unsafe impl FromBytes for PcirStruct {} + impl PcirStruct { fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { - if data.len() < core::mem::size_of::<PcirStruct>() { - dev_err!(dev, "Not enough data for PcirStruct\n"); - return Err(EINVAL); - } - - let mut signature = [0u8; 4]; - signature.copy_from_slice(&data[0..4]); + let (pcir, _) = PcirStruct::from_bytes_copy_prefix(data).ok_or(EINVAL)?; // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e). - if &signature != b"PCIR" && &signature != b"NPDS" { - dev_err!(dev, "Invalid signature for PcirStruct: {:?}\n", signature); + if &pcir.signature != b"PCIR" && &pcir.signature != b"NPDS" { + dev_err!( + dev, + "Invalid signature for PcirStruct: {:?}\n", + pcir.signature + ); return Err(EINVAL); } - let mut class_code = [0u8; 3]; - class_code.copy_from_slice(&data[13..16]); - - let image_len = u16::from_le_bytes([data[16], data[17]]); - if image_len == 0 { + if pcir.image_len == 0 { dev_err!(dev, "Invalid image length: 0\n"); return Err(EINVAL); } - 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, - 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]]), - }) + Ok(pcir) } /// Check if this is the last image in the ROM. @@ -328,7 +357,7 @@ impl PcirStruct { /// Calculate image size in bytes from 512-byte blocks. fn image_size_bytes(&self) -> usize { - self.image_len as usize * 512 + usize::from(self.image_len) * 512 } } @@ -356,30 +385,19 @@ struct BitHeader { checksum: u8, } +// SAFETY: all bit patterns are valid for `BitHeader`. +unsafe impl FromBytes for BitHeader {} + impl BitHeader { fn new(data: &[u8]) -> Result<Self> { - if data.len() < core::mem::size_of::<Self>() { - return Err(EINVAL); - } - - let mut signature = [0u8; 4]; - signature.copy_from_slice(&data[2..6]); + let (header, _) = BitHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?; // Check header ID and signature - let id = u16::from_le_bytes([data[0], data[1]]); - if id != 0xB8FF || &signature != b"BIT\0" { + if header.id != 0xB8FF || &header.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], - }) + Ok(header) } } @@ -406,13 +424,13 @@ impl BitToken { let header = &image.bit_header; // Offset to the first token entry - let tokens_start = image.bit_offset + header.header_size as usize; + let tokens_start = image.bit_offset + usize::from(header.header_size); - for i in 0..header.token_entries as usize { - let entry_offset = tokens_start + (i * header.token_size as usize); + for i in 0..usize::from(header.token_entries) { + let entry_offset = tokens_start + (i * usize::from(header.token_size)); // Make sure we don't go out of bounds - if entry_offset + header.token_size as usize > image.base.data.len() { + if entry_offset + usize::from(header.token_size) > image.base.data.len() { return Err(EINVAL); } @@ -530,35 +548,29 @@ struct NpdeStruct { last_image: u8, } +// SAFETY: all bit patterns are valid for `NpdeStruct`. +unsafe impl FromBytes for NpdeStruct {} + impl NpdeStruct { fn new(dev: &device::Device, data: &[u8]) -> Option<Self> { - if data.len() < core::mem::size_of::<Self>() { - dev_dbg!(dev, "Not enough data for NpdeStruct\n"); - return None; - } - - let mut signature = [0u8; 4]; - signature.copy_from_slice(&data[0..4]); + let (npde, _) = NpdeStruct::from_bytes_copy_prefix(data)?; // Signature should be "NPDE" (0x4544504E). - if &signature != b"NPDE" { - dev_dbg!(dev, "Invalid signature for NpdeStruct: {:?}\n", signature); + if &npde.signature != b"NPDE" { + dev_dbg!( + dev, + "Invalid signature for NpdeStruct: {:?}\n", + npde.signature + ); return None; } - let subimage_len = u16::from_le_bytes([data[8], data[9]]); - if subimage_len == 0 { + if npde.subimage_len == 0 { dev_dbg!(dev, "Invalid subimage length: 0\n"); return None; } - Some(NpdeStruct { - signature, - npci_data_ext_rev: u16::from_le_bytes([data[4], data[5]]), - npci_data_ext_len: u16::from_le_bytes([data[6], data[7]]), - subimage_len, - last_image: data[10], - }) + Some(npde) } /// Check if this is the last image in the ROM. @@ -568,7 +580,7 @@ impl NpdeStruct { /// Calculate image size in bytes from 512-byte blocks. fn image_size_bytes(&self) -> usize { - self.subimage_len as usize * 512 + usize::from(self.subimage_len) * 512 } /// Try to find NPDE in the data, the NPDE is right after the PCIR. @@ -580,8 +592,8 @@ impl NpdeStruct { ) -> Option<Self> { // Calculate the offset where NPDE might be located // NPDE should be right after the PCIR structure, aligned to 16 bytes - let pcir_offset = rom_header.pci_data_struct_offset as usize; - let npde_start = (pcir_offset + pcir.pci_data_struct_len as usize + 0x0F) & !0x0F; + let pcir_offset = usize::from(rom_header.pci_data_struct_offset); + let npde_start = (pcir_offset + usize::from(pcir.pci_data_struct_len) + 0x0F) & !0x0F; // Check if we have enough data if npde_start + core::mem::size_of::<Self>() > data.len() { @@ -594,108 +606,29 @@ impl NpdeStruct { } } -// Use a macro to implement BiosImage enum and methods. This avoids having to -// repeat each enum type when implementing functions like base() in BiosImage. -macro_rules! bios_image { - ( - $($variant:ident: $class:ident),* $(,)? - ) => { - // BiosImage enum with variants for each image type - enum BiosImage { - $($variant($class)),* - } - - impl BiosImage { - /// Get a reference to the common BIOS image data regardless of type - fn base(&self) -> &BiosImageBase { - match self { - $(Self::$variant(img) => &img.base),* - } - } - - /// Returns a string representing the type of BIOS image - fn image_type_str(&self) -> &'static str { - match self { - $(Self::$variant(_) => stringify!($variant)),* - } - } - } - } -} - -impl BiosImage { - /// Check if this is the last image. - fn is_last(&self) -> bool { - let base = self.base(); - - // For NBSI images (type == 0x70), return true as they're - // considered the last image - if matches!(self, Self::Nbsi(_)) { - return true; - } - - // For other image types, check the NPDE first if available - if let Some(ref npde) = base.npde { - return npde.is_last(); - } - - // Otherwise, fall back to checking the PCIR last_image flag - base.pcir.is_last() - } - - /// Get the image size in bytes. - fn image_size_bytes(&self) -> usize { - let base = self.base(); - - // Prefer NPDE image size if available - if let Some(ref npde) = base.npde { - return npde.image_size_bytes(); - } - - // Otherwise, fall back to the PCIR image size - base.pcir.image_size_bytes() - } - - /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which - /// triggers the constructor of the specific BiosImage enum variant. - fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { - let base = BiosImageBase::new(dev, data)?; - let image = base.into_image().inspect_err(|e| { - dev_err!(dev, "Failed to create BiosImage: {:?}\n", e); - })?; - - Ok(image) - } -} - -bios_image! { - PciAt: PciAtBiosImage, // PCI-AT compatible BIOS image - Efi: EfiBiosImage, // EFI (Extensible Firmware Interface) - Nbsi: NbsiBiosImage, // NBSI (Nvidia Bios System Interface) - FwSec: FwSecBiosBuilder, // FWSEC (Firmware Security) -} - /// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain. /// /// It contains the BIT header and the BIT tokens. struct PciAtBiosImage { - base: BiosImageBase, + base: BiosImage, bit_header: BitHeader, bit_offset: usize, } +#[expect(dead_code)] struct EfiBiosImage { - base: BiosImageBase, + base: BiosImage, // EFI-specific fields can be added here in the future. } +#[expect(dead_code)] struct NbsiBiosImage { - base: BiosImageBase, + base: BiosImage, // NBSI-specific fields can be added here in the future. } struct FwSecBiosBuilder { - base: BiosImageBase, + base: BiosImage, /// These are temporary fields that are used during the construction of the /// [`FwSecBiosBuilder`]. /// @@ -714,37 +647,16 @@ struct FwSecBiosBuilder { /// /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode. pub(crate) struct FwSecBiosImage { - base: BiosImageBase, + base: BiosImage, /// The offset of the Falcon ucode. falcon_ucode_offset: usize, } -// Convert from BiosImageBase to BiosImage -impl TryFrom<BiosImageBase> for BiosImage { - type Error = Error; - - fn try_from(base: BiosImageBase) -> Result<Self> { - match base.pcir.code_type { - 0x00 => Ok(BiosImage::PciAt(base.try_into()?)), - 0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })), - 0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })), - 0xE0 => Ok(BiosImage::FwSec(FwSecBiosBuilder { - base, - falcon_data_offset: None, - pmu_lookup_table: None, - falcon_ucode_offset: None, - })), - _ => Err(EINVAL), - } - } -} - /// BIOS Image structure containing various headers and reference fields to all BIOS images. /// -/// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that -/// Rust favors composition of types over inheritance. +/// A BiosImage struct is embedded into all image types and implements common operations. #[expect(dead_code)] -struct BiosImageBase { +struct BiosImage { /// Used for logging. dev: ARef<device::Device>, /// PCI ROM Expansion Header @@ -757,12 +669,40 @@ struct BiosImageBase { data: KVec<u8>, } -impl BiosImageBase { - fn into_image(self) -> Result<BiosImage> { - BiosImage::try_from(self) +impl BiosImage { + /// Get the image size in bytes. + fn image_size_bytes(&self) -> usize { + // Prefer NPDE image size if available + if let Some(ref npde) = self.npde { + npde.image_size_bytes() + } else { + // Otherwise, fall back to the PCIR image size + self.pcir.image_size_bytes() + } + } + + /// Get the BIOS image type. + fn image_type(&self) -> Result<BiosImageType> { + BiosImageType::try_from(self.pcir.code_type) + } + + /// Check if this is the last image. + fn is_last(&self) -> bool { + // For NBSI images, return true as they're considered the last image. + if self.image_type() == Ok(BiosImageType::Nbsi) { + return true; + } + + // For other image types, check the NPDE first if available + if let Some(ref npde) = self.npde { + return npde.is_last(); + } + + // Otherwise, fall back to checking the PCIR last_image flag + self.pcir.is_last() } - /// Creates a new BiosImageBase from raw byte data. + /// Creates a new BiosImage from raw byte data. fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { // Ensure we have enough data for the ROM header. if data.len() < 26 { @@ -775,7 +715,7 @@ impl BiosImageBase { .inspect_err(|e| dev_err!(dev, "Failed to create PciRomHeader: {:?}\n", e))?; // Get the PCI Data Structure using the pointer from the ROM header. - let pcir_offset = rom_header.pci_data_struct_offset as usize; + let pcir_offset = usize::from(rom_header.pci_data_struct_offset); let pcir_data = data .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>()) .ok_or(EINVAL) @@ -802,7 +742,7 @@ impl BiosImageBase { let mut data_copy = KVec::new(); data_copy.extend_from_slice(data, GFP_KERNEL)?; - Ok(BiosImageBase { + Ok(BiosImage { dev: dev.into(), rom_header, pcir, @@ -843,12 +783,12 @@ impl PciAtBiosImage { let token = self.get_bit_token(BIT_TOKEN_ID_FALCON_DATA)?; // Make sure we don't go out of bounds - if token.data_offset as usize + 4 > self.base.data.len() { + if usize::from(token.data_offset) + 4 > self.base.data.len() { return Err(EINVAL); } // read the 4 bytes at the offset specified in the token - let offset = token.data_offset as usize; + let offset = usize::from(token.data_offset); let bytes: [u8; 4] = self.base.data[offset..offset + 4].try_into().map_err(|_| { dev_err!(self.base.dev, "Failed to convert data slice to array"); EINVAL @@ -856,7 +796,7 @@ impl PciAtBiosImage { let data_ptr = u32::from_le_bytes(bytes); - if (data_ptr as usize) < self.base.data.len() { + if (usize::from_safe_cast(data_ptr)) < self.base.data.len() { dev_err!(self.base.dev, "Falcon data pointer out of bounds\n"); return Err(EINVAL); } @@ -865,10 +805,10 @@ impl PciAtBiosImage { } } -impl TryFrom<BiosImageBase> for PciAtBiosImage { +impl TryFrom<BiosImage> for PciAtBiosImage { type Error = Error; - fn try_from(base: BiosImageBase) -> Result<Self> { + fn try_from(base: BiosImage) -> Result<Self> { let data_slice = &base.data; let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?; @@ -904,29 +844,34 @@ impl PmuLookupTableEntry { } } +#[repr(C)] +struct PmuLookupTableHeader { + version: u8, + header_len: u8, + entry_len: u8, + entry_count: u8, +} + +// SAFETY: all bit patterns are valid for `PmuLookupTableHeader`. +unsafe impl FromBytes for PmuLookupTableHeader {} + /// 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, + header: PmuLookupTableHeader, table_data: KVec<u8>, } impl PmuLookupTable { fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { - if data.len() < 4 { - return Err(EINVAL); - } + let (header, _) = PmuLookupTableHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?; - let header_len = data[1] as usize; - let entry_len = data[2] as usize; - let entry_count = data[3] as usize; + let header_len = usize::from(header.header_len); + let entry_len = usize::from(header.entry_len); + let entry_count = usize::from(header.entry_count); let required_bytes = header_len + (entry_count * entry_len); @@ -947,27 +892,21 @@ impl PmuLookupTable { dev_dbg!(dev, "PMU entry: {:02x?}\n", &data[i..][..entry_len]); } - Ok(PmuLookupTable { - version: data[0], - header_len: header_len as u8, - entry_len: entry_len as u8, - entry_count: entry_count as u8, - table_data, - }) + Ok(PmuLookupTable { header, table_data }) } fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> { - if idx >= self.entry_count { + if idx >= self.header.entry_count { return Err(EINVAL); } - let index = (idx as usize) * self.entry_len as usize; + let index = (usize::from(idx)) * usize::from(self.header.entry_len); PmuLookupTableEntry::new(&self.table_data[index..]) } // find entry by type value fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> { - for i in 0..self.entry_count { + for i in 0..self.header.entry_count { let entry = self.lookup_index(i)?; if entry.application_id == entry_type { return Ok(entry); @@ -984,7 +923,7 @@ impl FwSecBiosBuilder { pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result { - let mut offset = pci_at_image.falcon_data_ptr()? as usize; + let mut offset = usize::from_safe_cast(pci_at_image.falcon_data_ptr()?); let mut pmu_in_first_fwsec = false; // The falcon data pointer assumes that the PciAt and FWSEC images @@ -1025,7 +964,7 @@ impl FwSecBiosBuilder { .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD) { Ok(entry) => { - let mut ucode_offset = entry.data as usize; + let mut ucode_offset = usize::from_safe_cast(entry.data); ucode_offset -= pci_at_image.base.data.len(); if ucode_offset < first_fwsec.base.data.len() { dev_err!(self.base.dev, "Falcon Ucode offset not in second Fwsec.\n"); @@ -1111,7 +1050,7 @@ impl FwSecBiosImage { // The ucode data follows the descriptor. let ucode_data_offset = falcon_ucode_offset + desc.size(); - let size = (desc.imem_load_size + desc.dmem_load_size) as usize; + let size = usize::from_safe_cast(desc.imem_load_size + desc.dmem_load_size); // Get the data slice, checking bounds in a single operation. self.base @@ -1130,8 +1069,8 @@ impl FwSecBiosImage { pub(crate) fn sigs(&self, desc: &FalconUCodeDescV3) -> Result<&[Bcrt30Rsa3kSignature]> { // The signatures data follows the descriptor. let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>(); - let sigs_size = - desc.signature_count as usize * core::mem::size_of::<Bcrt30Rsa3kSignature>(); + let sigs_count = usize::from(desc.signature_count); + let sigs_size = sigs_count * core::mem::size_of::<Bcrt30Rsa3kSignature>(); // Make sure the data is within bounds. if sigs_data_offset + sigs_size > self.base.data.len() { @@ -1151,7 +1090,7 @@ impl FwSecBiosImage { .as_ptr() .add(sigs_data_offset) .cast::<Bcrt30Rsa3kSignature>(), - desc.signature_count as usize, + sigs_count, ) }) } |
