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