xref: /linux/drivers/gpu/nova-core/firmware/fwsec.rs (revision 220994d61cebfc04f071d69049127657c7e8191b)
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 {
from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self132     fn from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self {
133         Self(sig)
134     }
135 }
136 
137 impl AsRef<[u8]> for Bcrt30Rsa3kSignature {
as_ref(&self) -> &[u8]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/
transmute<'a, 'b, T: Sized + FromBytes>( fw: &'a DmaObject, offset: usize, ) -> Result<&'b T>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.
transmute_mut<'a, 'b, T: Sized + FromBytes>( fw: &'a mut DmaObject, offset: usize, ) -> Result<&'b mut T>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 // We need to load full DMEM pages.
206 const DMEM_LOAD_SIZE_ALIGN: u32 = 256;
207 
208 impl FalconLoadParams for FwsecFirmware {
imem_load_params(&self) -> FalconLoadTarget209     fn imem_load_params(&self) -> FalconLoadTarget {
210         FalconLoadTarget {
211             src_start: 0,
212             dst_start: self.desc.imem_phys_base,
213             len: self.desc.imem_load_size,
214         }
215     }
216 
dmem_load_params(&self) -> FalconLoadTarget217     fn dmem_load_params(&self) -> FalconLoadTarget {
218         FalconLoadTarget {
219             src_start: self.desc.imem_load_size,
220             dst_start: self.desc.dmem_phys_base,
221             // TODO[NUMM]: replace with `align_up` once it lands.
222             len: self
223                 .desc
224                 .dmem_load_size
225                 .next_multiple_of(DMEM_LOAD_SIZE_ALIGN),
226         }
227     }
228 
brom_params(&self) -> FalconBromParams229     fn brom_params(&self) -> FalconBromParams {
230         FalconBromParams {
231             pkc_data_offset: self.desc.pkc_data_offset,
232             engine_id_mask: self.desc.engine_id_mask,
233             ucode_id: self.desc.ucode_id,
234         }
235     }
236 
boot_addr(&self) -> u32237     fn boot_addr(&self) -> u32 {
238         0
239     }
240 }
241 
242 impl Deref for FwsecFirmware {
243     type Target = DmaObject;
244 
deref(&self) -> &Self::Target245     fn deref(&self) -> &Self::Target {
246         &self.ucode.0
247     }
248 }
249 
250 impl FalconFirmware for FwsecFirmware {
251     type Target = Gsp;
252 }
253 
254 impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self>255     fn new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
256         let desc = bios.fwsec_image().header(dev)?;
257         let ucode = bios.fwsec_image().ucode(dev, desc)?;
258         let mut dma_object = DmaObject::from_data(dev, ucode)?;
259 
260         let hdr_offset = (desc.imem_load_size + desc.interface_offset) as usize;
261         // SAFETY: we have exclusive access to `dma_object`.
262         let hdr: &FalconAppifHdrV1 = unsafe { transmute(&dma_object, hdr_offset) }?;
263 
264         if hdr.version != 1 {
265             return Err(EINVAL);
266         }
267 
268         // Find the DMEM mapper section in the firmware.
269         for i in 0..hdr.entry_count as usize {
270             let app: &FalconAppifV1 =
271             // SAFETY: we have exclusive access to `dma_object`.
272             unsafe {
273                 transmute(
274                     &dma_object,
275                     hdr_offset + hdr.header_size as usize + i * hdr.entry_size as usize
276                 )
277             }?;
278 
279             if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
280                 continue;
281             }
282 
283             // SAFETY: we have exclusive access to `dma_object`.
284             let dmem_mapper: &mut FalconAppifDmemmapperV3 = unsafe {
285                 transmute_mut(
286                     &mut dma_object,
287                     (desc.imem_load_size + app.dmem_base) as usize,
288                 )
289             }?;
290 
291             // SAFETY: we have exclusive access to `dma_object`.
292             let frts_cmd: &mut FrtsCmd = unsafe {
293                 transmute_mut(
294                     &mut dma_object,
295                     (desc.imem_load_size + dmem_mapper.cmd_in_buffer_offset) as usize,
296                 )
297             }?;
298 
299             frts_cmd.read_vbios = ReadVbios {
300                 ver: 1,
301                 hdr: size_of::<ReadVbios>() as u32,
302                 addr: 0,
303                 size: 0,
304                 flags: 2,
305             };
306 
307             dmem_mapper.init_cmd = match cmd {
308                 FwsecCommand::Frts {
309                     frts_addr,
310                     frts_size,
311                 } => {
312                     frts_cmd.frts_region = FrtsRegion {
313                         ver: 1,
314                         hdr: size_of::<FrtsRegion>() as u32,
315                         addr: (frts_addr >> 12) as u32,
316                         size: (frts_size >> 12) as u32,
317                         ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
318                     };
319 
320                     NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS
321                 }
322                 FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
323             };
324 
325             // Return early as we found and patched the DMEMMAPPER region.
326             return Ok(Self(dma_object, PhantomData));
327         }
328 
329         Err(ENOTSUPP)
330     }
331 }
332 
333 impl FwsecFirmware {
334     /// Extract the Fwsec firmware from `bios` and patch it to run on `falcon` with the `cmd`
335     /// command.
new( dev: &Device<device::Bound>, falcon: &Falcon<Gsp>, bar: &Bar0, bios: &Vbios, cmd: FwsecCommand, ) -> Result<Self>336     pub(crate) fn new(
337         dev: &Device<device::Bound>,
338         falcon: &Falcon<Gsp>,
339         bar: &Bar0,
340         bios: &Vbios,
341         cmd: FwsecCommand,
342     ) -> Result<Self> {
343         let ucode_dma = FirmwareDmaObject::<Self, _>::new_fwsec(dev, bios, cmd)?;
344 
345         // Patch signature if needed.
346         let desc = bios.fwsec_image().header(dev)?;
347         let ucode_signed = if desc.signature_count != 0 {
348             let sig_base_img = (desc.imem_load_size + desc.pkc_data_offset) as usize;
349             let desc_sig_versions = u32::from(desc.signature_versions);
350             let reg_fuse_version =
351                 falcon.signature_reg_fuse_version(bar, desc.engine_id_mask, desc.ucode_id)?;
352             dev_dbg!(
353                 dev,
354                 "desc_sig_versions: {:#x}, reg_fuse_version: {}\n",
355                 desc_sig_versions,
356                 reg_fuse_version
357             );
358             let signature_idx = {
359                 let reg_fuse_version_bit = 1 << reg_fuse_version;
360 
361                 // Check if the fuse version is supported by the firmware.
362                 if desc_sig_versions & reg_fuse_version_bit == 0 {
363                     dev_err!(
364                         dev,
365                         "no matching signature: {:#x} {:#x}\n",
366                         reg_fuse_version_bit,
367                         desc_sig_versions,
368                     );
369                     return Err(EINVAL);
370                 }
371 
372                 // `desc_sig_versions` has one bit set per included signature. Thus, the index of
373                 // the signature to patch is the number of bits in `desc_sig_versions` set to `1`
374                 // before `reg_fuse_version_bit`.
375 
376                 // Mask of the bits of `desc_sig_versions` to preserve.
377                 let reg_fuse_version_mask = reg_fuse_version_bit.wrapping_sub(1);
378 
379                 (desc_sig_versions & reg_fuse_version_mask).count_ones() as usize
380             };
381 
382             dev_dbg!(dev, "patching signature with index {}\n", signature_idx);
383             let signature = bios
384                 .fwsec_image()
385                 .sigs(dev, desc)
386                 .and_then(|sigs| sigs.get(signature_idx).ok_or(EINVAL))?;
387 
388             ucode_dma.patch_signature(signature, sig_base_img)?
389         } else {
390             ucode_dma.no_patch_signature()
391         };
392 
393         Ok(FwsecFirmware {
394             desc: desc.clone(),
395             ucode: ucode_signed,
396         })
397     }
398 
399     /// Loads the FWSEC firmware into `falcon` and execute it.
run( &self, dev: &Device<device::Bound>, falcon: &Falcon<Gsp>, bar: &Bar0, ) -> Result<()>400     pub(crate) fn run(
401         &self,
402         dev: &Device<device::Bound>,
403         falcon: &Falcon<Gsp>,
404         bar: &Bar0,
405     ) -> Result<()> {
406         // Reset falcon, load the firmware, and run it.
407         falcon
408             .reset(bar)
409             .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
410         falcon
411             .dma_load(bar, self)
412             .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
413         let (mbox0, _) = falcon
414             .boot(bar, Some(0), None)
415             .inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
416         if mbox0 != 0 {
417             dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
418             Err(EIO)
419         } else {
420             Ok(())
421         }
422     }
423 }
424