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