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