linux-loongson/drivers/gpu/nova-core/firmware/fwsec.rs
Danilo Krummrich 43ad65eca2 gpu: nova-core: consider clippy::cast_lossless
Fix all warnings caused by `clippy::cast_lossless`, which is going to be
enabled by [1].

Cc: Alexandre Courbot <acourbot@nvidia.com>
Cc: Miguel Ojeda <ojeda@kernel.org>
Link: https://lore.kernel.org/r/20250615-ptr-as-ptr-v12-5-f43b024581e8@gmail.com [1]
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Tested-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://lore.kernel.org/r/20250624132337.2242-2-dakr@kernel.org
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2025-06-25 01:17:22 +02:00

424 lines
14 KiB
Rust

// 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.
use core::marker::PhantomData;
use core::mem::{align_of, size_of};
use core::ops::Deref;
use kernel::device::{self, Device};
use kernel::prelude::*;
use kernel::transmute::FromBytes;
use crate::dma::DmaObject;
use crate::driver::Bar0;
use crate::falcon::gsp::Gsp;
use crate::falcon::{Falcon, FalconBromParams, FalconFirmware, FalconLoadParams, FalconLoadTarget};
use crate::firmware::{FalconUCodeDescV3, FirmwareDmaObject, FirmwareSignature, Signed, Unsigned};
use crate::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 {}
#[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 {}
#[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 {}
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 {}
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.
#[expect(dead_code)]
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 {}
/// Reinterpret the area starting from `offset` in `fw` as an instance of `T` (which must implement
/// [`FromBytes`]) and return a reference to it.
///
/// # Safety
///
/// Callers must ensure that the region of memory returned is not written for as long as the
/// returned reference is alive.
///
/// TODO[TRSM][COHA]: Remove this and `transmute_mut` once `CoherentAllocation::as_slice` is
/// available and we have a way to transmute objects implementing FromBytes, e.g.:
/// https://lore.kernel.org/lkml/20250330234039.29814-1-christiansantoslima21@gmail.com/
unsafe fn transmute<'a, 'b, T: Sized + FromBytes>(
fw: &'a DmaObject,
offset: usize,
) -> Result<&'b T> {
if offset + size_of::<T>() > fw.size() {
return Err(EINVAL);
}
if (fw.start_ptr() as usize + offset) % align_of::<T>() != 0 {
return Err(EINVAL);
}
// SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
// large enough the contains an instance of `T`, which implements `FromBytes`.
Ok(unsafe { &*(fw.start_ptr().add(offset).cast::<T>()) })
}
/// Reinterpret the area starting from `offset` in `fw` as a mutable instance of `T` (which must
/// implement [`FromBytes`]) and return a reference to it.
///
/// # Safety
///
/// Callers must ensure that the region of memory returned is not read or written for as long as
/// the returned reference is alive.
unsafe fn transmute_mut<'a, 'b, T: Sized + FromBytes>(
fw: &'a mut DmaObject,
offset: usize,
) -> Result<&'b mut T> {
if offset + size_of::<T>() > fw.size() {
return Err(EINVAL);
}
if (fw.start_ptr_mut() as usize + offset) % align_of::<T>() != 0 {
return Err(EINVAL);
}
// SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
// large enough the contains an instance of `T`, which implements `FromBytes`.
Ok(unsafe { &mut *(fw.start_ptr_mut().add(offset).cast::<T>()) })
}
/// 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: FalconUCodeDescV3,
/// GPU-accessible DMA object containing the firmware.
ucode: FirmwareDmaObject<Self, Signed>,
}
// We need to load full DMEM pages.
const DMEM_LOAD_SIZE_ALIGN: u32 = 256;
impl FalconLoadParams for FwsecFirmware {
fn imem_load_params(&self) -> FalconLoadTarget {
FalconLoadTarget {
src_start: 0,
dst_start: self.desc.imem_phys_base,
len: self.desc.imem_load_size,
}
}
fn dmem_load_params(&self) -> FalconLoadTarget {
FalconLoadTarget {
src_start: self.desc.imem_load_size,
dst_start: self.desc.dmem_phys_base,
// TODO[NUMM]: replace with `align_up` once it lands.
len: self
.desc
.dmem_load_size
.next_multiple_of(DMEM_LOAD_SIZE_ALIGN),
}
}
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 Deref for FwsecFirmware {
type Target = DmaObject;
fn deref(&self) -> &Self::Target {
&self.ucode.0
}
}
impl FalconFirmware for FwsecFirmware {
type Target = Gsp;
}
impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
fn new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
let desc = bios.fwsec_image().header(dev)?;
let ucode = bios.fwsec_image().ucode(dev, desc)?;
let mut dma_object = DmaObject::from_data(dev, ucode)?;
let hdr_offset = (desc.imem_load_size + desc.interface_offset) as usize;
// SAFETY: we have exclusive access to `dma_object`.
let hdr: &FalconAppifHdrV1 = unsafe { transmute(&dma_object, hdr_offset) }?;
if hdr.version != 1 {
return Err(EINVAL);
}
// Find the DMEM mapper section in the firmware.
for i in 0..hdr.entry_count as usize {
let app: &FalconAppifV1 =
// SAFETY: we have exclusive access to `dma_object`.
unsafe {
transmute(
&dma_object,
hdr_offset + hdr.header_size as usize + i * hdr.entry_size as usize
)
}?;
if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
continue;
}
// SAFETY: we have exclusive access to `dma_object`.
let dmem_mapper: &mut FalconAppifDmemmapperV3 = unsafe {
transmute_mut(
&mut dma_object,
(desc.imem_load_size + app.dmem_base) as usize,
)
}?;
// SAFETY: we have exclusive access to `dma_object`.
let frts_cmd: &mut FrtsCmd = unsafe {
transmute_mut(
&mut dma_object,
(desc.imem_load_size + dmem_mapper.cmd_in_buffer_offset) as usize,
)
}?;
frts_cmd.read_vbios = ReadVbios {
ver: 1,
hdr: size_of::<ReadVbios>() as u32,
addr: 0,
size: 0,
flags: 2,
};
dmem_mapper.init_cmd = match cmd {
FwsecCommand::Frts {
frts_addr,
frts_size,
} => {
frts_cmd.frts_region = FrtsRegion {
ver: 1,
hdr: size_of::<FrtsRegion>() as u32,
addr: (frts_addr >> 12) as u32,
size: (frts_size >> 12) as u32,
ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
};
NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS
}
FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
};
// Return early as we found and patched the DMEMMAPPER region.
return Ok(Self(dma_object, 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 = FirmwareDmaObject::<Self, _>::new_fwsec(dev, bios, cmd)?;
// Patch signature if needed.
let desc = bios.fwsec_image().header(dev)?;
let ucode_signed = if desc.signature_count != 0 {
let sig_base_img = (desc.imem_load_size + desc.pkc_data_offset) as usize;
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);
(desc_sig_versions & reg_fuse_version_mask).count_ones() as usize
};
dev_dbg!(dev, "patching signature with index {}\n", signature_idx);
let signature = bios
.fwsec_image()
.sigs(dev, 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: desc.clone(),
ucode: ucode_signed,
})
}
/// Loads the FWSEC firmware into `falcon` and execute it.
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
.dma_load(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(())
}
}
}