550 lines · 16.0 KB · Rust
RawDownload
// 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);        } 

Lines 1–500 of 550.