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