182eaa14eSJohn Hubbard // SPDX-License-Identifier: GPL-2.0 282eaa14eSJohn Hubbard // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 382eaa14eSJohn Hubbard 482eaa14eSJohn Hubbard //! FSP (Foundation Security Processor) interface for Hopper/Blackwell GPUs. 582eaa14eSJohn Hubbard //! 682eaa14eSJohn Hubbard //! Hopper/Blackwell use a simplified firmware boot sequence: FMC, then FSP, then GSP. 782eaa14eSJohn Hubbard //! Unlike Turing/Ampere/Ada, there is no SEC2 (Security Engine 2) usage. 882eaa14eSJohn Hubbard //! FSP handles secure boot directly using FMC firmware and Chain of Trust. 982eaa14eSJohn Hubbard 1082eaa14eSJohn Hubbard use kernel::{ 1182eaa14eSJohn Hubbard device, 12d317e458SJohn Hubbard dma::Coherent, 1382eaa14eSJohn Hubbard io::poll::read_poll_timeout, 1482eaa14eSJohn Hubbard prelude::*, 15d317e458SJohn Hubbard ptr::{ 16d317e458SJohn Hubbard Alignable, 17d317e458SJohn Hubbard Alignment, // 18d317e458SJohn Hubbard }, 19d317e458SJohn Hubbard sizes::SZ_2M, 20e9e2a24dSJohn Hubbard time::Delta, 21e9e2a24dSJohn Hubbard transmute::{ 22e9e2a24dSJohn Hubbard AsBytes, 23e9e2a24dSJohn Hubbard FromBytes, // 24e9e2a24dSJohn Hubbard }, 2582eaa14eSJohn Hubbard }; 2682eaa14eSJohn Hubbard 2782eaa14eSJohn Hubbard use crate::{ 2882eaa14eSJohn Hubbard driver::Bar0, 2982eaa14eSJohn Hubbard falcon::{ 3082eaa14eSJohn Hubbard fsp::Fsp as FspEngine, 3182eaa14eSJohn Hubbard Falcon, // 3282eaa14eSJohn Hubbard }, 33d317e458SJohn Hubbard fb::FbLayout, 34d317e458SJohn Hubbard firmware::fsp::{ 35d317e458SJohn Hubbard FmcSignatures, 36d317e458SJohn Hubbard FspFirmware, // 37d317e458SJohn Hubbard }, 3882eaa14eSJohn Hubbard gpu::Chipset, 39d317e458SJohn Hubbard gsp::GspFmcBootParams, 40e9e2a24dSJohn Hubbard mctp::{ 41e9e2a24dSJohn Hubbard MctpHeader, 42e9e2a24dSJohn Hubbard NvdmHeader, 43e9e2a24dSJohn Hubbard NvdmType, // 44e9e2a24dSJohn Hubbard }, 45d317e458SJohn Hubbard num, 4682eaa14eSJohn Hubbard regs, // 4782eaa14eSJohn Hubbard }; 4882eaa14eSJohn Hubbard 4982eaa14eSJohn Hubbard mod hal; 5082eaa14eSJohn Hubbard 51e9e2a24dSJohn Hubbard /// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`). 52e9e2a24dSJohn Hubbard #[repr(C, packed)] 53e9e2a24dSJohn Hubbard #[derive(Clone, Copy)] 54e9e2a24dSJohn Hubbard struct NvdmPayloadCommandResponse { 55e9e2a24dSJohn Hubbard task_id: u32, 56e9e2a24dSJohn Hubbard command_nvdm_type: u32, 57e9e2a24dSJohn Hubbard error_code: u32, 58e9e2a24dSJohn Hubbard } 59e9e2a24dSJohn Hubbard 60e9e2a24dSJohn Hubbard /// Complete FSP response structure with MCTP and NVDM headers. 61e9e2a24dSJohn Hubbard #[repr(C, packed)] 62e9e2a24dSJohn Hubbard #[derive(Clone, Copy)] 63e9e2a24dSJohn Hubbard struct FspResponse { 64e9e2a24dSJohn Hubbard mctp_header: MctpHeader, 65e9e2a24dSJohn Hubbard nvdm_header: NvdmHeader, 66e9e2a24dSJohn Hubbard response: NvdmPayloadCommandResponse, 67e9e2a24dSJohn Hubbard } 68e9e2a24dSJohn Hubbard 69e9e2a24dSJohn Hubbard // SAFETY: FspResponse is a packed C struct with only integral fields. 70e9e2a24dSJohn Hubbard unsafe impl FromBytes for FspResponse {} 71e9e2a24dSJohn Hubbard 72e9e2a24dSJohn Hubbard /// Trait implemented by types representing a message to send to FSP. 73e9e2a24dSJohn Hubbard /// 74e9e2a24dSJohn Hubbard /// This provides [`Fsp::send_sync_fsp`] with the information it needs to send 75e9e2a24dSJohn Hubbard /// a given message, following the same pattern as GSP's `CommandToGsp`. 76e9e2a24dSJohn Hubbard trait MessageToFsp: AsBytes { 77e9e2a24dSJohn Hubbard /// NVDM type identifying this message to FSP. 78e9e2a24dSJohn Hubbard const NVDM_TYPE: NvdmType; 79e9e2a24dSJohn Hubbard } 80e9e2a24dSJohn Hubbard 81d317e458SJohn Hubbard /// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main 82d317e458SJohn Hubbard /// message body sent to FSP for Chain of Trust boot. 83d317e458SJohn Hubbard #[repr(C, packed)] 84d317e458SJohn Hubbard #[derive(Clone, Copy, Zeroable)] 85d317e458SJohn Hubbard struct NvdmPayloadCot { 86d317e458SJohn Hubbard version: u16, 87d317e458SJohn Hubbard size: u16, 88d317e458SJohn Hubbard gsp_fmc_sysmem_offset: u64, 89d317e458SJohn Hubbard frts_sysmem_offset: u64, 90d317e458SJohn Hubbard frts_sysmem_size: u32, 91d317e458SJohn Hubbard frts_vidmem_offset: u64, 92d317e458SJohn Hubbard frts_vidmem_size: u32, 93d317e458SJohn Hubbard sigs: FmcSignatures, 94d317e458SJohn Hubbard gsp_boot_args_sysmem_offset: u64, 95d317e458SJohn Hubbard } 96d317e458SJohn Hubbard 97d317e458SJohn Hubbard /// Complete FSP message structure with MCTP and NVDM headers. 98d317e458SJohn Hubbard #[repr(C)] 99d317e458SJohn Hubbard #[derive(Clone, Copy)] 100d317e458SJohn Hubbard struct FspMessage { 101d317e458SJohn Hubbard mctp_header: MctpHeader, 102d317e458SJohn Hubbard nvdm_header: NvdmHeader, 103d317e458SJohn Hubbard cot: NvdmPayloadCot, 104d317e458SJohn Hubbard } 105d317e458SJohn Hubbard 106d317e458SJohn Hubbard impl FspMessage { 107d317e458SJohn Hubbard /// Returns an in-place initializer for [`FspMessage`]. 108d317e458SJohn Hubbard fn new<'a>( 109d317e458SJohn Hubbard fb_layout: &FbLayout, 110d317e458SJohn Hubbard fsp_fw: &'a FspFirmware, 111d317e458SJohn Hubbard args: &'a FmcBootArgs, 112d317e458SJohn Hubbard ) -> Result<impl Init<Self> + 'a> { 113d317e458SJohn Hubbard // frts_offset is relative to FB end: FRTS_location = FB_END - frts_offset 114d317e458SJohn Hubbard let frts_vidmem_offset = if !args.resume { 115d317e458SJohn Hubbard let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size); 116d317e458SJohn Hubbard 117d317e458SJohn Hubbard frts_reserved_size 118d317e458SJohn Hubbard .align_up(Alignment::new::<SZ_2M>()) 119d317e458SJohn Hubbard .ok_or(EINVAL)? 120d317e458SJohn Hubbard } else { 121d317e458SJohn Hubbard 0 122d317e458SJohn Hubbard }; 123d317e458SJohn Hubbard 124d317e458SJohn Hubbard let frts_size: u32 = if !args.resume { 125d317e458SJohn Hubbard fb_layout.frts.len().try_into()? 126d317e458SJohn Hubbard } else { 127d317e458SJohn Hubbard 0 128d317e458SJohn Hubbard }; 129d317e458SJohn Hubbard 130d317e458SJohn Hubbard let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version(); 131d317e458SJohn Hubbard let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>(); 132d317e458SJohn Hubbard 133d317e458SJohn Hubbard Ok(init!(Self { 134d317e458SJohn Hubbard mctp_header: MctpHeader::single_packet(), 135d317e458SJohn Hubbard nvdm_header: NvdmHeader::new(NvdmType::Cot), 136d317e458SJohn Hubbard // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using 137d317e458SJohn Hubbard // `chain`. 138d317e458SJohn Hubbard cot <- pin_init::init_zeroed(), 139d317e458SJohn Hubbard }) 140d317e458SJohn Hubbard .chain(move |msg| { 141d317e458SJohn Hubbard msg.cot.version = version; 142d317e458SJohn Hubbard msg.cot.size = size; 143d317e458SJohn Hubbard msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle(); 144d317e458SJohn Hubbard msg.cot.frts_vidmem_offset = frts_vidmem_offset; 145d317e458SJohn Hubbard msg.cot.frts_vidmem_size = frts_size; 146d317e458SJohn Hubbard // frts_sysmem_* intentionally left at zero for now, but will be needed for e.g. 147d317e458SJohn Hubbard // systems without VRAM. 148d317e458SJohn Hubbard msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle(); 149d317e458SJohn Hubbard msg.cot.sigs = *fsp_fw.fmc_sigs; 150d317e458SJohn Hubbard 151d317e458SJohn Hubbard Ok(()) 152d317e458SJohn Hubbard })) 153d317e458SJohn Hubbard } 154d317e458SJohn Hubbard } 155d317e458SJohn Hubbard 156d317e458SJohn Hubbard // SAFETY: `FspMessage` is `#[repr(C)]` with no padding, so all of its 157d317e458SJohn Hubbard // bytes are initialized. 158d317e458SJohn Hubbard unsafe impl AsBytes for FspMessage {} 159d317e458SJohn Hubbard 160d317e458SJohn Hubbard impl MessageToFsp for FspMessage { 161d317e458SJohn Hubbard const NVDM_TYPE: NvdmType = NvdmType::Cot; 162d317e458SJohn Hubbard } 163d317e458SJohn Hubbard 164d317e458SJohn Hubbard /// Bundled arguments for FMC boot via FSP Chain of Trust. 165d317e458SJohn Hubbard pub(crate) struct FmcBootArgs { 166d317e458SJohn Hubbard chipset: Chipset, 167d317e458SJohn Hubbard fmc_boot_params: Coherent<GspFmcBootParams>, 168d317e458SJohn Hubbard resume: bool, 169d317e458SJohn Hubbard } 170d317e458SJohn Hubbard 171d317e458SJohn Hubbard impl FmcBootArgs { 172d317e458SJohn Hubbard /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter 173d317e458SJohn Hubbard /// structure that FSP will read. 174d317e458SJohn Hubbard pub(crate) fn new( 175d317e458SJohn Hubbard dev: &device::Device<device::Bound>, 176d317e458SJohn Hubbard chipset: Chipset, 177d317e458SJohn Hubbard wpr_meta_addr: u64, 178d317e458SJohn Hubbard libos_addr: u64, 179d317e458SJohn Hubbard resume: bool, 180d317e458SJohn Hubbard ) -> Result<Self> { 181d317e458SJohn Hubbard let init = GspFmcBootParams::new(wpr_meta_addr, libos_addr); 182d317e458SJohn Hubbard 183d317e458SJohn Hubbard Ok(Self { 184d317e458SJohn Hubbard chipset, 185d317e458SJohn Hubbard fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?, 186d317e458SJohn Hubbard resume, 187d317e458SJohn Hubbard }) 188d317e458SJohn Hubbard } 189a69a9e23SJohn Hubbard 190a69a9e23SJohn Hubbard /// DMA address of the FMC boot parameters, needed after boot for lockdown 191a69a9e23SJohn Hubbard /// release polling. 192a69a9e23SJohn Hubbard pub(crate) fn boot_params_dma_handle(&self) -> u64 { 193a69a9e23SJohn Hubbard self.fmc_boot_params.dma_handle() 194a69a9e23SJohn Hubbard } 195d317e458SJohn Hubbard } 196d317e458SJohn Hubbard 19782eaa14eSJohn Hubbard /// FSP interface for Hopper/Blackwell GPUs. 19882eaa14eSJohn Hubbard /// 19982eaa14eSJohn Hubbard /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot 20082eaa14eSJohn Hubbard /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent 20182eaa14eSJohn Hubbard /// Chain of Trust boot. 20282eaa14eSJohn Hubbard pub(crate) struct Fsp { 20382eaa14eSJohn Hubbard falcon: Falcon<FspEngine>, 20482eaa14eSJohn Hubbard fsp_fw: FspFirmware, 20582eaa14eSJohn Hubbard } 20682eaa14eSJohn Hubbard 20782eaa14eSJohn Hubbard impl Fsp { 20882eaa14eSJohn Hubbard /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface. 20982eaa14eSJohn Hubbard /// 21082eaa14eSJohn Hubbard /// Polls the thermal scratch register until FSP signals boot completion or the timeout 21182eaa14eSJohn Hubbard /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the 21282eaa14eSJohn Hubbard /// interface is not used before secure boot has completed. 21382eaa14eSJohn Hubbard pub(crate) fn wait_secure_boot( 21482eaa14eSJohn Hubbard dev: &device::Device<device::Bound>, 215*99676aedSGary Guo bar: Bar0<'_>, 21682eaa14eSJohn Hubbard chipset: Chipset, 21782eaa14eSJohn Hubbard fsp_fw: FspFirmware, 21882eaa14eSJohn Hubbard ) -> Result<Fsp> { 21982eaa14eSJohn Hubbard /// FSP secure boot completion timeout in milliseconds. 22082eaa14eSJohn Hubbard const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; 22182eaa14eSJohn Hubbard 22282eaa14eSJohn Hubbard let hal = hal::fsp_hal(chipset).ok_or(ENOTSUPP)?; 22382eaa14eSJohn Hubbard let falcon = Falcon::<FspEngine>::new(dev, chipset)?; 22482eaa14eSJohn Hubbard 22582eaa14eSJohn Hubbard read_poll_timeout( 22682eaa14eSJohn Hubbard || Ok(hal.fsp_boot_status(bar)), 22782eaa14eSJohn Hubbard |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS, 22882eaa14eSJohn Hubbard Delta::from_millis(10), 22982eaa14eSJohn Hubbard Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS), 23082eaa14eSJohn Hubbard ) 23182eaa14eSJohn Hubbard .inspect_err(|e| { 23282eaa14eSJohn Hubbard dev_err!(dev, "FSP secure boot completion error: {:?}\n", e); 23382eaa14eSJohn Hubbard })?; 23482eaa14eSJohn Hubbard 23582eaa14eSJohn Hubbard Ok(Fsp { falcon, fsp_fw }) 23682eaa14eSJohn Hubbard } 237e9e2a24dSJohn Hubbard 238e9e2a24dSJohn Hubbard /// Sends a message to FSP and waits for the response. 239*99676aedSGary Guo fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: Bar0<'_>, msg: &M) -> Result 240e9e2a24dSJohn Hubbard where 241e9e2a24dSJohn Hubbard M: MessageToFsp, 242e9e2a24dSJohn Hubbard { 243e9e2a24dSJohn Hubbard self.falcon.send_msg(bar, msg.as_bytes())?; 244e9e2a24dSJohn Hubbard 245e9e2a24dSJohn Hubbard let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| { 246e9e2a24dSJohn Hubbard dev_err!(dev, "FSP response error: {:?}\n", e); 247e9e2a24dSJohn Hubbard })?; 248e9e2a24dSJohn Hubbard 249e9e2a24dSJohn Hubbard let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 250e9e2a24dSJohn Hubbard dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); 251e9e2a24dSJohn Hubbard EIO 252e9e2a24dSJohn Hubbard })?; 253e9e2a24dSJohn Hubbard 254e9e2a24dSJohn Hubbard let mctp_header = response.mctp_header; 255e9e2a24dSJohn Hubbard let nvdm_header = response.nvdm_header; 256e9e2a24dSJohn Hubbard let command_nvdm_type = response.response.command_nvdm_type; 257e9e2a24dSJohn Hubbard let error_code = response.response.error_code; 258e9e2a24dSJohn Hubbard 259e9e2a24dSJohn Hubbard if !mctp_header.is_single_packet() { 260e9e2a24dSJohn Hubbard dev_err!( 261e9e2a24dSJohn Hubbard dev, 262e9e2a24dSJohn Hubbard "Unexpected MCTP header in FSP reply: {:x?}\n", 263e9e2a24dSJohn Hubbard mctp_header, 264e9e2a24dSJohn Hubbard ); 265e9e2a24dSJohn Hubbard return Err(EIO); 266e9e2a24dSJohn Hubbard } 267e9e2a24dSJohn Hubbard 268e9e2a24dSJohn Hubbard if !nvdm_header.validate(NvdmType::FspResponse) { 269e9e2a24dSJohn Hubbard dev_err!( 270e9e2a24dSJohn Hubbard dev, 271e9e2a24dSJohn Hubbard "Unexpected NVDM header in FSP reply: {:x?}\n", 272e9e2a24dSJohn Hubbard nvdm_header, 273e9e2a24dSJohn Hubbard ); 274e9e2a24dSJohn Hubbard return Err(EIO); 275e9e2a24dSJohn Hubbard } 276e9e2a24dSJohn Hubbard 277e9e2a24dSJohn Hubbard if command_nvdm_type != u8::from(M::NVDM_TYPE).into() { 278e9e2a24dSJohn Hubbard dev_err!( 279e9e2a24dSJohn Hubbard dev, 280e9e2a24dSJohn Hubbard "Expected NVDM type {:?} in reply, got {:#x}\n", 281e9e2a24dSJohn Hubbard M::NVDM_TYPE, 282e9e2a24dSJohn Hubbard command_nvdm_type 283e9e2a24dSJohn Hubbard ); 284e9e2a24dSJohn Hubbard return Err(EIO); 285e9e2a24dSJohn Hubbard } 286e9e2a24dSJohn Hubbard 287e9e2a24dSJohn Hubbard if error_code != 0 { 288e9e2a24dSJohn Hubbard dev_err!( 289e9e2a24dSJohn Hubbard dev, 290e9e2a24dSJohn Hubbard "NVDM command {:?} failed with error {:#x}\n", 291e9e2a24dSJohn Hubbard M::NVDM_TYPE, 292e9e2a24dSJohn Hubbard error_code 293e9e2a24dSJohn Hubbard ); 294e9e2a24dSJohn Hubbard return Err(EIO); 295e9e2a24dSJohn Hubbard } 296e9e2a24dSJohn Hubbard 297e9e2a24dSJohn Hubbard Ok(()) 298e9e2a24dSJohn Hubbard } 299d317e458SJohn Hubbard 300d317e458SJohn Hubbard /// Boots GSP FMC via FSP Chain of Trust. 301d317e458SJohn Hubbard /// 302d317e458SJohn Hubbard /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it 303d317e458SJohn Hubbard /// to FSP, and waits for the response. 304d317e458SJohn Hubbard pub(crate) fn boot_fmc( 305d317e458SJohn Hubbard &mut self, 306d317e458SJohn Hubbard dev: &device::Device<device::Bound>, 307*99676aedSGary Guo bar: Bar0<'_>, 308d317e458SJohn Hubbard fb_layout: &FbLayout, 309d317e458SJohn Hubbard args: &FmcBootArgs, 310d317e458SJohn Hubbard ) -> Result { 311d317e458SJohn Hubbard dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset); 312d317e458SJohn Hubbard 313d317e458SJohn Hubbard let msg = KBox::init(FspMessage::new(fb_layout, &self.fsp_fw, args)?, GFP_KERNEL)?; 314d317e458SJohn Hubbard 315d317e458SJohn Hubbard self.send_sync_fsp(dev, bar, &*msg)?; 316d317e458SJohn Hubbard 317d317e458SJohn Hubbard dev_dbg!(dev, "FSP Chain of Trust completed successfully\n"); 318d317e458SJohn Hubbard Ok(()) 319d317e458SJohn Hubbard } 32082eaa14eSJohn Hubbard } 321