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