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