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 191 /// FSP interface for Hopper/Blackwell GPUs. 192 /// 193 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot 194 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent 195 /// Chain of Trust boot. 196 pub(crate) struct Fsp { 197 falcon: Falcon<FspEngine>, 198 fsp_fw: FspFirmware, 199 } 200 201 impl Fsp { 202 /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface. 203 /// 204 /// Polls the thermal scratch register until FSP signals boot completion or the timeout 205 /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the 206 /// interface is not used before secure boot has completed. 207 pub(crate) fn wait_secure_boot( 208 dev: &device::Device<device::Bound>, 209 bar: &Bar0, 210 chipset: Chipset, 211 fsp_fw: FspFirmware, 212 ) -> Result<Fsp> { 213 /// FSP secure boot completion timeout in milliseconds. 214 const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; 215 216 let hal = hal::fsp_hal(chipset).ok_or(ENOTSUPP)?; 217 let falcon = Falcon::<FspEngine>::new(dev, chipset)?; 218 219 read_poll_timeout( 220 || Ok(hal.fsp_boot_status(bar)), 221 |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS, 222 Delta::from_millis(10), 223 Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS), 224 ) 225 .inspect_err(|e| { 226 dev_err!(dev, "FSP secure boot completion error: {:?}\n", e); 227 })?; 228 229 Ok(Fsp { falcon, fsp_fw }) 230 } 231 232 /// Sends a message to FSP and waits for the response. 233 fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result 234 where 235 M: MessageToFsp, 236 { 237 self.falcon.send_msg(bar, msg.as_bytes())?; 238 239 let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| { 240 dev_err!(dev, "FSP response error: {:?}\n", e); 241 })?; 242 243 let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 244 dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); 245 EIO 246 })?; 247 248 let mctp_header = response.mctp_header; 249 let nvdm_header = response.nvdm_header; 250 let command_nvdm_type = response.response.command_nvdm_type; 251 let error_code = response.response.error_code; 252 253 if !mctp_header.is_single_packet() { 254 dev_err!( 255 dev, 256 "Unexpected MCTP header in FSP reply: {:x?}\n", 257 mctp_header, 258 ); 259 return Err(EIO); 260 } 261 262 if !nvdm_header.validate(NvdmType::FspResponse) { 263 dev_err!( 264 dev, 265 "Unexpected NVDM header in FSP reply: {:x?}\n", 266 nvdm_header, 267 ); 268 return Err(EIO); 269 } 270 271 if command_nvdm_type != u8::from(M::NVDM_TYPE).into() { 272 dev_err!( 273 dev, 274 "Expected NVDM type {:?} in reply, got {:#x}\n", 275 M::NVDM_TYPE, 276 command_nvdm_type 277 ); 278 return Err(EIO); 279 } 280 281 if error_code != 0 { 282 dev_err!( 283 dev, 284 "NVDM command {:?} failed with error {:#x}\n", 285 M::NVDM_TYPE, 286 error_code 287 ); 288 return Err(EIO); 289 } 290 291 Ok(()) 292 } 293 294 /// Boots GSP FMC via FSP Chain of Trust. 295 /// 296 /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it 297 /// to FSP, and waits for the response. 298 pub(crate) fn boot_fmc( 299 &mut self, 300 dev: &device::Device<device::Bound>, 301 bar: &Bar0, 302 fb_layout: &FbLayout, 303 args: &FmcBootArgs, 304 ) -> Result { 305 dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset); 306 307 let msg = KBox::init(FspMessage::new(fb_layout, &self.fsp_fw, args)?, GFP_KERNEL)?; 308 309 self.send_sync_fsp(dev, bar, &*msg)?; 310 311 dev_dbg!(dev, "FSP Chain of Trust completed successfully\n"); 312 Ok(()) 313 } 314 } 315