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::FbSizes, 35 firmware::fsp::{ 36 FmcSignatures, 37 FspFirmware, // 38 }, 39 gpu::Chipset, 40 gsp::{ 41 GspFmcBootParams, 42 GspFwWprMeta, 43 LibosMemoryRegionInitArgument, // 44 }, 45 mctp::{ 46 MctpHeader, 47 NvdmHeader, 48 NvdmType, // 49 }, 50 num, 51 regs, // 52 }; 53 54 mod hal; 55 56 /// PRC message sub-command. 57 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 58 #[repr(u8)] 59 enum PrcMessageSubcmd { 60 /// Read a PRC knob value. 61 Read = 0x0c, 62 } 63 64 impl From<PrcMessageSubcmd> for u8 { 65 fn from(value: PrcMessageSubcmd) -> Self { 66 value as u8 67 } 68 } 69 70 /// PRC object identifier. 71 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 72 #[repr(u8)] 73 enum PrcObjectId { 74 /// vGPU mode configuration knob. 75 VgpuMode = 0x29, 76 } 77 78 impl From<PrcObjectId> for u8 { 79 fn from(value: PrcObjectId) -> Self { 80 value as u8 81 } 82 } 83 84 kernel::impl_flags!( 85 /// PRC request flags. 86 #[derive(Clone, Copy, Default, PartialEq, Eq)] 87 struct PrcFlags(u8); 88 89 /// Individual PRC request flag. 90 #[derive(Clone, Copy, PartialEq, Eq)] 91 enum PrcFlag { 92 /// Request the active knob value for the current boot. 93 Active = 1 << 1, 94 } 95 ); 96 97 /// vGPU operating mode as reported by FSP via the PRC protocol. 98 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 99 pub(crate) enum VgpuMode { 100 /// vGPU support is disabled on this GPU. 101 Disabled, 102 /// vGPU support is enabled on this GPU. 103 Enabled, 104 } 105 106 /// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`). 107 #[repr(C, packed)] 108 #[derive(Clone, Copy)] 109 struct NvdmPayloadCommandResponse { 110 task_id: u32, 111 command_nvdm_type: u32, 112 error_code: u32, 113 } 114 115 /// PRC message payload. 116 /// 117 /// Sent to FSP to query or modify a device configuration knob. 118 #[repr(C, packed)] 119 #[derive(Clone, Copy)] 120 struct NvdmPayloadPrc { 121 sub_message_id: u8, 122 flags: u8, 123 object_id: u8, 124 reserved: u8, 125 } 126 127 impl NvdmPayloadPrc { 128 /// Constructs a PRC payload from typed protocol fields. 129 fn new(subcmd: PrcMessageSubcmd, object_id: PrcObjectId, flags: PrcFlags) -> Self { 130 Self { 131 sub_message_id: subcmd.into(), 132 flags: flags.into(), 133 object_id: object_id.into(), 134 reserved: 0, 135 } 136 } 137 } 138 139 // SAFETY: NvdmPayloadPrc is a packed C struct with only integral fields. 140 unsafe impl AsBytes for NvdmPayloadPrc {} 141 142 /// PRC response payload containing the knob state value. 143 #[repr(C, packed)] 144 #[derive(Clone, Copy)] 145 struct NvdmPayloadPrcResponse { 146 value_low: u8, 147 value_high: u8, 148 reserved1: u8, 149 reserved2: u8, 150 } 151 152 impl NvdmPayloadPrcResponse { 153 /// Returns the PRC knob value as a little-endian 16-bit integer. 154 fn value(self) -> u16 { 155 u16::from(self.value_low) | (u16::from(self.value_high) << 8) 156 } 157 } 158 159 impl TryFrom<NvdmPayloadPrcResponse> for VgpuMode { 160 type Error = kernel::error::Error; 161 162 fn try_from(value: NvdmPayloadPrcResponse) -> Result<Self> { 163 match value.value() { 164 0 => Ok(VgpuMode::Disabled), 165 1 => Ok(VgpuMode::Enabled), 166 _ => Err(EINVAL), 167 } 168 } 169 } 170 171 /// Common MCTP and NVDM headers shared by all FSP messages. 172 #[repr(C, packed)] 173 #[derive(Clone, Copy)] 174 struct FspMessageHeader { 175 mctp_header: MctpHeader, 176 nvdm_header: NvdmHeader, 177 } 178 179 // SAFETY: FspMessageHeader is a packed C struct with only integral fields. 180 unsafe impl AsBytes for FspMessageHeader {} 181 182 // SAFETY: FspMessageHeader is a packed C struct with only integral fields. 183 unsafe impl FromBytes for FspMessageHeader {} 184 185 impl FspMessageHeader { 186 /// Construct a standard FSP message header for the given NVDM type. 187 fn new(nvdm_type: NvdmType) -> Self { 188 Self { 189 mctp_header: MctpHeader::single_packet(), 190 nvdm_header: NvdmHeader::new(nvdm_type), 191 } 192 } 193 } 194 195 /// Common FSP response header with MCTP, NVDM and command response payloads. 196 #[repr(C, packed)] 197 #[derive(Clone, Copy)] 198 struct FspResponseHeader { 199 header: FspMessageHeader, 200 response: NvdmPayloadCommandResponse, 201 } 202 203 // SAFETY: FspResponseHeader is a packed C struct with only integral fields. 204 unsafe impl FromBytes for FspResponseHeader {} 205 206 /// Complete FSP PRC response including the knob state payload. 207 #[repr(C, packed)] 208 #[derive(Clone, Copy)] 209 struct FspPrcResponse { 210 header: FspResponseHeader, 211 prc_data: NvdmPayloadPrcResponse, 212 } 213 214 // SAFETY: FspPrcResponse is a packed C struct with only integral fields. 215 unsafe impl FromBytes for FspPrcResponse {} 216 217 /// Trait implemented by types representing a message to send to FSP. 218 /// 219 /// This provides [`Fsp::send_sync_fsp`] with the information it needs to send 220 /// a given message, following the same pattern as GSP's `CommandToGsp`. 221 trait MessageToFsp: AsBytes { 222 /// NVDM type identifying this message to FSP. 223 const NVDM_TYPE: NvdmType; 224 } 225 226 /// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main 227 /// message body sent to FSP for Chain of Trust boot. 228 #[repr(C, packed)] 229 #[derive(Clone, Copy, Zeroable)] 230 struct NvdmPayloadCot { 231 version: u16, 232 size: u16, 233 gsp_fmc_sysmem_offset: u64, 234 frts_sysmem_offset: u64, 235 frts_sysmem_size: u32, 236 frts_vidmem_offset: u64, 237 frts_vidmem_size: u32, 238 sigs: FmcSignatures, 239 gsp_boot_args_sysmem_offset: u64, 240 } 241 242 /// Complete FSP COT (Chain of Trust) message structure. 243 #[repr(C)] 244 #[derive(Clone, Copy)] 245 struct FspCotMessage { 246 header: FspMessageHeader, 247 cot: NvdmPayloadCot, 248 } 249 250 impl FspCotMessage { 251 /// Computes the FRTS vidmem offset for the Chain-of-Trust message. It is measured backwards 252 /// from the end of the framebuffer. 253 fn frts_vidmem_offset(hal: &dyn hal::FspHal, fb_info: &FbSizes) -> Result<u64> { 254 let mut offset = hal.fb_end_reserved_size(); 255 256 // As per OpenRM's `kfspPrepareBootCommands_GH100`. 257 if fb_info.pmu_reserved_size != 0 { 258 offset = (offset + u64::from(fb_info.pmu_reserved_size)) 259 // The 2 MiB alignment is r570-specific. 260 .align_up(Alignment::new::<SZ_2M>()) 261 .ok_or(EINVAL)?; 262 } 263 264 Ok(offset) 265 } 266 267 /// Returns an in-place initializer for [`FspCotMessage`]. 268 fn new<'a>( 269 fb_info: &FbSizes, 270 fsp_fw: &'a FspFirmware, 271 args: &'a FmcBootArgs<'_>, 272 ) -> Result<impl Init<Self> + 'a> { 273 let hal = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?; 274 275 let frts_vidmem_offset = if !args.resume { 276 Self::frts_vidmem_offset(hal, fb_info)? 277 } else { 278 0 279 }; 280 281 let frts_size: u32 = if !args.resume { 282 fb_info.frts_size.try_into()? 283 } else { 284 0 285 }; 286 287 let version = hal.cot_version(); 288 let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>(); 289 290 Ok(init!(Self { 291 header: FspMessageHeader::new(NvdmType::Cot), 292 // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using 293 // `chain`. 294 cot <- pin_init::init_zeroed(), 295 }) 296 .chain(move |msg| { 297 msg.cot.version = version; 298 msg.cot.size = size; 299 msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_address(); 300 msg.cot.frts_vidmem_offset = frts_vidmem_offset; 301 msg.cot.frts_vidmem_size = frts_size; 302 // frts_sysmem_* are left at zero because this path places FRTS in vidmem. The sysmem 303 // fields point to an FRTS buffer in sysmem instead, for systems without VRAM. 304 msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_address(); 305 msg.cot.sigs = *fsp_fw.fmc_sigs; 306 307 Ok(()) 308 })) 309 } 310 } 311 312 // SAFETY: `FspCotMessage` is `#[repr(C)]` with no padding, so all of its 313 // bytes are initialized. 314 unsafe impl AsBytes for FspCotMessage {} 315 316 /// Complete FSP PRC message. 317 #[repr(C, packed)] 318 #[derive(Clone, Copy)] 319 struct FspPrcMessage { 320 header: FspMessageHeader, 321 prc: NvdmPayloadPrc, 322 } 323 324 impl FspPrcMessage { 325 /// Constructs a PRC message. 326 fn new(subcmd: PrcMessageSubcmd, object_id: PrcObjectId, flags: PrcFlags) -> Self { 327 Self { 328 header: FspMessageHeader::new(NvdmType::Prc), 329 prc: NvdmPayloadPrc::new(subcmd, object_id, flags), 330 } 331 } 332 } 333 334 // SAFETY: FspPrcMessage is a packed C struct with only integral fields. 335 unsafe impl AsBytes for FspPrcMessage {} 336 337 impl MessageToFsp for FspCotMessage { 338 const NVDM_TYPE: NvdmType = NvdmType::Cot; 339 } 340 341 impl MessageToFsp for FspPrcMessage { 342 const NVDM_TYPE: NvdmType = NvdmType::Prc; 343 } 344 345 /// Bundled arguments for FMC boot via FSP Chain of Trust. 346 pub(crate) struct FmcBootArgs<'a> { 347 chipset: Chipset, 348 fmc_boot_params: Coherent<GspFmcBootParams>, 349 resume: bool, 350 // Additional dependencies required to be kept alive for FMC boot. 351 _wpr_meta: Coherent<GspFwWprMeta>, 352 _libos: &'a Coherent<[LibosMemoryRegionInitArgument]>, 353 } 354 355 impl<'a> FmcBootArgs<'a> { 356 /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter 357 /// structure that FSP will read. 358 pub(crate) fn new( 359 dev: &device::Device<device::Bound>, 360 chipset: Chipset, 361 wpr_meta: Coherent<GspFwWprMeta>, 362 libos: &'a Coherent<[LibosMemoryRegionInitArgument]>, 363 resume: bool, 364 ) -> Result<Self> { 365 let init = GspFmcBootParams::new(wpr_meta.dma_address(), libos.dma_address()); 366 367 Ok(Self { 368 chipset, 369 fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?, 370 resume, 371 _wpr_meta: wpr_meta, 372 _libos: libos, 373 }) 374 } 375 376 /// Returns the FMC boot parameters allocation. 377 pub(crate) fn boot_params(&self) -> &Coherent<GspFmcBootParams> { 378 &self.fmc_boot_params 379 } 380 } 381 382 /// FSP interface for Hopper/Blackwell GPUs. 383 /// 384 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot 385 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent 386 /// Chain of Trust boot. 387 pub(crate) struct Fsp<'a> { 388 falcon: Falcon<'a, FspEngine>, 389 fsp_fw: FspFirmware, 390 } 391 392 impl<'a> Fsp<'a> { 393 /// Attempts to create a `Fsp` instance. 394 /// 395 /// This can involve waiting for FSP secure boot completion, but should be instantaneous in 396 /// practice. 397 /// 398 /// If `chipset` doesn't support FSP, `Ok(None)` is returned. 399 pub(crate) fn try_new( 400 dev: &'a device::Device<device::Bound>, 401 bar: Bar0<'a>, 402 chipset: Chipset, 403 ) -> Result<Option<Self>> { 404 match hal::fsp_hal(chipset) { 405 None => Ok(None), 406 Some(hal) => Self::wait_secure_boot(dev, bar, chipset, hal).map(Option::Some), 407 } 408 } 409 410 /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface. 411 /// 412 /// Polls the thermal scratch register until FSP signals boot completion or the timeout 413 /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the 414 /// interface is not used before secure boot has completed. 415 fn wait_secure_boot( 416 dev: &'a device::Device<device::Bound>, 417 bar: Bar0<'a>, 418 chipset: Chipset, 419 hal: &'static dyn hal::FspHal, 420 ) -> Result<Fsp<'a>> { 421 /// FSP secure boot completion timeout in milliseconds. 422 const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; 423 424 let falcon = Falcon::<FspEngine>::new(dev, chipset, bar)?; 425 let fsp_fw = FspFirmware::new(dev, chipset)?; 426 427 read_poll_timeout( 428 || Ok(hal.fsp_boot_status(bar)), 429 |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS, 430 Delta::from_millis(10), 431 Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS), 432 ) 433 .inspect_err(|e| { 434 dev_err!(dev, "FSP secure boot completion error: {:?}\n", e); 435 })?; 436 437 Ok(Fsp { falcon, fsp_fw }) 438 } 439 440 /// Sends a message to FSP and waits for the response. 441 /// Returns the full response buffer on success. 442 fn send_sync_fsp<M>(&mut self, dev: &device::Device, msg: &M) -> Result<KVec<u8>> 443 where 444 M: MessageToFsp, 445 { 446 self.falcon.send_msg(msg.as_bytes())?; 447 448 let response_buf = self.falcon.recv_msg().inspect_err(|e| { 449 dev_err!(dev, "FSP response error: {:?}\n", e); 450 })?; 451 452 let (response, _) = 453 FspResponseHeader::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 454 dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); 455 EIO 456 })?; 457 458 let mctp_header = response.header.mctp_header; 459 let nvdm_header = response.header.nvdm_header; 460 let command_nvdm_type = response.response.command_nvdm_type; 461 let error_code = response.response.error_code; 462 463 if !mctp_header.is_single_packet() { 464 dev_err!( 465 dev, 466 "Unexpected MCTP header in FSP reply: {:x?}\n", 467 mctp_header, 468 ); 469 return Err(EIO); 470 } 471 472 if !nvdm_header.validate(NvdmType::FspResponse) { 473 dev_err!( 474 dev, 475 "Unexpected NVDM header in FSP reply: {:x?}\n", 476 nvdm_header, 477 ); 478 return Err(EIO); 479 } 480 481 if command_nvdm_type.try_into_bounded() != Some(M::NVDM_TYPE.into()) { 482 dev_err!( 483 dev, 484 "Expected NVDM type {:?} in reply, got {:#x}\n", 485 M::NVDM_TYPE, 486 command_nvdm_type 487 ); 488 return Err(EIO); 489 } 490 491 if error_code != 0 { 492 dev_err!( 493 dev, 494 "NVDM command {:?} failed with error {:#x}\n", 495 M::NVDM_TYPE, 496 error_code 497 ); 498 return Err(EIO); 499 } 500 501 Ok(response_buf) 502 } 503 504 /// Reads the active vGPU mode from FSP using the PRC protocol. 505 /// 506 /// Queries FSP's Management Partition for the active vGPU mode knob value. 507 pub(crate) fn read_vgpu_mode( 508 &mut self, 509 dev: &device::Device<device::Bound>, 510 ) -> Result<VgpuMode> { 511 let msg = FspPrcMessage::new( 512 PrcMessageSubcmd::Read, 513 PrcObjectId::VgpuMode, 514 PrcFlags::from(PrcFlag::Active), 515 ); 516 517 let response_buf = self.send_sync_fsp(dev, &msg)?; 518 let (prc_response, _) = 519 FspPrcResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 520 dev_err!(dev, "PRC response too small: {}\n", response_buf.len()); 521 EIO 522 })?; 523 524 let prc_data = prc_response.prc_data; 525 526 VgpuMode::try_from(prc_data).inspect_err(|_| { 527 dev_err!(dev, "Unexpected vGPU mode value: {:#x}\n", prc_data.value()); 528 }) 529 } 530 531 /// Boots GSP FMC via FSP Chain of Trust. 532 /// 533 /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it 534 /// to FSP, and waits for the response. 535 pub(crate) fn boot_fmc( 536 &mut self, 537 dev: &device::Device<device::Bound>, 538 fb_info: &FbSizes, 539 args: &FmcBootArgs<'_>, 540 ) -> Result { 541 dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset); 542 543 let msg = KBox::init(FspCotMessage::new(fb_info, &self.fsp_fw, args)?, GFP_KERNEL)?; 544 545 let _response_buf = self.send_sync_fsp(dev, &*msg)?; 546 547 dev_dbg!(dev, "FSP Chain of Trust completed successfully\n"); 548 Ok(()) 549 } 550 } 551