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