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