mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
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,
|
|
)
|
|
})
|
|
}
|
|
}
|