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