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