diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-04 08:53:30 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-04 08:53:30 -0800 |
| commit | 6dfafbd0299a60bfb5d5e277fdf100037c7ded07 (patch) | |
| tree | accb0e721120091f05107277744187d09e86fe5a /drivers/gpu/nova-core/vbios.rs | |
| parent | 94e244d9ccab578f83a218ec58376d025014fcce (diff) | |
| parent | 0692602defb0c273f80dec9c564ca50726404aca (diff) | |
Merge tag 'drm-next-2025-12-03' of https://gitlab.freedesktop.org/drm/kernel
Pull drm updates from Dave Airlie:
"There was a rather late merge of a new color pipeline feature, that
some userspace projects are blocked on, and has seen a lot of work in
amdgpu. This should have seen some time in -next. There is additional
support for this for Intel, that if it arrives in the next day or two
I'll pass it on in another pull request and you can decide if you want
to take it.
Highlights:
- Arm Ethos NPU accelerator driver
- new DRM color pipeline support
- amdgpu will now run discrete SI/CIK cards instead of radeon, which
enables vulkan support in userspace
- msm gets gen8 gpu support
- initial Xe3P support in xe
Full detail summary:
New driver:
- Arm Ethos-U65/U85 accel driver
Core:
- support the drm color pipeline in vkms/amdgfx
- add support for drm colorop pipeline
- add COLOR PIPELINE plane property
- add DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE
- throttle dirty worker with vblank
- use drm_for_each_bridge_in_chain_scoped in drm's bridge code
- Ensure drm_client_modeset tests are enabled in UML
- add simulated vblank interrupt - use in drivers
- dumb buffer sizing helper
- move freeing of drm client memory to driver
- crtc sharpness strength property
- stop using system_wq in scheduler/drivers
- support emergency restore in drm-client
Rust:
- make slice::as_flattened usable on all supported rustc
- add FromBytes::from_bytes_prefix() method
- remove redundant device ptr from Rust GEM object
- Change how AlwaysRefCounted is implemented for GEM objects
gpuvm:
- Add deferred vm_bo cleanup to GPUVM (for rust)
atomic:
- cleanup and improve state handling interfaces
buddy:
- optimize block management
dma-buf:
- heaps: Create heap per CMA reserved location
- improve userspace documentation
dp:
- add POST_LT_ADJ_REQ training sequence
- DPCD dSC quirk for synaptics panamera devices
- helpers to query branch DSC max throughput
ttm:
- Rename ttm_bo_put to ttm_bo_fini
- allow page protection flags on risc-v
- rework pipelined eviction fence handling
amdgpu:
- enable amdgpu by default for SI/CI dGPUs
- enable DC by default on SI
- refactor CIK/SI enablement
- add ABM KMS property
- Re-enable DM idle optimizations
- DC Analog encoders support
- Powerplay fixes for fiji/iceland
- Enable DC on bonaire by default
- HMM cleanup
- Add new RAS framework
- DML2.1 updates
- YCbCr420 fixes
- DC FP fixes
- DMUB fixes
- LTTPR fixes
- DTBCLK fixes
- DMU cursor offload handling
- Userq validation improvements
- Unify shutdown callback handling
- Suspend improvements
- Power limit code cleanup
- SR-IOV fixes
- AUX backlight fixes
- DCN 3.5 fixes
- HDMI compliance fixes
- DCN 4.0.1 cursor updates
- DCN interrupt fix
- DC KMS full update improvements
- Add additional HDCP traces
- DCN 3.2 fixes
- DP MST fixes
- Add support for new SR-IOV mailbox interface
- UQ reset support
- HDP flush rework
- VCE1 support
amdkfd:
- HMM cleanups
- Relax checks on save area overallocations
- Fix GPU mappings after prefetch
radeon:
- refactor CIK/SI enablement
xe:
- Initial Xe3P support
- panic support on VRAM for display
- fix stolen size check
- Loosen used tracking restriction
- New SR-IOV debugfs structure and debugfs updates
- Hide the GPU madvise flag behind a VM_BIND flag
- Always expose VRAM provisioning data on discrete GPUs
- Allow VRAM mappings for userptr when used with SVM
- Allow pinning of p2p dma-buf
- Use per-tile debugfs where appropriate
- Add documentation for Execution Queues
- PF improvements
- VF migration recovery redesign work
- User / Kernel VRAM partitioning
- Update Tile-based messages
- Allow configfs to disable specific GT types
- VF provisioning and migration improvements
- use SVM range helpers in PT layer
- Initial CRI support
- access VF registers using dedicated MMIO view
- limit number of jobs per exec queue
- add sriov_admin sysfs tree
- more crescent island specific support
- debugfs residency counter
- SRIOV migration work
- runtime registers for GFX 35
i915:
- add initial Xe3p_LPD display version 35 support
- Enable LNL+ content adaptive sharpness filter
- Use optimized VRR guardband
- Enable Xe3p LT PHY
- enable FBC support for Xe3p_LPD display
- add display 30.02 firmware support
- refactor SKL+ watermark latency setup
- refactor fbdev handling
- call i915/xe runtime PM via function pointers
- refactor i915/xe stolen memory/display interfaces
- use display version instead of gfx version in display code
- extend i915_display_info with Type-C port details
- lots of display cleanups/refactorings
- set O_LARGEFILE in __create_shmem
- skuip guc communication warning on reset
- fix time conversions
- defeature DRRS on LNL+
- refactor intel_frontbuffer split between i915/xe/display
- convert inteL_rom interfaces to struct drm_device
- unify display register polling interfaces
- aovid lock inversion when pinning to GGTT on CHV/BXT+VTD
panel:
- Add KD116N3730A08/A12, chromebook mt8189
- JT101TM023, LQ079L1SX01,
- GLD070WX3-SL01 MIPI DSI
- Samsung LTL106AL0, Samsung LTL106AL01
- Raystar RFF500F-AWH-DNN
- Winstar WF70A8SYJHLNGA
- Wanchanglong w552946aaa
- Samsung SOFEF00
- Lenovo X13s panel
- ilitek-ili9881c - add rpi 5" support
- visionx-rm69299 - add backlight support
- edp - support AUI B116XAN02.0
bridge:
- improve ref counting
- ti-sn65dsi86 - add support for DP mode with HPD
- synopsis: support CEC, init timer with correct freq
- ASL CS5263 DP-to-HDMI bridge support
nova-core:
- introduce bitfield! macro
- introduce safe integer converters
- GSP inits to fully booted state on Ampere
- Use more future-proof register for GPU identification
nova-drm:
- select NOVA_CORE
- 64-bit only
nouveau:
- improve reclocking on tegra 186+
- add large page and compression support
msm:
- GPU:
- Gen8 support: A840 (Kaanapali) and X2-85 (Glymur)
- A612 support
- MDSS:
- Added support for Glymur and QCS8300 platforms
- DPU:
- Enabled Quad-Pipe support, unlocking higher resolutions support
- Added support for Glymur platform
- Documented DPU on QCS8300 platform as supported
- DisplayPort:
- Added support for Glymur platform
- Added support lame remapping inside DP block
- Documented DisplayPort controller on QCS8300 and SM6150/QCS615
as supported
tegra:
- NVJPG driver
panfrost:
- display JM contexts over debugfs
- export JM contexts to userspace
- improve error and job handling
panthor:
- support custom ASN_HASH for mt8196
- support mali-G1 GPU
- flush shmem write before mapping buffers uncached
- make timeout per-queue instead of per-job
mediatek:
- MT8195/88 HDMIv2/DDCv2 support
rockchip:
- dsi: add support for RK3368
amdxdna:
- enhance runtime PM
- last hardware error reading uapi
- support firmware debug output
- add resource and telemetry data uapi
- preemption support
imx:
- add driver for HDMI TX Parallel audio interface
ivpu:
- add support for user-managed preemption buffer
- add userptr support
- update JSM firware API to 3.33.0
- add better alloc/free warnings
- fix page fault in unbind all bos
- rework bind/unbind of imported buffers
- enable MCA ECC signalling
- split fw runtime and global memory buffers
- add fdinfo memory statistics
tidss:
- convert to drm logging
- logging cleanup
ast:
- refactor generation init paths
- add per chip generation detect_tx_chip
- set quirks for each chip model
atmel-hlcdc:
- set LCDC_ATTRE register in plane disable
- set correct values for plane scaler
solomon:
- use drm helper for get_modes and move_valid
sitronix:
- fix output position when clearing screens
qaic:
- support dma-buf exports
- support new firmware's READ_DATA implementation
- sahara AIC200 image table update
- add sysfs support
- add coredump support
- add uevents support
- PM support
sun4i:
- layer refactors to decouple plane from output
- improve DE33 support
vc4:
- switch to generic CEC helpers
komeda:
- use drm_ logging functions
vkms:
- configfs support for display configuration
vgem:
- fix fence timer deadlock
etnaviv:
- add HWDB entry for GC8000 Nano Ultra VIP r6205"
* tag 'drm-next-2025-12-03' of https://gitlab.freedesktop.org/drm/kernel: (1869 commits)
Revert "drm/amd: Skip power ungate during suspend for VPE"
drm/amdgpu: use common defines for HUB faults
drm/amdgpu/gmc12: add amdgpu_vm_handle_fault() handling
drm/amdgpu/gmc11: add amdgpu_vm_handle_fault() handling
drm/amdgpu: use static ids for ACP platform devs
drm/amdgpu/sdma6: Update SDMA 6.0.3 FW version to include UMQ protected-fence fix
drm/amdgpu: Forward VMID reservation errors
drm/amdgpu/gmc8: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc7: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Cache VM fault info
drm/amdgpu/gmc6: Don't print MC client as it's unknown
drm/amdgpu/cz_ih: Enable soft IRQ handler ring
drm/amdgpu/tonga_ih: Enable soft IRQ handler ring
drm/amdgpu/iceland_ih: Enable soft IRQ handler ring
drm/amdgpu/cik_ih: Enable soft IRQ handler ring
drm/amdgpu/si_ih: Enable soft IRQ handler ring
drm/amd/display: fix typo in display_mode_core_structs.h
drm/amd/display: fix Smart Power OLED not working after S4
drm/amd/display: Move RGB-type check for audio sync to DCE HW sequence
...
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, ) }) } |
