xref: /linux/drivers/gpu/nova-core/fsp.rs (revision e9e2a24d9493f50a3cc73e112b0dbdf91f319851)
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     io::poll::read_poll_timeout,
13     prelude::*,
14     time::Delta,
15     transmute::{
16         AsBytes,
17         FromBytes, //
18     },
19 };
20 
21 use crate::{
22     driver::Bar0,
23     falcon::{
24         fsp::Fsp as FspEngine,
25         Falcon, //
26     },
27     firmware::fsp::FspFirmware,
28     gpu::Chipset,
29     mctp::{
30         MctpHeader,
31         NvdmHeader,
32         NvdmType, //
33     },
34     regs, //
35 };
36 
37 mod hal;
38 
39 /// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`).
40 #[repr(C, packed)]
41 #[derive(Clone, Copy)]
42 struct NvdmPayloadCommandResponse {
43     task_id: u32,
44     command_nvdm_type: u32,
45     error_code: u32,
46 }
47 
48 /// Complete FSP response structure with MCTP and NVDM headers.
49 #[repr(C, packed)]
50 #[derive(Clone, Copy)]
51 struct FspResponse {
52     mctp_header: MctpHeader,
53     nvdm_header: NvdmHeader,
54     response: NvdmPayloadCommandResponse,
55 }
56 
57 // SAFETY: FspResponse is a packed C struct with only integral fields.
58 unsafe impl FromBytes for FspResponse {}
59 
60 /// Trait implemented by types representing a message to send to FSP.
61 ///
62 /// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
63 /// a given message, following the same pattern as GSP's `CommandToGsp`.
64 trait MessageToFsp: AsBytes {
65     /// NVDM type identifying this message to FSP.
66     const NVDM_TYPE: NvdmType;
67 }
68 
69 /// FSP interface for Hopper/Blackwell GPUs.
70 ///
71 /// An `Fsp` is produced by [`Fsp::wait_secure_boot`], which only returns once FSP secure boot
72 /// has completed. It owns the FSP falcon and the FMC firmware, which are used for the subsequent
73 /// Chain of Trust boot.
74 pub(crate) struct Fsp {
75     falcon: Falcon<FspEngine>,
76     #[expect(dead_code)]
77     fsp_fw: FspFirmware,
78 }
79 
80 impl Fsp {
81     /// Waits for FSP secure boot completion, then returns the [`Fsp`] interface.
82     ///
83     /// Polls the thermal scratch register until FSP signals boot completion or the timeout
84     /// elapses. Returning an [`Fsp`] only on success guarantees, at the API level, that the
85     /// interface is not used before secure boot has completed.
86     pub(crate) fn wait_secure_boot(
87         dev: &device::Device<device::Bound>,
88         bar: &Bar0,
89         chipset: Chipset,
90         fsp_fw: FspFirmware,
91     ) -> Result<Fsp> {
92         /// FSP secure boot completion timeout in milliseconds.
93         const FSP_SECURE_BOOT_TIMEOUT_MS: i64 = 5000;
94 
95         let hal = hal::fsp_hal(chipset).ok_or(ENOTSUPP)?;
96         let falcon = Falcon::<FspEngine>::new(dev, chipset)?;
97 
98         read_poll_timeout(
99             || Ok(hal.fsp_boot_status(bar)),
100             |&status| status == regs::NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS,
101             Delta::from_millis(10),
102             Delta::from_millis(FSP_SECURE_BOOT_TIMEOUT_MS),
103         )
104         .inspect_err(|e| {
105             dev_err!(dev, "FSP secure boot completion error: {:?}\n", e);
106         })?;
107 
108         Ok(Fsp { falcon, fsp_fw })
109     }
110 
111     /// Sends a message to FSP and waits for the response.
112     #[expect(dead_code)]
113     fn send_sync_fsp<M>(&mut self, dev: &device::Device, bar: &Bar0, msg: &M) -> Result
114     where
115         M: MessageToFsp,
116     {
117         self.falcon.send_msg(bar, msg.as_bytes())?;
118 
119         let response_buf = self.falcon.recv_msg(bar).inspect_err(|e| {
120             dev_err!(dev, "FSP response error: {:?}\n", e);
121         })?;
122 
123         let (response, _) = FspResponse::from_bytes_prefix(&response_buf[..]).ok_or_else(|| {
124             dev_err!(dev, "FSP response too small: {}\n", response_buf.len());
125             EIO
126         })?;
127 
128         let mctp_header = response.mctp_header;
129         let nvdm_header = response.nvdm_header;
130         let command_nvdm_type = response.response.command_nvdm_type;
131         let error_code = response.response.error_code;
132 
133         if !mctp_header.is_single_packet() {
134             dev_err!(
135                 dev,
136                 "Unexpected MCTP header in FSP reply: {:x?}\n",
137                 mctp_header,
138             );
139             return Err(EIO);
140         }
141 
142         if !nvdm_header.validate(NvdmType::FspResponse) {
143             dev_err!(
144                 dev,
145                 "Unexpected NVDM header in FSP reply: {:x?}\n",
146                 nvdm_header,
147             );
148             return Err(EIO);
149         }
150 
151         if command_nvdm_type != u8::from(M::NVDM_TYPE).into() {
152             dev_err!(
153                 dev,
154                 "Expected NVDM type {:?} in reply, got {:#x}\n",
155                 M::NVDM_TYPE,
156                 command_nvdm_type
157             );
158             return Err(EIO);
159         }
160 
161         if error_code != 0 {
162             dev_err!(
163                 dev,
164                 "NVDM command {:?} failed with error {:#x}\n",
165                 M::NVDM_TYPE,
166                 error_code
167             );
168             return Err(EIO);
169         }
170 
171         Ok(())
172     }
173 }
174