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