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