xref: /linux/drivers/gpu/nova-core/fsp.rs (revision ebc053cf0b4c8ed6bff9a0de6b25f819473ba83a)
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