xref: /linux/drivers/gpu/nova-core/firmware/fwsec.rs (revision 9725005e2b4bac2f490bef2165eab18fc36b5b67)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! FWSEC is a High Secure firmware that is extracted from the BIOS and performs the first step of
4 //! the GSP startup by creating the WPR2 memory region and copying critical areas of the VBIOS into
5 //! it after authenticating them, ensuring they haven't been tampered with. It runs on the GSP
6 //! falcon.
7 //!
8 //! Before being run, it needs to be patched in two areas:
9 //!
10 //! - The command to be run, as this firmware can perform several tasks ;
11 //! - The ucode signature, so the GSP falcon can run FWSEC in HS mode.
12 
13 use core::marker::PhantomData;
14 
15 use kernel::{
16     device::{
17         self,
18         Device, //
19     },
20     prelude::*,
21     transmute::{
22         AsBytes,
23         FromBytes, //
24     },
25 };
26 
27 use crate::{
28     driver::Bar0,
29     falcon::{
30         gsp::Gsp,
31         Falcon,
32         FalconBromParams,
33         FalconDmaLoadTarget,
34         FalconDmaLoadable,
35         FalconFirmware, //
36     },
37     firmware::{
38         FalconUCodeDesc,
39         FirmwareObject,
40         FirmwareSignature,
41         Signed,
42         Unsigned, //
43     },
44     num::FromSafeCast,
45     vbios::Vbios,
46 };
47 
48 const NVFW_FALCON_APPIF_ID_DMEMMAPPER: u32 = 0x4;
49 
50 #[repr(C)]
51 #[derive(Debug)]
52 struct FalconAppifHdrV1 {
53     version: u8,
54     header_size: u8,
55     entry_size: u8,
56     entry_count: u8,
57 }
58 // SAFETY: Any byte sequence is valid for this struct.
59 unsafe impl FromBytes for FalconAppifHdrV1 {}
60 
61 #[repr(C, packed)]
62 #[derive(Debug)]
63 struct FalconAppifV1 {
64     id: u32,
65     dmem_base: u32,
66 }
67 // SAFETY: Any byte sequence is valid for this struct.
68 unsafe impl FromBytes for FalconAppifV1 {}
69 
70 #[derive(Debug)]
71 #[repr(C, packed)]
72 struct FalconAppifDmemmapperV3 {
73     signature: u32,
74     version: u16,
75     size: u16,
76     cmd_in_buffer_offset: u32,
77     cmd_in_buffer_size: u32,
78     cmd_out_buffer_offset: u32,
79     cmd_out_buffer_size: u32,
80     nvf_img_data_buffer_offset: u32,
81     nvf_img_data_buffer_size: u32,
82     printf_buffer_hdr: u32,
83     ucode_build_time_stamp: u32,
84     ucode_signature: u32,
85     init_cmd: u32,
86     ucode_feature: u32,
87     ucode_cmd_mask0: u32,
88     ucode_cmd_mask1: u32,
89     multi_tgt_tbl: u32,
90 }
91 // SAFETY: Any byte sequence is valid for this struct.
92 unsafe impl FromBytes for FalconAppifDmemmapperV3 {}
93 // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
94 unsafe impl AsBytes for FalconAppifDmemmapperV3 {}
95 
96 #[derive(Debug)]
97 #[repr(C, packed)]
98 struct ReadVbios {
99     ver: u32,
100     hdr: u32,
101     addr: u64,
102     size: u32,
103     flags: u32,
104 }
105 // SAFETY: Any byte sequence is valid for this struct.
106 unsafe impl FromBytes for ReadVbios {}
107 // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
108 unsafe impl AsBytes for ReadVbios {}
109 
110 #[derive(Debug)]
111 #[repr(C, packed)]
112 struct FrtsRegion {
113     ver: u32,
114     hdr: u32,
115     addr: u32,
116     size: u32,
117     ftype: u32,
118 }
119 // SAFETY: Any byte sequence is valid for this struct.
120 unsafe impl FromBytes for FrtsRegion {}
121 // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
122 unsafe impl AsBytes for FrtsRegion {}
123 
124 const NVFW_FRTS_CMD_REGION_TYPE_FB: u32 = 2;
125 
126 #[repr(C, packed)]
127 struct FrtsCmd {
128     read_vbios: ReadVbios,
129     frts_region: FrtsRegion,
130 }
131 // SAFETY: Any byte sequence is valid for this struct.
132 unsafe impl FromBytes for FrtsCmd {}
133 // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
134 unsafe impl AsBytes for FrtsCmd {}
135 
136 const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS: u32 = 0x15;
137 const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB: u32 = 0x19;
138 
139 /// Command for the [`FwsecFirmware`] to execute.
140 pub(crate) enum FwsecCommand {
141     /// Asks [`FwsecFirmware`] to carve out the WPR2 area and place a verified copy of the VBIOS
142     /// image into it.
143     Frts { frts_addr: u64, frts_size: u64 },
144     /// Asks [`FwsecFirmware`] to load pre-OS apps on the PMU.
145     #[expect(dead_code)]
146     Sb,
147 }
148 
149 /// Size of the signatures used in FWSEC.
150 const BCRT30_RSA3K_SIG_SIZE: usize = 384;
151 
152 /// A single signature that can be patched into a FWSEC image.
153 #[repr(transparent)]
154 pub(crate) struct Bcrt30Rsa3kSignature([u8; BCRT30_RSA3K_SIG_SIZE]);
155 
156 /// SAFETY: A signature is just an array of bytes.
157 unsafe impl FromBytes for Bcrt30Rsa3kSignature {}
158 
159 impl From<[u8; BCRT30_RSA3K_SIG_SIZE]> for Bcrt30Rsa3kSignature {
160     fn from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self {
161         Self(sig)
162     }
163 }
164 
165 impl AsRef<[u8]> for Bcrt30Rsa3kSignature {
166     fn as_ref(&self) -> &[u8] {
167         &self.0
168     }
169 }
170 
171 impl FirmwareSignature<FwsecFirmware> for Bcrt30Rsa3kSignature {}
172 
173 /// The FWSEC microcode, extracted from the BIOS and to be run on the GSP falcon.
174 ///
175 /// It is responsible for e.g. carving out the WPR2 region as the first step of the GSP bootflow.
176 pub(crate) struct FwsecFirmware {
177     /// Descriptor of the firmware.
178     desc: FalconUCodeDesc,
179     /// Object containing the firmware binary.
180     ucode: FirmwareObject<Self, Signed>,
181 }
182 
183 impl FalconDmaLoadable for FwsecFirmware {
184     fn as_slice(&self) -> &[u8] {
185         self.ucode.0.as_slice()
186     }
187 
188     fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
189         self.desc.imem_sec_load_params()
190     }
191 
192     fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
193         self.desc.imem_ns_load_params()
194     }
195 
196     fn dmem_load_params(&self) -> FalconDmaLoadTarget {
197         self.desc.dmem_load_params()
198     }
199 }
200 
201 impl FalconFirmware for FwsecFirmware {
202     type Target = Gsp;
203 
204     fn brom_params(&self) -> FalconBromParams {
205         FalconBromParams {
206             pkc_data_offset: self.desc.pkc_data_offset(),
207             engine_id_mask: self.desc.engine_id_mask(),
208             ucode_id: self.desc.ucode_id(),
209         }
210     }
211 
212     fn boot_addr(&self) -> u32 {
213         0
214     }
215 }
216 
217 impl FirmwareObject<FwsecFirmware, Unsigned> {
218     fn new_fwsec(bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
219         let desc = bios.fwsec_image().header()?;
220         let mut ucode = KVVec::new();
221         ucode.extend_from_slice(bios.fwsec_image().ucode(&desc)?, GFP_KERNEL)?;
222 
223         let hdr_offset = desc
224             .imem_load_size()
225             .checked_add(desc.interface_offset())
226             .map(usize::from_safe_cast)
227             .ok_or(EINVAL)?;
228 
229         let hdr = ucode
230             .get(hdr_offset..)
231             .and_then(FalconAppifHdrV1::from_bytes_prefix)
232             .ok_or(EINVAL)?
233             .0;
234 
235         if hdr.version != 1 {
236             return Err(EINVAL);
237         }
238 
239         // Find the DMEM mapper section in the firmware.
240         for i in 0..usize::from(hdr.entry_count) {
241             // CALC: hdr_offset + header_size + i * entry_size.
242             let entry_offset = hdr_offset
243                 .checked_add(usize::from(hdr.header_size))
244                 .and_then(|o| o.checked_add(i.checked_mul(usize::from(hdr.entry_size))?))
245                 .ok_or(EINVAL)?;
246 
247             let app = ucode
248                 .get(entry_offset..)
249                 .and_then(FalconAppifV1::from_bytes_prefix)
250                 .ok_or(EINVAL)?
251                 .0;
252 
253             if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
254                 continue;
255             }
256             let dmem_base = app.dmem_base;
257 
258             let dmem_mapper_offset = desc
259                 .imem_load_size()
260                 .checked_add(dmem_base)
261                 .map(usize::from_safe_cast)
262                 .ok_or(EINVAL)?;
263 
264             let dmem_mapper = ucode
265                 .get_mut(dmem_mapper_offset..)
266                 .and_then(FalconAppifDmemmapperV3::from_bytes_mut_prefix)
267                 .ok_or(EINVAL)?
268                 .0;
269 
270             dmem_mapper.init_cmd = match cmd {
271                 FwsecCommand::Frts { .. } => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS,
272                 FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
273             };
274             let cmd_in_buffer_offset = dmem_mapper.cmd_in_buffer_offset;
275 
276             let frts_cmd_offset = desc
277                 .imem_load_size()
278                 .checked_add(cmd_in_buffer_offset)
279                 .map(usize::from_safe_cast)
280                 .ok_or(EINVAL)?;
281 
282             let frts_cmd = ucode
283                 .get_mut(frts_cmd_offset..)
284                 .and_then(FrtsCmd::from_bytes_mut_prefix)
285                 .ok_or(EINVAL)?
286                 .0;
287 
288             frts_cmd.read_vbios = ReadVbios {
289                 ver: 1,
290                 hdr: u32::try_from(size_of::<ReadVbios>())?,
291                 addr: 0,
292                 size: 0,
293                 flags: 2,
294             };
295             if let FwsecCommand::Frts {
296                 frts_addr,
297                 frts_size,
298             } = cmd
299             {
300                 frts_cmd.frts_region = FrtsRegion {
301                     ver: 1,
302                     hdr: u32::try_from(size_of::<FrtsRegion>())?,
303                     addr: u32::try_from(frts_addr >> 12)?,
304                     size: u32::try_from(frts_size >> 12)?,
305                     ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
306                 };
307             }
308 
309             // Return early as we found and patched the DMEMMAPPER region.
310             return Ok(Self(ucode, PhantomData));
311         }
312 
313         Err(ENOTSUPP)
314     }
315 }
316 
317 impl FwsecFirmware {
318     /// Extract the Fwsec firmware from `bios` and patch it to run on `falcon` with the `cmd`
319     /// command.
320     pub(crate) fn new(
321         dev: &Device<device::Bound>,
322         falcon: &Falcon<Gsp>,
323         bar: &Bar0,
324         bios: &Vbios,
325         cmd: FwsecCommand,
326     ) -> Result<Self> {
327         let ucode_dma = FirmwareObject::<Self, _>::new_fwsec(bios, cmd)?;
328 
329         // Patch signature if needed.
330         let desc = bios.fwsec_image().header()?;
331         let ucode_signed = if desc.signature_count() != 0 {
332             let sig_base_img = desc
333                 .imem_load_size()
334                 .checked_add(desc.pkc_data_offset())
335                 .map(usize::from_safe_cast)
336                 .ok_or(EINVAL)?;
337             let desc_sig_versions = u32::from(desc.signature_versions());
338             let reg_fuse_version =
339                 falcon.signature_reg_fuse_version(bar, desc.engine_id_mask(), desc.ucode_id())?;
340             dev_dbg!(
341                 dev,
342                 "desc_sig_versions: {:#x}, reg_fuse_version: {}\n",
343                 desc_sig_versions,
344                 reg_fuse_version
345             );
346             let signature_idx = {
347                 let reg_fuse_version_bit = 1 << reg_fuse_version;
348 
349                 // Check if the fuse version is supported by the firmware.
350                 if desc_sig_versions & reg_fuse_version_bit == 0 {
351                     dev_err!(
352                         dev,
353                         "no matching signature: {:#x} {:#x}\n",
354                         reg_fuse_version_bit,
355                         desc_sig_versions,
356                     );
357                     return Err(EINVAL);
358                 }
359 
360                 // `desc_sig_versions` has one bit set per included signature. Thus, the index of
361                 // the signature to patch is the number of bits in `desc_sig_versions` set to `1`
362                 // before `reg_fuse_version_bit`.
363 
364                 // Mask of the bits of `desc_sig_versions` to preserve.
365                 let reg_fuse_version_mask = reg_fuse_version_bit.wrapping_sub(1);
366 
367                 usize::from_safe_cast((desc_sig_versions & reg_fuse_version_mask).count_ones())
368             };
369 
370             dev_dbg!(dev, "patching signature with index {}\n", signature_idx);
371             let signature = bios
372                 .fwsec_image()
373                 .sigs(&desc)
374                 .and_then(|sigs| sigs.get(signature_idx).ok_or(EINVAL))?;
375 
376             ucode_dma.patch_signature(signature, sig_base_img)?
377         } else {
378             ucode_dma.no_patch_signature()
379         };
380 
381         Ok(FwsecFirmware {
382             desc,
383             ucode: ucode_signed,
384         })
385     }
386 
387     /// Loads the FWSEC firmware into `falcon` and execute it.
388     pub(crate) fn run(
389         &self,
390         dev: &Device<device::Bound>,
391         falcon: &Falcon<Gsp>,
392         bar: &Bar0,
393     ) -> Result<()> {
394         // Reset falcon, load the firmware, and run it.
395         falcon
396             .reset(bar)
397             .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
398         falcon
399             .load(dev, bar, self)
400             .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
401         let (mbox0, _) = falcon
402             .boot(bar, Some(0), None)
403             .inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
404         if mbox0 != 0 {
405             dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
406             Err(EIO)
407         } else {
408             Ok(())
409         }
410     }
411 }
412