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