mirror of
https://github.com/torvalds/linux.git
synced 2026-10-07 11:06:03 +02:00
The `dma_handle` naming is inherited from the C API, but what this really describes is the device DMA address; everything named `dma_handle` is actually a `dma_addr_t`. This naming introduces some confusion on the Rust API side, as handles are supposed to be opaque tokens, yet we were doing address computation on values returned by `dma_handle`. Rename `dma_handle` to `dma_address` while nova-core is still its only user. Suggested-by: John Hubbard <jhubbard@nvidia.com> Suggested-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/all/DK75LUA4NLGI.3P29AIZQE20V2@kernel.org/ Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> Reviewed-by: Robin Murphy <robin.murphy@arm.com> Link: https://patch.msgid.link/20260805-falcon-dma-projections-v2-2-4cc9f3f13ee9@nvidia.com [ Rebase and fix up build failures due to newly introduced dma_handle() calls. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
551 lines
16 KiB
Rust
551 lines
16 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
//! FSP (Foundation Security Processor) interface for Hopper/Blackwell GPUs.
|
|
//!
|
|
//! Hopper/Blackwell use a simplified firmware boot sequence: FMC, then FSP, then GSP.
|
|
//! Unlike Turing/Ampere/Ada, there is no SEC2 (Security Engine 2) usage.
|
|
//! FSP handles secure boot directly using FMC firmware and Chain of Trust.
|
|
|
|
use kernel::{
|
|
device,
|
|
dma::Coherent,
|
|
io::poll::read_poll_timeout,
|
|
num::TryIntoBounded,
|
|
prelude::*,
|
|
ptr::{
|
|
Alignable,
|
|
Alignment, //
|
|
},
|
|
sizes::SZ_2M,
|
|
time::Delta,
|
|
transmute::{
|
|
AsBytes,
|
|
FromBytes, //
|
|
},
|
|
};
|
|
|
|
use crate::{
|
|
driver::Bar0,
|
|
falcon::{
|
|
fsp::Fsp as FspEngine,
|
|
Falcon, //
|
|
},
|
|
fb::FbSizes,
|
|
firmware::fsp::{
|
|
FmcSignatures,
|
|
FspFirmware, //
|
|
},
|
|
gpu::Chipset,
|
|
gsp::{
|
|
GspFmcBootParams,
|
|
GspFwWprMeta,
|
|
LibosMemoryRegionInitArgument, //
|
|
},
|
|
mctp::{
|
|
MctpHeader,
|
|
NvdmHeader,
|
|
NvdmType, //
|
|
},
|
|
num,
|
|
regs, //
|
|
};
|
|
|
|
mod hal;
|
|
|
|
/// PRC message sub-command.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
enum PrcMessageSubcmd {
|
|
/// Read a PRC knob value.
|
|
Read = 0x0c,
|
|
}
|
|
|
|
impl From<PrcMessageSubcmd> for u8 {
|
|
fn from(value: PrcMessageSubcmd) -> Self {
|
|
value as u8
|
|
}
|
|
}
|
|
|
|
/// PRC object identifier.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
enum PrcObjectId {
|
|
/// vGPU mode configuration knob.
|
|
VgpuMode = 0x29,
|
|
}
|
|
|
|
impl From<PrcObjectId> for u8 {
|
|
fn from(value: PrcObjectId) -> Self {
|
|
value as u8
|
|
}
|
|
}
|
|
|
|
kernel::impl_flags!(
|
|
/// PRC request flags.
|
|
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
|
struct PrcFlags(u8);
|
|
|
|
/// Individual PRC request flag.
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum PrcFlag {
|
|
/// Request the active knob value for the current boot.
|
|
Active = 1 << 1,
|
|
}
|
|
);
|
|
|
|
/// vGPU operating mode as reported by FSP via the PRC protocol.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum VgpuMode {
|
|
/// vGPU support is disabled on this GPU.
|
|
Disabled,
|
|
/// vGPU support is enabled on this GPU.
|
|
Enabled,
|
|
}
|
|
|
|
/// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`).
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct NvdmPayloadCommandResponse {
|
|
task_id: u32,
|
|
command_nvdm_type: u32,
|
|
error_code: u32,
|
|
}
|
|
|
|
/// PRC message payload.
|
|
///
|
|
/// Sent to FSP to query or modify a device configuration knob.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct NvdmPayloadPrc {
|
|
sub_message_id: u8,
|
|
flags: u8,
|
|
object_id: u8,
|
|
reserved: u8,
|
|
}
|
|
|
|
impl NvdmPayloadPrc {
|
|
/// Constructs a PRC payload from typed protocol fields.
|
|
fn new(subcmd: PrcMessageSubcmd, object_id: PrcObjectId, flags: PrcFlags) -> Self {
|
|
Self {
|
|
sub_message_id: subcmd.into(),
|
|
flags: flags.into(),
|
|
object_id: object_id.into(),
|
|
reserved: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
// SAFETY: NvdmPayloadPrc is a packed C struct with only integral fields.
|
|
unsafe impl AsBytes for NvdmPayloadPrc {}
|
|
|
|
/// PRC response payload containing the knob state value.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct NvdmPayloadPrcResponse {
|
|
value_low: u8,
|
|
value_high: u8,
|
|
reserved1: u8,
|
|
reserved2: u8,
|
|
}
|
|
|
|
impl NvdmPayloadPrcResponse {
|
|
/// Returns the PRC knob value as a little-endian 16-bit integer.
|
|
fn value(self) -> u16 {
|
|
u16::from(self.value_low) | (u16::from(self.value_high) << 8)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<NvdmPayloadPrcResponse> for VgpuMode {
|
|
type Error = kernel::error::Error;
|
|
|
|
fn try_from(value: NvdmPayloadPrcResponse) -> Result<Self> {
|
|
match value.value() {
|
|
0 => Ok(VgpuMode::Disabled),
|
|
1 => Ok(VgpuMode::Enabled),
|
|
_ => Err(EINVAL),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Common MCTP and NVDM headers shared by all FSP messages.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct FspMessageHeader {
|
|
mctp_header: MctpHeader,
|
|
nvdm_header: NvdmHeader,
|
|
}
|
|
|
|
// SAFETY: FspMessageHeader is a packed C struct with only integral fields.
|
|
unsafe impl AsBytes for FspMessageHeader {}
|
|
|
|
// SAFETY: FspMessageHeader is a packed C struct with only integral fields.
|
|
unsafe impl FromBytes for FspMessageHeader {}
|
|
|
|
impl FspMessageHeader {
|
|
/// Construct a standard FSP message header for the given NVDM type.
|
|
fn new(nvdm_type: NvdmType) -> Self {
|
|
Self {
|
|
mctp_header: MctpHeader::single_packet(),
|
|
nvdm_header: NvdmHeader::new(nvdm_type),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Common FSP response header with MCTP, NVDM and command response payloads.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct FspResponseHeader {
|
|
header: FspMessageHeader,
|
|
response: NvdmPayloadCommandResponse,
|
|
}
|
|
|
|
// SAFETY: FspResponseHeader is a packed C struct with only integral fields.
|
|
unsafe impl FromBytes for FspResponseHeader {}
|
|
|
|
/// Complete FSP PRC response including the knob state payload.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct FspPrcResponse {
|
|
header: FspResponseHeader,
|
|
prc_data: NvdmPayloadPrcResponse,
|
|
}
|
|
|
|
// SAFETY: FspPrcResponse is a packed C struct with only integral fields.
|
|
unsafe impl FromBytes for FspPrcResponse {}
|
|
|
|
/// Trait implemented by types representing a message to send to FSP.
|
|
///
|
|
/// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
|
|
/// a given message, following the same pattern as GSP's `CommandToGsp`.
|
|
trait MessageToFsp: AsBytes {
|
|
/// NVDM type identifying this message to FSP.
|
|
const NVDM_TYPE: NvdmType;
|
|
}
|
|
|
|
/// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main
|
|
/// message body sent to FSP for Chain of Trust boot.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy, Zeroable)]
|
|
struct NvdmPayloadCot {
|
|
version: u16,
|
|
size: u16,
|
|
gsp_fmc_sysmem_offset: u64,
|
|
frts_sysmem_offset: u64,
|
|
frts_sysmem_size: u32,
|
|
frts_vidmem_offset: u64,
|
|
frts_vidmem_size: u32,
|
|
sigs: FmcSignatures,
|
|
gsp_boot_args_sysmem_offset: u64,
|
|
}
|
|
|
|
/// Complete FSP COT (Chain of Trust) message structure.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
struct FspCotMessage {
|
|
header: FspMessageHeader,
|
|
cot: NvdmPayloadCot,
|
|
}
|
|
|
|
impl FspCotMessage {
|
|
/// Computes the FRTS vidmem offset for the Chain-of-Trust message. It is measured backwards
|
|
/// from the end of the framebuffer.
|
|
fn frts_vidmem_offset(hal: &dyn hal::FspHal, fb_info: &FbSizes) -> Result<u64> {
|
|
let mut offset = hal.fb_end_reserved_size();
|
|
|
|
// As per OpenRM's `kfspPrepareBootCommands_GH100`.
|
|
if fb_info.pmu_reserved_size != 0 {
|
|
offset = (offset + u64::from(fb_info.pmu_reserved_size))
|
|
// The 2 MiB alignment is r570-specific.
|
|
.align_up(Alignment::new::<SZ_2M>())
|
|
.ok_or(EINVAL)?;
|
|
}
|
|
|
|
Ok(offset)
|
|
}
|
|
|
|
/// Returns an in-place initializer for [`FspCotMessage`].
|
|
fn new<'a>(
|
|
fb_info: &FbSizes,
|
|
fsp_fw: &'a FspFirmware,
|
|
args: &'a FmcBootArgs<'_>,
|
|
) -> Result<impl Init<Self> + 'a> {
|
|
let hal = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?;
|
|
|
|
let frts_vidmem_offset = if !args.resume {
|
|
Self::frts_vidmem_offset(hal, fb_info)?
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let frts_size: u32 = if !args.resume {
|
|
fb_info.frts_size.try_into()?
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let version = hal.cot_version();
|
|
let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
|
|
|
|
Ok(init!(Self {
|
|
header: FspMessageHeader::new(NvdmType::Cot),
|
|
// The payload is packed, so we cannot use `init!`. Initialize it member-by-member using
|
|
// `chain`.
|
|
cot <- pin_init::init_zeroed(),
|
|
})
|
|
.chain(move |msg| {
|
|
msg.cot.version = version;
|
|
msg.cot.size = size;
|
|
msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_address();
|
|
msg.cot.frts_vidmem_offset = frts_vidmem_offset;
|
|
msg.cot.frts_vidmem_size = frts_size;
|
|
// frts_sysmem_* are left at zero because this path places FRTS in vidmem. The sysmem
|
|
// fields point to an FRTS buffer in sysmem instead, for systems without VRAM.
|
|
msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_address();
|
|
msg.cot.sigs = *fsp_fw.fmc_sigs;
|
|
|
|
Ok(())
|
|
}))
|
|
}
|
|
}
|
|
|
|
// SAFETY: `FspCotMessage` is `#[repr(C)]` with no padding, so all of its
|
|
// bytes are initialized.
|
|
unsafe impl AsBytes for FspCotMessage {}
|
|
|
|
/// Complete FSP PRC message.
|
|
#[repr(C, packed)]
|
|
#[derive(Clone, Copy)]
|
|
struct FspPrcMessage {
|
|
header: FspMessageHeader,
|
|
prc: NvdmPayloadPrc,
|
|
}
|
|
|
|
impl FspPrcMessage {
|
|
/// Constructs a PRC message.
|
|
fn new(subcmd: PrcMessageSubcmd, object_id: PrcObjectId, flags: PrcFlags) -> Self {
|
|
Self {
|
|
header: FspMessageHeader::new(NvdmType::Prc),
|
|
prc: NvdmPayloadPrc::new(subcmd, object_id, flags),
|
|
}
|
|
}
|
|
}
|
|
|
|
// SAFETY: FspPrcMessage is a packed C struct with only integral fields.
|
|
unsafe impl AsBytes for FspPrcMessage {}
|
|
|
|
impl MessageToFsp for FspCotMessage {
|
|
const NVDM_TYPE: NvdmType = NvdmType::Cot;
|
|
}
|
|
|
|
impl MessageToFsp for FspPrcMessage {
|
|
const NVDM_TYPE: NvdmType = NvdmType::Prc;
|
|
}
|
|
|
|
/// Bundled arguments for FMC boot via FSP Chain of Trust.
|
|
pub(crate) struct FmcBootArgs<'a> {
|
|
chipset: Chipset,
|
|
fmc_boot_params: Coherent<GspFmcBootParams>,
|
|
resume: bool,
|
|
// Additional dependencies required to be kept alive for FMC boot.
|
|
_wpr_meta: Coherent<GspFwWprMeta>,
|
|
_libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
|
|
}
|
|
|
|
impl<'a> FmcBootArgs<'a> {
|
|
/// Builds FMC boot arguments, allocating the DMA-coherent boot parameter
|
|
/// structure that FSP will read.
|
|
pub(crate) fn new(
|
|
dev: &device::Device<device::Bound>,
|
|
chipset: Chipset,
|
|
wpr_meta: Coherent<GspFwWprMeta>,
|
|
libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
|
|
resume: bool,
|
|
) -> Result<Self> {
|
|
let init = GspFmcBootParams::new(wpr_meta.dma_address(), libos.dma_address());
|
|
|
|
Ok(Self {
|
|
chipset,
|
|
fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?,
|
|
resume,
|
|
_wpr_meta: wpr_meta,
|
|
_libos: libos,
|
|
})
|
|
}
|
|
|
|
/// Returns the FMC boot parameters allocation.
|
|
pub(crate) fn boot_params(&self) -> &Coherent<GspFmcBootParams> {
|
|
&self.fmc_boot_params
|
|
}
|
|
}
|
|
|
|
/// FSP interface for Hopper/Blackwell GPUs.
|
|
///
|
|
/// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
|
|
/// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent
|
|
/// Chain of Trust boot.
|
|
pub(crate) struct Fsp<'a> {
|
|
falcon: Falcon<'a, FspEngine>,
|
|
fsp_fw: FspFirmware,
|
|
}
|
|
|
|
impl<'a> Fsp<'a> {
|
|
/// Attempts to create a `Fsp` instance.
|
|
///
|
|
/// This can involve waiting for FSP secure boot completion, but should be instantaneous in
|
|
/// practice.
|
|
///
|
|
/// If `chipset` doesn't support FSP, `Ok(None)` is returned.
|
|
pub(crate) fn try_new(
|
|
dev: &'a device::Device<device::Bound>,
|
|
bar: Bar0<'a>,
|
|
chipset: Chipset,
|
|
) -> Result<Option<Self>> {
|
|
match hal::fsp_hal(chipset) {
|
|
None => Ok(None),
|
|
Some(hal) => Self::wait_secure_boot(dev, bar, chipset, hal).map(Option::Some),
|
|
}
|
|
}
|
|
|
|
/// Waits for FSP secure boot completion, then returns the [`Fsp`] interface.
|
|
///
|
|
/// Polls the thermal scratch register until FSP signals boot completion or the timeout
|
|
/// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the
|
|
/// interface is not used before secure boot has completed.
|
|
fn wait_secure_boot(
|
|
dev: &'a device::Device<device::Bound>,
|
|
bar: Bar0<'a>,
|
|
chipset: Chipset,
|
|
hal: &'static dyn hal::FspHal,
|
|
) -> Result<Fsp<'a>> {
|
|
/// FSP secure boot completion timeout in milliseconds.
|
|
const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000;
|
|
|
|
let falcon = Falcon::<FspEngine>::new(dev, chipset, bar)?;
|
|
let fsp_fw = FspFirmware::new(dev, chipset)?;
|
|
|
|
read_poll_timeout(
|
|
|| Ok(hal.fsp_boot_status(bar)),
|
|
|&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS,
|
|
Delta::from_millis(10),
|
|
Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS),
|
|
)
|
|
.inspect_err(|e| {
|
|
dev_err!(dev, "FSP secure boot completion error: {:?}\n", e);
|
|
})?;
|
|
|
|
Ok(Fsp { falcon, fsp_fw })
|
|
}
|
|
|
|
/// Sends a message to FSP and waits for the response.
|
|
/// Returns the full response buffer on success.
|
|
fn send_sync_fsp<M>(&mut self, dev: &device::Device, msg: &M) -> Result<KVec<u8>>
|
|
where
|
|
M: MessageToFsp,
|
|
{
|
|
self.falcon.send_msg(msg.as_bytes())?;
|
|
|
|
let response_buf = self.falcon.recv_msg().inspect_err(|e| {
|
|
dev_err!(dev, "FSP response error: {:?}\n", e);
|
|
})?;
|
|
|
|
let (response, _) =
|
|
FspResponseHeader::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
|
|
dev_err!(dev, "FSP response too small: {}\n", response_buf.len());
|
|
EIO
|
|
})?;
|
|
|
|
let mctp_header = response.header.mctp_header;
|
|
let nvdm_header = response.header.nvdm_header;
|
|
let command_nvdm_type = response.response.command_nvdm_type;
|
|
let error_code = response.response.error_code;
|
|
|
|
if !mctp_header.is_single_packet() {
|
|
dev_err!(
|
|
dev,
|
|
"Unexpected MCTP header in FSP reply: {:x?}\n",
|
|
mctp_header,
|
|
);
|
|
return Err(EIO);
|
|
}
|
|
|
|
if !nvdm_header.validate(NvdmType::FspResponse) {
|
|
dev_err!(
|
|
dev,
|
|
"Unexpected NVDM header in FSP reply: {:x?}\n",
|
|
nvdm_header,
|
|
);
|
|
return Err(EIO);
|
|
}
|
|
|
|
if command_nvdm_type.try_into_bounded() != Some(M::NVDM_TYPE.into()) {
|
|
dev_err!(
|
|
dev,
|
|
"Expected NVDM type {:?} in reply, got {:#x}\n",
|
|
M::NVDM_TYPE,
|
|
command_nvdm_type
|
|
);
|
|
return Err(EIO);
|
|
}
|
|
|
|
if error_code != 0 {
|
|
dev_err!(
|
|
dev,
|
|
"NVDM command {:?} failed with error {:#x}\n",
|
|
M::NVDM_TYPE,
|
|
error_code
|
|
);
|
|
return Err(EIO);
|
|
}
|
|
|
|
Ok(response_buf)
|
|
}
|
|
|
|
/// Reads the active vGPU mode from FSP using the PRC protocol.
|
|
///
|
|
/// Queries FSP's Management Partition for the active vGPU mode knob value.
|
|
pub(crate) fn read_vgpu_mode(
|
|
&mut self,
|
|
dev: &device::Device<device::Bound>,
|
|
) -> Result<VgpuMode> {
|
|
let msg = FspPrcMessage::new(
|
|
PrcMessageSubcmd::Read,
|
|
PrcObjectId::VgpuMode,
|
|
PrcFlags::from(PrcFlag::Active),
|
|
);
|
|
|
|
let response_buf = self.send_sync_fsp(dev, &msg)?;
|
|
let (prc_response, _) =
|
|
FspPrcResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
|
|
dev_err!(dev, "PRC response too small: {}\n", response_buf.len());
|
|
EIO
|
|
})?;
|
|
|
|
let prc_data = prc_response.prc_data;
|
|
|
|
VgpuMode::try_from(prc_data).inspect_err(|_| {
|
|
dev_err!(dev, "Unexpected vGPU mode value: {:#x}\n", prc_data.value());
|
|
})
|
|
}
|
|
|
|
/// Boots GSP FMC via FSP Chain of Trust.
|
|
///
|
|
/// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
|
|
/// to FSP, and waits for the response.
|
|
pub(crate) fn boot_fmc(
|
|
&mut self,
|
|
dev: &device::Device<device::Bound>,
|
|
fb_info: &FbSizes,
|
|
args: &FmcBootArgs<'_>,
|
|
) -> Result {
|
|
dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
|
|
|
|
let msg = KBox::init(FspCotMessage::new(fb_info, &self.fsp_fw, args)?, GFP_KERNEL)?;
|
|
|
|
let _response_buf = self.send_sync_fsp(dev, &*msg)?;
|
|
|
|
dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
|
|
Ok(())
|
|
}
|
|
}
|