xref: /linux/drivers/gpu/nova-core/firmware/booter.rs (revision 82ed3243219d160601fdb98742633bee7dc6f360)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! Support for loading and patching the `Booter` firmware. `Booter` is a Heavy Secured firmware
4 //! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon
5 //! (and optionally unload it through a separate firmware image).
6 
7 use core::{
8     marker::PhantomData,
9     ops::Deref, //
10 };
11 
12 use kernel::{
13     device,
14     prelude::*,
15     transmute::FromBytes, //
16 };
17 
18 use crate::{
19     dma::DmaObject,
20     driver::Bar0,
21     falcon::{
22         sec2::Sec2,
23         Falcon,
24         FalconBromParams,
25         FalconFirmware,
26         FalconLoadParams,
27         FalconLoadTarget, //
28     },
29     firmware::{
30         BinFirmware,
31         FirmwareDmaObject,
32         FirmwareSignature,
33         Signed,
34         Unsigned, //
35     },
36     gpu::Chipset,
37     num::{
38         FromSafeCast,
39         IntoSafeCast, //
40     },
41 };
42 
43 /// Local convenience function to return a copy of `S` by reinterpreting the bytes starting at
44 /// `offset` in `slice`.
45 fn frombytes_at<S: FromBytes + Sized>(slice: &[u8], offset: usize) -> Result<S> {
46     slice
47         .get(offset..offset + size_of::<S>())
48         .and_then(S::from_bytes_copy)
49         .ok_or(EINVAL)
50 }
51 
52 /// Heavy-Secured firmware header.
53 ///
54 /// Such firmwares have an application-specific payload that needs to be patched with a given
55 /// signature.
56 #[repr(C)]
57 #[derive(Debug, Clone)]
58 struct HsHeaderV2 {
59     /// Offset to the start of the signatures.
60     sig_prod_offset: u32,
61     /// Size in bytes of the signatures.
62     sig_prod_size: u32,
63     /// Offset to a `u32` containing the location at which to patch the signature in the microcode
64     /// image.
65     patch_loc_offset: u32,
66     /// Offset to a `u32` containing the index of the signature to patch.
67     patch_sig_offset: u32,
68     /// Start offset to the signature metadata.
69     meta_data_offset: u32,
70     /// Size in bytes of the signature metadata.
71     meta_data_size: u32,
72     /// Offset to a `u32` containing the number of signatures in the signatures section.
73     num_sig_offset: u32,
74     /// Offset of the application-specific header.
75     header_offset: u32,
76     /// Size in bytes of the application-specific header.
77     header_size: u32,
78 }
79 
80 // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
81 unsafe impl FromBytes for HsHeaderV2 {}
82 
83 /// Heavy-Secured Firmware image container.
84 ///
85 /// This provides convenient access to the fields of [`HsHeaderV2`] that are actually indices to
86 /// read from in the firmware data.
87 struct HsFirmwareV2<'a> {
88     hdr: HsHeaderV2,
89     fw: &'a [u8],
90 }
91 
92 impl<'a> HsFirmwareV2<'a> {
93     /// Interprets the header of `bin_fw` as a [`HsHeaderV2`] and returns an instance of
94     /// `HsFirmwareV2` for further parsing.
95     ///
96     /// Fails if the header pointed at by `bin_fw` is not within the bounds of the firmware image.
97     fn new(bin_fw: &BinFirmware<'a>) -> Result<Self> {
98         frombytes_at::<HsHeaderV2>(bin_fw.fw, bin_fw.hdr.header_offset.into_safe_cast())
99             .map(|hdr| Self { hdr, fw: bin_fw.fw })
100     }
101 
102     /// Returns the location at which the signatures should be patched in the microcode image.
103     ///
104     /// Fails if the offset of the patch location is outside the bounds of the firmware
105     /// image.
106     fn patch_location(&self) -> Result<u32> {
107         frombytes_at::<u32>(self.fw, self.hdr.patch_loc_offset.into_safe_cast())
108     }
109 
110     /// Returns an iterator to the signatures of the firmware. The iterator can be empty if the
111     /// firmware is unsigned.
112     ///
113     /// Fails if the pointed signatures are outside the bounds of the firmware image.
114     fn signatures_iter(&'a self) -> Result<impl Iterator<Item = BooterSignature<'a>>> {
115         let num_sig = frombytes_at::<u32>(self.fw, self.hdr.num_sig_offset.into_safe_cast())?;
116         let iter = match self.hdr.sig_prod_size.checked_div(num_sig) {
117             // If there are no signatures, return an iterator that will yield zero elements.
118             None => (&[] as &[u8]).chunks_exact(1),
119             Some(sig_size) => {
120                 let patch_sig =
121                     frombytes_at::<u32>(self.fw, self.hdr.patch_sig_offset.into_safe_cast())?;
122                 let signatures_start = usize::from_safe_cast(self.hdr.sig_prod_offset + patch_sig);
123 
124                 self.fw
125                     // Get signatures range.
126                     .get(
127                         signatures_start
128                             ..signatures_start + usize::from_safe_cast(self.hdr.sig_prod_size),
129                     )
130                     .ok_or(EINVAL)?
131                     .chunks_exact(sig_size.into_safe_cast())
132             }
133         };
134 
135         // Map the byte slices into signatures.
136         Ok(iter.map(BooterSignature))
137     }
138 }
139 
140 /// Signature parameters, as defined in the firmware.
141 #[repr(C)]
142 struct HsSignatureParams {
143     /// Fuse version to use.
144     fuse_ver: u32,
145     /// Mask of engine IDs this firmware applies to.
146     engine_id_mask: u32,
147     /// ID of the microcode.
148     ucode_id: u32,
149 }
150 
151 // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
152 unsafe impl FromBytes for HsSignatureParams {}
153 
154 impl HsSignatureParams {
155     /// Returns the signature parameters contained in `hs_fw`.
156     ///
157     /// Fails if the meta data parameter of `hs_fw` is outside the bounds of the firmware image, or
158     /// if its size doesn't match that of [`HsSignatureParams`].
159     fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> {
160         let start = usize::from_safe_cast(hs_fw.hdr.meta_data_offset);
161         let end = start
162             .checked_add(hs_fw.hdr.meta_data_size.into_safe_cast())
163             .ok_or(EINVAL)?;
164 
165         hs_fw
166             .fw
167             .get(start..end)
168             .and_then(Self::from_bytes_copy)
169             .ok_or(EINVAL)
170     }
171 }
172 
173 /// Header for code and data load offsets.
174 #[repr(C)]
175 #[derive(Debug, Clone)]
176 struct HsLoadHeaderV2 {
177     // Offset at which the code starts.
178     os_code_offset: u32,
179     // Total size of the code, for all apps.
180     os_code_size: u32,
181     // Offset at which the data starts.
182     os_data_offset: u32,
183     // Size of the data.
184     os_data_size: u32,
185     // Number of apps following this header. Each app is described by a [`HsLoadHeaderV2App`].
186     num_apps: u32,
187 }
188 
189 // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
190 unsafe impl FromBytes for HsLoadHeaderV2 {}
191 
192 impl HsLoadHeaderV2 {
193     /// Returns the load header contained in `hs_fw`.
194     ///
195     /// Fails if the header pointed at by `hs_fw` is not within the bounds of the firmware image.
196     fn new(hs_fw: &HsFirmwareV2<'_>) -> Result<Self> {
197         frombytes_at::<Self>(hs_fw.fw, hs_fw.hdr.header_offset.into_safe_cast())
198     }
199 }
200 
201 /// Header for app code loader.
202 #[repr(C)]
203 #[derive(Debug, Clone)]
204 struct HsLoadHeaderV2App {
205     /// Offset at which to load the app code.
206     offset: u32,
207     /// Length in bytes of the app code.
208     len: u32,
209 }
210 
211 // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
212 unsafe impl FromBytes for HsLoadHeaderV2App {}
213 
214 impl HsLoadHeaderV2App {
215     /// Returns the [`HsLoadHeaderV2App`] for app `idx` of `hs_fw`.
216     ///
217     /// Fails if `idx` is larger than the number of apps declared in `hs_fw`, or if the header is
218     /// not within the bounds of the firmware image.
219     fn new(hs_fw: &HsFirmwareV2<'_>, idx: u32) -> Result<Self> {
220         let load_hdr = HsLoadHeaderV2::new(hs_fw)?;
221         if idx >= load_hdr.num_apps {
222             Err(EINVAL)
223         } else {
224             frombytes_at::<Self>(
225                 hs_fw.fw,
226                 usize::from_safe_cast(hs_fw.hdr.header_offset)
227                     // Skip the load header...
228                     .checked_add(size_of::<HsLoadHeaderV2>())
229                     // ... and jump to app header `idx`.
230                     .and_then(|offset| {
231                         offset
232                             .checked_add(usize::from_safe_cast(idx).checked_mul(size_of::<Self>())?)
233                     })
234                     .ok_or(EINVAL)?,
235             )
236         }
237     }
238 }
239 
240 /// Signature for Booter firmware. Their size is encoded into the header and not known a compile
241 /// time, so we just wrap a byte slices on which we can implement [`FirmwareSignature`].
242 struct BooterSignature<'a>(&'a [u8]);
243 
244 impl<'a> AsRef<[u8]> for BooterSignature<'a> {
245     fn as_ref(&self) -> &[u8] {
246         self.0
247     }
248 }
249 
250 impl<'a> FirmwareSignature<BooterFirmware> for BooterSignature<'a> {}
251 
252 /// The `Booter` loader firmware, responsible for loading the GSP.
253 pub(crate) struct BooterFirmware {
254     // Load parameters for Secure `IMEM` falcon memory.
255     imem_sec_load_target: FalconLoadTarget,
256     // Load parameters for Non-Secure `IMEM` falcon memory,
257     // used only on Turing and GA100
258     imem_ns_load_target: Option<FalconLoadTarget>,
259     // Load parameters for `DMEM` falcon memory.
260     dmem_load_target: FalconLoadTarget,
261     // BROM falcon parameters.
262     brom_params: FalconBromParams,
263     // Device-mapped firmware image.
264     ucode: FirmwareDmaObject<Self, Signed>,
265 }
266 
267 impl FirmwareDmaObject<BooterFirmware, Unsigned> {
268     fn new_booter(dev: &device::Device<device::Bound>, data: &[u8]) -> Result<Self> {
269         DmaObject::from_data(dev, data).map(|ucode| Self(ucode, PhantomData))
270     }
271 }
272 
273 #[derive(Copy, Clone, Debug, PartialEq)]
274 pub(crate) enum BooterKind {
275     Loader,
276     #[expect(unused)]
277     Unloader,
278 }
279 
280 impl BooterFirmware {
281     /// Parses the Booter firmware contained in `fw`, and patches the correct signature so it is
282     /// ready to be loaded and run on `falcon`.
283     pub(crate) fn new(
284         dev: &device::Device<device::Bound>,
285         kind: BooterKind,
286         chipset: Chipset,
287         ver: &str,
288         falcon: &Falcon<<Self as FalconFirmware>::Target>,
289         bar: &Bar0,
290     ) -> Result<Self> {
291         let fw_name = match kind {
292             BooterKind::Loader => "booter_load",
293             BooterKind::Unloader => "booter_unload",
294         };
295         let fw = super::request_firmware(dev, chipset, fw_name, ver)?;
296         let bin_fw = BinFirmware::new(&fw)?;
297 
298         // The binary firmware embeds a Heavy-Secured firmware.
299         let hs_fw = HsFirmwareV2::new(&bin_fw)?;
300 
301         // The Heavy-Secured firmware embeds a firmware load descriptor.
302         let load_hdr = HsLoadHeaderV2::new(&hs_fw)?;
303 
304         // Offset in `ucode` where to patch the signature.
305         let patch_loc = hs_fw.patch_location()?;
306 
307         let sig_params = HsSignatureParams::new(&hs_fw)?;
308         let brom_params = FalconBromParams {
309             // `load_hdr.os_data_offset` is an absolute index, but `pkc_data_offset` is from the
310             // signature patch location.
311             pkc_data_offset: patch_loc
312                 .checked_sub(load_hdr.os_data_offset)
313                 .ok_or(EINVAL)?,
314             engine_id_mask: u16::try_from(sig_params.engine_id_mask).map_err(|_| EINVAL)?,
315             ucode_id: u8::try_from(sig_params.ucode_id).map_err(|_| EINVAL)?,
316         };
317         let app0 = HsLoadHeaderV2App::new(&hs_fw, 0)?;
318 
319         // Object containing the firmware microcode to be signature-patched.
320         let ucode = bin_fw
321             .data()
322             .ok_or(EINVAL)
323             .and_then(|data| FirmwareDmaObject::<Self, _>::new_booter(dev, data))?;
324 
325         let ucode_signed = {
326             let mut signatures = hs_fw.signatures_iter()?.peekable();
327 
328             if signatures.peek().is_none() {
329                 // If there are no signatures, then the firmware is unsigned.
330                 ucode.no_patch_signature()
331             } else {
332                 // Obtain the version from the fuse register, and extract the corresponding
333                 // signature.
334                 let reg_fuse_version = falcon.signature_reg_fuse_version(
335                     bar,
336                     brom_params.engine_id_mask,
337                     brom_params.ucode_id,
338                 )?;
339 
340                 // `0` means the last signature should be used.
341                 const FUSE_VERSION_USE_LAST_SIG: u32 = 0;
342                 let signature = match reg_fuse_version {
343                     FUSE_VERSION_USE_LAST_SIG => signatures.last(),
344                     // Otherwise hardware fuse version needs to be subtracted to obtain the index.
345                     reg_fuse_version => {
346                         let Some(idx) = sig_params.fuse_ver.checked_sub(reg_fuse_version) else {
347                             dev_err!(dev, "invalid fuse version for Booter firmware\n");
348                             return Err(EINVAL);
349                         };
350                         signatures.nth(idx.into_safe_cast())
351                     }
352                 }
353                 .ok_or(EINVAL)?;
354 
355                 ucode.patch_signature(&signature, patch_loc.into_safe_cast())?
356             }
357         };
358 
359         // There are two versions of Booter, one for Turing/GA100, and another for
360         // GA102+.  The extraction of the IMEM sections differs between the two
361         // versions.  Unfortunately, the file names are the same, and the headers
362         // don't indicate the versions.  The only way to differentiate is by the Chipset.
363         let (imem_sec_dst_start, imem_ns_load_target) = if chipset <= Chipset::GA100 {
364             (
365                 app0.offset,
366                 Some(FalconLoadTarget {
367                     src_start: 0,
368                     dst_start: load_hdr.os_code_offset,
369                     len: load_hdr.os_code_size,
370                 }),
371             )
372         } else {
373             (0, None)
374         };
375 
376         Ok(Self {
377             imem_sec_load_target: FalconLoadTarget {
378                 src_start: app0.offset,
379                 dst_start: imem_sec_dst_start,
380                 len: app0.len,
381             },
382             imem_ns_load_target,
383             dmem_load_target: FalconLoadTarget {
384                 src_start: load_hdr.os_data_offset,
385                 dst_start: 0,
386                 len: load_hdr.os_data_size,
387             },
388             brom_params,
389             ucode: ucode_signed,
390         })
391     }
392 }
393 
394 impl FalconLoadParams for BooterFirmware {
395     fn imem_sec_load_params(&self) -> FalconLoadTarget {
396         self.imem_sec_load_target.clone()
397     }
398 
399     fn imem_ns_load_params(&self) -> Option<FalconLoadTarget> {
400         self.imem_ns_load_target.clone()
401     }
402 
403     fn dmem_load_params(&self) -> FalconLoadTarget {
404         self.dmem_load_target.clone()
405     }
406 
407     fn brom_params(&self) -> FalconBromParams {
408         self.brom_params.clone()
409     }
410 
411     fn boot_addr(&self) -> u32 {
412         if let Some(ns_target) = &self.imem_ns_load_target {
413             ns_target.dst_start
414         } else {
415             self.imem_sec_load_target.src_start
416         }
417     }
418 }
419 
420 impl Deref for BooterFirmware {
421     type Target = DmaObject;
422 
423     fn deref(&self) -> &Self::Target {
424         &self.ucode.0
425     }
426 }
427 
428 impl FalconFirmware for BooterFirmware {
429     type Target = Sec2;
430 }
431