mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
core: - add docbook for DRM_IOCTL_SYNCOBJ_EVENTFD - change signature of drm_connector_attach_hdr_output_metadata_property - dedup counter and timestamp retrieval in vblank code - parse AMD VSDB v3 in CTA extension blocks - add P230, Y7, XYYY2101010, T430, XVUY210101010 formats - don't call drop master on file close if not master - use drm_printf_indent in atomic / bridge - fix 32b format descriptions - docs: fix toctree - hdmi: add common TMDS character rates - fix drm_syncobj_find_fence leak rust: - introduce Higher-Ranked lifetime types - replace drvdata with scoped registration data - add GPUVM immediate mode abstraction for rust GPU drivers - introduce DeviceContext type state for drm::Device bridge: - clarify drm_bridge_get/put - create drm_get_bridge_by_endpoint and use it - analogix_dp: add panel probing - ite-it6211 - use drm audio hdmi helpers buddy: - add lockdep annotations dp: - add PR and VRR updates - mst: fix buffer overflows - add Adaptive Sync SDP decoding support - fix OOB reads in dp-mst ttm: - bump fpfn/lpfn to 64-bit scheduler: - change default to fair scheduler - map runqueue 1:1 with scheduler dma-buf: - port selftests to kunit - convert dma-buf system/heap allocators to module - add separate DMABUF_HEAPS_SYSTEM_CC_SHARED Kconfig udmabuf: - revert hugetlb support - fix error with CONFIG_DMA_API_DEBUG dma-fence: - fix tracepoints lifetime - remove unused signal on any support ras: - add clear error counter netlink command to drm ras gpusvm: - reject VMAs with VM_IO or VM_PFNMAP when creating SVM ranges - use IOVA allocations pagemap: - use IOVA allocations panels: - update to use ref counts - add support for CSW PNB601LS1-2, LGD LP116WHA-SPB1 - add support for waveshare panels - CMN N116BCN-EA1, CMN N140HCA-EEK, IVO M140NWFQ R5, - IVO, R140NWFW R0, BOE NT140*, BOE NV133FHM-N4F, - AUO B140*, AUO B133HAN06.6 and AUO B116XTN02.3 eDP panels - Surface Pro 12 Panel xe: - add CRI PCI-IDs - debugfs add multi-lrc info - engine init cleanup - PF fair scheduling auto provisioning - system controller support for CRI/Xe3p - PXP state machine fixes - Reset/wedge/unload corner case fixes - Wedge path memory allocation fixes - PAT type cleanups - Reject unsafe PAT for CPU cached memory - OA improvements for CRI device memory - kernel doc syntax in xe headers - xe_drm.h documentation fixes - include guard cleanups - VF CCS memory pool - i915/xe step unification - Xe3p GT tuning fixes - forcewake cleanup in GT and GuC - admin-only PF mode - enable hwmon energy attributes for CRI - enable GT_MI_USER_INTERRUPT - refactor emit functions - oa workarounds - multi_queue: allow QUEUE_TIMESTAMP register - convert stolen memory to ttm range manager - use xe2 style blitter as a feature flag - make drm_driver const - add/use IRQ page to HW engine definition - fix oops when display disabled i915: - enable PIPEDMC_ERROR interrupt - more common display code refactoring - restructure DP/HDMI sink format handling - eliminate FB usage from lowlevel pinning code - panel replay bw optimization - integrate sharpness filter into the scaler - new fb_pin abstraction for xe/i915 fb transparent handling - skip inactive MST connectors on HDCP - start switching to display specific registers - use polling when irq unavailable - Adaptive-sync SDP prep amdgpu: - use drm_display_info for AMD VSDB data - Initial HDMI 2.1 FRL support - Initial DCN 4.2.1 support - GART fixes for non-4k pages - GC 11.5.6/SDMA 6.4.0/and other new IPs - GFX9/DCE6/Hawaii/SDMA4/GART/Userq fixes - Finish support for using multiple SDMA queues for TTM operations - SWSMU updates - GC 12.1 updates - SMU 15.0.8 updates - DCN 4.2 updates - DC type conversion fixes - Enable DC power module - Replay/PSR updates - SMU 13.x updates - Compute queue quantum MQD updates - ASPM fix - Align VKMS with common implementation - DC analog support fixes - UVD 3 fixes - TCC harvesting fixes for SI - GC 11 APU module reload fix - NBIO 6.3.2 support - IH 7.1 updates - DC cursor fixes - VCN/JPEG user fence fixes - DC support for connectors without DDC - Prefer ROM BAR for default VGA device - DC bandwidth fixes - Add PTL support for profiler - Introduce dc_plane_cm and migrate surface update color path - Add FRL registers for HDMI 2.1 - Restructure VM state machine - Auxless ALPM support - GEM_OP locking/warning fixes - switch to system_dfl_wq amdkfd: - GPUVM TLB flush fix - Hotplug fix - Boundary check fixes - SVM fixes - CRIU fixes - add profiler API - MES 12.1 updates msm: - core: - fix shrinker documentation - IFPC enabled for gen8 - PERFCNTR_CONFIG ioctl support - GPU: - reworked UBWC handling - a810 support - MDSS: - add support for Milos platform - reworked UBWC handling - DisplayPort: - reworked HPD handling as prep for MST - DPU: - Milos platform support - reworked UBWC handling - DSI: - Milos platform support nova: - Hopper/Blackwell enablement (GH100/GB100/GB202) - FSP support - 32-bit firmware support - HAL functions - refactor GSP boot/unload - GA100 support - VBIOS hardening/refactoring - Adopt higher order lifetime types tyr: - define register blocks - add shmem backed GEM objects - adopt higher order lifetime types - move clock cleanup into Drop radeon: - Hawaii SMU fixes - CS parser fix - use struct drm_edid instead of edid amdxdna: - export per-client BO memory via fdinfo - AIE4 device support - support medium/lower power modes - expandable device heap support - revert read-only user-pointer BO mappings ivpu: - support frequency limiting panthor: - enable GEM shrinker support - add eviction and reclaim info to fdinfo v3d: - enable runtime PM mgag200: - support XRGB1555 + C8 ast: - support XRGB1555 + C8 - use constants for lots of registers - fix register handling imagination: - fence handling refactoring nouveau: - fix sched double call - expose VBIOS on GSP-RM systems - add GA100 support virtio: - add VIRTIO_GPU_F_BLOB_ALIGNMENT flag - add deferred mapping support gud: - add RCade Display Adapter hibmc: - fix no connectors usage mediatek: - hdmi: convert error handling - simplify mtk_crtc allocation exynos: - move fbdev emulation to drm client buffers - use drm format helpers for geometry/size - adopt core DMA tracking - fix framebuffer offset handling renesas: - add RZ/T2H SOC support versilicon: - add cursor plane support tegra: - use drm client for framebuffer -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEEKbZHaGwW9KfbeusDHTzWXnEhr4FAmoyOXkACgkQDHTzWXnE hr5Pxg//VwqDYgf8CwWtLwXqHYBhGDwj1PDA/zLoosm32fXABKhzPMA5XcaPHBKh Y7Wyo7988cxXFQ0BRltKP1S3d04ck5ZbBA/qpwwX9Pz0u8HhNMicwLd9ASlWqqkP ogFIj3J8PsjlTMVI2cnzQ7s0TMQoQorUaNl13vQnITDPSNncCAunrKixpzVagHeK /ARlIg4kqSbvlKAhkvps7FbJUG3l/g901HdAcspp0bn6d3+4zz8Yg+yg/b4Xngdh Sn6UGDxwrNeBSHP2Spj5/rGSOuo4L8Fr1rS0+KEwRnEqrg+qdq9mo9l0xQsJV9/e 4ql6nssEMC/CIHwHomsgCdxdOsj2DiXLUucJKBwavHHSWNHFgdLhNSdaUcwrkui3 sLmahOEXFwFIR3K1X++547Wux9DhAWDcvq/SnxzN0dQBpN0AmpE0/o/3Vb7Vw4dO /cfvI4Y1WGviJCs58p36tqYGx8dDPuMh9tiUtQvBXBkkS87MqBX8wM49qHSlzEOV EkQfGV1cLMBJpZZkMezrqCNyIS7ghzXJ+K/IruAPrgvN7/kCY7hRclUT+Z/1Mqvc mw3Japbcy7P+BMKnlNfCZsDXg04kYZC5MOaMeiJOLg6qfHBHITD8gP1zct+RikIS d+43kYwt43dPj53jUUXpREoAezSoF2ktncFChB2tdeaMlb6oJIM= =86oS -----END PGP SIGNATURE----- Merge tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel Pull drm updates from Dave Airlie: "Highlights: - xe: add initial CRI platform support - amdgpu: initial HDMI 2.1 FRL support - rust: add some new type concepts for device lifetimes - scheduler: moves to a fair algorithm and lots of cleanups But it's mostly the usual mountain of changes across the board. core: - add docbook for DRM_IOCTL_SYNCOBJ_EVENTFD - change signature of drm_connector_attach_hdr_output_metadata_property - dedup counter and timestamp retrieval in vblank code - parse AMD VSDB v3 in CTA extension blocks - add P230, Y7, XYYY2101010, T430, XVUY210101010 formats - don't call drop master on file close if not master - use drm_printf_indent in atomic / bridge - fix 32b format descriptions - docs: fix toctree - hdmi: add common TMDS character rates - fix drm_syncobj_find_fence leak rust: - introduce Higher-Ranked lifetime types - replace drvdata with scoped registration data - add GPUVM immediate mode abstraction for rust GPU drivers - introduce DeviceContext type state for drm::Device bridge: - clarify drm_bridge_get/put - create drm_get_bridge_by_endpoint and use it - analogix_dp: add panel probing - ite-it6211 - use drm audio hdmi helpers buddy: - add lockdep annotations dp: - add PR and VRR updates - mst: fix buffer overflows - add Adaptive Sync SDP decoding support - fix OOB reads in dp-mst ttm: - bump fpfn/lpfn to 64-bit scheduler: - change default to fair scheduler - map runqueue 1:1 with scheduler dma-buf: - port selftests to kunit - convert dma-buf system/heap allocators to module - add separate DMABUF_HEAPS_SYSTEM_CC_SHARED Kconfig udmabuf: - revert hugetlb support - fix error with CONFIG_DMA_API_DEBUG dma-fence: - fix tracepoints lifetime - remove unused signal on any support ras: - add clear error counter netlink command to drm ras gpusvm: - reject VMAs with VM_IO or VM_PFNMAP when creating SVM ranges - use IOVA allocations pagemap: - use IOVA allocations panels: - update to use ref counts - add support for CSW PNB601LS1-2, LGD LP116WHA-SPB1 - add support for waveshare panels - CMN N116BCN-EA1, CMN N140HCA-EEK, IVO M140NWFQ R5, - IVO, R140NWFW R0, BOE NT140*, BOE NV133FHM-N4F, - AUO B140*, AUO B133HAN06.6 and AUO B116XTN02.3 eDP panels - Surface Pro 12 Panel xe: - add CRI PCI-IDs - debugfs add multi-lrc info - engine init cleanup - PF fair scheduling auto provisioning - system controller support for CRI/Xe3p - PXP state machine fixes - Reset/wedge/unload corner case fixes - Wedge path memory allocation fixes - PAT type cleanups - Reject unsafe PAT for CPU cached memory - OA improvements for CRI device memory - kernel doc syntax in xe headers - xe_drm.h documentation fixes - include guard cleanups - VF CCS memory pool - i915/xe step unification - Xe3p GT tuning fixes - forcewake cleanup in GT and GuC - admin-only PF mode - enable hwmon energy attributes for CRI - enable GT_MI_USER_INTERRUPT - refactor emit functions - oa workarounds - multi_queue: allow QUEUE_TIMESTAMP register - convert stolen memory to ttm range manager - use xe2 style blitter as a feature flag - make drm_driver const - add/use IRQ page to HW engine definition - fix oops when display disabled i915: - enable PIPEDMC_ERROR interrupt - more common display code refactoring - restructure DP/HDMI sink format handling - eliminate FB usage from lowlevel pinning code - panel replay bw optimization - integrate sharpness filter into the scaler - new fb_pin abstraction for xe/i915 fb transparent handling - skip inactive MST connectors on HDCP - start switching to display specific registers - use polling when irq unavailable - Adaptive-sync SDP prep amdgpu: - use drm_display_info for AMD VSDB data - Initial HDMI 2.1 FRL support - Initial DCN 4.2.1 support - GART fixes for non-4k pages - GC 11.5.6/SDMA 6.4.0/and other new IPs - GFX9/DCE6/Hawaii/SDMA4/GART/Userq fixes - Finish support for using multiple SDMA queues for TTM operations - SWSMU updates - GC 12.1 updates - SMU 15.0.8 updates - DCN 4.2 updates - DC type conversion fixes - Enable DC power module - Replay/PSR updates - SMU 13.x updates - Compute queue quantum MQD updates - ASPM fix - Align VKMS with common implementation - DC analog support fixes - UVD 3 fixes - TCC harvesting fixes for SI - GC 11 APU module reload fix - NBIO 6.3.2 support - IH 7.1 updates - DC cursor fixes - VCN/JPEG user fence fixes - DC support for connectors without DDC - Prefer ROM BAR for default VGA device - DC bandwidth fixes - Add PTL support for profiler - Introduce dc_plane_cm and migrate surface update color path - Add FRL registers for HDMI 2.1 - Restructure VM state machine - Auxless ALPM support - GEM_OP locking/warning fixes - switch to system_dfl_wq amdkfd: - GPUVM TLB flush fix - Hotplug fix - Boundary check fixes - SVM fixes - CRIU fixes - add profiler API - MES 12.1 updates msm: - core: - fix shrinker documentation - IFPC enabled for gen8 - PERFCNTR_CONFIG ioctl support - GPU: - reworked UBWC handling - a810 support - MDSS: - add support for Milos platform - reworked UBWC handling - DisplayPort: - reworked HPD handling as prep for MST - DPU: - Milos platform support - reworked UBWC handling - DSI: - Milos platform support nova: - Hopper/Blackwell enablement (GH100/GB100/GB202) - FSP support - 32-bit firmware support - HAL functions - refactor GSP boot/unload - GA100 support - VBIOS hardening/refactoring - Adopt higher order lifetime types tyr: - define register blocks - add shmem backed GEM objects - adopt higher order lifetime types - move clock cleanup into Drop radeon: - Hawaii SMU fixes - CS parser fix - use struct drm_edid instead of edid amdxdna: - export per-client BO memory via fdinfo - AIE4 device support - support medium/lower power modes - expandable device heap support - revert read-only user-pointer BO mappings ivpu: - support frequency limiting panthor: - enable GEM shrinker support - add eviction and reclaim info to fdinfo v3d: - enable runtime PM mgag200: - support XRGB1555 + C8 ast: - support XRGB1555 + C8 - use constants for lots of registers - fix register handling imagination: - fence handling refactoring nouveau: - fix sched double call - expose VBIOS on GSP-RM systems - add GA100 support virtio: - add VIRTIO_GPU_F_BLOB_ALIGNMENT flag - add deferred mapping support gud: - add RCade Display Adapter hibmc: - fix no connectors usage mediatek: - hdmi: convert error handling - simplify mtk_crtc allocation exynos: - move fbdev emulation to drm client buffers - use drm format helpers for geometry/size - adopt core DMA tracking - fix framebuffer offset handling renesas: - add RZ/T2H SOC support versilicon: - add cursor plane support tegra: - use drm client for framebuffer" * tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel: (1731 commits) dma-buf: move system_cc_shared heap under separate Kconfig accel/amdxdna: Clear sva pointer after unbind agp/amd64: Fix broken error propagation in agp_amd64_probe() accel/amdxdna: Require carveout when PASID and force_iova are disabled drm/amdkfd: always resume_all after suspend_all drm/amdgpu/gfx: move fault and EOP IRQ get/put to hw_init/hw_fini drm/amd/display: Consult MCCS FreeSync cap only if requested & supported drm/amd/pm: Use strscpy in profile mode parsing drm/amdkfd: Fix infinite loop parsing CRAT with zero subtype length drm/amdkfd: fix sysfs topology prop length on buffer truncation drm/amdgpu: drop retry loop in amdgpu_hmm_range_get_pages drm/amd/pm: bound OD parameter parsing to stack array size drm/amd/pm: Stop pp_od_clk_voltage emit at PAGE_SIZE drm/amdkfd: Unwind debug trap enable on copy_to_user failure drm/amdgpu: validate the mes firmware version for gfx12.1 drm/amdgpu: validate the mes firmware version for gfx12 drm/amdgpu: compare MES firmware version ucode for gfx11 drm/amdkfd: Add bounds check for AMDKFD_IOC_WAIT_EVENTS drm/amdgpu: restart the CS if some parts of the VM are still invalidated drm/amd/display: use unsigned types for local pipe and REG_GET counters ...
1005 lines
33 KiB
Rust
1005 lines
33 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//! VBIOS extraction and parsing.
|
|
|
|
use kernel::{
|
|
device,
|
|
io::Io,
|
|
prelude::*,
|
|
ptr::{
|
|
Alignable,
|
|
Alignment, //
|
|
},
|
|
register,
|
|
sizes::SZ_4K,
|
|
sync::aref::ARef,
|
|
transmute::FromBytes,
|
|
};
|
|
|
|
use zerocopy::FromBytes as _;
|
|
|
|
use crate::{
|
|
driver::Bar0,
|
|
firmware::{
|
|
fwsec::Bcrt30Rsa3kSignature,
|
|
FalconUCodeDesc,
|
|
FalconUCodeDescV2,
|
|
FalconUCodeDescV3, //
|
|
},
|
|
num::FromSafeCast,
|
|
};
|
|
|
|
/// 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),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Vbios Reader for constructing the VBIOS data.
|
|
struct VbiosIterator<'a> {
|
|
dev: &'a device::Device,
|
|
bar0: Bar0<'a>,
|
|
/// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference
|
|
/// or copying into other data structures. It is the entire scanned contents of the VBIOS which
|
|
/// 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: KVVec<u8>,
|
|
/// Current offset of the [`Iterator`].
|
|
current_offset: usize,
|
|
/// Indicate whether the last image has been found.
|
|
last_found: bool,
|
|
}
|
|
|
|
impl<'a> VbiosIterator<'a> {
|
|
/// The offset of the VBIOS ROM in the BAR0 space.
|
|
const ROM_OFFSET: usize = 0x300000;
|
|
/// The maximum length of the VBIOS ROM to scan into.
|
|
const BIOS_MAX_SCAN_LEN: usize = 0x100000;
|
|
/// The size to read ahead when parsing initial BIOS image headers.
|
|
const BIOS_READ_AHEAD_SIZE: usize = 1024;
|
|
|
|
/// Return the byte offset where the PCI Expansion ROM images begin in the GPU's ROM.
|
|
///
|
|
/// The GPU's ROM may begin with an Init-from-ROM (IFR) header that precedes the PCI Expansion
|
|
/// ROM images (VBIOS). When present, the PROM shadow method must parse this header to determine
|
|
/// the offset where the PCI ROM images actually begin, and adjust all subsequent reads
|
|
/// accordingly.
|
|
///
|
|
/// On most GPUs this is not needed because the IFR microcode has already applied the ROM offset
|
|
/// so that PROM reads transparently skip the header. On GA100, for some reason, the IFR offset
|
|
/// is not applied to PROM reads. Therefore, the search for the PCI expansion must skip the IFR
|
|
/// header, if found.
|
|
fn rom_offset(dev: &device::Device, bar0: Bar0<'_>) -> Result<usize> {
|
|
// IFR Header in VBIOS.
|
|
register! {
|
|
NV_PBUS_IFR_FMT_FIXED0(u32) @ 0x300000 {
|
|
31:0 signature;
|
|
}
|
|
}
|
|
|
|
register! {
|
|
NV_PBUS_IFR_FMT_FIXED1(u32) @ 0x300004 {
|
|
30:16 fixed_data_size;
|
|
15:8 version => u8;
|
|
}
|
|
}
|
|
|
|
register! {
|
|
NV_PBUS_IFR_FMT_FIXED2(u32) @ 0x300008 {
|
|
19:0 total_data_size;
|
|
}
|
|
}
|
|
|
|
/// IFR signature.
|
|
const NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE: u32 = u32::from_le_bytes(*b"NVGI");
|
|
/// ROM directory signature.
|
|
const NV_ROM_DIRECTORY_IDENTIFIER: u32 = u32::from_le_bytes(*b"RFRD");
|
|
/// Offset of the NV_PMGR_ROM_ADDR_OFFSET register in IFR Extended section.
|
|
const IFR_SW_EXT_ROM_ADDR_OFFSET: usize = 4;
|
|
/// Size of Redundant Firmware Flash Status section.
|
|
const RFW_FLASH_STATUS_SIZE: usize = SZ_4K;
|
|
/// Offset in the ROM Directory of the PCI Option ROM offset.
|
|
const PCI_OPTION_ROM_OFFSET: usize = 8;
|
|
|
|
let signature = bar0.read(NV_PBUS_IFR_FMT_FIXED0).signature();
|
|
|
|
if signature == NV_PBUS_IFR_FMT_FIXED0_SIGNATURE_VALUE {
|
|
let fixed1 = bar0.read(NV_PBUS_IFR_FMT_FIXED1);
|
|
|
|
match fixed1.version() {
|
|
1 | 2 => {
|
|
let fixed_data_size = usize::from(fixed1.fixed_data_size());
|
|
let pmgr_rom_addr_offset = fixed_data_size + IFR_SW_EXT_ROM_ADDR_OFFSET;
|
|
bar0.try_read32(Self::ROM_OFFSET + pmgr_rom_addr_offset)
|
|
.map(usize::from_safe_cast)
|
|
}
|
|
3 => {
|
|
let fixed2 = bar0.read(NV_PBUS_IFR_FMT_FIXED2);
|
|
let total_data_size = usize::from(fixed2.total_data_size());
|
|
let flash_status_offset =
|
|
usize::from_safe_cast(bar0.try_read32(Self::ROM_OFFSET + total_data_size)?);
|
|
let dir_offset = flash_status_offset + RFW_FLASH_STATUS_SIZE;
|
|
let dir_sig = bar0.try_read32(Self::ROM_OFFSET + dir_offset)?;
|
|
if dir_sig != NV_ROM_DIRECTORY_IDENTIFIER {
|
|
dev_err!(dev, "could not find IFR ROM directory\n");
|
|
return Err(EINVAL);
|
|
}
|
|
bar0.try_read32(Self::ROM_OFFSET + dir_offset + PCI_OPTION_ROM_OFFSET)
|
|
.map(usize::from_safe_cast)
|
|
}
|
|
_ => {
|
|
dev_err!(dev, "unsupported IFR header version {}\n", fixed1.version());
|
|
Err(EINVAL)
|
|
}
|
|
}
|
|
} else {
|
|
Ok(0)
|
|
}
|
|
}
|
|
|
|
fn new(dev: &'a device::Device, bar0: Bar0<'a>) -> Result<Self> {
|
|
Ok(Self {
|
|
dev,
|
|
bar0,
|
|
data: KVVec::new(),
|
|
current_offset: Self::rom_offset(dev, bar0)?,
|
|
last_found: false,
|
|
})
|
|
}
|
|
|
|
/// Read bytes from the ROM at the current end of the data vector.
|
|
fn read_more(&mut self, len: usize) -> Result {
|
|
let start = self.data.len();
|
|
let end = start + len;
|
|
|
|
if end > Self::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 {
|
|
dev_err!(
|
|
self.dev,
|
|
"VBIOS read length {} is not a multiple of 4\n",
|
|
len
|
|
);
|
|
return Err(EINVAL);
|
|
}
|
|
|
|
self.data.reserve(len, GFP_KERNEL)?;
|
|
// Read ROM data bytes and push directly to `data`.
|
|
for addr in (start..end).step_by(core::mem::size_of::<u32>()) {
|
|
// Read 32-bit word from the VBIOS ROM
|
|
let word = self.bar0.try_read32(Self::ROM_OFFSET + addr)?;
|
|
|
|
// Convert the `u32` to a 4 byte array and push each byte.
|
|
word.to_ne_bytes()
|
|
.iter()
|
|
.try_for_each(|&b| self.data.push(b, GFP_KERNEL))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Read bytes at a specific offset, filling any gap.
|
|
fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result {
|
|
let end = offset.checked_add(len).ok_or(EINVAL)?;
|
|
|
|
self.read_more(end.saturating_sub(self.data.len()))
|
|
}
|
|
|
|
/// Read a BIOS image at a specific offset and create a [`BiosImage`] from it.
|
|
///
|
|
/// `self.data` is extended as needed and a new [`BiosImage`] is returned.
|
|
/// `context` is a string describing the operation for error reporting.
|
|
fn read_bios_image_at_offset(
|
|
&mut self,
|
|
offset: usize,
|
|
len: usize,
|
|
context: &str,
|
|
) -> Result<BiosImage> {
|
|
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,
|
|
"Failed to read more at offset {:#x}: {:?}\n",
|
|
offset,
|
|
e
|
|
)
|
|
})?;
|
|
}
|
|
|
|
BiosImage::new(self.dev, &self.data[offset..end]).inspect_err(|err| {
|
|
dev_err!(
|
|
self.dev,
|
|
"Failed to {} at offset {:#x}: {:?}\n",
|
|
context,
|
|
offset,
|
|
err
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for VbiosIterator<'a> {
|
|
type Item = Result<BiosImage>;
|
|
|
|
/// Iterate over all VBIOS images until the last image is detected or offset
|
|
/// exceeds scan limit.
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.last_found {
|
|
return None;
|
|
}
|
|
|
|
if self.current_offset >= Self::BIOS_MAX_SCAN_LEN {
|
|
dev_err!(self.dev, "Error: exceeded BIOS scan limit, stopping scan\n");
|
|
return None;
|
|
}
|
|
|
|
// Parse image headers first to get image size.
|
|
let image_size = match self.read_bios_image_at_offset(
|
|
self.current_offset,
|
|
Self::BIOS_READ_AHEAD_SIZE,
|
|
"parse initial BIOS image headers",
|
|
) {
|
|
Ok(image) => image.image_size_bytes(),
|
|
Err(e) => return Some(Err(e)),
|
|
};
|
|
|
|
// Now create a new `BiosImage` with the full image data.
|
|
let full_image = match self.read_bios_image_at_offset(
|
|
self.current_offset,
|
|
image_size,
|
|
"parse full BIOS image",
|
|
) {
|
|
Ok(image) => image,
|
|
Err(e) => return Some(Err(e)),
|
|
};
|
|
|
|
self.last_found = full_image.is_last();
|
|
|
|
// Advance to next image (aligned to 512 bytes).
|
|
self.current_offset += image_size;
|
|
self.current_offset = self.current_offset.align_up(Alignment::new::<512>())?;
|
|
|
|
Some(Ok(full_image))
|
|
}
|
|
}
|
|
|
|
pub(crate) struct Vbios {
|
|
fwsec_image: FwSecBiosImage,
|
|
}
|
|
|
|
impl Vbios {
|
|
/// Probe for VBIOS extraction.
|
|
///
|
|
/// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore.
|
|
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 fwsec_section: Option<KVVec<u8>> = None;
|
|
|
|
// Parse all VBIOS images in the ROM
|
|
for image_result in VbiosIterator::new(dev, bar0)? {
|
|
let image = image_result?;
|
|
|
|
dev_dbg!(
|
|
dev,
|
|
"Found BIOS image: size: {:#x}, type: {:?}, last: {}\n",
|
|
image.image_size_bytes(),
|
|
image.image_type(),
|
|
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) => {
|
|
// Silently ignore any extra PCI-AT images.
|
|
if pci_at_image.is_none() {
|
|
pci_at_image = Some(PciAtBiosImage::try_from(image)?);
|
|
}
|
|
}
|
|
Ok(BiosImageType::FwSec) => fwsec_section = Some(image.data),
|
|
_ => {
|
|
// Ignore other image types or unknown types
|
|
}
|
|
}
|
|
}
|
|
|
|
// Using all the images, setup the falcon data pointer in Fwsec.
|
|
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"
|
|
);
|
|
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 {
|
|
&self.fwsec_image
|
|
}
|
|
}
|
|
|
|
/// PCI Data Structure as defined in PCI Firmware Specification
|
|
#[derive(Debug, Clone)]
|
|
#[repr(C)]
|
|
struct PcirStruct {
|
|
/// PCI Data Structure signature ("PCIR" or "NPDS")
|
|
signature: [u8; 4],
|
|
/// PCI Vendor ID (e.g., 0x10DE for NVIDIA)
|
|
vendor_id: u16,
|
|
/// PCI Device ID
|
|
device_id: u16,
|
|
/// Device List Pointer
|
|
device_list_ptr: u16,
|
|
/// PCI Data Structure Length
|
|
pci_data_struct_len: u16,
|
|
/// PCI Data Structure Revision
|
|
pci_data_struct_rev: u8,
|
|
/// Class code (3 bytes, 0x03 for display controller)
|
|
class_code: [u8; 3],
|
|
/// Size of this image in 512-byte blocks
|
|
image_len: u16,
|
|
/// Revision Level of the Vendor's ROM
|
|
vendor_rom_rev: u16,
|
|
/// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI)
|
|
code_type: u8,
|
|
/// Last image indicator (0x00 = Not last image, 0x80 = Last image)
|
|
last_image: u8,
|
|
/// Maximum Run-time Image Length (units of 512 bytes)
|
|
max_runtime_image_len: u16,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `PcirStruct`.
|
|
unsafe impl FromBytes for PcirStruct {}
|
|
|
|
impl PcirStruct {
|
|
/// The bit in `last_image` that indicates the last image.
|
|
const LAST_IMAGE_BIT_MASK: u8 = 0x80;
|
|
|
|
fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
|
|
let (pcir, _) = PcirStruct::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
|
|
|
|
// Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e).
|
|
if &pcir.signature != b"PCIR" && &pcir.signature != b"NPDS" {
|
|
dev_err!(
|
|
dev,
|
|
"Invalid signature for PcirStruct: {:?}\n",
|
|
pcir.signature
|
|
);
|
|
return Err(EINVAL);
|
|
}
|
|
|
|
if pcir.image_len == 0 {
|
|
dev_err!(dev, "Invalid image length: 0\n");
|
|
return Err(EINVAL);
|
|
}
|
|
|
|
Ok(pcir)
|
|
}
|
|
|
|
/// Check if this is the last image in the ROM.
|
|
fn is_last(&self) -> bool {
|
|
self.last_image & Self::LAST_IMAGE_BIT_MASK != 0
|
|
}
|
|
|
|
/// Calculate image size in bytes from 512-byte blocks.
|
|
fn image_size_bytes(&self) -> usize {
|
|
usize::from(self.image_len) * 512
|
|
}
|
|
}
|
|
|
|
/// BIOS Information Table (BIT) Header.
|
|
///
|
|
/// This is the head of the BIT table, that is used to locate the Falcon data. The BIT table (with
|
|
/// its header) is in the [`PciAtBiosImage`] and the falcon data it is pointing to is in the
|
|
/// [`FwSecBiosImage`].
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C)]
|
|
struct BitHeader {
|
|
/// 0h: BIT Header Identifier (BMP=0x7FFF/BIT=0xB8FF)
|
|
id: u16,
|
|
/// 2h: BIT Header Signature ("BIT\0")
|
|
signature: [u8; 4],
|
|
/// 6h: Binary Coded Decimal Version, ex: 0x0100 is 1.00.
|
|
bcd_version: u16,
|
|
/// 8h: Size of BIT Header (in bytes)
|
|
header_size: u8,
|
|
/// 9h: Size of BIT Tokens (in bytes)
|
|
token_size: u8,
|
|
/// 10h: Number of token entries that follow
|
|
token_entries: u8,
|
|
/// 11h: BIT Header Checksum
|
|
checksum: u8,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `BitHeader`.
|
|
unsafe impl FromBytes for BitHeader {}
|
|
|
|
impl BitHeader {
|
|
fn new(data: &[u8]) -> Result<Self> {
|
|
let (header, _) = BitHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
|
|
|
|
// Check header ID and signature
|
|
if header.id != 0xB8FF || &header.signature != b"BIT\0" {
|
|
return Err(EINVAL);
|
|
}
|
|
|
|
Ok(header)
|
|
}
|
|
}
|
|
|
|
/// BIT Token Entry: Records in the BIT table followed by the BIT header.
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C)]
|
|
struct BitToken {
|
|
/// 00h: Token identifier
|
|
id: u8,
|
|
/// 01h: Version of the token data
|
|
data_version: u8,
|
|
/// 02h: Size of token data in bytes
|
|
data_size: u16,
|
|
/// 04h: Offset to the token data
|
|
data_offset: u16,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `BitToken`.
|
|
unsafe impl FromBytes for BitToken {}
|
|
|
|
impl BitToken {
|
|
/// BIT token ID for Falcon data.
|
|
const ID_FALCON_DATA: u8 = 0x70;
|
|
|
|
/// 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 i in 0..usize::from(header.token_entries) {
|
|
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)?;
|
|
|
|
let (token, _) = BitToken::from_bytes_copy_prefix(entry).ok_or(EINVAL)?;
|
|
|
|
// Check if this token has the requested ID
|
|
if token.id == token_id {
|
|
return Ok(token);
|
|
}
|
|
}
|
|
|
|
// Token not found
|
|
Err(ENOENT)
|
|
}
|
|
}
|
|
|
|
/// PCI ROM Expansion Header as defined in PCI Firmware Specification.
|
|
///
|
|
/// 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)]
|
|
#[repr(C)]
|
|
struct PciRomHeader {
|
|
/// 00h: Signature (0xAA55)
|
|
signature: 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,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `PciRomHeader`.
|
|
unsafe impl FromBytes for PciRomHeader {}
|
|
|
|
impl PciRomHeader {
|
|
fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
|
|
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 rom_header.signature {
|
|
0xAA55 | 0x4E56 => {}
|
|
_ => {
|
|
dev_err!(dev, "ROM signature unknown {:#x}\n", rom_header.signature);
|
|
return Err(EINVAL);
|
|
}
|
|
}
|
|
|
|
Ok(rom_header)
|
|
}
|
|
}
|
|
|
|
/// NVIDIA PCI Data Extension Structure.
|
|
///
|
|
/// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the
|
|
/// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but
|
|
/// are needed for traversing the BIOS images. It is expected to be present in all BIOS images
|
|
/// except for NBSI images.
|
|
#[derive(Debug, Clone)]
|
|
#[repr(C)]
|
|
struct NpdeStruct {
|
|
/// 00h: Signature ("NPDE")
|
|
signature: [u8; 4],
|
|
/// 04h: NVIDIA PCI Data Extension Revision
|
|
npci_data_ext_rev: u16,
|
|
/// 06h: NVIDIA PCI Data Extension Length
|
|
npci_data_ext_len: u16,
|
|
/// 08h: Sub-image Length (in 512-byte units)
|
|
subimage_len: u16,
|
|
/// 0Ah: Last image indicator flag
|
|
last_image: u8,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `NpdeStruct`.
|
|
unsafe impl FromBytes for NpdeStruct {}
|
|
|
|
impl NpdeStruct {
|
|
/// The bit in `last_image` that indicates the last image.
|
|
const LAST_IMAGE_BIT_MASK: u8 = 0x80;
|
|
|
|
fn new(dev: &device::Device, data: &[u8]) -> Option<Self> {
|
|
let (npde, _) = NpdeStruct::from_bytes_copy_prefix(data)?;
|
|
|
|
// Signature should be "NPDE" (0x4544504E).
|
|
if &npde.signature != b"NPDE" {
|
|
dev_dbg!(
|
|
dev,
|
|
"Invalid signature for NpdeStruct: {:?}\n",
|
|
npde.signature
|
|
);
|
|
return None;
|
|
}
|
|
|
|
if npde.subimage_len == 0 {
|
|
dev_dbg!(dev, "Invalid subimage length: 0\n");
|
|
return None;
|
|
}
|
|
|
|
Some(npde)
|
|
}
|
|
|
|
/// Check if this is the last image in the ROM.
|
|
fn is_last(&self) -> bool {
|
|
self.last_image & Self::LAST_IMAGE_BIT_MASK != 0
|
|
}
|
|
|
|
/// Calculate image size in bytes from 512-byte blocks.
|
|
fn image_size_bytes(&self) -> usize {
|
|
usize::from(self.subimage_len) * 512
|
|
}
|
|
|
|
/// Try to find NPDE in the data, the NPDE is right after the PCIR.
|
|
fn find_in_data(
|
|
dev: &device::Device,
|
|
data: &[u8],
|
|
rom_header: &PciRomHeader,
|
|
pcir: &PcirStruct,
|
|
) -> 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 = 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() {
|
|
dev_dbg!(dev, "Not enough data for NPDE\n");
|
|
return None;
|
|
}
|
|
|
|
// Try to create NPDE from the data
|
|
NpdeStruct::new(dev, &data[npde_start..])
|
|
}
|
|
}
|
|
|
|
/// 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: BiosImage,
|
|
bit_header: BitHeader,
|
|
bit_offset: 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.
|
|
pub(crate) struct FwSecBiosImage {
|
|
/// Used for logging.
|
|
dev: ARef<device::Device>,
|
|
/// FWSEC data.
|
|
data: KVVec<u8>,
|
|
/// The offset of the Falcon ucode.
|
|
falcon_ucode_offset: usize,
|
|
}
|
|
|
|
/// 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.
|
|
struct BiosImage {
|
|
/// PCI Data Structure
|
|
pcir: PcirStruct,
|
|
/// NVIDIA PCI Data Extension (optional)
|
|
npde: Option<NpdeStruct>,
|
|
/// Image data (includes ROM header and PCIR)
|
|
data: KVVec<u8>,
|
|
}
|
|
|
|
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 BiosImage from raw byte data.
|
|
fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
|
|
// Parse the ROM header.
|
|
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);
|
|
let pcir_data = data
|
|
.get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>())
|
|
.ok_or(EINVAL)
|
|
.inspect_err(|_| {
|
|
dev_err!(
|
|
dev,
|
|
"PCIR offset {:#x} out of bounds (data length: {})\n",
|
|
pcir_offset,
|
|
data.len()
|
|
);
|
|
dev_err!(
|
|
dev,
|
|
"Consider reading more data for construction of BiosImage\n"
|
|
);
|
|
})?;
|
|
|
|
let pcir = PcirStruct::new(dev, pcir_data)
|
|
.inspect_err(|e| dev_err!(dev, "Failed to create PcirStruct: {:?}\n", e))?;
|
|
|
|
// Look for NPDE structure if this is not an NBSI image (type != 0x70).
|
|
let npde = NpdeStruct::find_in_data(dev, data, &rom_header, &pcir);
|
|
|
|
// Create a copy of the data.
|
|
let mut data_copy = KVVec::new();
|
|
data_copy.extend_from_slice(data, GFP_KERNEL)?;
|
|
|
|
Ok(BiosImage {
|
|
pcir,
|
|
npde,
|
|
data: data_copy,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl PciAtBiosImage {
|
|
/// Find a byte pattern in a slice.
|
|
fn find_byte_pattern(haystack: &[u8], needle: &[u8]) -> Result<usize> {
|
|
haystack
|
|
.windows(needle.len())
|
|
.position(|window| window == needle)
|
|
.ok_or(EINVAL)
|
|
}
|
|
|
|
/// Find the BIT header in the [`PciAtBiosImage`].
|
|
fn find_bit_header(data: &[u8]) -> Result<(BitHeader, usize)> {
|
|
let bit_pattern = [0xff, 0xb8, b'B', b'I', b'T', 0x00];
|
|
let bit_offset = Self::find_byte_pattern(data, &bit_pattern)?;
|
|
let bit_header = BitHeader::new(&data[bit_offset..])?;
|
|
|
|
Ok((bit_header, bit_offset))
|
|
}
|
|
|
|
/// Get a BIT token entry from the BIT table in the [`PciAtBiosImage`]
|
|
fn get_bit_token(&self, token_id: u8) -> Result<BitToken> {
|
|
BitToken::from_id(self, token_id)
|
|
}
|
|
|
|
/// Find the Falcon data offset from the start of the FWSEC region.
|
|
///
|
|
/// 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, dev: &device::Device) -> Result<usize> {
|
|
let token = self.get_bit_token(BitToken::ID_FALCON_DATA)?;
|
|
let offset = usize::from(token.data_offset);
|
|
|
|
// 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)?;
|
|
|
|
usize::from_safe_cast(ptr)
|
|
.checked_sub(data.len())
|
|
.ok_or(EINVAL)
|
|
.inspect_err(|_| {
|
|
dev_err!(dev, "Falcon data pointer out of bounds\n");
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<BiosImage> for PciAtBiosImage {
|
|
type Error = Error;
|
|
|
|
fn try_from(base: BiosImage) -> Result<Self> {
|
|
let data_slice = &base.data;
|
|
let (bit_header, bit_offset) = PciAtBiosImage::find_bit_header(data_slice)?;
|
|
|
|
Ok(PciAtBiosImage {
|
|
base,
|
|
bit_header,
|
|
bit_offset,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`].
|
|
///
|
|
/// See the [`PmuLookupTable`] description for more information.
|
|
#[repr(C, packed)]
|
|
struct PmuLookupTableEntry {
|
|
application_id: u8,
|
|
target_id: u8,
|
|
data: u32,
|
|
}
|
|
|
|
// SAFETY: all bit patterns are valid for `PmuLookupTableEntry`.
|
|
unsafe impl FromBytes for PmuLookupTableEntry {}
|
|
|
|
impl PmuLookupTableEntry {
|
|
/// PMU lookup table application ID for firmware security license ucode.
|
|
#[expect(dead_code)]
|
|
const APPID_FIRMWARE_SEC_LIC: u8 = 0x05;
|
|
/// PMU lookup table application ID for debug FWSEC ucode.
|
|
#[expect(dead_code)]
|
|
const APPID_FWSEC_DBG: u8 = 0x45;
|
|
/// PMU lookup table application ID for production FWSEC ucode.
|
|
const APPID_FWSEC_PROD: u8 = 0x85;
|
|
}
|
|
|
|
#[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.
|
|
struct PmuLookupTable {
|
|
entries: KVVec<PmuLookupTableEntry>,
|
|
}
|
|
|
|
impl PmuLookupTable {
|
|
fn new(dev: &device::Device, data: &[u8]) -> Result<Self> {
|
|
let (header, _) = PmuLookupTableHeader::from_bytes_copy_prefix(data).ok_or(EINVAL)?;
|
|
|
|
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 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");
|
|
})?;
|
|
|
|
let mut entries = KVVec::with_capacity(entry_count, GFP_KERNEL)?;
|
|
for i in 0..entry_count {
|
|
let (entry, _) = PmuLookupTableEntry::from_bytes_copy_prefix(&data[i * entry_len..])
|
|
.ok_or(EINVAL)?;
|
|
entries.push(entry, GFP_KERNEL)?;
|
|
}
|
|
|
|
Ok(PmuLookupTable { entries })
|
|
}
|
|
|
|
// find entry by type value
|
|
fn find_entry_by_type(&self, entry_type: u8) -> Result<&PmuLookupTableEntry> {
|
|
self.entries
|
|
.iter()
|
|
.find(|entry| entry.application_id == entry_type)
|
|
.ok_or(EINVAL)
|
|
}
|
|
}
|
|
|
|
impl FwSecBiosImage {
|
|
/// Build the final `FwSecBiosImage` from the PCI-AT and FWSEC BIOS images.
|
|
fn new(
|
|
dev: &device::Device,
|
|
pci_at_image: PciAtBiosImage,
|
|
data: KVVec<u8>,
|
|
) -> Result<FwSecBiosImage> {
|
|
let offset = pci_at_image.falcon_data_offset(dev)?;
|
|
|
|
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(PmuLookupTableEntry::APPID_FWSEC_PROD)
|
|
.inspect_err(|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())
|
|
.ok_or(EINVAL)
|
|
.inspect_err(|_| {
|
|
dev_err!(dev, "Falcon Ucode offset not in Fwsec.\n");
|
|
})?;
|
|
|
|
Ok(FwSecBiosImage {
|
|
dev: dev.into(),
|
|
data,
|
|
falcon_ucode_offset,
|
|
})
|
|
}
|
|
|
|
/// Get the FwSec header ([`FalconUCodeDesc`]).
|
|
pub(crate) fn header(&self) -> Result<FalconUCodeDesc> {
|
|
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)?;
|
|
match ver {
|
|
2 => {
|
|
let v2 = FalconUCodeDescV2::read_from_prefix(data)
|
|
.map_err(|_| EINVAL)?
|
|
.0;
|
|
Ok(FalconUCodeDesc::V2(v2))
|
|
}
|
|
3 => {
|
|
let v3 = FalconUCodeDescV3::from_bytes_copy_prefix(data)
|
|
.ok_or(EINVAL)?
|
|
.0;
|
|
Ok(FalconUCodeDesc::V3(v3))
|
|
}
|
|
_ => {
|
|
dev_err!(self.dev, "invalid fwsec firmware version: {:?}\n", ver);
|
|
Err(EINVAL)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the ucode data as a byte slice
|
|
pub(crate) fn ucode(&self, desc: &FalconUCodeDesc) -> Result<&[u8]> {
|
|
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.
|
|
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.dev,
|
|
"fwsec ucode data not contained within BIOS bounds\n"
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Get the signatures as a byte slice
|
|
pub(crate) fn sigs(&self, desc: &FalconUCodeDesc) -> Result<&[Bcrt30Rsa3kSignature]> {
|
|
let hdr_size = match desc {
|
|
FalconUCodeDesc::V2(_v2) => core::mem::size_of::<FalconUCodeDescV2>(),
|
|
FalconUCodeDesc::V3(_v3) => core::mem::size_of::<FalconUCodeDescV3>(),
|
|
};
|
|
// The signatures data follows the descriptor.
|
|
let sigs_data_offset = self.falcon_ucode_offset + hdr_size;
|
|
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.data.len() {
|
|
dev_err!(
|
|
self.dev,
|
|
"fwsec signatures data not contained within BIOS bounds\n"
|
|
);
|
|
return Err(ERANGE);
|
|
}
|
|
|
|
// SAFETY: we checked that `data + sigs_data_offset + (signature_count *
|
|
// sizeof::<Bcrt30Rsa3kSignature>()` is within the bounds of `data`.
|
|
Ok(unsafe {
|
|
core::slice::from_raw_parts(
|
|
self.data
|
|
.as_ptr()
|
|
.add(sigs_data_offset)
|
|
.cast::<Bcrt30Rsa3kSignature>(),
|
|
sigs_count,
|
|
)
|
|
})
|
|
}
|
|
}
|