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::{ 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 /// Returns an in-place initializer for [`FspCotMessage`]. 255 fn new<'a>( 256 fb_layout: &FbLayout, 257 fsp_fw: &'a FspFirmware, 258 args: &'a FmcBootArgs<'_>, 259 ) -> Result<impl Init<Self> + 'a> { 260 // frts_vidmem_offset is measured from the end of FB, so FRTS sits at 261 // (end of FB) - frts_vidmem_offset. 262 let frts_vidmem_offset = if !args.resume { 263 let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size); 264 265 frts_reserved_size 266 .align_up(Alignment::new::<SZ_2M>()) 267 .ok_or(EINVAL)? 268 } else { 269 0 270 }; 271 272 let frts_size: u32 = if !args.resume { 273 fb_layout.frts.len().try_into()? 274 } else { 275 0 276 }; 277 278 let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version(); 279 let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>(); 280 281 Ok(init!(Self { 282 header: FspMessageHeader::new(NvdmType::Cot), 283 // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using 284 // `chain`. 285 cot <- pin_init::init_zeroed(), 286 }) 287 .chain(move |msg| { 288 msg.cot.version = version; 289 msg.cot.size = size; 290 msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle(); 291 msg.cot.frts_vidmem_offset = frts_vidmem_offset; 292 msg.cot.frts_vidmem_size = frts_size; 293 // frts_sysmem_* are left at zero because this path places FRTS in vidmem. The sysmem 294 // fields point to an FRTS buffer in sysmem instead, for systems without VRAM. 295 msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle(); 296 msg.cot.sigs = *fsp_fw.fmc_sigs; 297 298 Ok(()) 299 })) 300 } 301 } 302 303 // SAFETY: `FspCotMessage` is `#[repr(C)]` with no padding, so all of its 304 // bytes are initialized. 305 unsafe impl AsBytes for FspCotMessage {} 306 307 /// Complete FSP PRC message. 308 #[repr(C, packed)] 309 #[derive(Clone, Copy)] 310 struct FspPrcMessage { 311 header: FspMessageHeader, 312 prc: NvdmPayloadPrc, 313 } 314 315 impl FspPrcMessage { 316 /// Constructs a PRC message. 317 fn new(subcmd: PrcMessageSubcmd, object_id: PrcObjectId, flags: PrcFlags) -> Self { 318 Self { 319 header: FspMessageHeader::new(NvdmType::Prc), 320 prc: NvdmPayloadPrc::new(subcmd, object_id, flags), 321 } 322 } 323 } 324 325 // SAFETY: FspPrcMessage is a packed C struct with only integral fields. 326 unsafe impl AsBytes for FspPrcMessage {} 327 328 impl MessageToFsp for FspCotMessage { 329 const NVDM_TYPE: NvdmType = NvdmType::Cot; 330 } 331 332 impl MessageToFsp for FspPrcMessage { 333 const NVDM_TYPE: NvdmType = NvdmType::Prc; 334 } 335 336 /// Bundled arguments for FMC boot via FSP Chain of Trust. 337 pub(crate) struct FmcBootArgs<'a> { 338 chipset: Chipset, 339 fmc_boot_params: Coherent<GspFmcBootParams>, 340 resume: bool, 341 // Additional dependencies required to be kept alive for FMC boot. 342 _wpr_meta: &'a Coherent<GspFwWprMeta>, 343 _libos: &'a Coherent<[LibosMemoryRegionInitArgument]>, 344 } 345 346 impl<'a> FmcBootArgs<'a> { 347 /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter 348 /// structure that FSP will read. 349 pub(crate) fn new( 350 dev: &device::Device<device::Bound>, 351 chipset: Chipset, 352 wpr_meta: &'a Coherent<GspFwWprMeta>, 353 libos: &'a Coherent<[LibosMemoryRegionInitArgument]>, 354 resume: bool, 355 ) -> Result<Self> { 356 let init = GspFmcBootParams::new(wpr_meta.dma_handle(), libos.dma_handle()); 357 358 Ok(Self { 359 chipset, 360 fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?, 361 resume, 362 _wpr_meta: wpr_meta, 363 _libos: libos, 364 }) 365 } 366 367 /// Returns the FMC boot parameters allocation. 368 pub(crate) fn boot_params(&self) -> &Coherent<GspFmcBootParams> { 369 &self.fmc_boot_params 370 } 371 } 372 373 /// FSP interface for Hopper/Blackwell GPUs. 374 /// 375 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot 376 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent 377 /// Chain of Trust boot. 378 pub(crate) struct Fsp<'a> { 379 falcon: Falcon<'a, FspEngine>, 380 fsp_fw: FspFirmware, 381 } 382 383 impl<'a> Fsp<'a> { 384 /// Attempts to create a `Fsp` instance. 385 /// 386 /// This can involve waiting for FSP secure boot completion, but should be instantaneous in 387 /// practice. 388 /// 389 /// If `chipset` doesn't support FSP, `Ok(None)` is returned. 390 pub(crate) fn try_new( 391 dev: &'a device::Device<device::Bound>, 392 bar: Bar0<'a>, 393 chipset: Chipset, 394 ) -> Result<Option<Self>> { 395 match hal::fsp_hal(chipset) { 396 None => Ok(None), 397 Some(hal) => Self::wait_secure_boot(dev, bar, chipset, hal).map(Option::Some), 398 } 399 } 400 401 /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface. 402 /// 403 /// Polls the thermal scratch register until FSP signals boot completion or the timeout 404 /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the 405 /// interface is not used before secure boot has completed. 406 fn wait_secure_boot( 407 dev: &'a device::Device<device::Bound>, 408 bar: Bar0<'a>, 409 chipset: Chipset, 410 hal: &'static dyn hal::FspHal, 411 ) -> Result<Fsp<'a>> { 412 /// FSP secure boot completion timeout in milliseconds. 413 const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000; 414 415 let falcon = Falcon::<FspEngine>::new(dev, chipset, bar)?; 416 let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?; 417 418 read_poll_timeout( 419 || Ok(hal.fsp_boot_status(bar)), 420 |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS, 421 Delta::from_millis(10), 422 Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS), 423 ) 424 .inspect_err(|e| { 425 dev_err!(dev, "FSP secure boot completion error: {:?}\n", e); 426 })?; 427 428 Ok(Fsp { falcon, fsp_fw }) 429 } 430 431 /// Sends a message to FSP and waits for the response. 432 /// Returns the full response buffer on success. 433 fn send_sync_fsp<M>(&mut self, dev: &device::Device, msg: &M) -> Result<KVec<u8>> 434 where 435 M: MessageToFsp, 436 { 437 self.falcon.send_msg(msg.as_bytes())?; 438 439 let response_buf = self.falcon.recv_msg().inspect_err(|e| { 440 dev_err!(dev, "FSP response error: {:?}\n", e); 441 })?; 442 443 let (response, _) = 444 FspResponseHeader::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 445 dev_err!(dev, "FSP response too small: {}\n", response_buf.len()); 446 EIO 447 })?; 448 449 let mctp_header = response.header.mctp_header; 450 let nvdm_header = response.header.nvdm_header; 451 let command_nvdm_type = response.response.command_nvdm_type; 452 let error_code = response.response.error_code; 453 454 if !mctp_header.is_single_packet() { 455 dev_err!( 456 dev, 457 "Unexpected MCTP header in FSP reply: {:x?}\n", 458 mctp_header, 459 ); 460 return Err(EIO); 461 } 462 463 if !nvdm_header.validate(NvdmType::FspResponse) { 464 dev_err!( 465 dev, 466 "Unexpected NVDM header in FSP reply: {:x?}\n", 467 nvdm_header, 468 ); 469 return Err(EIO); 470 } 471 472 if command_nvdm_type.try_into_bounded() != Some(M::NVDM_TYPE.into()) { 473 dev_err!( 474 dev, 475 "Expected NVDM type {:?} in reply, got {:#x}\n", 476 M::NVDM_TYPE, 477 command_nvdm_type 478 ); 479 return Err(EIO); 480 } 481 482 if error_code != 0 { 483 dev_err!( 484 dev, 485 "NVDM command {:?} failed with error {:#x}\n", 486 M::NVDM_TYPE, 487 error_code 488 ); 489 return Err(EIO); 490 } 491 492 Ok(response_buf) 493 } 494 495 /// Reads the active vGPU mode from FSP using the PRC protocol. 496 /// 497 /// Queries FSP's Management Partition for the active vGPU mode knob value. 498 pub(crate) fn read_vgpu_mode( 499 &mut self, 500 dev: &device::Device<device::Bound>, 501 ) -> Result<VgpuMode> { 502 let msg = FspPrcMessage::new( 503 PrcMessageSubcmd::Read, 504 PrcObjectId::VgpuMode, 505 PrcFlags::from(PrcFlag::Active), 506 ); 507 508 let response_buf = self.send_sync_fsp(dev, &msg)?; 509 let (prc_response, _) = 510 FspPrcResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| { 511 dev_err!(dev, "PRC response too small: {}\n", response_buf.len()); 512 EIO 513 })?; 514 515 let prc_data = prc_response.prc_data; 516 517 VgpuMode::try_from(prc_data).inspect_err(|_| { 518 dev_err!(dev, "Unexpected vGPU mode value: {:#x}\n", prc_data.value()); 519 }) 520 } 521 522 /// Boots GSP FMC via FSP Chain of Trust. 523 /// 524 /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it 525 /// to FSP, and waits for the response. 526 pub(crate) fn boot_fmc( 527 &mut self, 528 dev: &device::Device<device::Bound>, 529 fb_layout: &FbLayout, 530 args: &FmcBootArgs<'_>, 531 ) -> Result { 532 dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset); 533 534 let msg = KBox::init( 535 FspCotMessage::new(fb_layout, &self.fsp_fw, args)?, 536 GFP_KERNEL, 537 )?; 538 539 let _response_buf = self.send_sync_fsp(dev, &*msg)?; 540 541 dev_dbg!(dev, "FSP Chain of Trust completed successfully\n"); 542 Ok(()) 543 } 544 } 545