// 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