1 // SPDX-License-Identifier: GPL-2.0
2
3 //! FWSEC is a High Secure firmware that is extracted from the BIOS and performs the first step of
4 //! the GSP startup by creating the WPR2 memory region and copying critical areas of the VBIOS into
5 //! it after authenticating them, ensuring they haven't been tampered with. It runs on the GSP
6 //! falcon.
7 //!
8 //! Before being run, it needs to be patched in two areas:
9 //!
10 //! - The command to be run, as this firmware can perform several tasks ;
11 //! - The ucode signature, so the GSP falcon can run FWSEC in HS mode.
12
13 use core::marker::PhantomData;
14 use core::mem::{align_of, size_of};
15 use core::ops::Deref;
16
17 use kernel::device::{self, Device};
18 use kernel::prelude::*;
19 use kernel::transmute::FromBytes;
20
21 use crate::dma::DmaObject;
22 use crate::driver::Bar0;
23 use crate::falcon::gsp::Gsp;
24 use crate::falcon::{Falcon, FalconBromParams, FalconFirmware, FalconLoadParams, FalconLoadTarget};
25 use crate::firmware::{FalconUCodeDescV3, FirmwareDmaObject, FirmwareSignature, Signed, Unsigned};
26 use crate::vbios::Vbios;
27
28 const NVFW_FALCON_APPIF_ID_DMEMMAPPER: u32 = 0x4;
29
30 #[repr(C)]
31 #[derive(Debug)]
32 struct FalconAppifHdrV1 {
33 version: u8,
34 header_size: u8,
35 entry_size: u8,
36 entry_count: u8,
37 }
38 // SAFETY: any byte sequence is valid for this struct.
39 unsafe impl FromBytes for FalconAppifHdrV1 {}
40
41 #[repr(C, packed)]
42 #[derive(Debug)]
43 struct FalconAppifV1 {
44 id: u32,
45 dmem_base: u32,
46 }
47 // SAFETY: any byte sequence is valid for this struct.
48 unsafe impl FromBytes for FalconAppifV1 {}
49
50 #[derive(Debug)]
51 #[repr(C, packed)]
52 struct FalconAppifDmemmapperV3 {
53 signature: u32,
54 version: u16,
55 size: u16,
56 cmd_in_buffer_offset: u32,
57 cmd_in_buffer_size: u32,
58 cmd_out_buffer_offset: u32,
59 cmd_out_buffer_size: u32,
60 nvf_img_data_buffer_offset: u32,
61 nvf_img_data_buffer_size: u32,
62 printf_buffer_hdr: u32,
63 ucode_build_time_stamp: u32,
64 ucode_signature: u32,
65 init_cmd: u32,
66 ucode_feature: u32,
67 ucode_cmd_mask0: u32,
68 ucode_cmd_mask1: u32,
69 multi_tgt_tbl: u32,
70 }
71 // SAFETY: any byte sequence is valid for this struct.
72 unsafe impl FromBytes for FalconAppifDmemmapperV3 {}
73
74 #[derive(Debug)]
75 #[repr(C, packed)]
76 struct ReadVbios {
77 ver: u32,
78 hdr: u32,
79 addr: u64,
80 size: u32,
81 flags: u32,
82 }
83 // SAFETY: any byte sequence is valid for this struct.
84 unsafe impl FromBytes for ReadVbios {}
85
86 #[derive(Debug)]
87 #[repr(C, packed)]
88 struct FrtsRegion {
89 ver: u32,
90 hdr: u32,
91 addr: u32,
92 size: u32,
93 ftype: u32,
94 }
95 // SAFETY: any byte sequence is valid for this struct.
96 unsafe impl FromBytes for FrtsRegion {}
97
98 const NVFW_FRTS_CMD_REGION_TYPE_FB: u32 = 2;
99
100 #[repr(C, packed)]
101 struct FrtsCmd {
102 read_vbios: ReadVbios,
103 frts_region: FrtsRegion,
104 }
105 // SAFETY: any byte sequence is valid for this struct.
106 unsafe impl FromBytes for FrtsCmd {}
107
108 const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS: u32 = 0x15;
109 const NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB: u32 = 0x19;
110
111 /// Command for the [`FwsecFirmware`] to execute.
112 pub(crate) enum FwsecCommand {
113 /// Asks [`FwsecFirmware`] to carve out the WPR2 area and place a verified copy of the VBIOS
114 /// image into it.
115 Frts { frts_addr: u64, frts_size: u64 },
116 /// Asks [`FwsecFirmware`] to load pre-OS apps on the PMU.
117 #[expect(dead_code)]
118 Sb,
119 }
120
121 /// Size of the signatures used in FWSEC.
122 const BCRT30_RSA3K_SIG_SIZE: usize = 384;
123
124 /// A single signature that can be patched into a FWSEC image.
125 #[repr(transparent)]
126 pub(crate) struct Bcrt30Rsa3kSignature([u8; BCRT30_RSA3K_SIG_SIZE]);
127
128 /// SAFETY: A signature is just an array of bytes.
129 unsafe impl FromBytes for Bcrt30Rsa3kSignature {}
130
131 impl From<[u8; BCRT30_RSA3K_SIG_SIZE]> for Bcrt30Rsa3kSignature {
from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self132 fn from(sig: [u8; BCRT30_RSA3K_SIG_SIZE]) -> Self {
133 Self(sig)
134 }
135 }
136
137 impl AsRef<[u8]> for Bcrt30Rsa3kSignature {
as_ref(&self) -> &[u8]138 fn as_ref(&self) -> &[u8] {
139 &self.0
140 }
141 }
142
143 impl FirmwareSignature<FwsecFirmware> for Bcrt30Rsa3kSignature {}
144
145 /// Reinterpret the area starting from `offset` in `fw` as an instance of `T` (which must implement
146 /// [`FromBytes`]) and return a reference to it.
147 ///
148 /// # Safety
149 ///
150 /// Callers must ensure that the region of memory returned is not written for as long as the
151 /// returned reference is alive.
152 ///
153 /// TODO[TRSM][COHA]: Remove this and `transmute_mut` once `CoherentAllocation::as_slice` is
154 /// available and we have a way to transmute objects implementing FromBytes, e.g.:
155 /// https://lore.kernel.org/lkml/20250330234039.29814-1-christiansantoslima21@gmail.com/
transmute<'a, 'b, T: Sized + FromBytes>( fw: &'a DmaObject, offset: usize, ) -> Result<&'b T>156 unsafe fn transmute<'a, 'b, T: Sized + FromBytes>(
157 fw: &'a DmaObject,
158 offset: usize,
159 ) -> Result<&'b T> {
160 if offset + size_of::<T>() > fw.size() {
161 return Err(EINVAL);
162 }
163 if (fw.start_ptr() as usize + offset) % align_of::<T>() != 0 {
164 return Err(EINVAL);
165 }
166
167 // SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
168 // large enough the contains an instance of `T`, which implements `FromBytes`.
169 Ok(unsafe { &*(fw.start_ptr().add(offset).cast::<T>()) })
170 }
171
172 /// Reinterpret the area starting from `offset` in `fw` as a mutable instance of `T` (which must
173 /// implement [`FromBytes`]) and return a reference to it.
174 ///
175 /// # Safety
176 ///
177 /// Callers must ensure that the region of memory returned is not read or written for as long as
178 /// the returned reference is alive.
transmute_mut<'a, 'b, T: Sized + FromBytes>( fw: &'a mut DmaObject, offset: usize, ) -> Result<&'b mut T>179 unsafe fn transmute_mut<'a, 'b, T: Sized + FromBytes>(
180 fw: &'a mut DmaObject,
181 offset: usize,
182 ) -> Result<&'b mut T> {
183 if offset + size_of::<T>() > fw.size() {
184 return Err(EINVAL);
185 }
186 if (fw.start_ptr_mut() as usize + offset) % align_of::<T>() != 0 {
187 return Err(EINVAL);
188 }
189
190 // SAFETY: we have checked that the pointer is properly aligned that its pointed memory is
191 // large enough the contains an instance of `T`, which implements `FromBytes`.
192 Ok(unsafe { &mut *(fw.start_ptr_mut().add(offset).cast::<T>()) })
193 }
194
195 /// The FWSEC microcode, extracted from the BIOS and to be run on the GSP falcon.
196 ///
197 /// It is responsible for e.g. carving out the WPR2 region as the first step of the GSP bootflow.
198 pub(crate) struct FwsecFirmware {
199 /// Descriptor of the firmware.
200 desc: FalconUCodeDescV3,
201 /// GPU-accessible DMA object containing the firmware.
202 ucode: FirmwareDmaObject<Self, Signed>,
203 }
204
205 // We need to load full DMEM pages.
206 const DMEM_LOAD_SIZE_ALIGN: u32 = 256;
207
208 impl FalconLoadParams for FwsecFirmware {
imem_load_params(&self) -> FalconLoadTarget209 fn imem_load_params(&self) -> FalconLoadTarget {
210 FalconLoadTarget {
211 src_start: 0,
212 dst_start: self.desc.imem_phys_base,
213 len: self.desc.imem_load_size,
214 }
215 }
216
dmem_load_params(&self) -> FalconLoadTarget217 fn dmem_load_params(&self) -> FalconLoadTarget {
218 FalconLoadTarget {
219 src_start: self.desc.imem_load_size,
220 dst_start: self.desc.dmem_phys_base,
221 // TODO[NUMM]: replace with `align_up` once it lands.
222 len: self
223 .desc
224 .dmem_load_size
225 .next_multiple_of(DMEM_LOAD_SIZE_ALIGN),
226 }
227 }
228
brom_params(&self) -> FalconBromParams229 fn brom_params(&self) -> FalconBromParams {
230 FalconBromParams {
231 pkc_data_offset: self.desc.pkc_data_offset,
232 engine_id_mask: self.desc.engine_id_mask,
233 ucode_id: self.desc.ucode_id,
234 }
235 }
236
boot_addr(&self) -> u32237 fn boot_addr(&self) -> u32 {
238 0
239 }
240 }
241
242 impl Deref for FwsecFirmware {
243 type Target = DmaObject;
244
deref(&self) -> &Self::Target245 fn deref(&self) -> &Self::Target {
246 &self.ucode.0
247 }
248 }
249
250 impl FalconFirmware for FwsecFirmware {
251 type Target = Gsp;
252 }
253
254 impl FirmwareDmaObject<FwsecFirmware, Unsigned> {
new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self>255 fn new_fwsec(dev: &Device<device::Bound>, bios: &Vbios, cmd: FwsecCommand) -> Result<Self> {
256 let desc = bios.fwsec_image().header(dev)?;
257 let ucode = bios.fwsec_image().ucode(dev, desc)?;
258 let mut dma_object = DmaObject::from_data(dev, ucode)?;
259
260 let hdr_offset = (desc.imem_load_size + desc.interface_offset) as usize;
261 // SAFETY: we have exclusive access to `dma_object`.
262 let hdr: &FalconAppifHdrV1 = unsafe { transmute(&dma_object, hdr_offset) }?;
263
264 if hdr.version != 1 {
265 return Err(EINVAL);
266 }
267
268 // Find the DMEM mapper section in the firmware.
269 for i in 0..hdr.entry_count as usize {
270 let app: &FalconAppifV1 =
271 // SAFETY: we have exclusive access to `dma_object`.
272 unsafe {
273 transmute(
274 &dma_object,
275 hdr_offset + hdr.header_size as usize + i * hdr.entry_size as usize
276 )
277 }?;
278
279 if app.id != NVFW_FALCON_APPIF_ID_DMEMMAPPER {
280 continue;
281 }
282
283 // SAFETY: we have exclusive access to `dma_object`.
284 let dmem_mapper: &mut FalconAppifDmemmapperV3 = unsafe {
285 transmute_mut(
286 &mut dma_object,
287 (desc.imem_load_size + app.dmem_base) as usize,
288 )
289 }?;
290
291 // SAFETY: we have exclusive access to `dma_object`.
292 let frts_cmd: &mut FrtsCmd = unsafe {
293 transmute_mut(
294 &mut dma_object,
295 (desc.imem_load_size + dmem_mapper.cmd_in_buffer_offset) as usize,
296 )
297 }?;
298
299 frts_cmd.read_vbios = ReadVbios {
300 ver: 1,
301 hdr: size_of::<ReadVbios>() as u32,
302 addr: 0,
303 size: 0,
304 flags: 2,
305 };
306
307 dmem_mapper.init_cmd = match cmd {
308 FwsecCommand::Frts {
309 frts_addr,
310 frts_size,
311 } => {
312 frts_cmd.frts_region = FrtsRegion {
313 ver: 1,
314 hdr: size_of::<FrtsRegion>() as u32,
315 addr: (frts_addr >> 12) as u32,
316 size: (frts_size >> 12) as u32,
317 ftype: NVFW_FRTS_CMD_REGION_TYPE_FB,
318 };
319
320 NVFW_FALCON_APPIF_DMEMMAPPER_CMD_FRTS
321 }
322 FwsecCommand::Sb => NVFW_FALCON_APPIF_DMEMMAPPER_CMD_SB,
323 };
324
325 // Return early as we found and patched the DMEMMAPPER region.
326 return Ok(Self(dma_object, PhantomData));
327 }
328
329 Err(ENOTSUPP)
330 }
331 }
332
333 impl FwsecFirmware {
334 /// Extract the Fwsec firmware from `bios` and patch it to run on `falcon` with the `cmd`
335 /// command.
new( dev: &Device<device::Bound>, falcon: &Falcon<Gsp>, bar: &Bar0, bios: &Vbios, cmd: FwsecCommand, ) -> Result<Self>336 pub(crate) fn new(
337 dev: &Device<device::Bound>,
338 falcon: &Falcon<Gsp>,
339 bar: &Bar0,
340 bios: &Vbios,
341 cmd: FwsecCommand,
342 ) -> Result<Self> {
343 let ucode_dma = FirmwareDmaObject::<Self, _>::new_fwsec(dev, bios, cmd)?;
344
345 // Patch signature if needed.
346 let desc = bios.fwsec_image().header(dev)?;
347 let ucode_signed = if desc.signature_count != 0 {
348 let sig_base_img = (desc.imem_load_size + desc.pkc_data_offset) as usize;
349 let desc_sig_versions = u32::from(desc.signature_versions);
350 let reg_fuse_version =
351 falcon.signature_reg_fuse_version(bar, desc.engine_id_mask, desc.ucode_id)?;
352 dev_dbg!(
353 dev,
354 "desc_sig_versions: {:#x}, reg_fuse_version: {}\n",
355 desc_sig_versions,
356 reg_fuse_version
357 );
358 let signature_idx = {
359 let reg_fuse_version_bit = 1 << reg_fuse_version;
360
361 // Check if the fuse version is supported by the firmware.
362 if desc_sig_versions & reg_fuse_version_bit == 0 {
363 dev_err!(
364 dev,
365 "no matching signature: {:#x} {:#x}\n",
366 reg_fuse_version_bit,
367 desc_sig_versions,
368 );
369 return Err(EINVAL);
370 }
371
372 // `desc_sig_versions` has one bit set per included signature. Thus, the index of
373 // the signature to patch is the number of bits in `desc_sig_versions` set to `1`
374 // before `reg_fuse_version_bit`.
375
376 // Mask of the bits of `desc_sig_versions` to preserve.
377 let reg_fuse_version_mask = reg_fuse_version_bit.wrapping_sub(1);
378
379 (desc_sig_versions & reg_fuse_version_mask).count_ones() as usize
380 };
381
382 dev_dbg!(dev, "patching signature with index {}\n", signature_idx);
383 let signature = bios
384 .fwsec_image()
385 .sigs(dev, desc)
386 .and_then(|sigs| sigs.get(signature_idx).ok_or(EINVAL))?;
387
388 ucode_dma.patch_signature(signature, sig_base_img)?
389 } else {
390 ucode_dma.no_patch_signature()
391 };
392
393 Ok(FwsecFirmware {
394 desc: desc.clone(),
395 ucode: ucode_signed,
396 })
397 }
398
399 /// Loads the FWSEC firmware into `falcon` and execute it.
run( &self, dev: &Device<device::Bound>, falcon: &Falcon<Gsp>, bar: &Bar0, ) -> Result<()>400 pub(crate) fn run(
401 &self,
402 dev: &Device<device::Bound>,
403 falcon: &Falcon<Gsp>,
404 bar: &Bar0,
405 ) -> Result<()> {
406 // Reset falcon, load the firmware, and run it.
407 falcon
408 .reset(bar)
409 .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
410 falcon
411 .dma_load(bar, self)
412 .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
413 let (mbox0, _) = falcon
414 .boot(bar, Some(0), None)
415 .inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
416 if mbox0 != 0 {
417 dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
418 Err(EIO)
419 } else {
420 Ok(())
421 }
422 }
423 }
424