gpu: nova-core: vbios: harden various array accesses and refactor
Subsystems (7)
v5 cover gpu: nova-core: vbios: harden various array accesses and refactor
We have some code that accesses arrays based on values from firmware. This patch series makes a bunch of those accesses more robust. This series only touches accesses that are not guaranteed to be safe by local invariants - some accesses are safe due to earlier checks and I haven't modified those.
This series also refactors and removes some code that can be simplified. In particular, it removes FwSecBiosBuilder, removes unused fields, and moves type and constant definitions closer to their usages.
---
Changes in v5:
- Remove "two fwsec image" logic, instead build contiguous fwsec block
- Extra patch to remove unused rom_header member
- Remove no longer useful debug logging
- Also moved IFR register defs into fn and removed pub(crate) (forgot
to do this before)
- Link to v4: https://patch.msgid.link/[email protected]
Changes in v4:
- Split BIOS_MAX_SCAN_LEN patch into multiple per review comments
- Consolidate bios max scan length checking
- Convert more structs to use FromBytes since we assume little endian
- Remove unused PciRomHeader fields
- Drop unused types / imports
- Move constants+helpers closer to usage locations
- Link to v3: https://patch.msgid.link/[email protected]
Changes in v3:
- Use first PCI-AT and FWSEC images instead of erroring.
- Expand commit messages.
- Add Joel's Reviewed-by's (thanks!)
- Link to v2: https://patch.msgid.link/[email protected]
Changes in v2:
- Add Joel's reviewed-by tags.
- Remove unnecessary code like falcon_data_offset from FwSecBiosBuilder
- Push offset handling into falcon_data_ptr (renamed)
- Simplify setup_falcon_data
- Add checking for spurious PCI-AT and FWSEC images.
- Remove FwSecBiosBuilder
- Link to v1: https://patch.msgid.link/[email protected]
---
Eliot Courtney (22):
gpu: nova-core: vbios: stop scanning at BIOS_MAX_SCAN_LEN
gpu: nova-core: vbios: use checked arithmetic for bios image range end
gpu: nova-core: vbios: avoid reading too far in read_more_at_offset
gpu: nova-core: vbios: read BitToken using FromBytes
gpu: nova-core: vbios: use checked ops and accesses in FwSecBiosImage::ucode
gpu: nova-core: vbios: use checked access in FwSecBiosImage::header
gpu: nova-core: vbios: use checked accesses in setup_falcon_data
gpu: nova-core: vbios: drop unused falcon_data_offset from FwSecBiosBuilder
gpu: nova-core: vbios: keep PmuLookupTable local in setup_falcon_data
gpu: nova-core: vbios: compute FWSEC-relative Falcon data offset
gpu: nova-core: vbios: simplify setup_falcon_data
gpu: nova-core: vbios: read PMU lookup entries using FromBytes
gpu: nova-core: vbios: store PMU lookup entries in a KVVec
gpu: nova-core: vbios: construct FwSecBiosImage directly from BIOS images
gpu: nova-core: vbios: use the first PCI-AT image
gpu: nova-core: vbios: use single logical block for the FWSEC section
gpu: nova-core: vbios: use let-else in Vbios::new
gpu: nova-core: vbios: remove unnecessary fields in PciRomHeader
gpu: nova-core: vbios: drop unused image wrappers
gpu: nova-core: vbios: drop redundant TryFrom import
gpu: nova-core: vbios: move constants and functions to be associated
gpu: nova-core: vbios: remove unused rom_header field
Documentation/gpu/nova/core/vbios.rst | 2 +- drivers/gpu/nova-core/vbios.rs | 701 +++++++++++++--------------------- 2 files changed, 268 insertions(+), 435 deletions(-)
---
base-commit: 8bfe9d72cf2064f679c4192dba84be79eb70675d
change-id: 20260409-fix-vbios-d668e9c21d23
Best regards,
Signature · 1 line
Eliot Courtney <[email protected]>
PATCH v5 1/22 gpu: nova-core: vbios: stop scanning at BIOS_MAX_SCAN_LEN
Current code lets current_offset go to BIOS_MAX_SCAN_LEN which is one byte too far.
--- drivers/gpu/nova-core/vbios.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/gpu/nova-core/vbios.rs +1 -1
@@ -272,7 +272,7 @@ fn next(&mut self) -> Option<Self::Item> {return None ; } -if self.current_offset >BIOS_MAX_SCAN_LEN { +if self.current_offset >=BIOS_MAX_SCAN_LEN {dev_err! (self.dev,"Error: exceeded BIOS scan limit, stopping scan\n" );return None ; }
Signature · 1 line
2.54.0
PATCH v5 2/22 gpu: nova-core: vbios: use checked arithmetic for bios image range end
In reply to Eliot Courtneyread_bios_image_at_offset is called with a length from the VBIOS header, so we should be more defensive here and use checked arithmetic.
--- drivers/gpu/nova-core/vbios.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/gpu/nova-core/vbios.rs +3 -3
@@ -238,8 +238,8 @@ fn read_bios_image_at_offset( len: usize, context: &str, ) -> Result<BiosImage> { - let data_len = self.data.len(); - if offset + len > data_len { + let end = offset.checked_add(len).ok_or(EINVAL)?; + if end > self.data.len() { self.read_more_at_offset(offset, len).inspect_err(|e| { dev_err!( self.dev,
@@ -250,7 +250,7 @@ fn read_bios_image_at_offset( })?; } - BiosImage::new(self.dev, &self.data[offset..offset + len]).inspect_err(|err| { + BiosImage::new(self.dev, &self.data[offset..end]).inspect_err(|err| { dev_err!( self.dev, "Failed to {} at offset {:#x}: {:?}\n",
Signature · 1 line
2.54.0
PATCH v5 3/22 gpu: nova-core: vbios: avoid reading too far in read_more_at_offset
In reply to Eliot CourtneyFix bug where read_more_at_offset would unnecessarily read more data. This happens when the window to read has some part cached and some part not. It would read len bytes instead of just the uncached portion, which could read past BIOS_MAX_SCAN_LEN.
--- drivers/gpu/nova-core/vbios.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/vbios.rs +11 -14
@@ -185,8 +185,13 @@ fn new(dev: &'a device::Device, bar0: &'a Bar0) -> Result<Self> {/// Read bytes from the ROM at the current end of the data vector. fn read_more (&mut self, len:usize ) ->Result { -let current_len = self.data.len (); -let start =ROM_OFFSET + current_len; +let start = self.data.len (); +let end = start + len; + +if end >BIOS_MAX_SCAN_LEN { +dev_err! (self.dev,"Error: exceeded BIOS scan limit.\n" ); +return Err (EINVAL ); + }// Ensure length is a multiple of 4 for 32-bit reads if len % core::mem::size_of ::<u32 >() !=0 {
@@ -200,9 +205,9 @@ fn read_more(&mut self, len: usize) -> Result { self.data.reserve (len,GFP_KERNEL )?;// Read ROM data bytes and push directly to `data`. -for addrin (start..start + len).step_by (core::mem::size_of ::<u32 >()) { +for addrin (start..end).step_by (core::mem::size_of ::<u32 >()) {// Read 32-bit word from the VBIOS ROM -let word = self.bar0.try_read32 (addr)?; +let word = self.bar0.try_read32 (ROM_OFFSET + addr)?;// Convert the `u32` to a 4 byte array and push each byte. word.to_ne_bytes ()
@@ -215,17 +220,9 @@ fn read_more(&mut self, len: usize) -> Result {/// Read bytes at a specific offset, filling any gap. fn read_more_at_offset (&mut self, offset:usize , len:usize ) ->Result { -if offset >BIOS_MAX_SCAN_LEN { -dev_err! (self.dev,"Error: exceeded BIOS scan limit.\n" ); -return Err (EINVAL ); - } +let end = offset.checked_add (len).ok_or (EINVAL )?; -// If `offset` is beyond current data size, fill the gap first. -let current_len = self.data.len (); -let gap_bytes = offset.saturating_sub (current_len); - -// Now read the requested bytes at the offset. - self.read_more (gap_bytes + len) + self.read_more (end.saturating_sub (self.data.len ())) }/// Read a BIOS image at a specific offset and create a [`BiosImage`] from it.
Signature · 1 line
2.54.0
PATCH v5 4/22 gpu: nova-core: vbios: read BitToken using FromBytes
In reply to Eliot CourtneyIf header.token_size is smaller than BitToken, then we currently can read past the end of image.base.data. Use checked arithmetic for computing offsets and simplify reading it in using FromBytes.
--- drivers/gpu/nova-core/vbios.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-)
drivers/gpu/nova-core/vbios.rs +18 -19
@@ -486,7 +486,7 @@ fn new(data: &[u8]) -> Result<Self> {/// BIT Token Entry: Records in the BIT table followed by the BIT header. #[derive( Debug , Clone , Copy )] -#[expect(dead_code)] +#[repr( C )] struct BitToken {/// 00h: Token identifier id:u8 ,
@@ -498,6 +498,9 @@ struct BitToken { data_offset: u16, } +// SAFETY: all bit patterns are valid for `BitToken`. +unsafe impl FromBytes for BitToken {} +// Define the token ID for the Falcon data const BIT_TOKEN_ID_FALCON_DATA :u8 =0x70 ;
@@ -505,32 +508,28 @@ impl BitToken {/// Find a BIT token entry by BIT ID in a PciAtBiosImage fn from_id (image: &PciAtBiosImage , token_id:u8 ) ->Result <Self > {let header = &image.bit_header; +let entry_size = usize::from (header.token_size);// Offset to the first token entry let tokens_start = image.bit_offset + usize::from (header.header_size);for iin 0 ..usize::from (header.token_entries) { -let entry_offset = tokens_start + (i * usize::from (header.token_size)); +let entry_offset = i + .checked_mul (entry_size) + .and_then (|offset| tokens_start.checked_add (offset)) + .ok_or (EINVAL )?; +let entry = image + .base + .data + .get (entry_offset..) + .and_then (|data| data.get (..entry_size)) + .ok_or (EINVAL )?; -// Make sure we don't go out of bounds -if entry_offset + usize::from (header.token_size) > image.base.data.len () { -return Err (EINVAL ); - } +let (token, _) =BitToken ::from_bytes_copy_prefix (entry).ok_or (EINVAL )?;// Check if this token has the requested ID -if image.base.data[entry_offset] == token_id { -return Ok (BitToken { - id: image.base.data[entry_offset], - data_version: image.base.data[entry_offset +1 ], - data_size: u16::from_le_bytes ([ - image.base.data[entry_offset +2 ], - image.base.data[entry_offset +3 ], - ]), - data_offset: u16::from_le_bytes ([ - image.base.data[entry_offset +4 ], - image.base.data[entry_offset +5 ], - ]), - }); +if token.id == token_id { +return Ok (token); } }
Signature · 1 line
2.54.0
PATCH v5 5/22 gpu: nova-core: vbios: use checked ops and accesses in `FwSecBiosImage::ucode`
In reply to Eliot CourtneyUse checked arithmetic and access for extracting the microcode since the offsets are firmware derived.
--- drivers/gpu/nova-core/vbios.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-)
drivers/gpu/nova-core/vbios.rs +8 -6
@@ -1110,16 +1110,18 @@ pub(crate) fn header(&self) -> Result<FalconUCodeDesc> { /// Get the ucode data as a byte slice pub(crate) fn ucode(&self, desc: &FalconUCodeDesc) -> Result<&[u8]> { - let falcon_ucode_offset = self.falcon_ucode_offset; + let size = usize::from_safe_cast( + desc.imem_load_size() + .checked_add(desc.dmem_load_size()) + .ok_or(ERANGE)?, + ); // The ucode data follows the descriptor. - let ucode_data_offset = falcon_ucode_offset + desc.size(); - 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 .data - .get(ucode_data_offset..ucode_data_offset + size) + .get(self.falcon_ucode_offset..) + .and_then(|data| data.get(desc.size()..)) + .and_then(|data| data.get(..size)) .ok_or(ERANGE) .inspect_err(|_| { dev_err!(
Signature · 1 line
2.54.0
PATCH v5 6/22 gpu: nova-core: vbios: use checked access in `FwSecBiosImage::header`
In reply to Eliot CourtneyUse checked access in FwSecBiosImage::header for getting the header version since the value is firmware derived.
--- drivers/gpu/nova-core/vbios.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-)
drivers/gpu/nova-core/vbios.rs +7 -10
@@ -1077,17 +1077,14 @@ fn build(self) -> Result<FwSecBiosImage> {impl FwSecBiosImage {/// Get the FwSec header ([`FalconUCodeDesc`]). pub (crate )fn header (&self) ->Result <FalconUCodeDesc > { -// Get the falcon ucode offset that was found in setup_falcon_data. -let falcon_ucode_offset = self.falcon_ucode_offset; +let data = self + .base + .data + .get (self.falcon_ucode_offset..) + .ok_or (EINVAL )?; -// Read the first 4 bytes to get the version. -let hdr_bytes: [u8 ;4 ] = self.base.data[falcon_ucode_offset..falcon_ucode_offset +4 ] - .try_into () - .map_err (|_|EINVAL )?; -let hdr = u32::from_le_bytes (hdr_bytes); -let ver = (hdr &0xff00 ) >>8 ; - -let data = self.base.data.get (falcon_ucode_offset..).ok_or (EINVAL )?; +// Read the version byte from the header. +let ver = data.get (1 ).copied ().ok_or (EINVAL )?;match ver {2 => {let v2 =FalconUCodeDescV2 ::from_bytes_copy_prefix (data)
Signature · 1 line
2.54.0
PATCH v5 7/22 gpu: nova-core: vbios: use checked accesses in `setup_falcon_data`
In reply to Eliot CourtneyUse checked arithmetic for ucode_offset in setup_falcon_data. This prevents a malformed firmware from causing a panic.
--- drivers/gpu/nova-core/vbios.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
drivers/gpu/nova-core/vbios.rs +9 -8
@@ -1036,14 +1036,15 @@ fn setup_falcon_data( .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD) { Ok(entry) => { - 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"); - return Err(EINVAL); - } - ucode_offset -= first_fwsec.base.data.len(); - self.falcon_ucode_offset = Some(ucode_offset); + self.falcon_ucode_offset = Some( + usize::from_safe_cast(entry.data) + .checked_sub(pci_at_image.base.data.len()) + .and_then(|o| o.checked_sub(first_fwsec.base.data.len())) + .ok_or(EINVAL) + .inspect_err(|_| { + dev_err!(self.base.dev, "Falcon Ucode offset not in second Fwsec.\n"); + })?, + ); } Err(e) => { dev_err!(
Signature · 1 line
2.54.0
PATCH v5 8/22 gpu: nova-core: vbios: drop unused falcon_data_offset from FwSecBiosBuilder
In reply to Eliot CourtneyThis is unused, so we can remove it.
--- drivers/gpu/nova-core/vbios.rs | 5 ----- 1 file changed, 5 deletions(-)
drivers/gpu/nova-core/vbios.rs +0 -5
@@ -338,7 +338,6 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { Ok(BiosImageType::FwSec) => {let fwsec =FwSecBiosBuilder { base: image, - falcon_data_offset:None , pmu_lookup_table:None , falcon_ucode_offset:None , };
@@ -712,8 +711,6 @@ struct FwSecBiosBuilder {/// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new /// [`FwSecBiosImage`]. /// -/// The offset of the Falcon data from the start of Fwsec image. - falcon_data_offset: Option<usize>,/// The [`PmuLookupTable`] starts at the offset of the falcon data pointer. pmu_lookup_table: Option<PmuLookupTable>,/// The offset of the Falcon ucode.
@@ -1015,8 +1012,6 @@ fn setup_falcon_data( offset -= first_fwsec.base.data.len (); } - self.falcon_data_offset =Some (offset); - if pmu_in_first_fwsec { self.pmu_lookup_table = Some(PmuLookupTable::new( &self.base.dev,
Signature · 1 line
2.54.0
PATCH v5 9/22 gpu: nova-core: vbios: keep PmuLookupTable local in setup_falcon_data
In reply to Eliot CourtneyThis does not need to be stored in FwSecBiosBuilder so we can remove it from there, and just create and use it locally in setup_falcon_data.
--- drivers/gpu/nova-core/vbios.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-)
drivers/gpu/nova-core/vbios.rs +6 -19
@@ -338,7 +338,6 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { Ok(BiosImageType::FwSec) => {let fwsec =FwSecBiosBuilder { base: image, - pmu_lookup_table:None , falcon_ucode_offset:None , };if first_fwsec_image.is_none () {
@@ -711,8 +710,6 @@ struct FwSecBiosBuilder {/// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new /// [`FwSecBiosImage`]. /// -/// The [`PmuLookupTable`] starts at the offset of the falcon data pointer. - pmu_lookup_table: Option<PmuLookupTable>,/// The offset of the Falcon ucode. falcon_ucode_offset: Option<usize>, }
@@ -1012,24 +1009,14 @@ fn setup_falcon_data( offset -= first_fwsec.base.data.len (); } -if pmu_in_first_fwsec { - self.pmu_lookup_table =Some (PmuLookupTable ::new ( - &self.base.dev, - &first_fwsec.base.data[offset..], - )?); +let pmu_lookup_data =if pmu_in_first_fwsec { + &first_fwsec.base.data[offset..] }else { - self.pmu_lookup_table =Some (PmuLookupTable ::new ( - &self.base.dev, - &self.base.data[offset..], - )?); - } + self.base.data.get (offset..).ok_or (EINVAL )? + }; +let pmu_lookup_table =PmuLookupTable ::new (&self.base.dev, pmu_lookup_data)?; -match self - .pmu_lookup_table - .as_ref () - .ok_or (EINVAL )? - .find_entry_by_type (FALCON_UCODE_ENTRY_APPID_FWSEC_PROD ) - { +match pmu_lookup_table.find_entry_by_type (FALCON_UCODE_ENTRY_APPID_FWSEC_PROD ) {Ok (entry) => { self.falcon_ucode_offset =Some ( usize::from_safe_cast(entry.data)
Signature · 1 line
2.54.0
PATCH v5 10/22 gpu: nova-core: vbios: compute FWSEC-relative Falcon data offset
In reply to Eliot CourtneyPush the computation of the falcon data offset into a helper function. The subtraction to create the offset should be checked, and by doing this the check can be folded into the existing check in falcon_data_ptr.
--- drivers/gpu/nova-core/vbios.rs | 48 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 29 deletions(-)
drivers/gpu/nova-core/vbios.rs +19 -29
@@ -846,33 +846,29 @@ fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {BitToken ::from_id (self, token_id) } -/// Find the Falcon data pointer structure in the [`PciAtBiosImage`]. +/// Find the Falcon data offset from the start of the FWSEC region. /// -/// This is just a 4 byte structure that contains a pointer to the Falcon data in the FWSEC -/// image. -fn falcon_data_ptr (&self) ->Result <u32 > { +/// The BIT table contains a 4-byte pointer to the Falcon data. Testing shows this pointer +/// treats the PCI-AT and FWSEC images as logically contiguous even when an EFI image sits in +/// between them, so subtract the PCI-AT image size here to convert it to a FWSEC-relative +/// offset. +fn falcon_data_offset (&self) ->Result <usize > {let token = self.get_bit_token (BIT_TOKEN_ID_FALCON_DATA )?; - -// Make sure we don't go out of bounds -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 = 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\n" ); -EINVAL - })?; -let data_ptr = u32::from_le_bytes (bytes); +// Read the 4-byte falcon data pointer at the offset specified in the token. +let data = &self.base.data; +let (ptr, _) = data + .get (offset..) + .and_then (u32::from_bytes_copy_prefix) + .ok_or (EINVAL )?; -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 ); - } - -Ok (data_ptr) + usize::from_safe_cast (ptr) + .checked_sub (data.len ()) + .ok_or (EINVAL ) + .inspect_err (|_| { +dev_err! (self.base.dev,"Falcon data pointer out of bounds\n" ); + }) } }
@@ -989,15 +985,9 @@ fn setup_falcon_data( pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result { - let mut offset = usize::from_safe_cast(pci_at_image.falcon_data_ptr()?); + let mut offset = pci_at_image.falcon_data_offset()?;let mut pmu_in_first_fwsec =false ; -// The falcon data pointer assumes that the PciAt and FWSEC images -// are contiguous in memory. However, testing shows the EFI image sits in -// between them. So calculate the offset from the end of the PciAt image -// rather than the start of it. Compensate. - offset -= pci_at_image.base.data.len (); -// The offset is now from the start of the first Fwsec image, however // the offset points to a location in the second Fwsec image. Since // the fwsec images are contiguous, subtract the length of the first Fwsec
Signature · 1 line
2.54.0
PATCH v5 11/22 gpu: nova-core: vbios: simplify setup_falcon_data
In reply to Eliot CourtneyThe code first computes pmu_in_first_fwsec or adjusts the offset and then uses it in a branch just once to get the correct source for the PMU table. This can be simplified to a single branch while also avoiding the mutation of offset. Also, adjust the code after this to keep the success case non-nested.
--- drivers/gpu/nova-core/vbios.rs | 54 ++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 31 deletions(-)
drivers/gpu/nova-core/vbios.rs +23 -31
@@ -985,48 +985,40 @@ fn setup_falcon_data( pci_at_image: &PciAtBiosImage, first_fwsec: &FwSecBiosBuilder, ) -> Result { - let mut offset = pci_at_image.falcon_data_offset()?; -let mut pmu_in_first_fwsec =false ; + let offset = pci_at_image.falcon_data_offset()?; -// The offset is now from the start of the first Fwsec image, however -// the offset points to a location in the second Fwsec image. Since -// the fwsec images are contiguous, subtract the length of the first Fwsec -// image from the offset to get the offset to the start of the second -// Fwsec image. -if offset < first_fwsec.base.data.len () { - pmu_in_first_fwsec =true ; +// The offset is from the start of the first FwSec image, but it +// may point into the second FwSec image. Treat the two FwSec images +// as contiguous here and subtract the first image length when the +// target lies in the second one. +let pmu_lookup_data =if offset < first_fwsec.base.data.len () { + first_fwsec.base.data.get (offset..) }else { - offset -= first_fwsec.base.data.len (); + self.base.data.get (offset - first_fwsec.base.data.len ()..) } + .ok_or (EINVAL )?; -let pmu_lookup_data =if pmu_in_first_fwsec { - &first_fwsec.base.data[offset..] - }else { - self.base.data.get (offset..).ok_or (EINVAL )? - };let pmu_lookup_table =PmuLookupTable ::new (&self.base.dev, pmu_lookup_data)?; -match pmu_lookup_table.find_entry_by_type (FALCON_UCODE_ENTRY_APPID_FWSEC_PROD ) { -Ok (entry) => { - self.falcon_ucode_offset =Some ( - usize::from_safe_cast (entry.data) - .checked_sub (pci_at_image.base.data.len ()) - .and_then (|o| o.checked_sub (first_fwsec.base.data.len ())) - .ok_or (EINVAL ) - .inspect_err (|_| { -dev_err! (self.base.dev,"Falcon Ucode offset not in second Fwsec.\n" ); - })?, - ); - } -Err (e) => { +let entry = pmu_lookup_table + .find_entry_by_type (FALCON_UCODE_ENTRY_APPID_FWSEC_PROD ) + .inspect_err (|e| {dev_err! ( self.base.dev,"PmuLookupTableEntry not found, error: {:?}\n" , e ); -return Err (EINVAL ); - } - } + })?; + +let falcon_ucode_offset = usize::from_safe_cast (entry.data) + .checked_sub (pci_at_image.base.data.len ()) + .and_then (|o| o.checked_sub (first_fwsec.base.data.len ())) + .ok_or (EINVAL ) + .inspect_err (|_| { +dev_err! (self.base.dev,"Falcon Ucode offset not in second Fwsec.\n" ); + })?; + + self.falcon_ucode_offset =Some (falcon_ucode_offset);Ok (()) }
Signature · 1 line
2.54.0
PATCH v5 12/22 gpu: nova-core: vbios: read PMU lookup entries using FromBytes
In reply to Eliot CourtneyThis simplifies the construction of PmuLookupTableEntry and is allowed now that the driver can assume it is little endian.
--- drivers/gpu/nova-core/vbios.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-)
drivers/gpu/nova-core/vbios.rs +9 -14
@@ -897,19 +897,8 @@ struct PmuLookupTableEntry { data: u32, } -impl PmuLookupTableEntry { -fn new (data: &[u8 ]) ->Result <Self > { -if data.len () < core::mem::size_of ::<Self >() { -return Err (EINVAL ); - } - -Ok (PmuLookupTableEntry { - application_id: data[0 ], - target_id: data[1 ], - data: u32::from_le_bytes (data[2 ..6 ].try_into ().map_err (|_|EINVAL )?), - }) - } -} +// SAFETY: all bit patterns are valid for `PmuLookupTableEntry`. +unsafe impl FromBytes for PmuLookupTableEntry {}#[repr( C )] struct PmuLookupTableHeader {
@@ -963,7 +952,13 @@ fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> { }let index = (usize::from (idx)) * usize::from (self.header.entry_len); -PmuLookupTableEntry ::new (&self.table_data[index..]) +let (entry, _) = self + .table_data + .get (index..) + .and_then (PmuLookupTableEntry ::from_bytes_copy_prefix) + .ok_or (EINVAL )?; + +Ok (entry) }// find entry by type value
Signature · 1 line
2.54.0
PATCH v5 13/22 gpu: nova-core: vbios: store PMU lookup entries in a KVVec
In reply to Eliot CourtneyThe current code copies the data into a KVec and parses it on demand. We can simplify the code by storing the parsed entries.
--- drivers/gpu/nova-core/vbios.rs | 56 ++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 38 deletions(-)
drivers/gpu/nova-core/vbios.rs +18 -38
@@ -917,8 +917,7 @@ unsafe impl FromBytes for PmuLookupTableHeader {}/// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to /// locate the Falcon Ucode. struct PmuLookupTable { - header:PmuLookupTableHeader , - table_data:KVec <u8 >, + entries:KVVec <PmuLookupTableEntry >, }impl PmuLookupTable {
@@ -929,48 +928,29 @@ fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {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); +let data = data + .get (header_len..header_len + entry_count * entry_len) + .ok_or (EINVAL ) + .inspect_err (|_| { +dev_err! (dev,"PmuLookupTable data length less than required\n" ); + })?; -if data.len () < required_bytes { -dev_err! (dev,"PmuLookupTable data length less than required\n" ); -return Err (EINVAL ); +let mut entries =KVVec ::with_capacity (entry_count,GFP_KERNEL )?; +for iin 0 ..entry_count { +let (entry, _) =PmuLookupTableEntry ::from_bytes_copy_prefix (&data[i * entry_len..]) + .ok_or (EINVAL )?; + entries.push (entry,GFP_KERNEL )?; } -// Create a copy of only the table data -let table_data = { -let mut ret =KVec ::new (); - ret.extend_from_slice (&data[header_len..required_bytes],GFP_KERNEL )?; - ret - }; - -Ok (PmuLookupTable { header, table_data }) - } - -fn lookup_index (&self, idx:u8 ) ->Result <PmuLookupTableEntry > { -if idx >= self.header.entry_count { -return Err (EINVAL ); - } - -let index = (usize::from (idx)) * usize::from (self.header.entry_len); -let (entry, _) = self - .table_data - .get (index..) - .and_then (PmuLookupTableEntry ::from_bytes_copy_prefix) - .ok_or (EINVAL )?; - -Ok (entry) +Ok (PmuLookupTable { entries }) }// find entry by type value -fn find_entry_by_type (&self, entry_type:u8 ) ->Result <PmuLookupTableEntry > { -for iin 0 ..self.header.entry_count { -let entry = self.lookup_index (i)?; -if entry.application_id == entry_type { -return Ok (entry); - } - } - -Err (EINVAL ) +fn find_entry_by_type (&self, entry_type:u8 ) ->Result <&PmuLookupTableEntry > { + self.entries + .iter () + .find (|entry| entry.application_id == entry_type) + .ok_or (EINVAL ) } }
Signature · 1 line
2.54.0
Danilo Krummrich
maintainerRe: [PATCH v5 13/22] gpu: nova-core: vbios: store PMU lookup entries in a KVVec
drivers/gpu/nova-core/vbios.rs unsafe impl FromBytes for PmuLookupTableHeader {} · line 920
Quoted 2 lines · Eliot Courtney
drivers/gpu/nova-core/vbios.rs +1
-1- table_data: KVec<u8>, + entries: KVVec<PmuLookupTableEntry>,
Here an in at least one other place the patch series changes kmalloc() to kvmalloc(). Do we really expect this to potentially exceed KMALLOC_MAX_SIZE?
Re: [PATCH v5 13/22] gpu: nova-core: vbios: store PMU lookup entries in a KVVec
Quoted 5 lines · Danilo Krummrich
drivers/gpu/nova-core/vbios.rs unsafe impl FromBytes for PmuLookupTableHeader {} · line 920
Quoted 2 lines · Eliot Courtney
drivers/gpu/nova-core/vbios.rs +1
-1- table_data: KVec<u8>, + entries: KVVec<PmuLookupTableEntry>,Here an in at least one other place the patch series changes
kmalloc()tokvmalloc(). Do we really expect this to potentially exceedKMALLOC_MAX_SIZE?
It won't exceed KMALLOC_MAX_SIZE, AFAICT. IIUC KVVec tries kmalloc first then falls back to vmalloc. Is there a disadvantage to using KVVec?
In this case we don't need the memory to be physically contiguous, so I thought it would be best to try to document that in the type.
But yeah please LMK if the above reasoning is wrong, I can change it back to KVec. I didn't see any existing guidelines for this for rust - if we want to choose based on the expected size of the allocation, then I think KVec is the right choice. If we want to choose based on the guarantees/contract (physically contiguous memory) then I think KVVec makes more sense.
PATCH v5 14/22 gpu: nova-core: vbios: construct `FwSecBiosImage` directly from BIOS images
In reply to Eliot CourtneyFwSecBiosBuilder now only contains falcon_ucode_offset which just gets passed directly into FwSecBiosImage. Remove FwSecBiosBuilder and construct FwSecBiosImage directly, as a simplification.
--- drivers/gpu/nova-core/vbios.rs | 88 ++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 59 deletions(-)
drivers/gpu/nova-core/vbios.rs +29 -59
@@ -315,8 +315,8 @@ impl Vbios {pub (crate )fn new (dev: &device::Device , bar0: &Bar0 ) ->Result <Vbios > {// Images to extract from iteration let mut pci_at_image:Option <PciAtBiosImage > =None ; -let mut first_fwsec_image:Option <FwSecBiosBuilder > =None ; -let mut second_fwsec_image:Option <FwSecBiosBuilder > =None ; +let mut first_fwsec_image:Option <BiosImage > =None ; +let mut second_fwsec_image:Option <BiosImage > =None ;// Parse all VBIOS images in the ROM for image_resultin VbiosIterator ::new (dev, bar0)? {
@@ -336,14 +336,10 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { pci_at_image =Some (PciAtBiosImage ::try_from (image)?); } Ok(BiosImageType::FwSec) => { -let fwsec =FwSecBiosBuilder { - base: image, - falcon_ucode_offset:None , - };if first_fwsec_image.is_none () { - first_fwsec_image =Some (fwsec); + first_fwsec_image =Some (image); }else { - second_fwsec_image =Some (fwsec); + second_fwsec_image =Some (image); } } _ => {
@@ -353,15 +349,13 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { }// Using all the images, setup the falcon data pointer in Fwsec. -if let (Some (mut second),Some (first),Some (pci_at)) = +if let (Some (second),Some (first),Some (pci_at)) = (second_fwsec_image, first_fwsec_image, pci_at_image) { - second - .setup_falcon_data (&pci_at, &first) +let fwsec_image =FwSecBiosImage ::new (pci_at, first, second) .inspect_err (|e|dev_err! (dev,"Falcon data setup failed: {:?}\n" , e))?; -Ok (Vbios { - fwsec_image: second.build ()?, - }) + +Ok (Vbios { fwsec_image }) }else {dev_err! ( dev,
@@ -702,18 +696,6 @@ struct NbsiBiosImage {// NBSI-specific fields can be added here in the future. } -struct FwSecBiosBuilder { - base:BiosImage , -/// These are temporary fields that are used during the construction of the -/// [`FwSecBiosBuilder`]. -/// -/// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new -/// [`FwSecBiosImage`]. -/// -/// The offset of the Falcon ucode. - falcon_ucode_offset:Option <usize >, -} -/// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode. /// /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode.
@@ -954,32 +936,33 @@ fn find_entry_by_type(&self, entry_type: u8) -> Result<&PmuLookupTableEntry> { } } -impl FwSecBiosBuilder { - fn setup_falcon_data( - &mut self, - pci_at_image: &PciAtBiosImage, - first_fwsec: &FwSecBiosBuilder, - ) -> Result { +impl FwSecBiosImage { + /// Build the final `FwSecBiosImage` from the PCI-AT and FWSEC BIOS images. + fn new( + pci_at_image: PciAtBiosImage, + first_fwsec: BiosImage, + second_fwsec: BiosImage, + ) -> Result<FwSecBiosImage> { let offset = pci_at_image.falcon_data_offset()?; // The offset is from the start of the first FwSec image, but it // may point into the second FwSec image. Treat the two FwSec images // as contiguous here and subtract the first image length when the // target lies in the second one. - let pmu_lookup_data = if offset < first_fwsec.base.data.len() { - first_fwsec.base.data.get(offset..) + let pmu_lookup_data = if offset < first_fwsec.data.len() { + first_fwsec.data.get(offset..) } else { - self.base.data.get(offset - first_fwsec.base.data.len()..) + second_fwsec.data.get(offset - first_fwsec.data.len()..) } .ok_or(EINVAL)?; - let pmu_lookup_table = PmuLookupTable::new(&self.base.dev, pmu_lookup_data)?; + let pmu_lookup_table = PmuLookupTable::new(&second_fwsec.dev, pmu_lookup_data)?; let entry = pmu_lookup_table .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD) .inspect_err(|e| { dev_err!( - self.base.dev, + second_fwsec.dev, "PmuLookupTableEntry not found, error: {:?}\n", e );
@@ -987,34 +970,21 @@ fn setup_falcon_data(let falcon_ucode_offset = usize::from_safe_cast (entry.data) .checked_sub (pci_at_image.base.data.len ()) - .and_then (|o| o.checked_sub (first_fwsec.base.data.len ())) + .and_then (|o| o.checked_sub (first_fwsec.data.len ())) .ok_or (EINVAL ) .inspect_err (|_| { -dev_err! (self.base.dev,"Falcon Ucode offset not in second Fwsec.\n" ); +dev_err! ( + second_fwsec.dev, +"Falcon Ucode offset not in second Fwsec.\n" + ); })?; - self.falcon_ucode_offset =Some (falcon_ucode_offset); -Ok (()) +Ok (FwSecBiosImage { + base: second_fwsec, + falcon_ucode_offset, + }) } -/// Build the final FwSecBiosImage from this builder -fn build (self) ->Result <FwSecBiosImage > { -let ret =FwSecBiosImage { - base: self.base, - falcon_ucode_offset: self.falcon_ucode_offset.ok_or (EINVAL )?, - }; - -if cfg! (debug_assertions) { -// Print the desc header for debugging -let desc = ret.header ()?; -dev_dbg! (ret.base.dev,"PmuLookupTableEntry desc: {:#?}\n" , desc); - } - -Ok (ret) - } -} - -impl FwSecBiosImage {/// Get the FwSec header ([`FalconUCodeDesc`]). pub (crate )fn header (&self) ->Result <FalconUCodeDesc > {let data = self
Signature · 1 line
2.54.0
PATCH v5 15/22 gpu: nova-core: vbios: use the first PCI-AT image
In reply to Eliot CourtneyCurrently, PCI-AT takes the final image if multiple exist. Use the first one instead, to match nouveau behaviour.
--- drivers/gpu/nova-core/vbios.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/gpu/nova-core/vbios.rs +4 -1
@@ -333,7 +333,10 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> {// 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)?); +// Silently ignore any extra PCI-AT images. +if pci_at_image.is_none () { + pci_at_image =Some (PciAtBiosImage ::try_from (image)?); + } }Ok (BiosImageType ::FwSec ) => {if first_fwsec_image.is_none () {
Signature · 1 line
2.54.0
Danilo Krummrich
maintainerRe: [PATCH v5 15/22] gpu: nova-core: vbios: use the first PCI-AT image
Review pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { · line 342
Quoted 12 lines · Eliot Courtney
(quoted diff) +4
-1@@ -333,7 +333,10 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { // 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)?); + // Silently ignore any extra PCI-AT images. + if pci_at_image.is_none() { + pci_at_image = Some(PciAtBiosImage::try_from(image)?); + } } Ok(BiosImageType::FwSec) => { if first_fwsec_image.is_none() {
This does produce the following warning:
CLIPPY [M] drivers/gpu/nova-core/nova_core.o
warning: this if can be collapsed into the outer match
--> drivers/gpu/nova-core/vbios.rs:337:21
|
337 | / if pci_at_image.is_none() {
338 | | pci_at_image = Some(PciAtBiosImage::try_from(image)?);
339 | | }
| |_____________________^
| = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.95.0/index.html#collapsible_match
= note: -W clippy::collapsible-match implied by -W clippy::all
= help: to override -W clippy::all add #[allow(clippy::collapsible_match)]
help: collapse nested if block
|
335 ~ Ok(BiosImageType::PciAt)
336 | // Silently ignore any extra PCI-AT images.
337 ~ if pci_at_image.is_none() => {
338 | pci_at_image = Some(PciAtBiosImage::try_from(image)?);
339 ~ }
|
warning: 1 warning emitted
I can fix it up this way:
drivers/gpu/nova-core/vbios.rs +2 -3
@@ -332,11 +332,10 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> {// Convert to a specific image type match BiosImageType ::try_from (image.pcir.code_type) { -Ok (BiosImageType ::PciAt ) => { +Ok (BiosImageType ::PciAt )// Silently ignore any extra PCI-AT images. -if pci_at_image.is_none () { +if pci_at_image.is_none () => { pci_at_image =Some (PciAtBiosImage ::try_from (image)?); - } }Ok (BiosImageType ::FwSec ) => {if first_fwsec_image.is_none () {
However, I'm not exactly sure it really is an improvement in terms of readability and whether this is a prime example for this lint.
Re: [PATCH v5 15/22] gpu: nova-core: vbios: use the first PCI-AT image
Quoted 3 lines · Danilo Krummrich
However, I'm not exactly sure it really is an improvement in terms of readability and whether this is a prime example for this lint.
The lint is globally allowed since v7.1-rc3, commit 838d852da850 ("rust: allow clippy::collapsible_match globally"), so please feel free to ignore it.
(Same for clippy::collapsible_if.)
Cheers,
Miguel
Danilo Krummrich
maintainerRe: [PATCH v5 15/22] gpu: nova-core: vbios: use the first PCI-AT image
Quoted 10 lines · Miguel Ojeda
Quoted 3 lines · Danilo Krummrich
However, I'm not exactly sure it really is an improvement in terms of readability and whether this is a prime example for this lint.
The lint is globally allowed since v7.1-rc3, commit
838d852da850("rust: allowclippy::collapsible_matchglobally"), so please feel free to ignore it.(Same for
clippy::collapsible_if.)
Ah, I somehow missed that -- thanks!
PATCH v5 16/22 gpu: nova-core: vbios: use single logical block for the FWSEC section
In reply to Eliot CourtneyCurrently, FWSEC takes the first image and the last image. Treat the first FWSEC image and all following image data as one logical block for building the final FWSEC image. This avoids explicitly tracking two FWSEC images.
--- drivers/gpu/nova-core/vbios.rs | 99 ++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 62 deletions(-)
drivers/gpu/nova-core/vbios.rs +37 -62
@@ -84,7 +84,7 @@ struct VbiosIterator<'a> {/// progressively extends. It is used so that we do not re-read any contents that are already /// read as we use the cumulative length read so far, and re-read any gaps as we extend the /// length. - data: KVec<u8>, + data: KVVec<u8>,/// Current offset of the [`Iterator`]. current_offset: usize,/// Indicate whether the last image has been found.
@@ -177,7 +177,7 @@ fn new(dev: &'a device::Device, bar0: &'a Bar0) -> Result<Self> {Ok (Self { dev, bar0, - data:KVec ::new (), + data:KVVec ::new (), current_offset:vbios_rom_offset (dev, bar0)?, last_found:false , })
@@ -315,8 +315,7 @@ impl Vbios {pub (crate )fn new (dev: &device::Device , bar0: &Bar0 ) ->Result <Vbios > {// Images to extract from iteration let mut pci_at_image:Option <PciAtBiosImage > =None ; -let mut first_fwsec_image:Option <BiosImage > =None ; -let mut second_fwsec_image:Option <BiosImage > =None ; +let mut fwsec_section:Option <KVVec <u8 >> =None ;// Parse all VBIOS images in the ROM for image_resultin VbiosIterator ::new (dev, bar0)? {
@@ -330,6 +329,13 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { image.is_last () ); +// Once we have found the first FWSEC image, grab all data after that as the FWSEC +// section. This is indexed as one logical block to build the final FWSEC image. +if let Some (data) = fwsec_section.as_mut () { + data.extend_from_slice (&image.data,GFP_KERNEL )?; +continue ; + } +// Convert to a specific image type match BiosImageType ::try_from (image.pcir.code_type) {Ok (BiosImageType ::PciAt ) => {
@@ -338,13 +344,7 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { pci_at_image =Some (PciAtBiosImage ::try_from (image)?); } } - Ok(BiosImageType::FwSec) => { -if first_fwsec_image.is_none () { - first_fwsec_image =Some (image); - }else { - second_fwsec_image =Some (image); - } - } + Ok(BiosImageType::FwSec) => fwsec_section = Some(image.data), _ => {// Ignore other image types or unknown types }
@@ -352,10 +352,8 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { }// Using all the images, setup the falcon data pointer in Fwsec. -if let (Some (second),Some (first),Some (pci_at)) = - (second_fwsec_image, first_fwsec_image, pci_at_image) - { -let fwsec_image =FwSecBiosImage ::new (pci_at, first, second) +if let (Some (pci_at),Some (fwsec_section)) = (pci_at_image, fwsec_section) { +let fwsec_image =FwSecBiosImage ::new (dev, pci_at, fwsec_section) .inspect_err (|e|dev_err! (dev,"Falcon data setup failed: {:?}\n" , e))?;Ok (Vbios { fwsec_image })
@@ -703,7 +701,10 @@ struct NbsiBiosImage {/// /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode. pub (crate )struct FwSecBiosImage { - base:BiosImage , +/// Used for logging. + dev:ARef <device::Device >, +/// FWSEC data. + data:KVVec <u8 >,/// The offset of the Falcon ucode. falcon_ucode_offset:usize , }
@@ -713,8 +714,6 @@ pub(crate) struct FwSecBiosImage {/// A BiosImage struct is embedded into all image types and implements common operations. #[expect(dead_code)] struct BiosImage { -/// Used for logging. - dev:ARef <device::Device >,/// PCI ROM Expansion Header rom_header:PciRomHeader ,/// PCI Data Structure
@@ -722,7 +721,7 @@ struct BiosImage {/// NVIDIA PCI Data Extension (optional) npde: Option<NpdeStruct>,/// Image data (includes ROM header and PCIR) - data: KVec<u8>, + data: KVVec<u8>, }impl BiosImage {
@@ -795,11 +794,10 @@ fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {let npde =NpdeStruct ::find_in_data (dev, data, &rom_header, &pcir);// Create a copy of the data. -let mut data_copy =KVec ::new (); +let mut data_copy =KVVec ::new (); data_copy.extend_from_slice (data,GFP_KERNEL )?;Ok (BiosImage { - dev: dev.into (), rom_header, pcir, npde,
@@ -837,7 +835,7 @@ fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {/// treats the PCI-AT and FWSEC images as logically contiguous even when an EFI image sits in /// between them, so subtract the PCI-AT image size here to convert it to a FWSEC-relative /// offset. -fn falcon_data_offset (&self) ->Result <usize > { +fn falcon_data_offset (&self, dev: &device::Device ) ->Result <usize > {let token = self.get_bit_token (BIT_TOKEN_ID_FALCON_DATA )?;let offset = usize::from (token.data_offset);
@@ -852,7 +850,7 @@ fn falcon_data_offset(&self) -> Result<usize> { .checked_sub(data.len()) .ok_or (EINVAL ) .inspect_err (|_| { -dev_err! (self.base.dev,"Falcon data pointer out of bounds\n" ); +dev_err! (dev,"Falcon data pointer out of bounds\n" ); }) } }
@@ -942,59 +940,38 @@ fn find_entry_by_type(&self, entry_type: u8) -> Result<&PmuLookupTableEntry> {impl FwSecBiosImage {/// Build the final `FwSecBiosImage` from the PCI-AT and FWSEC BIOS images. fn new ( + dev: &device::Device , pci_at_image:PciAtBiosImage , - first_fwsec:BiosImage , - second_fwsec:BiosImage , + data:KVVec <u8 >, ) ->Result <FwSecBiosImage > { -let offset = pci_at_image.falcon_data_offset ()?; +let offset = pci_at_image.falcon_data_offset (dev)?; -// The offset is from the start of the first FwSec image, but it -// may point into the second FwSec image. Treat the two FwSec images -// as contiguous here and subtract the first image length when the -// target lies in the second one. -let pmu_lookup_data =if offset < first_fwsec.data.len () { - first_fwsec.data.get (offset..) - }else { - second_fwsec.data.get (offset - first_fwsec.data.len ()..) - } - .ok_or (EINVAL )?; - -let pmu_lookup_table =PmuLookupTable ::new (&second_fwsec.dev, pmu_lookup_data)?; +let pmu_lookup_data = data.get (offset..).ok_or (EINVAL )?; +let pmu_lookup_table =PmuLookupTable ::new (dev, pmu_lookup_data)?;let entry = pmu_lookup_table .find_entry_by_type (FALCON_UCODE_ENTRY_APPID_FWSEC_PROD ) .inspect_err (|e| { -dev_err! ( - second_fwsec.dev, -"PmuLookupTableEntry not found, error: {:?}\n" , - e - ); +dev_err! (dev,"PmuLookupTableEntry not found, error: {:?}\n" , e); })?;let falcon_ucode_offset = usize::from_safe_cast (entry.data) .checked_sub (pci_at_image.base.data.len ()) - .and_then (|o| o.checked_sub (first_fwsec.data.len ())) .ok_or (EINVAL ) .inspect_err (|_| { -dev_err! ( - second_fwsec.dev, -"Falcon Ucode offset not in second Fwsec.\n" - ); +dev_err! (dev,"Falcon Ucode offset not in Fwsec.\n" ); })?;Ok (FwSecBiosImage { - base: second_fwsec, + dev: dev.into (), + data, falcon_ucode_offset, }) }/// Get the FwSec header ([`FalconUCodeDesc`]). pub (crate )fn header (&self) ->Result <FalconUCodeDesc > { -let data = self - .base - .data - .get (self.falcon_ucode_offset..) - .ok_or (EINVAL )?; +let data = self.data.get (self.falcon_ucode_offset..).ok_or (EINVAL )?;// Read the version byte from the header. let ver = data.get (1 ).copied ().ok_or (EINVAL )?;
@@ -1012,7 +989,7 @@ pub(crate) fn header(&self) -> Result<FalconUCodeDesc> {Ok (FalconUCodeDesc ::V3 (v3)) } _ => { -dev_err! (self.base.dev,"invalid fwsec firmware version: {:?}\n" , ver); +dev_err! (self.dev,"invalid fwsec firmware version: {:?}\n" , ver);Err (EINVAL ) } }
@@ -1027,15 +1004,14 @@ pub(crate) fn ucode(&self, desc: &FalconUCodeDesc) -> Result<&[u8]> { );// The ucode data follows the descriptor. - self.base - .data + self.data .get (self.falcon_ucode_offset..) .and_then (|data| data.get (desc.size ()..)) .and_then (|data| data.get (..size)) .ok_or (ERANGE ) .inspect_err (|_| {dev_err! ( - self.base.dev, + self.dev,"fwsec ucode data not contained within BIOS bounds\n" ) })
@@ -1053,9 +1029,9 @@ pub(crate) fn sigs(&self, desc: &FalconUCodeDesc) -> Result<&[Bcrt30Rsa3kSignatulet 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 () { +if sigs_data_offset + sigs_size > self.data.len () {dev_err! ( - self.base.dev, + self.dev,"fwsec signatures data not contained within BIOS bounds\n" );return Err (ERANGE );
@@ -1065,8 +1041,7 @@ pub(crate) fn sigs(&self, desc: &FalconUCodeDesc) -> Result<&[Bcrt30Rsa3kSignatu// sizeof::<Bcrt30Rsa3kSignature>()` is within the bounds of `data`. Ok (unsafe { core::slice::from_raw_parts ( - self.base - .data + self.data .as_ptr () .add (sigs_data_offset) .cast::<Bcrt30Rsa3kSignature>(),
Signature · 1 line
2.54.0
PATCH v5 17/22 gpu: nova-core: vbios: use let-else in Vbios::new
In reply to Eliot CourtneyImprove readability by moving the success path outside of a nested branch.
--- drivers/gpu/nova-core/vbios.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
drivers/gpu/nova-core/vbios.rs +8 -8
@@ -352,18 +352,18 @@ pub(crate) fn new(dev: &device::Device, bar0: &Bar0) -> Result<Vbios> { }// Using all the images, setup the falcon data pointer in Fwsec. -if let (Some (pci_at),Some (fwsec_section)) = (pci_at_image, fwsec_section) { -let fwsec_image =FwSecBiosImage ::new (dev, pci_at, fwsec_section) - .inspect_err (|e|dev_err! (dev,"Falcon data setup failed: {:?}\n" , e))?; - -Ok (Vbios { fwsec_image }) - }else { +let (Some (pci_at),Some (fwsec_section)) = (pci_at_image, fwsec_section)else {dev_err! ( dev,"Missing required images for falcon data setup, skipping\n" ); -Err (EINVAL ) - } +return Err (EINVAL ); + }; + +let fwsec_image =FwSecBiosImage ::new (dev, pci_at, fwsec_section) + .inspect_err (|e|dev_err! (dev,"Falcon data setup failed: {:?}\n" , e))?; + +Ok (Vbios { fwsec_image }) }pub (crate )fn fwsec_image (&self) -> &FwSecBiosImage {
Signature · 1 line
2.54.0
PATCH v5 18/22 gpu: nova-core: vbios: remove unnecessary fields in PciRomHeader
In reply to Eliot CourtneyRemove unnecessary fields in PciRomHeader. This allows a simplification to use FromBytes instead of reading fields piecemeal. A lot of these checks were redundant as well since it checks the size of the data first in BiosImage.
--- drivers/gpu/nova-core/vbios.rs | 68 ++++++++++-------------------------------- 1 file changed, 16 insertions(+), 52 deletions(-)
drivers/gpu/nova-core/vbios.rs +16 -52
@@ -533,67 +533,38 @@ fn from_id(image: &PciAtBiosImage, token_id: u8) -> Result<Self> {/// PCI ROM Expansion Header as defined in PCI Firmware Specification. /// -/// This is header is at the beginning of every image in the set of images in the ROM. It contains -/// a pointer to the PCI Data Structure which describes the image. For "NBSI" images (NoteBook -/// System Information), the ROM header deviates from the standard and contains an offset to the -/// NBSI image however we do not yet parse that in this module and keep it for future reference. +/// This header is at the beginning of every image in the set of images in the ROM. It contains a +/// pointer to the PCI Data Structure which describes the image. #[derive( Debug , Clone , Copy )] -#[expect(dead_code)] +#[repr( C )] struct PciRomHeader {/// 00h: Signature (0xAA55) signature:u16 , -/// 02h: Reserved bytes for processor architecture unique data (20 bytes) - reserved: [u8 ;20 ], -/// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image) - nbsi_data_offset:Option <u16 >, +/// 02h: Reserved bytes for processor architecture unique data (22 bytes) + reserved: [u8 ;22 ],/// 18h: Pointer to PCI Data Structure (offset from start of ROM image) pci_data_struct_offset:u16 , -/// 1Ah: Size of block (this is NBSI-specific) - size_of_block:Option <u32 >, } +// SAFETY: all bit patterns are valid for `PciRomHeader`. +unsafe impl FromBytes for PciRomHeader {} +impl PciRomHeader {fn new (dev: &device::Device , data: &[u8 ]) ->Result <Self > { -if data.len () <26 { -// Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock. -return Err (EINVAL ); - } - -let signature = u16::from_le_bytes ([data[0 ], data[1 ]]); +let (rom_header, _) =PciRomHeader ::from_bytes_copy_prefix (data) + .ok_or (EINVAL ) + .inspect_err (|_|dev_err! (dev,"Not enough data for ROM header\n" ))?;// Check for valid ROM signatures. -match signature { +match rom_header.signature {0xAA55 |0x4E56 => {} _ => { -dev_err! (dev,"ROM signature unknown {:#x}\n" , signature); +dev_err! (dev,"ROM signature unknown {:#x}\n" , rom_header.signature);return Err (EINVAL ); } } -// Read the pointer to the PCI Data Structure at offset 0x18. -let pci_data_struct_ptr = u16::from_le_bytes ([data[24 ], data[25 ]]); - -// Try to read optional fields if enough data. -let mut size_of_block =None ; -let mut nbsi_data_offset =None ; - -if data.len () >=30 { -// Read size_of_block at offset 0x1A. - size_of_block =Some (u32::from_le_bytes ([data[26 ], data[27 ], data[28 ], data[29 ]])); - } - -// For NBSI images, try to read the nbsiDataOffset at offset 0x16. -if data.len () >=24 { - nbsi_data_offset =Some (u16::from_le_bytes ([data[22 ], data[23 ]])); - } - -Ok (PciRomHeader { - signature, - reserved: [0u8 ;20 ], - pci_data_struct_offset: pci_data_struct_ptr, - size_of_block, - nbsi_data_offset, - }) +Ok (rom_header) } }
@@ -712,9 +683,9 @@ pub(crate) struct FwSecBiosImage {/// BIOS Image structure containing various headers and reference fields to all BIOS images. /// /// A BiosImage struct is embedded into all image types and implements common operations. -#[expect(dead_code)] struct BiosImage {/// PCI ROM Expansion Header +#[expect(dead_code)] rom_header:PciRomHeader ,/// PCI Data Structure pcir:PcirStruct ,
@@ -759,15 +730,8 @@ fn is_last(&self) -> bool {/// 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 { -dev_err! (dev,"Not enough data for ROM header\n" ); -return Err (EINVAL ); - } -// Parse the ROM header. -let rom_header =PciRomHeader ::new (dev, &data[0 ..26 ]) - .inspect_err (|e|dev_err! (dev,"Failed to create PciRomHeader: {:?}\n" , e))?; +let rom_header =PciRomHeader ::new (dev, data)?;// Get the PCI Data Structure using the pointer from the ROM header. let pcir_offset = usize::from (rom_header.pci_data_struct_offset);
Signature · 1 line
2.54.0
PATCH v5 19/22 gpu: nova-core: vbios: drop unused image wrappers
In reply to Eliot CourtneyThese are unused currently, and it is probably sufficient to just check the type of BIOS image in the future.
--- drivers/gpu/nova-core/vbios.rs | 12 ------------ 1 file changed, 12 deletions(-)
drivers/gpu/nova-core/vbios.rs +0 -12
@@ -656,18 +656,6 @@ struct PciAtBiosImage { bit_offset: usize, } -#[expect(dead_code)] -struct EfiBiosImage { - base:BiosImage , -// EFI-specific fields can be added here in the future. -} - -#[expect(dead_code)] -struct NbsiBiosImage { - base:BiosImage , -// NBSI-specific fields can be added here in the future. -} -/// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode. /// /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode.
Signature · 1 line
2.54.0
PATCH v5 20/22 gpu: nova-core: vbios: drop redundant TryFrom import
In reply to Eliot CourtneyThis is unused.
--- drivers/gpu/nova-core/vbios.rs | 2 -- 1 file changed, 2 deletions(-)
drivers/gpu/nova-core/vbios.rs +0 -2
@@ -2,8 +2,6 @@//! VBIOS extraction and parsing. -use core::convert::TryFrom ; -use kernel::{ device, io::Io ,
Signature · 1 line
2.54.0
PATCH v5 21/22 gpu: nova-core: vbios: move constants and functions to be associated
In reply to Eliot CourtneyMove constants and functions to be inside the impls of the types they are related to. This makes it more obvious what each type and value is for.
--- Documentation/gpu/nova/core/vbios.rst | 2 +- drivers/gpu/nova-core/vbios.rs | 220 +++++++++++++++++----------------- 2 files changed, 113 insertions(+), 109 deletions(-)
Documentation/gpu/nova/core/vbios.rst +1 -1
@@ -232,7 +232,7 @@ Falcon data in the VBIOS which contains the PMU lookup table. This lookup table used to find the required Falcon ucode based on an application ID. The location of the PMU lookup table is found by scanning the BIT (`BIOS Information Table`_) -tokens for a token with the id `BIT_TOKEN_ID_FALCON_DATA` (0x70) which indicates the +tokens for a token with the Falcon data token id (0x70) which indicates the offset of the same from the start of the VBIOS image. Unfortunately, the offset does not account for the EFI image located between the PciAt and FwSec images. The `vbios.rs` code compensates for this with appropriate arithmetic.
drivers/gpu/nova-core/vbios.rs +112 -108
320 lines not on this page · raw message
Signature · 1 line
2.54.0
PATCH v5 22/22 gpu: nova-core: vbios: remove unused rom_header field
In reply to Eliot CourtneyThis is only used during construction, so we can remove it.
--- drivers/gpu/nova-core/vbios.rs | 4 ---- 1 file changed, 4 deletions(-)
drivers/gpu/nova-core/vbios.rs +0 -4
@@ -663,9 +663,6 @@ pub(crate) struct FwSecBiosImage {/// /// A BiosImage struct is embedded into all image types and implements common operations. struct BiosImage { -/// PCI ROM Expansion Header -#[expect(dead_code)] - rom_header:PciRomHeader ,/// PCI Data Structure pcir:PcirStruct ,/// NVIDIA PCI Data Extension (optional)
@@ -741,7 +738,6 @@ fn new(dev: &device::Device, data: &[u8]) -> Result<Self> { data_copy.extend_from_slice (data,GFP_KERNEL )?;Ok (BiosImage { - rom_header, pcir, npde, data: data_copy,
Signature · 1 line
2.54.0
Danilo Krummrich
maintainerQuoted 11 lines · Eliot Courtney
We have some code that accesses arrays based on values from firmware. This patch series makes a bunch of those accesses more robust. This series only touches accesses that are not guaranteed to be safe by local invariants - some accesses are safe due to earlier checks and I haven't modified those.
This series also refactors and removes some code that can be simplified. In particular, it removes
FwSecBiosBuilder, removes unused fields, and moves type and constant definitions closer to their usages.Signed-off-by Eliot Courtney <[email protected]>
Going to pick this up now, everything else should be easy to follow up subsequently.
Thanks,
Danilo
Quoted 1 line · Eliot Courtney
[PATCH v5 00/22] gpu: nova-core: vbios: harden various array accesses and refactor
Applied, thanks!
Branch: drm-rust-next
Tree: https://gitlab.freedesktop.org/drm/rust/kernel.git
[1/22] gpu: nova-core: vbios: stop scanning at BIOS_MAX_SCAN_LEN
commit: fc7c1054b6f9
[2/22] gpu: nova-core: vbios: use checked arithmetic for bios image range end
commit: 7a1d09e477b6
[3/22] gpu: nova-core: vbios: avoid reading too far in read_more_at_offset
commit: 33f1402bcfa6
[4/22] gpu: nova-core: vbios: read BitToken using FromBytes
commit: 237c252be0db
[5/22] gpu: nova-core: vbios: use checked ops and accesses in `FwSecBiosImage::ucode`
commit: 7c62d0b00652
[6/22] gpu: nova-core: vbios: use checked access in `FwSecBiosImage::header`
commit: 25ad950b4ee3
[7/22] gpu: nova-core: vbios: use checked accesses in `setup_falcon_data`
commit: 051ae1b21ff7
[8/22] gpu: nova-core: vbios: drop unused falcon_data_offset from FwSecBiosBuilder
commit: 56f7c0b3800e
[9/22] gpu: nova-core: vbios: keep PmuLookupTable local in setup_falcon_data
commit: 8cf15cf2641b
[10/22] gpu: nova-core: vbios: compute FWSEC-relative Falcon data offset
commit: b2a48fc068ea
[11/22] gpu: nova-core: vbios: simplify setup_falcon_data
commit: 99e110a36885
[12/22] gpu: nova-core: vbios: read PMU lookup entries using FromBytes
commit: c22095fddad7
[13/22] gpu: nova-core: vbios: store PMU lookup entries in a KVVec
commit: 620e7ac19664
[14/22] gpu: nova-core: vbios: construct `FwSecBiosImage` directly from BIOS images
commit: 7e545bed7b1a
[15/22] gpu: nova-core: vbios: use the first PCI-AT image
commit: 433730a61f13
[16/22] gpu: nova-core: vbios: use single logical block for the FWSEC section
commit: 43e7bef8c054
[17/22] gpu: nova-core: vbios: use let-else in Vbios::new
commit: e8baefdffd4e
[18/22] gpu: nova-core: vbios: remove unnecessary fields in PciRomHeader
commit: 84eb369da613
[19/22] gpu: nova-core: vbios: drop unused image wrappers
commit: 91a8ec505e09
[20/22] gpu: nova-core: vbios: drop redundant TryFrom import
commit: 16c41263240e
[21/22] gpu: nova-core: vbios: move constants and functions to be associated
commit: c70fe8b2bacf
[22/22] gpu: nova-core: vbios: remove unused rom_header field
commit: 2cf1840b0fa7The patches will appear in the next linux-next integration (typically within 24 hours on weekdays).
The patches are queued up for the upcoming merge window for the next major kernel release.
Quoted 11 lines · Eliot Courtney
We have some code that accesses arrays based on values from firmware. This patch series makes a bunch of those accesses more robust. This series only touches accesses that are not guaranteed to be safe by local invariants - some accesses are safe due to earlier checks and I haven't modified those.
This series also refactors and removes some code that can be simplified. In particular, it removes
FwSecBiosBuilder, removes unused fields, and moves type and constant definitions closer to their usages.Signed-off-by Eliot Courtney <[email protected]>
A little bit after the party, but as requested:
(made sure probe succeeded on Turing/Ampere/Blackwell).