projects/linux·blob master · refs

550 lines · 16.0 KiB

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
10use 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
28use 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
54mod hal;
55
56/// PRC message sub-command.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[repr(u8)]
59enum PrcMessageSubcmd {
60 /// Read a PRC knob value.
61 Read = 0x0c,
62}
63
64impl 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)]
73enum PrcObjectId {
74 /// vGPU mode configuration knob.
75 VgpuMode = 0x29,
76}
77
78impl From<PrcObjectId> for u8 {
79 fn from(value: PrcObjectId) -> Self {
80 value as u8
81 }
82}
83
84kernel::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)]
99pub(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)]
109struct 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)]
120struct NvdmPayloadPrc {
121 sub_message_id: u8,
122 flags: u8,
123 object_id: u8,
124 reserved: u8,
125}
126
127impl 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.
140unsafe impl AsBytes for NvdmPayloadPrc {}
141
142/// PRC response payload containing the knob state value.
143#[repr(C, packed)]
144#[derive(Clone, Copy)]
145struct NvdmPayloadPrcResponse {
146 value_low: u8,
147 value_high: u8,
148 reserved1: u8,
149 reserved2: u8,
150}
151
152impl 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
159impl 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)]
174struct FspMessageHeader {
175 mctp_header: MctpHeader,
176 nvdm_header: NvdmHeader,
177}
178
179// SAFETY: FspMessageHeader is a packed C struct with only integral fields.
180unsafe impl AsBytes for FspMessageHeader {}
181
182// SAFETY: FspMessageHeader is a packed C struct with only integral fields.
183unsafe impl FromBytes for FspMessageHeader {}
184
185impl 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)]
198struct FspResponseHeader {
199 header: FspMessageHeader,
200 response: NvdmPayloadCommandResponse,
201}
202
203// SAFETY: FspResponseHeader is a packed C struct with only integral fields.
204unsafe impl FromBytes for FspResponseHeader {}
205
206/// Complete FSP PRC response including the knob state payload.
207#[repr(C, packed)]
208#[derive(Clone, Copy)]
209struct FspPrcResponse {
210 header: FspResponseHeader,
211 prc_data: NvdmPayloadPrcResponse,
212}
213
214// SAFETY: FspPrcResponse is a packed C struct with only integral fields.
215unsafe 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`.
221trait 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)]
230struct 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)]
245struct FspCotMessage {
246 header: FspMessageHeader,
247 cot: NvdmPayloadCot,
248}
249
250impl 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.
314unsafe impl AsBytes for FspCotMessage {}
315
316/// Complete FSP PRC message.
317#[repr(C, packed)]
318#[derive(Clone, Copy)]
319struct FspPrcMessage {
320 header: FspMessageHeader,
321 prc: NvdmPayloadPrc,
322}
323
324impl 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.
335unsafe impl AsBytes for FspPrcMessage {}
336
337impl MessageToFsp for FspCotMessage {
338 const NVDM_TYPE: NvdmType = NvdmType::Cot;
339}
340
341impl MessageToFsp for FspPrcMessage {
342 const NVDM_TYPE: NvdmType = NvdmType::Prc;
343}
344
345/// Bundled arguments for FMC boot via FSP Chain of Trust.
346pub(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
355impl<'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.
387pub(crate) struct Fsp<'a> {
388 falcon: Falcon<'a, FspEngine>,
389 fsp_fw: FspFirmware,
390}
391
392impl<'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}