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