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