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