xref: /linux/drivers/gpu/nova-core/fsp.rs (revision d317e4585fa39bcee4d075f5c485494b0f238713)
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 
191 /// FSP interface for Hopper/Blackwell GPUs.
192 ///
193 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
194 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent
195 /// Chain of Trust boot.
196 pub(crate) struct Fsp {
197     falcon: Falcon<FspEngine>,
198     fsp_fw: FspFirmware,
199 }
200 
201 impl Fsp {
202     /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface.
203     ///
204     /// Polls the thermal scratch register until FSP signals boot completion or the timeout
205     /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the
206     /// interface is not used before secure boot has completed.
207     pub(crate) fn wait_secure_boot(
208         dev: &device::Device<device::Bound>,
209         bar: &Bar0,
210         chipset: Chipset,
211         fsp_fw: FspFirmware,
212     ) -> Result<Fsp> {
213         /// FSP secure boot completion timeout in milliseconds.
214         const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000;
215 
216         let hal = hal::fsp_hal(chipset).ok_or(ENOTSUPP)?;
217         let falcon = Falcon::<FspEngine>::new(dev, chipset)?;
218 
219         read_poll_timeout(
220             || Ok(hal.fsp_boot_status(bar)),
221             |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS,
222             Delta::from_millis(10),
223             Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS),
224         )
225         .inspect_err(|e| {
226             dev_err!(dev, "FSP secure boot completion error: {:?}\n", e);
227         })?;
228 
229         Ok(Fsp { falcon, fsp_fw })
230     }
231 
232     /// Sends a message to FSP and waits for the response.
233     fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result
234     where
235         M: MessageToFsp,
236     {
237         self.falcon.send_msg(bar, msg.as_bytes())?;
238 
239         let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| {
240             dev_err!(dev, "FSP response error: {:?}\n", e);
241         })?;
242 
243         let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
244             dev_err!(dev, "FSP response too small: {}\n", response_buf.len());
245             EIO
246         })?;
247 
248         let mctp_header = response.mctp_header;
249         let nvdm_header = response.nvdm_header;
250         let command_nvdm_type = response.response.command_nvdm_type;
251         let error_code = response.response.error_code;
252 
253         if !mctp_header.is_single_packet() {
254             dev_err!(
255                 dev,
256                 "Unexpected MCTP header in FSP reply: {:x?}\n",
257                 mctp_header,
258             );
259             return Err(EIO);
260         }
261 
262         if !nvdm_header.validate(NvdmType::FspResponse) {
263             dev_err!(
264                 dev,
265                 "Unexpected NVDM header in FSP reply: {:x?}\n",
266                 nvdm_header,
267             );
268             return Err(EIO);
269         }
270 
271         if command_nvdm_type != u8::from(M::NVDM_TYPE).into() {
272             dev_err!(
273                 dev,
274                 "Expected NVDM type {:?} in reply, got {:#x}\n",
275                 M::NVDM_TYPE,
276                 command_nvdm_type
277             );
278             return Err(EIO);
279         }
280 
281         if error_code != 0 {
282             dev_err!(
283                 dev,
284                 "NVDM command {:?} failed with error {:#x}\n",
285                 M::NVDM_TYPE,
286                 error_code
287             );
288             return Err(EIO);
289         }
290 
291         Ok(())
292     }
293 
294     /// Boots GSP FMC via FSP Chain of Trust.
295     ///
296     /// Builds the CoT message from the pre-configured [`FmcBootArgs`], sends it
297     /// to FSP, and waits for the response.
298     pub(crate) fn boot_fmc(
299         &mut self,
300         dev: &device::Device<device::Bound>,
301         bar: &Bar0,
302         fb_layout: &FbLayout,
303         args: &FmcBootArgs,
304     ) -> Result {
305         dev_dbg!(dev, "Starting FSP boot sequence for {}\n", args.chipset);
306 
307         let msg = KBox::init(FspMessage::new(fb_layout, &self.fsp_fw, args)?, GFP_KERNEL)?;
308 
309         self.send_sync_fsp(dev, bar, &*msg)?;
310 
311         dev_dbg!(dev, "FSP Chain of Trust completed successfully\n");
312         Ok(())
313     }
314 }
315