Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,463 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//! Support for loading and patching the `Booter` firmware. `Booter` is a Heavy Secured firmware
//! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon
//! (and optionally unload it through a separate firmware image).
use core::marker::PhantomData;
use kernel::{
device,
dma::Coherent,
prelude::*,
transmute::FromBytes, //
};
use crate::{
driver::Bar0,
falcon::{
sec2::Sec2,
Falcon,
FalconBromParams,
FalconDmaLoadTarget,
FalconDmaLoadable,
FalconFirmware, //
},
firmware::{
BinFirmware,
FirmwareObject,
FirmwareSignature,
Signed,
Unsigned, //
},
gpu::Chipset,
num::{
FromSafeCast,
IntoSafeCast, //
},
};
/// Local convenience function to return a copy of `S` by reinterpreting the bytes starting at
/// `offset` in `slice`.
fn frombytes_at<S: FromBytes + Sized>(slice: &[u8], offset: usize) -> Result<S> {
let end = offset.checked_add(size_of::<S>()).ok_or(EINVAL)?;
slice
.get(offset..end)
.and_then(S::from_bytes_copy)
.ok_or(EINVAL)
}
/// Heavy-Secured firmware header.
///
/// Such firmwares have an application-specific payload that needs to be patched with a given
/// signature.
#[repr(C)]
#[derive(Debug, Clone)]
struct HsHeaderV2 {
/// Offset to the start of the signatures.
sig_prod_offset: u32,
/// Size in bytes of the signatures.
sig_prod_size: u32,
/// Offset to a `u32` containing the location at which to patch the signature in the microcode
/// image.
patch_loc_offset: u32,
/// Offset to a `u32` containing the index of the signature to patch.
patch_sig_offset: u32,
/// Start offset to the signature metadata.
meta_data_offset: u32,
/// Size in bytes of the signature metadata.
meta_data_size: u32,
/// Offset to a `u32` containing the number of signatures in the signatures section.
num_sig_offset: u32,
/// Offset of the application-specific header.
header_offset: u32,
/// Size in bytes of the application-specific header.
header_size: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for HsHeaderV2 {}
/// Heavy-Secured Firmware image container.
///
/// This provides convenient access to the fields of [`HsHeaderV2`] that are actually indices to
/// read from in the firmware data.
struct HsFirmwareV2<'a> {
hdr: HsHeaderV2,
fw: &'a [u8],
}
impl<'a> HsFirmwareV2<'a> {
/// Interprets the header of `bin_fw` as a [`HsHeaderV2`] and returns an instance of
/// `HsFirmwareV2` for further parsing.
///
/// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
fn new(bin_fw: &BinFirmware<'a>) -> Result<Self> {
frombytes_at::<HsHeaderV2>(bin_fw.fw, bin_fw.hdr.header_offset.into_safe_cast())
.map(|hdr| Self { hdr, fw: bin_fw.fw })
}
/// Returns the location at which the signatures should be patched in the microcode image.
///
/// Fails if the offset of the patch location is outside the bounds of the firmware
/// image.
fn patch_location(&self) -> Result<u32> {
frombytes_at::<u32>(self.fw, self.hdr.patch_loc_offset.into_safe_cast())
}
/// Returns an iterator to the signatures of the firmware. The iterator can be empty if the
/// firmware is unsigned.
///
/// Fails if the pointed signatures are outside the bounds of the firmware image.
fn signatures_iter(&'a self) -> Result<impl Iterator<Item = BooterSignature<'a>>> {
let num_sig = frombytes_at::<u32>(self.fw, self.hdr.num_sig_offset.into_safe_cast())?;
let iter = match self.hdr.sig_prod_size.checked_div(num_sig) {
// If there are no signatures, return an iterator that will yield zero elements.
None => (&[] as &[u8]).chunks_exact(1),
Some(sig_size) => {
let patch_sig =
frombytes_at::<u32>(self.fw, self.hdr.patch_sig_offset.into_safe_cast())?;
let signatures_start = self
.hdr
.sig_prod_offset
.checked_add(patch_sig)
.map(usize::from_safe_cast)
.ok_or(EINVAL)?;
let signatures_end = signatures_start
.checked_add(usize::from_safe_cast(self.hdr.sig_prod_size))
.ok_or(EINVAL)?;
self.fw
// Get signatures range.
.get(signatures_start..signatures_end)
.ok_or(EINVAL)?
.chunks_exact(sig_size.into_safe_cast())
}
};
// Map the byte slices into signatures.
Ok(iter.map(BooterSignature))
}
}
/// Signature parameters, as defined in the firmware.
#[repr(C)]
struct HsSignatureParams {
/// Fuse version to use.
fuse_ver: u32,
/// Mask of engine IDs this firmware applies to.
engine_id_mask: u32,
/// ID of the microcode.
ucode_id: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for HsSignatureParams {}
impl HsSignatureParams {
/// Returns the signature parameters contained in `hs_fw`.
///
/// Fails if the meta data parameter of `hs_fw` is outside the bounds of the firmware image, or
/// if its size doesn't match that of [`HsSignatureParams`].
fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> {
let start = usize::from_safe_cast(hs_fw.hdr.meta_data_offset);
let end = start
.checked_add(hs_fw.hdr.meta_data_size.into_safe_cast())
.ok_or(EINVAL)?;
hs_fw
.fw
.get(start..end)
.and_then(Self::from_bytes_copy)
.ok_or(EINVAL)
}
}
/// Header for code and data load offsets.
#[repr(C)]
#[derive(Debug, Clone)]
struct HsLoadHeaderV2 {
// Offset at which the code starts.
os_code_offset: u32,
// Total size of the code, for all apps.
os_code_size: u32,
// Offset at which the data starts.
os_data_offset: u32,
// Size of the data.
os_data_size: u32,
// Number of apps following this header. Each app is described by a [`HsLoadHeaderV2App`].
num_apps: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for HsLoadHeaderV2 {}
impl HsLoadHeaderV2 {
/// Returns the load header contained in `hs_fw`.
///
/// Fails if the header pointed at by `hs_fw` is not within the bounds of the firmware image.
fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> {
frombytes_at::<Self>(hs_fw.fw, hs_fw.hdr.header_offset.into_safe_cast())
}
}
/// Header for app code loader.
#[repr(C)]
#[derive(Debug, Clone)]
struct HsLoadHeaderV2App {
/// Offset at which to load the app code.
offset: u32,
/// Length in bytes of the app code.
len: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for HsLoadHeaderV2App {}
impl HsLoadHeaderV2App {
/// Returns the [`HsLoadHeaderV2App`] for app `idx` of `hs_fw`.
///
/// Fails if `idx` is larger than the number of apps declared in `hs_fw`, or if the header is
/// not within the bounds of the firmware image.
fn new(hs_fw: &HsFirmwareV2<'_>, idx: u32) -> Result<Self> {
let load_hdr = HsLoadHeaderV2::new(hs_fw)?;
if idx >= load_hdr.num_apps {
Err(EINVAL)
} else {
frombytes_at::<Self>(
hs_fw.fw,
usize::from_safe_cast(hs_fw.hdr.header_offset)
// Skip the load header...
.checked_add(size_of::<HsLoadHeaderV2>())
// ... and jump to app header `idx`.
.and_then(|offset| {
offset
.checked_add(usize::from_safe_cast(idx).checked_mul(size_of::<Self>())?)
})
.ok_or(EINVAL)?,
)
}
}
}
/// Signature for Booter firmware. Their size is encoded into the header and not known a compile
/// time, so we just wrap a byte slices on which we can implement [`FirmwareSignature`].
struct BooterSignature<'a>(&'a [u8]);
impl<'a> AsRef<[u8]> for BooterSignature<'a> {
fn as_ref(&self) -> &[u8] {
self.0
}
}
impl<'a> FirmwareSignature<BooterFirmware> for BooterSignature<'a> {}
/// The `Booter` loader firmware, responsible for loading the GSP.
pub(crate) struct BooterFirmware {
// Load parameters for Secure `IMEM` falcon memory.
imem_sec_load_target: FalconDmaLoadTarget,
// Load parameters for Non-Secure `IMEM` falcon memory,
// used only on Turing and GA100
imem_ns_load_target: Option<FalconDmaLoadTarget>,
// Load parameters for `DMEM` falcon memory.
dmem_load_target: FalconDmaLoadTarget,
// BROM falcon parameters.
brom_params: FalconBromParams,
// Device-mapped firmware image.
ucode: FirmwareObject<Self, Signed>,
}
impl FirmwareObject<BooterFirmware, Unsigned> {
fn new_booter(data: &[u8]) -> Result<Self> {
let mut ucode = KVVec::new();
ucode.extend_from_slice(data, GFP_KERNEL)?;
Ok(Self(ucode, PhantomData))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum BooterKind {
Loader,
Unloader,
}
impl BooterFirmware {
/// Parses the Booter firmware contained in `fw`, and patches the correct signature so it is
/// ready to be loaded and run on `falcon`.
pub(crate) fn new(
dev: &device::Device<device::Bound>,
kind: BooterKind,
chipset: Chipset,
ver: &str,
falcon: &Falcon<<Self as FalconFirmware>::Target>,
bar: Bar0<'_>,
) -> Result<Self> {
let fw_name = match kind {
BooterKind::Loader => "booter_load",
BooterKind::Unloader => "booter_unload",
};
let fw = super::request_firmware(dev, chipset, fw_name, ver)?;
let bin_fw = BinFirmware::new(&fw)?;
// The binary firmware embeds a Heavy-Secured firmware.
let hs_fw = HsFirmwareV2::new(&bin_fw)?;
// The Heavy-Secured firmware embeds a firmware load descriptor.
let load_hdr = HsLoadHeaderV2::new(&hs_fw)?;
// Offset in `ucode` where to patch the signature.
let patch_loc = hs_fw.patch_location()?;
let sig_params = HsSignatureParams::new(&hs_fw)?;
let brom_params = FalconBromParams {
// `load_hdr.os_data_offset` is an absolute index, but `pkc_data_offset` is from the
// signature patch location.
pkc_data_offset: patch_loc
.checked_sub(load_hdr.os_data_offset)
.ok_or(EINVAL)?,
engine_id_mask: u16::try_from(sig_params.engine_id_mask).map_err(|_| EINVAL)?,
ucode_id: u8::try_from(sig_params.ucode_id).map_err(|_| EINVAL)?,
};
let app0 = HsLoadHeaderV2App::new(&hs_fw, 0)?;
// Object containing the firmware microcode to be signature-patched.
let ucode = bin_fw
.data()
.ok_or(EINVAL)
.and_then(FirmwareObject::<Self, _>::new_booter)?;
let ucode_signed = {
let mut signatures = hs_fw.signatures_iter()?.peekable();
if signatures.peek().is_none() {
// If there are no signatures, then the firmware is unsigned.
ucode.no_patch_signature()
} else {
// Obtain the version from the fuse register, and extract the corresponding
// signature.
let reg_fuse_version = falcon.signature_reg_fuse_version(
bar,
brom_params.engine_id_mask,
brom_params.ucode_id,
)?;
// `0` means the last signature should be used.
const FUSE_VERSION_USE_LAST_SIG: u32 = 0;
let signature = match reg_fuse_version {
FUSE_VERSION_USE_LAST_SIG => signatures.last(),
// Otherwise hardware fuse version needs to be subtracted to obtain the index.
reg_fuse_version => {
let Some(idx) = sig_params.fuse_ver.checked_sub(reg_fuse_version) else {
dev_err!(dev, "invalid fuse version for Booter firmware\n");
return Err(EINVAL);
};
signatures.nth(idx.into_safe_cast())
}
}
.ok_or(EINVAL)?;
ucode.patch_signature(&signature, patch_loc.into_safe_cast())?
}
};
// There are two versions of Booter, one for Turing/GA100, and another for
// GA102+. The extraction of the IMEM sections differs between the two
// versions. Unfortunately, the file names are the same, and the headers
// don't indicate the versions. The only way to differentiate is by the Chipset.
let (imem_sec_dst_start, imem_ns_load_target) = if chipset <= Chipset::GA100 {
(
app0.offset,
Some(FalconDmaLoadTarget {
src_start: 0,
dst_start: load_hdr.os_code_offset,
len: load_hdr.os_code_size,
}),
)
} else {
(0, None)
};
Ok(Self {
imem_sec_load_target: FalconDmaLoadTarget {
src_start: app0.offset,
dst_start: imem_sec_dst_start,
len: app0.len,
},
imem_ns_load_target,
dmem_load_target: FalconDmaLoadTarget {
src_start: load_hdr.os_data_offset,
dst_start: 0,
len: load_hdr.os_data_size,
},
brom_params,
ucode: ucode_signed,
})
}
/// Load and run the booter firmware on SEC2.
///
/// Resets SEC2, loads this firmware image, then boots with the WPR metadata
/// address passed via the SEC2 mailboxes.
pub(crate) fn run<T>(
&self,
dev: &device::Device<device::Bound>,
bar: Bar0<'_>,
sec2_falcon: &Falcon<Sec2>,
wpr_meta: &Coherent<T>,
) -> Result {
sec2_falcon.reset(bar)?;
sec2_falcon.load(dev, bar, self)?;
let wpr_handle = wpr_meta.dma_handle();
let (mbox0, mbox1) = sec2_falcon.boot(
bar,
Some(wpr_handle as u32),
Some((wpr_handle >> 32) as u32),
)?;
dev_dbg!(dev, "SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);
if mbox0 != 0 {
dev_err!(dev, "Booter-load failed with error {:#x}\n", mbox0);
return Err(ENODEV);
}
Ok(())
}
}
impl FalconDmaLoadable for BooterFirmware {
fn as_slice(&self) -> &[u8] {
self.ucode.0.as_slice()
}
fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
self.imem_sec_load_target.clone()
}
fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
self.imem_ns_load_target.clone()
}
fn dmem_load_params(&self) -> FalconDmaLoadTarget {
self.dmem_load_target.clone()
}
}
impl FalconFirmware for BooterFirmware {
type Target = Sec2;
fn brom_params(&self) -> FalconBromParams {
self.brom_params.clone()
}
fn boot_addr(&self) -> u32 {
if let Some(ns_target) = &self.imem_ns_load_target {
ns_target.dst_start
} else {
self.imem_sec_load_target.src_start
}
}
}
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//! FSP is a hardware unit that runs FMC firmware.
use kernel::{
device,
dma::Coherent,
firmware::Firmware,
prelude::*, //
};
use crate::{
firmware::elf,
gpu::Chipset, //
};
/// Size of the FSP SHA-384 hash, in bytes.
const FSP_HASH_SIZE: usize = 48;
/// Maximum size of the FSP public key (RSA-3072), in bytes.
///
/// The FMC ELF `publickey` section may be shorter, so the remaining bytes are zero-padded.
const FSP_PKEY_SIZE: usize = 384;
/// Maximum size of the FSP signature (RSA-3072), in bytes.
///
/// The FMC ELF `signature` section may be shorter, so the remaining bytes are zero-padded.
const FSP_SIG_SIZE: usize = 384;
/// Structure to hold FMC signatures.
///
/// C representation is used because this type is used for communication with the FSP.
#[derive(Debug, Clone, Copy, Zeroable)]
#[repr(C)]
pub(crate) struct FmcSignatures {
pub(crate) hash384: [u8; FSP_HASH_SIZE],
pub(crate) public_key: [u8; FSP_PKEY_SIZE],
pub(crate) signature: [u8; FSP_SIG_SIZE],
}
pub(crate) struct FspFirmware {
/// FMC firmware image data (only the "image" ELF section).
pub(crate) fmc_image: Coherent<[u8]>,
/// FMC firmware signatures.
pub(crate) fmc_sigs: KBox<FmcSignatures>,
}
impl FspFirmware {
pub(crate) fn new(
dev: &device::Device<device::Bound>,
chipset: Chipset,
ver: &str,
) -> Result<Self> {
let fw = super::request_firmware(dev, chipset, "fmc", ver)?;
// FSP expects only the "image" section, not the entire ELF file.
let fmc_image_data = elf::elf_section(fw.data(), "image").ok_or_else(|| {
dev_err!(dev, "FMC ELF file missing 'image' section\n");
EINVAL
})?;
let fmc_image = Coherent::from_slice(dev, fmc_image_data, GFP_KERNEL)?;
Ok(Self {
fmc_image,
fmc_sigs: Self::extract_fmc_signatures(&fw, dev)?,
})
}
/// Extract FMC firmware signatures for Chain of Trust verification.
///
/// Extracts real cryptographic signatures from FMC ELF32 firmware sections.
/// Returns signatures in a heap-allocated structure to prevent stack overflow.
fn extract_fmc_signatures(
fmc_fw: &Firmware,
dev: &device::Device,
) -> Result<KBox<FmcSignatures>> {
let get_section = |name: &str, max_len: usize| {
elf::elf_section(fmc_fw.data(), name)
.ok_or(EINVAL)
.inspect_err(|_| dev_err!(dev, "FMC firmware missing '{}' section\n", name))
.and_then(|section| {
if section.len() > max_len {
dev_err!(
dev,
"FMC {} section size {} > maximum {}\n",
name,
section.len(),
max_len
);
Err(EINVAL)
} else {
Ok(section)
}
})
};
let hash_section = get_section("hash", FSP_HASH_SIZE)?;
let pkey_section = get_section("publickey", FSP_PKEY_SIZE)?;
let sig_section = get_section("signature", FSP_SIG_SIZE)?;
// The hash section is a SHA-384 output: it must be exactly FSP_HASH_SIZE bytes.
if hash_section.len() != FSP_HASH_SIZE {
dev_err!(
dev,
"FMC hash section size {} != expected {}\n",
hash_section.len(),
FSP_HASH_SIZE
);
return Err(EINVAL);
}
// Initialize the signatures in place to avoid building the large `FmcSignatures` on the
// stack, then fill each section from the firmware.
let signatures = KBox::init(
pin_init::init_zeroed::<FmcSignatures>().chain(|sigs| {
// PANIC: src and dst lengths are both FSP_HASH_SIZE (verified above).
sigs.hash384.copy_from_slice(hash_section);
// PANIC: dst is sliced to src.len(); src.len() <= FSP_PKEY_SIZE per `get_section`.
sigs.public_key[..pkey_section.len()].copy_from_slice(pkey_section);
// PANIC: dst is sliced to src.len(); src.len() <= FSP_SIG_SIZE per `get_section`.
sigs.signature[..sig_section.len()].copy_from_slice(sig_section);
Ok(())
}),
GFP_KERNEL,
)?;
Ok(signatures)
}
}
@@ -0,0 +1,416 @@
// SPDX-License-Identifier: GPL-2.0
//! FWSEC is a High Secure firmware that is extracted from the BIOS and performs the first step of
//! the GSP startup by creating the WPR2 memory region and copying critical areas of the VBIOS into
//! it after authenticating them, ensuring they haven't been tampered with. It runs on the GSP
//! falcon.
//!
//! Before being run, it needs to be patched in two areas:
//!
//! - The command to be run, as this firmware can perform several tasks ;
//! - The ucode signature, so the GSP falcon can run FWSEC in HS mode.
pub(crate) mod bootloader;
use core::marker::PhantomData;
use kernel::{
device::{
self,
Device, //
},
prelude::*,
transmute::{
AsBytes,
FromBytes, //
},
};
use crate::{
driver::Bar0,
falcon::{
gsp::Gsp,
Falcon,
FalconBromParams,
FalconDmaLoadTarget,
FalconDmaLoadable,
FalconFirmware, //
},
firmware::{
FalconUCodeDesc,
FirmwareObject,
FirmwareSignature,
Signed,
Unsigned, //
},
num::FromSafeCast,
vbios::Vbios,
};
const NVFW_FALCON_APPIF_ID_DMEMMAPPER: u32 = 0x4;
#[repr(C)]
#[derive(Debug)]
struct FalconAppifHdrV1 {
version: u8,
header_size: u8,
entry_size: u8,
entry_count: u8,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for FalconAppifHdrV1 {}
#[repr(C, packed)]
#[derive(Debug)]
struct FalconAppifV1 {
id: u32,
dmem_base: u32,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for FalconAppifV1 {}
#[derive(Debug)]
#[repr(C, packed)]
struct FalconAppifDmemmapperV3 {
signature: u32,
version: u16,
size: u16,
cmd_in_buffer_offset: u32,
cmd_in_buffer_size: u32,
cmd_out_buffer_offset: u32,
cmd_out_buffer_size: u32,
nvf_img_data_buffer_offset: u32,
nvf_img_data_buffer_size: u32,
printf_buffer_hdr: u32,
ucode_build_time_stamp: u32,
ucode_signature: u32,
init_cmd: u32,
ucode_feature: u32,
ucode_cmd_mask0: u32,
ucode_cmd_mask1: u32,
multi_tgt_tbl: u32,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for FalconAppifDmemmapperV3 {}
// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
unsafe impl AsBytes for FalconAppifDmemmapperV3 {}
#[derive(Debug)]
#[repr(C, packed)]
struct ReadVbios {
ver: u32,
hdr: u32,
addr: u64,
size: u32,
flags: u32,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for ReadVbios {}
// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
unsafe impl AsBytes for ReadVbios {}
#[derive(Debug)]
#[repr(C, packed)]
struct FrtsRegion {
ver: u32,
hdr: u32,
addr: u32,
size: u32,
ftype: u32,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for FrtsRegion {}
// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
unsafe impl AsBytes for FrtsRegion {}
const NVFW_FRTS_CMD_REGION_TYPE_FB: u32 = 2;
#[repr(C, packed)]
struct FrtsCmd {
read_vbios: ReadVbios,
frts_region: FrtsRegion,
}
// SAFETY: Any byte sequence is valid for this struct.
unsafe impl FromBytes for FrtsCmd {}
// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
unsafe impl AsBytes for FrtsCmd {}
const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS: u32 = 0x15;
const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB: u32 = 0x19;
/// Command for the [`FwsecFirmware`] to execute.
pub(crate) enum FwsecCommand {
/// Asks [`FwsecFirmware`] to carve out the WPR2 area and place a verified copy of the VBIOS
/// image into it.
Frts { frts_addr: u64, frts_size: u64 },
/// Asks [`FwsecFirmware`] to load pre-OS apps on the PMU.
Sb,
}
/// Size of the signatures used in FWSEC.
const BCRT30_RSA3K_SIG_SIZE: usize = 384;
/// A single signature that can be patched into a FWSEC image.
#[repr(transparent)]
pub(crate) struct Bcrt30Rsa3kSignature([u8; BCRT30_RSA3K_SIG_SIZE]);
/// SAFETY: A signature is just an array of bytes.
unsafe impl FromBytes for Bcrt30Rsa3kSignature {}
impl From<[u8; BCRT30_RSA3K_SIG_SIZE]> for Bcrt30Rsa3kSignature {
fn from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self {
Self(sig)
}
}
impl AsRef<[u8]> for Bcrt30Rsa3kSignature {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl FirmwareSignature<FwsecFirmware> for Bcrt30Rsa3kSignature {}
/// The FWSEC microcode, extracted from the BIOS and to be run on the GSP falcon.
///
/// It is responsible for e.g. carving out the WPR2 region as the first step of the GSP bootflow.
pub(crate) struct FwsecFirmware {
/// Descriptor of the firmware.
desc: FalconUCodeDesc,
/// Object containing the firmware binary.
ucode: FirmwareObject<Self, Signed>,
}
impl FalconDmaLoadable for FwsecFirmware {
fn as_slice(&self) -> &[u8] {
self.ucode.0.as_slice()
}
fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
self.desc.imem_sec_load_params()
}
fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
self.desc.imem_ns_load_params()
}
fn dmem_load_params(&self) -> FalconDmaLoadTarget {
self.desc.dmem_load_params()
}
}
impl FalconFirmware for FwsecFirmware {
type Target = Gsp;
fn brom_params(&self) -> FalconBromParams {
FalconBromParams {
pkc_data_offset: self.desc.pkc_data_offset(),
engine_id_mask: self.desc.engine_id_mask(),
ucode_id: self.desc.ucode_id(),
}
}
fn boot_addr(&self) -> u32 {
0
}
}
impl FirmwareObject<FwsecFirmware, Unsigned> {
fn new_fwsec(bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
let desc = bios.fwsec_image().header()?;
let mut ucode = KVVec::new();
ucode.extend_from_slice(bios.fwsec_image().ucode(&desc)?, GFP_KERNEL)?;
let hdr_offset = desc
.imem_load_size()
.checked_add(desc.interface_offset())
.map(usize::from_safe_cast)
.ok_or(EINVAL)?;
let hdr = ucode
.get(hdr_offset..)
.and_then(FalconAppifHdrV1::from_bytes_prefix)
.ok_or(EINVAL)?
.0;
if hdr.version != 1 {
return Err(EINVAL);
}
// Find the DMEM mapper section in the firmware.
for i in 0..usize::from(hdr.entry_count) {
// CALC: hdr_offset + header_size + i * entry_size.
let entry_offset = hdr_offset
.checked_add(usize::from(hdr.header_size))
.and_then(|o| o.checked_add(i.checked_mul(usize::from(hdr.entry_size))?))
.ok_or(EINVAL)?;
let app = ucode
.get(entry_offset..)
.and_then(FalconAppifV1::from_bytes_prefix)
.ok_or(EINVAL)?
.0;
if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
continue;
}
let dmem_base = app.dmem_base;
let dmem_mapper_offset = desc
.imem_load_size()
.checked_add(dmem_base)
.map(usize::from_safe_cast)
.ok_or(EINVAL)?;
let dmem_mapper = ucode
.get_mut(dmem_mapper_offset..)
.and_then(FalconAppifDmemmapperV3::from_bytes_mut_prefix)
.ok_or(EINVAL)?
.0;
dmem_mapper.init_cmd = match cmd {
FwsecCommand::Frts { .. } => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS,
FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
};
let cmd_in_buffer_offset = dmem_mapper.cmd_in_buffer_offset;
let frts_cmd_offset = desc
.imem_load_size()
.checked_add(cmd_in_buffer_offset)
.map(usize::from_safe_cast)
.ok_or(EINVAL)?;
let frts_cmd = ucode
.get_mut(frts_cmd_offset..)
.and_then(FrtsCmd::from_bytes_mut_prefix)
.ok_or(EINVAL)?
.0;
frts_cmd.read_vbios = ReadVbios {
ver: 1,
hdr: u32::try_from(size_of::<ReadVbios>())?,
addr: 0,
size: 0,
flags: 2,
};
if let FwsecCommand::Frts {
frts_addr,
frts_size,
} = cmd
{
frts_cmd.frts_region = FrtsRegion {
ver: 1,
hdr: u32::try_from(size_of::<FrtsRegion>())?,
addr: u32::try_from(frts_addr >> 12)?,
size: u32::try_from(frts_size >> 12)?,
ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
};
}
// Return early as we found and patched the DMEMMAPPER region.
return Ok(Self(ucode, PhantomData));
}
Err(ENOTSUPP)
}
}
impl FwsecFirmware {
/// Extract the Fwsec firmware from `bios` and patch it to run on `falcon` with the `cmd`
/// command.
pub(crate) fn new(
dev: &Device<device::Bound>,
falcon: &Falcon<Gsp>,
bar: Bar0<'_>,
bios: &Vbios,
cmd: FwsecCommand,
) -> Result<Self> {
let ucode_dma = FirmwareObject::<Self, _>::new_fwsec(bios, cmd)?;
// Patch signature if needed.
let desc = bios.fwsec_image().header()?;
let ucode_signed = if desc.signature_count() != 0 {
let sig_base_img = desc
.imem_load_size()
.checked_add(desc.pkc_data_offset())
.map(usize::from_safe_cast)
.ok_or(EINVAL)?;
let desc_sig_versions = u32::from(desc.signature_versions());
let reg_fuse_version =
falcon.signature_reg_fuse_version(bar, desc.engine_id_mask(), desc.ucode_id())?;
dev_dbg!(
dev,
"desc_sig_versions: {:#x}, reg_fuse_version: {}\n",
desc_sig_versions,
reg_fuse_version
);
let signature_idx = {
let reg_fuse_version_bit = 1 << reg_fuse_version;
// Check if the fuse version is supported by the firmware.
if desc_sig_versions & reg_fuse_version_bit == 0 {
dev_err!(
dev,
"no matching signature: {:#x} {:#x}\n",
reg_fuse_version_bit,
desc_sig_versions,
);
return Err(EINVAL);
}
// `desc_sig_versions` has one bit set per included signature. Thus, the index of
// the signature to patch is the number of bits in `desc_sig_versions` set to `1`
// before `reg_fuse_version_bit`.
// Mask of the bits of `desc_sig_versions` to preserve.
let reg_fuse_version_mask = reg_fuse_version_bit.wrapping_sub(1);
usize::from_safe_cast((desc_sig_versions & reg_fuse_version_mask).count_ones())
};
dev_dbg!(dev, "patching signature with index {}\n", signature_idx);
let signature = bios
.fwsec_image()
.sigs(&desc)
.and_then(|sigs| sigs.get(signature_idx).ok_or(EINVAL))?;
ucode_dma.patch_signature(signature, sig_base_img)?
} else {
ucode_dma.no_patch_signature()
};
Ok(FwsecFirmware {
desc,
ucode: ucode_signed,
})
}
/// Loads the FWSEC firmware into `falcon` and execute it.
///
/// This must only be called on chipsets that do not need the FWSEC bootloader (i.e., where
/// [`Chipset::needs_fwsec_bootloader()`](crate::gpu::Chipset::needs_fwsec_bootloader) returns
/// `false`). On chipsets that do, use [`bootloader::FwsecFirmwareWithBl`] instead.
pub(crate) fn run(
&self,
dev: &Device<device::Bound>,
falcon: &Falcon<Gsp>,
bar: Bar0<'_>,
) -> Result<()> {
// Reset falcon, load the firmware, and run it.
falcon
.reset(bar)
.inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
falcon
.load(dev, bar, self)
.inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
let (mbox0, _) = falcon
.boot(bar, Some(0), None)
.inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
if mbox0 != 0 {
dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
Err(EIO)
} else {
Ok(())
}
}
}
@@ -0,0 +1,350 @@
// SPDX-License-Identifier: GPL-2.0
//! Bootloader support for the FWSEC firmware.
//!
//! On Turing, the FWSEC firmware is not loaded directly, but is instead loaded through a small
//! bootloader program that performs the required DMA operations. This bootloader itself needs to
//! be loaded using PIO.
use kernel::{
alloc::KVec,
device::{
self,
Device, //
},
dma::Coherent,
io::{
register::WithBase, //
Io,
},
prelude::*,
ptr::{
Alignable,
Alignment, //
},
sizes,
transmute::{
AsBytes,
FromBytes, //
},
};
use crate::{
driver::Bar0,
falcon::{
self,
gsp::Gsp,
Falcon,
FalconBromParams,
FalconDmaLoadable,
FalconFbifMemType,
FalconFbifTarget,
FalconFirmware,
FalconPioDmemLoadTarget,
FalconPioImemLoadTarget,
FalconPioLoadable, //
},
firmware::{
fwsec::FwsecFirmware,
request_firmware,
BinHdr,
FIRMWARE_VERSION, //
},
gpu::Chipset,
num::FromSafeCast,
regs,
};
/// Descriptor used by RM to figure out the requirements of the boot loader.
///
/// Most of its fields appear to be legacy and carry incorrect values, so they are left unused.
#[repr(C)]
#[derive(Debug, Clone)]
struct BootloaderDesc {
/// Starting tag of bootloader.
start_tag: u32,
/// DMEM load offset - unused here as we always load at offset `0`.
_dmem_load_off: u32,
/// Offset of code section in the image. Unused as there is only one section in the bootloader
/// binary.
_code_off: u32,
/// Size of code section in the image.
code_size: u32,
/// Offset of data section in the image. Unused as we build the data section ourselves.
_data_off: u32,
/// Size of data section in the image. Unused as we build the data section ourselves.
_data_size: u32,
}
// SAFETY: any byte sequence is valid for this struct.
unsafe impl FromBytes for BootloaderDesc {}
/// Structure used by the boot-loader to load the rest of the code.
///
/// This has to be filled by the GPU driver and copied into DMEM at offset
/// [`BootloaderDesc.dmem_load_off`].
#[repr(C, packed)]
#[derive(Debug, Clone)]
struct BootloaderDmemDescV2 {
/// Reserved, should always be first element.
reserved: [u32; 4],
/// 16B signature for secure code, 0s if no secure code.
signature: [u32; 4],
/// DMA context used by the bootloader while loading code/data.
ctx_dma: u32,
/// 256B-aligned physical FB address where code is located.
code_dma_base: u64,
/// Offset from `code_dma_base` where the non-secure code is located.
///
/// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
/// expected to be a mirror image of its loaded state.
///
/// Must be multiple of 256.
non_sec_code_off: u32,
/// Size of the non-secure code part.
non_sec_code_size: u32,
/// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
///
/// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
/// to be a mirror image of its loaded state.
///
/// Must be multiple of 256.
sec_code_off: u32,
/// Size of the secure code part.
sec_code_size: u32,
/// Code entry point invoked by the bootloader after code is loaded.
code_entry_point: u32,
/// 256B-aligned physical FB address where data is located.
data_dma_base: u64,
/// Size of data block (should be multiple of 256B).
data_size: u32,
/// Number of arguments to be passed to the target firmware being loaded.
argc: u32,
/// Arguments to be passed to the target firmware being loaded.
argv: u32,
}
// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
unsafe impl AsBytes for BootloaderDmemDescV2 {}
/// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load
/// operation.
pub(crate) struct FwsecFirmwareWithBl {
/// DMA object the bootloader will copy the firmware from.
_firmware_dma: Coherent<[u8]>,
/// Code of the bootloader to be loaded into non-secure IMEM.
ucode: KVec<u8>,
/// Descriptor to be loaded into DMEM for the bootloader to read.
dmem_desc: BootloaderDmemDescV2,
/// Range-validated start offset of the firmware code in IMEM.
imem_dst_start: u16,
/// BROM parameters of the loaded firmware.
brom_params: FalconBromParams,
/// Range-validated `desc.start_tag`.
start_tag: u16,
}
impl FwsecFirmwareWithBl {
/// Loads the bootloader firmware for `dev` and `chipset`, and wrap `firmware` so it can be
/// loaded using it.
pub(crate) fn new(
firmware: FwsecFirmware,
dev: &Device<device::Bound>,
chipset: Chipset,
) -> Result<Self> {
let fw = request_firmware(dev, chipset, "gen_bootloader", FIRMWARE_VERSION)?;
let hdr = fw
.data()
.get(0..size_of::<BinHdr>())
.and_then(BinHdr::from_bytes_copy)
.ok_or(EINVAL)?;
let desc = {
let desc_offset = usize::from_safe_cast(hdr.header_offset);
fw.data()
.get(desc_offset..)
.and_then(BootloaderDesc::from_bytes_copy_prefix)
.ok_or(EINVAL)?
.0
};
let ucode = {
let ucode_start = usize::from_safe_cast(hdr.data_offset);
let code_size = usize::from_safe_cast(desc.code_size);
// Align to falcon block size (256 bytes).
let aligned_code_size = code_size
.align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>())
.ok_or(EINVAL)?;
let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
ucode.extend_from_slice(
fw.data()
.get(ucode_start..ucode_start + code_size)
.ok_or(EINVAL)?,
GFP_KERNEL,
)?;
ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
ucode
};
// `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
// uses the same offset parameter for both.
//
// Thus, the start of the source object needs to be padded with the difference between the
// destination and source offsets.
//
// In practice, this is expected to always be zero but is required for code correctness.
let (align_padding, firmware_dma) = {
let align_padding = {
let imem_sec = firmware.imem_sec_load_params();
imem_sec
.dst_start
.checked_sub(imem_sec.src_start)
.map(usize::from_safe_cast)
.ok_or(EOVERFLOW)?
};
let mut firmware_obj = KVVec::new();
firmware_obj.extend_with(align_padding, 0u8, GFP_KERNEL)?;
firmware_obj.extend_from_slice(firmware.ucode.0.as_slice(), GFP_KERNEL)?;
(
align_padding,
Coherent::from_slice(dev, firmware_obj.as_slice(), GFP_KERNEL)?,
)
};
let dmem_desc = {
// Bootloader payload is in non-coherent system memory.
const FALCON_DMAIDX_PHYS_SYS_NCOH: u32 = 4;
let imem_sec = firmware.imem_sec_load_params();
let imem_ns = firmware.imem_ns_load_params().ok_or(EINVAL)?;
let dmem = firmware.dmem_load_params();
// The bootloader does not have a data destination offset field and copies the data at
// the start of DMEM, so it can only be used if the destination offset of the firmware
// is 0.
if dmem.dst_start != 0 {
return Err(EINVAL);
}
BootloaderDmemDescV2 {
reserved: [0; 4],
signature: [0; 4],
ctx_dma: FALCON_DMAIDX_PHYS_SYS_NCOH,
code_dma_base: firmware_dma.dma_handle(),
// `dst_start` is also valid as the source offset since the firmware DMA object is
// a mirror image of the target IMEM layout.
non_sec_code_off: imem_ns.dst_start,
non_sec_code_size: imem_ns.len,
// `dst_start` is also valid as the source offset since the firmware DMA object is
// a mirror image of the target IMEM layout.
sec_code_off: imem_sec.dst_start,
sec_code_size: imem_sec.len,
code_entry_point: 0,
// Start of data section is the added padding + the DMEM `src_start` field.
data_dma_base: firmware_dma
.dma_handle()
.checked_add(u64::from_safe_cast(align_padding))
.and_then(|offset| offset.checked_add(dmem.src_start.into()))
.ok_or(EOVERFLOW)?,
data_size: dmem.len,
argc: 0,
argv: 0,
}
};
// The bootloader's code must be loaded in the area right below the first 64K of IMEM.
const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K;
let imem_dst_start = BOOTLOADER_LOAD_CEILING
.checked_sub(ucode.len())
.ok_or(EOVERFLOW)?;
Ok(Self {
_firmware_dma: firmware_dma,
ucode,
dmem_desc,
brom_params: firmware.brom_params(),
imem_dst_start: u16::try_from(imem_dst_start)?,
start_tag: u16::try_from(desc.start_tag)?,
})
}
/// Loads the bootloader into `falcon` and execute it.
///
/// The bootloader will load the FWSEC firmware and then execute it. This function returns
/// after FWSEC has reached completion.
pub(crate) fn run(
&self,
dev: &Device<device::Bound>,
falcon: &Falcon<Gsp>,
bar: Bar0<'_>,
) -> Result<()> {
// Reset falcon, load the firmware, and run it.
falcon
.reset(bar)
.inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
falcon
.pio_load(bar, self)
.inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
// Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory.
bar.update(
regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>()
.try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma))
.ok_or(EINVAL)?,
|v| {
v.with_target(FalconFbifTarget::CoherentSysmem)
.with_mem_type(FalconFbifMemType::Physical)
},
);
let (mbox0, _) = falcon
.boot(bar, Some(0), None)
.inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
if mbox0 != 0 {
dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
Err(EIO)
} else {
Ok(())
}
}
}
impl FalconFirmware for FwsecFirmwareWithBl {
type Target = Gsp;
fn brom_params(&self) -> FalconBromParams {
self.brom_params.clone()
}
fn boot_addr(&self) -> u32 {
// On V2 platforms, the boot address is extracted from the generic bootloader, because the
// gbl is what actually copies FWSEC into memory, so that is what needs to be booted.
u32::from(self.start_tag) << 8
}
}
impl FalconPioLoadable for FwsecFirmwareWithBl {
fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
None
}
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
Some(FalconPioImemLoadTarget {
data: self.ucode.as_ref(),
dst_start: self.imem_dst_start,
secure: false,
start_tag: self.start_tag,
})
}
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
FalconPioDmemLoadTarget {
data: self.dmem_desc.as_bytes(),
dst_start: 0,
}
}
}
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::{
device,
dma::{
Coherent,
CoherentBox,
DataDirection,
DmaAddress, //
},
prelude::*,
scatterlist::{
Owned,
SGTable, //
},
};
use crate::{
firmware::{
elf,
riscv::RiscvFirmware, //
},
gpu::{
Architecture,
Chipset, //
},
gsp::GSP_PAGE_SIZE,
num::FromSafeCast,
};
/// GSP firmware with 3-level radix page tables for the GSP bootloader.
///
/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
/// space:
///
/// ```text
/// Level 0: 1 page, 1 entry -> points to first level 1 page
/// Level 1: Multiple pages/entries -> each entry points to a level 2 page
/// Level 2: Multiple pages/entries -> each entry points to a firmware page
/// ```
///
/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
/// Also known as "Radix3" firmware.
#[pin_data]
pub(crate) struct GspFirmware {
/// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
#[pin]
fw: SGTable<Owned<VVec<u8>>>,
/// Level 2 page table whose entries contain DMA addresses of firmware pages.
#[pin]
level2: SGTable<Owned<VVec<u8>>>,
/// Level 1 page table whose entries contain DMA addresses of level 2 pages.
#[pin]
level1: SGTable<Owned<VVec<u8>>>,
/// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
level0: Coherent<[u64]>,
/// Size in bytes of the firmware contained in [`Self::fw`].
pub(crate) size: usize,
/// Device-mapped GSP signatures matching the GPU's [`Chipset`].
pub(crate) signatures: Coherent<[u8]>,
/// GSP bootloader, verifies the GSP firmware before loading and running it.
pub(crate) bootloader: RiscvFirmware,
}
impl GspFirmware {
fn find_gsp_sigs_section(chipset: Chipset) -> &'static str {
match chipset.arch() {
Architecture::Turing if matches!(chipset, Chipset::TU116 | Chipset::TU117) => {
".fwsignature_tu11x"
}
Architecture::Turing => ".fwsignature_tu10x",
Architecture::Ampere if chipset == Chipset::GA100 => ".fwsignature_ga100",
Architecture::Ampere => ".fwsignature_ga10x",
Architecture::Ada => ".fwsignature_ad10x",
Architecture::Hopper => ".fwsignature_gh10x",
Architecture::BlackwellGB10x => ".fwsignature_gb10x",
Architecture::BlackwellGB20x => ".fwsignature_gb20x",
}
}
/// Loads the GSP firmware binaries, map them into `dev`'s address-space, and creates the page
/// tables expected by the GSP bootloader to load it.
pub(crate) fn new<'a>(
dev: &'a device::Device<device::Bound>,
chipset: Chipset,
ver: &'a str,
) -> impl PinInit<Self, Error> + 'a {
pin_init::pin_init_scope(move || {
let firmware = super::request_firmware(dev, chipset, "gsp", ver)?;
let fw_section = elf::elf_section(firmware.data(), ".fwimage").ok_or(EINVAL)?;
let size = fw_section.len();
// Move the firmware into a vmalloc'd vector and map it into the device address
// space.
let fw_vvec = VVec::with_capacity(fw_section.len(), GFP_KERNEL)
.and_then(|mut v| {
v.extend_from_slice(fw_section, GFP_KERNEL)?;
Ok(v)
})
.map_err(|_| ENOMEM)?;
Ok(try_pin_init!(Self {
fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),
level2 <- {
// Allocate the level 2 page table, map the firmware onto it, and map it into
// the device address space.
VVec::<u8>::with_capacity(
fw.iter().count() * core::mem::size_of::<u64>(),
GFP_KERNEL,
)
.map_err(|_| ENOMEM)
.and_then(|level2| map_into_lvl(&fw, level2))
.map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
},
level1 <- {
// Allocate the level 1 page table, map the level 2 page table onto it, and map
// it into the device address space.
VVec::<u8>::with_capacity(
level2.iter().count() * core::mem::size_of::<u64>(),
GFP_KERNEL,
)
.map_err(|_| ENOMEM)
.and_then(|level1| map_into_lvl(&level2, level1))
.map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
},
level0: {
// Allocate the level 0 page table as a device-visible DMA object, and map the
// level 1 page table onto it.
// Fill level 1 page entry.
let level1_entry = level1.iter().next().ok_or(EINVAL)?;
let level1_entry_addr = level1_entry.dma_address();
// Create level 0 page table data and fill its first entry with the level 1
// table.
let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
dev,
GSP_PAGE_SIZE / size_of::<u64>(),
GFP_KERNEL
)?;
level0[0] = level1_entry_addr.to_le();
level0.into()
},
size,
signatures: {
let sigs_section = Self::find_gsp_sigs_section(chipset);
elf::elf_section(firmware.data(), sigs_section)
.ok_or(EINVAL)
.and_then(|data| Coherent::from_slice(dev, data, GFP_KERNEL))?
},
bootloader: {
let bl = super::request_firmware(dev, chipset, "bootloader", ver)?;
RiscvFirmware::new(dev, &bl)?
},
}))
})
}
/// Returns the DMA handle of the radix3 level 0 page table.
pub(crate) fn radix3_dma_handle(&self) -> DmaAddress {
self.level0.dma_handle()
}
}
/// Build a page table from a scatter-gather list.
///
/// Takes each DMA-mapped region from `sg_table` and writes page table entries
/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
/// 4 consecutive page table entries.
fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
for sg_entry in sg_table.iter() {
// Number of pages we need to map.
let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);
for i in 0..num_pages {
let entry = sg_entry.dma_address()
+ (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));
dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
}
}
Ok(dst)
}
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0
//! Support for firmware binaries designed to run on a RISC-V core. Such firmwares files have a
//! dedicated header.
use kernel::{
device,
dma::Coherent,
firmware::Firmware,
prelude::*,
transmute::FromBytes, //
};
use crate::{
firmware::BinFirmware,
num::FromSafeCast, //
};
/// Descriptor for microcode running on a RISC-V core.
#[repr(C)]
#[derive(Debug)]
struct RmRiscvUCodeDesc {
version: u32,
bootloader_offset: u32,
bootloader_size: u32,
bootloader_param_offset: u32,
bootloader_param_size: u32,
riscv_elf_offset: u32,
riscv_elf_size: u32,
app_version: u32,
manifest_offset: u32,
manifest_size: u32,
monitor_data_offset: u32,
monitor_data_size: u32,
monitor_code_offset: u32,
monitor_code_size: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for RmRiscvUCodeDesc {}
impl RmRiscvUCodeDesc {
/// Interprets the header of `bin_fw` as a [`RmRiscvUCodeDesc`] and returns it.
///
/// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
fn new(bin_fw: &BinFirmware<'_>) -> Result<Self> {
let offset = usize::from_safe_cast(bin_fw.hdr.header_offset);
let end = offset.checked_add(size_of::<Self>()).ok_or(EINVAL)?;
bin_fw
.fw
.get(offset..end)
.and_then(Self::from_bytes_copy)
.ok_or(EINVAL)
}
}
/// A parsed firmware for a RISC-V core, ready to be loaded and run.
pub(crate) struct RiscvFirmware {
/// Offset at which the code starts in the firmware image.
pub(crate) code_offset: u32,
/// Offset at which the data starts in the firmware image.
pub(crate) data_offset: u32,
/// Offset at which the manifest starts in the firmware image.
pub(crate) manifest_offset: u32,
/// Application version.
pub(crate) app_version: u32,
/// Device-mapped firmware image.
pub(crate) ucode: Coherent<[u8]>,
}
impl RiscvFirmware {
/// Parses the RISC-V firmware image contained in `fw`.
pub(crate) fn new(dev: &device::Device<device::Bound>, fw: &Firmware) -> Result<Self> {
let bin_fw = BinFirmware::new(fw)?;
let riscv_desc = RmRiscvUCodeDesc::new(&bin_fw)?;
let ucode = {
let start = usize::from_safe_cast(bin_fw.hdr.data_offset);
let len = usize::from_safe_cast(bin_fw.hdr.data_size);
let end = start.checked_add(len).ok_or(EINVAL)?;
Coherent::from_slice(dev, fw.data().get(start..end).ok_or(EINVAL)?, GFP_KERNEL)?
};
Ok(Self {
ucode,
code_offset: riscv_desc.monitor_code_offset,
data_offset: riscv_desc.monitor_data_offset,
manifest_offset: riscv_desc.manifest_offset,
app_version: riscv_desc.app_version,
})
}
}