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