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 /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface. 235 /// 236 /// Polls the thermal scratch register until FSP signals boot completion or the timeout 237 /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the 238 /// interface is not used before secure boot has completed. 239 pub(crate) fn wait_secure_boot( 240 dev: &'a device::Device<device::Bound>, 241 bar: Bar0<'a>, 242 chipset: Chipset, 243 ) -> Result<Fsp<'a>> { 244 /// FSP secure boot completion timeout in milliseconds. 245 const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; 246 247 let hal = hal::fsp_hal(chipset).ok_or(ENOTSUPP)?; 248 let falcon = Falcon::<FspEngine>::new(dev, chipset, bar)?; 249 let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?; 250 251 read_poll_timeout( 252 || Ok(hal.fsp_boot_status(bar)), 253 |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS, 254 Delta::from_millis(10), 255 Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS), 256 ) 257 .inspect_err(|e| { 258 dev_err!(dev, "FSP secure boot completion error: {:?}\n", e); 259 })?; 260 261 Ok(Fsp { falcon, fsp_fw }) 262 } 263 264 /// Sends a message to FSP and waits for the response. 265 /// Returns the full response buffer on success. 266 fn send_sync_fsp<M>(&mut self, dev: &device::Device, msg: &M) -> Result<KVec<u8>> 267 where 268 M: MessageToFsp, 269 { 270 self.falcon.send_msg(msg.as_bytes())?; 271 272 let response_buf = self.falcon.recv_msg().inspect_err(|e| { 273 dev_err!(dev, "FSP response error: {:?}\n", e); 274 })?; 275 276 let (response, _) = 277 FspResponseHeader::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 278 dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); 279 EIO 280 })?; 281 282 let mctp_header = response.header.mctp_header; 283 let nvdm_header = response.header.nvdm_header; 284 let command_nvdm_type = response.response.command_nvdm_type; 285 let error_code = response.response.error_code; 286 287 if !mctp_header.is_single_packet() { 288 dev_err!( 289 dev, 290 "Unexpected MCTP header in FSP reply: {:x?}\n", 291 mctp_header, 292 ); 293 return Err(EIO); 294 } 295 296 if !nvdm_header.validate(NvdmType::FspResponse) { 297 dev_err!( 298 dev, 299 "Unexpected NVDM header in FSP reply: {:x?}\n", 300 nvdm_header, 301 ); 302 return Err(EIO); 303 } 304 305 if command_nvdm_type.try_into_bounded() != Some(M::NVDM_TYPE.into()) { 306 dev_err!( 307 dev, 308 "Expected NVDM type {:?} in reply, got {:#x}\n", 309 M::NVDM_TYPE, 310 command_nvdm_type 311 ); 312 return Err(EIO); 313 } 314 315 if error_code != 0 { 316 dev_err!( 317 dev, 318 "NVDM command {:?} failed with error {:#x}\n", 319 M::NVDM_TYPE, 320 error_code 321 ); 322 return Err(EIO); 323 } 324 325 Ok(response_buf) 326 } 327 328 /// Boots GSP FMC via FSP Chain of Trust. 329 /// 330 /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it 331 /// to FSP, and waits for the response. 332 pub(crate) fn boot_fmc( 333 &mut self, 334 dev: &device::Device<device::Bound>, 335 fb_layout: &FbLayout, 336 args: &FmcBootArgs, 337 ) -> Result { 338 dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset); 339 340 let msg = KBox::init( 341 FspCotMessage::new(fb_layout, &self.fsp_fw, args)?, 342 GFP_KERNEL, 343 )?; 344 345 let _response_buf = self.send_sync_fsp(dev, &*msg)?; 346 347 dev_dbg!(dev, "FSP Chain of Trust completed successfully\n"); 348 Ok(()) 349 } 350 } 351