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