xref: /linux/drivers/gpu/nova-core/fsp.rs (revision ce2d97f714c8a37cac506a41e52da45b09555b81)
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::GspFmcBootParams,
44     mctp::{
45         MctpHeader,
46         NvdmHeader,
47         NvdmType, //
48     },
49     num,
50     regs, //
51 };
52 
53 mod hal;
54 
55 /// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`).
56 #[repr(C, packed)]
57 #[derive(Clone, Copy)]
58 struct NvdmPayloadCommandResponse {
59     task_id: u32,
60     command_nvdm_type: u32,
61     error_code: u32,
62 }
63 
64 /// Common MCTP and NVDM headers shared by all FSP messages.
65 #[repr(C, packed)]
66 #[derive(Clone, Copy)]
67 struct FspMessageHeader {
68     mctp_header: MctpHeader,
69     nvdm_header: NvdmHeader,
70 }
71 
72 // SAFETY: FspMessageHeader is a packed C struct with only integral fields.
73 unsafe impl AsBytes for FspMessageHeader {}
74 
75 // SAFETY: FspMessageHeader is a packed C struct with only integral fields.
76 unsafe impl FromBytes for FspMessageHeader {}
77 
78 impl FspMessageHeader {
79     /// Construct a standard FSP message header for the given NVDM type.
80     fn new(nvdm_type: NvdmType) -> Self {
81         Self {
82             mctp_header: MctpHeader::single_packet(),
83             nvdm_header: NvdmHeader::new(nvdm_type),
84         }
85     }
86 }
87 
88 /// Common FSP response header with MCTP, NVDM and command response payloads.
89 #[repr(C, packed)]
90 #[derive(Clone, Copy)]
91 struct FspResponseHeader {
92     header: FspMessageHeader,
93     response: NvdmPayloadCommandResponse,
94 }
95 
96 // SAFETY: FspResponseHeader is a packed C struct with only integral fields.
97 unsafe impl FromBytes for FspResponseHeader {}
98 
99 /// Trait implemented by types representing a message to send to FSP.
100 ///
101 /// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
102 /// a given message, following the same pattern as GSP's `CommandToGsp`.
103 trait MessageToFsp: AsBytes {
104     /// NVDM type identifying this message to FSP.
105     const NVDM_TYPE: NvdmType;
106 }
107 
108 /// NVDM (NVIDIA Data Model) CoT (Chain of Trust) payload, the main
109 /// message body sent to FSP for Chain of Trust boot.
110 #[repr(C, packed)]
111 #[derive(Clone, Copy, Zeroable)]
112 struct NvdmPayloadCot {
113     version: u16,
114     size: u16,
115     gsp_fmc_sysmem_offset: u64,
116     frts_sysmem_offset: u64,
117     frts_sysmem_size: u32,
118     frts_vidmem_offset: u64,
119     frts_vidmem_size: u32,
120     sigs: FmcSignatures,
121     gsp_boot_args_sysmem_offset: u64,
122 }
123 
124 /// Complete FSP COT (Chain of Trust) message structure.
125 #[repr(C)]
126 #[derive(Clone, Copy)]
127 struct FspCotMessage {
128     header: FspMessageHeader,
129     cot: NvdmPayloadCot,
130 }
131 
132 impl FspCotMessage {
133     /// Returns an in-place initializer for [`FspCotMessage`].
134     fn new<'a>(
135         fb_layout: &FbLayout,
136         fsp_fw: &'a FspFirmware,
137         args: &'a FmcBootArgs,
138     ) -> Result<impl Init<Self> + 'a> {
139         // frts_vidmem_offset is measured from the end of FB, so FRTS sits at
140         // (end of FB) - frts_vidmem_offset.
141         let frts_vidmem_offset = if !args.resume {
142             let frts_reserved_size = fb_layout.heap.len() + u64::from(fb_layout.pmu_reserved_size);
143 
144             frts_reserved_size
145                 .align_up(Alignment::new::<SZ_2M>())
146                 .ok_or(EINVAL)?
147         } else {
148             0
149         };
150 
151         let frts_size: u32 = if !args.resume {
152             fb_layout.frts.len().try_into()?
153         } else {
154             0
155         };
156 
157         let version = hal::fsp_hal(args.chipset).ok_or(ENOTSUPP)?.cot_version();
158         let size = num::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
159 
160         Ok(init!(Self {
161             header: FspMessageHeader::new(NvdmType::Cot),
162             // The payload is packed, so we cannot use `init!`. Initialize it member-by-member using
163             // `chain`.
164             cot <- pin_init::init_zeroed(),
165         })
166         .chain(move |msg| {
167             msg.cot.version = version;
168             msg.cot.size = size;
169             msg.cot.gsp_fmc_sysmem_offset = fsp_fw.fmc_image.dma_handle();
170             msg.cot.frts_vidmem_offset = frts_vidmem_offset;
171             msg.cot.frts_vidmem_size = frts_size;
172             // frts_sysmem_* are left at zero because this path places FRTS in vidmem. The sysmem
173             // fields point to an FRTS buffer in sysmem instead, for systems without VRAM.
174             msg.cot.gsp_boot_args_sysmem_offset = args.fmc_boot_params.dma_handle();
175             msg.cot.sigs = *fsp_fw.fmc_sigs;
176 
177             Ok(())
178         }))
179     }
180 }
181 
182 // SAFETY: `FspCotMessage` is `#[repr(C)]` with no padding, so all of its
183 // bytes are initialized.
184 unsafe impl AsBytes for FspCotMessage {}
185 
186 impl MessageToFsp for FspCotMessage {
187     const NVDM_TYPE: NvdmType = NvdmType::Cot;
188 }
189 
190 /// Bundled arguments for FMC boot via FSP Chain of Trust.
191 pub(crate) struct FmcBootArgs {
192     chipset: Chipset,
193     fmc_boot_params: Coherent<GspFmcBootParams>,
194     resume: bool,
195 }
196 
197 impl FmcBootArgs {
198     /// Builds FMC boot arguments, allocating the DMA-coherent boot parameter
199     /// structure that FSP will read.
200     pub(crate) fn new(
201         dev: &device::Device<device::Bound>,
202         chipset: Chipset,
203         wpr_meta_addr: u64,
204         libos_addr: u64,
205         resume: bool,
206     ) -> Result<Self> {
207         let init = GspFmcBootParams::new(wpr_meta_addr, libos_addr);
208 
209         Ok(Self {
210             chipset,
211             fmc_boot_params: Coherent::<GspFmcBootParams>::init(dev, GFP_KERNEL, init)?,
212             resume,
213         })
214     }
215 
216     /// DMA address of the FMC boot parameters, needed after boot for lockdown
217     /// release polling.
218     pub(crate) fn boot_params_dma_handle(&self) -> u64 {
219         self.fmc_boot_params.dma_handle()
220     }
221 }
222 
223 /// FSP interface for Hopper/Blackwell GPUs.
224 ///
225 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
226 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent
227 /// Chain of Trust boot.
228 pub(crate) struct Fsp<'a> {
229     falcon: Falcon<'a, FspEngine>,
230     fsp_fw: FspFirmware,
231 }
232 
233 impl<'a> Fsp<'a> {
234     /// Attempts to create a `Fsp` instance.
235     ///
236     /// This can involve waiting for FSP secure boot completion, but should be instantaneous in
237     /// practice.
238     ///
239     /// If `chipset` doesn't support FSP, `Ok(None)` is returned.
240     pub(crate) fn try_new(
241         dev: &'a device::Device<device::Bound>,
242         bar: Bar0<'a>,
243         chipset: Chipset,
244     ) -> Result<Option<Self>> {
245         match hal::fsp_hal(chipset) {
246             None => Ok(None),
247             Some(hal) => Self::wait_secure_boot(dev, bar, chipset, hal).map(Option::Some),
248         }
249     }
250 
251     /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface.
252     ///
253     /// Polls the thermal scratch register until FSP signals boot completion or the timeout
254     /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the
255     /// interface is not used before secure boot has completed.
256     fn wait_secure_boot(
257         dev: &'a device::Device<device::Bound>,
258         bar: Bar0<'a>,
259         chipset: Chipset,
260         hal: &'static dyn hal::FspHal,
261     ) -> Result<Fsp<'a>> {
262         /// FSP secure boot completion timeout in milliseconds.
263         const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000;
264 
265         let falcon = Falcon::<FspEngine>::new(dev, chipset, bar)?;
266         let fsp_fw = FspFirmware::new(dev, chipset, FIRMWARE_VERSION)?;
267 
268         read_poll_timeout(
269             || Ok(hal.fsp_boot_status(bar)),
270             |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS,
271             Delta::from_millis(10),
272             Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS),
273         )
274         .inspect_err(|e| {
275             dev_err!(dev, "FSP secure boot completion error: {:?}\n", e);
276         })?;
277 
278         Ok(Fsp { falcon, fsp_fw })
279     }
280 
281     /// Sends a message to FSP and waits for the response.
282     /// Returns the full response buffer on success.
283     fn send_sync_fsp<M>(&mut self, dev: &device::Device, msg: &M) -> Result<KVec<u8>>
284     where
285         M: MessageToFsp,
286     {
287         self.falcon.send_msg(msg.as_bytes())?;
288 
289         let response_buf = self.falcon.recv_msg().inspect_err(|e| {
290             dev_err!(dev, "FSP response error: {:?}\n", e);
291         })?;
292 
293         let (response, _) =
294             FspResponseHeader::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
295                 dev_err!(dev, "FSP response too small: {}\n", response_buf.len());
296                 EIO
297             })?;
298 
299         let mctp_header = response.header.mctp_header;
300         let nvdm_header = response.header.nvdm_header;
301         let command_nvdm_type = response.response.command_nvdm_type;
302         let error_code = response.response.error_code;
303 
304         if !mctp_header.is_single_packet() {
305             dev_err!(
306                 dev,
307                 "Unexpected MCTP header in FSP reply: {:x?}\n",
308                 mctp_header,
309             );
310             return Err(EIO);
311         }
312 
313         if !nvdm_header.validate(NvdmType::FspResponse) {
314             dev_err!(
315                 dev,
316                 "Unexpected NVDM header in FSP reply: {:x?}\n",
317                 nvdm_header,
318             );
319             return Err(EIO);
320         }
321 
322         if command_nvdm_type.try_into_bounded() != Some(M::NVDM_TYPE.into()) {
323             dev_err!(
324                 dev,
325                 "Expected NVDM type {:?} in reply, got {:#x}\n",
326                 M::NVDM_TYPE,
327                 command_nvdm_type
328             );
329             return Err(EIO);
330         }
331 
332         if error_code != 0 {
333             dev_err!(
334                 dev,
335                 "NVDM command {:?} failed with error {:#x}\n",
336                 M::NVDM_TYPE,
337                 error_code
338             );
339             return Err(EIO);
340         }
341 
342         Ok(response_buf)
343     }
344 
345     /// Boots GSP FMC via FSP Chain of Trust.
346     ///
347     /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
348     /// to FSP, and waits for the response.
349     pub(crate) fn boot_fmc(
350         &mut self,
351         dev: &device::Device<device::Bound>,
352         fb_layout: &FbLayout,
353         args: &FmcBootArgs,
354     ) -> Result {
355         dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
356 
357         let msg = KBox::init(
358             FspCotMessage::new(fb_layout, &self.fsp_fw, args)?,
359             GFP_KERNEL,
360         )?;
361 
362         let _response_buf = self.send_sync_fsp(dev, &*msg)?;
363 
364         dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
365         Ok(())
366     }
367 }
368