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