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