xref: /linux/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs (revision 50b3e0c7c82f32e6ac3ead30f0e0ba96d36a4ff6)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! Bootloader support for the FWSEC firmware.
4 //!
5 //! On Turing, the FWSEC firmware is not loaded directly, but is instead loaded through a small
6 //! bootloader program that performs the required DMA operations. This bootloader itself needs to
7 //! be loaded using PIO.
8 
9 use kernel::{
10     alloc::KVec,
11     device::{
12         self,
13         Device, //
14     },
15     prelude::*,
16     ptr::{
17         Alignable,
18         Alignment, //
19     },
20     sizes,
21     transmute::{
22         AsBytes,
23         FromBytes, //
24     },
25 };
26 
27 use crate::{
28     dma::DmaObject,
29     driver::Bar0,
30     falcon::{
31         self,
32         gsp::Gsp,
33         Falcon,
34         FalconBromParams,
35         FalconDmaLoadable,
36         FalconEngine,
37         FalconFbifMemType,
38         FalconFbifTarget,
39         FalconFirmware,
40         FalconPioDmemLoadTarget,
41         FalconPioImemLoadTarget,
42         FalconPioLoadable, //
43     },
44     firmware::{
45         fwsec::FwsecFirmware,
46         request_firmware,
47         BinHdr,
48         FIRMWARE_VERSION, //
49     },
50     gpu::Chipset,
51     num::FromSafeCast,
52     regs,
53 };
54 
55 /// Descriptor used by RM to figure out the requirements of the boot loader.
56 ///
57 /// Most of its fields appear to be legacy and carry incorrect values, so they are left unused.
58 #[repr(C)]
59 #[derive(Debug, Clone)]
60 struct BootloaderDesc {
61     /// Starting tag of bootloader.
62     start_tag: u32,
63     /// DMEM load offset - unused here as we always load at offset `0`.
64     _dmem_load_off: u32,
65     /// Offset of code section in the image. Unused as there is only one section in the bootloader
66     /// binary.
67     _code_off: u32,
68     /// Size of code section in the image.
69     code_size: u32,
70     /// Offset of data section in the image. Unused as we build the data section ourselves.
71     _data_off: u32,
72     /// Size of data section in the image. Unused as we build the data section ourselves.
73     _data_size: u32,
74 }
75 // SAFETY: any byte sequence is valid for this struct.
76 unsafe impl FromBytes for BootloaderDesc {}
77 
78 /// Structure used by the boot-loader to load the rest of the code.
79 ///
80 /// This has to be filled by the GPU driver and copied into DMEM at offset
81 /// [`BootloaderDesc.dmem_load_off`].
82 #[repr(C, packed)]
83 #[derive(Debug, Clone)]
84 struct BootloaderDmemDescV2 {
85     /// Reserved, should always be first element.
86     reserved: [u32; 4],
87     /// 16B signature for secure code, 0s if no secure code.
88     signature: [u32; 4],
89     /// DMA context used by the bootloader while loading code/data.
90     ctx_dma: u32,
91     /// 256B-aligned physical FB address where code is located.
92     code_dma_base: u64,
93     /// Offset from `code_dma_base` where the non-secure code is located.
94     ///
95     /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
96     /// expected to be a mirror image of its loaded state.
97     ///
98     /// Must be multiple of 256.
99     non_sec_code_off: u32,
100     /// Size of the non-secure code part.
101     non_sec_code_size: u32,
102     /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
103     ///
104     /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
105     /// to be a mirror image of its loaded state.
106     ///
107     /// Must be multiple of 256.
108     sec_code_off: u32,
109     /// Size of the secure code part.
110     sec_code_size: u32,
111     /// Code entry point invoked by the bootloader after code is loaded.
112     code_entry_point: u32,
113     /// 256B-aligned physical FB address where data is located.
114     data_dma_base: u64,
115     /// Size of data block (should be multiple of 256B).
116     data_size: u32,
117     /// Number of arguments to be passed to the target firmware being loaded.
118     argc: u32,
119     /// Arguments to be passed to the target firmware being loaded.
120     argv: u32,
121 }
122 // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
123 unsafe impl AsBytes for BootloaderDmemDescV2 {}
124 
125 /// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load
126 /// operation.
127 pub(crate) struct FwsecFirmwareWithBl {
128     /// DMA object the bootloader will copy the firmware from.
129     _firmware_dma: DmaObject,
130     /// Code of the bootloader to be loaded into non-secure IMEM.
131     ucode: KVec<u8>,
132     /// Descriptor to be loaded into DMEM for the bootloader to read.
133     dmem_desc: BootloaderDmemDescV2,
134     /// Range-validated start offset of the firmware code in IMEM.
135     imem_dst_start: u16,
136     /// BROM parameters of the loaded firmware.
137     brom_params: FalconBromParams,
138     /// Range-validated `desc.start_tag`.
139     start_tag: u16,
140 }
141 
142 impl FwsecFirmwareWithBl {
143     /// Loads the bootloader firmware for `dev` and `chipset`, and wrap `firmware` so it can be
144     /// loaded using it.
145     pub(crate) fn new(
146         firmware: FwsecFirmware,
147         dev: &Device<device::Bound>,
148         chipset: Chipset,
149     ) -> Result<Self> {
150         let fw = request_firmware(dev, chipset, "gen_bootloader", FIRMWARE_VERSION)?;
151         let hdr = fw
152             .data()
153             .get(0..size_of::<BinHdr>())
154             .and_then(BinHdr::from_bytes_copy)
155             .ok_or(EINVAL)?;
156 
157         let desc = {
158             let desc_offset = usize::from_safe_cast(hdr.header_offset);
159 
160             fw.data()
161                 .get(desc_offset..)
162                 .and_then(BootloaderDesc::from_bytes_copy_prefix)
163                 .ok_or(EINVAL)?
164                 .0
165         };
166 
167         let ucode = {
168             let ucode_start = usize::from_safe_cast(hdr.data_offset);
169             let code_size = usize::from_safe_cast(desc.code_size);
170             // Align to falcon block size (256 bytes).
171             let aligned_code_size = code_size
172                 .align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>())
173                 .ok_or(EINVAL)?;
174 
175             let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
176             ucode.extend_from_slice(
177                 fw.data()
178                     .get(ucode_start..ucode_start + code_size)
179                     .ok_or(EINVAL)?,
180                 GFP_KERNEL,
181             )?;
182             ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
183 
184             ucode
185         };
186 
187         // `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
188         // uses the same offset parameter for both.
189         //
190         // Thus, the start of the source object needs to be padded with the difference between the
191         // destination and source offsets.
192         //
193         // In practice, this is expected to always be zero but is required for code correctness.
194         let (align_padding, firmware_dma) = {
195             let align_padding = {
196                 let imem_sec = firmware.imem_sec_load_params();
197 
198                 imem_sec
199                     .dst_start
200                     .checked_sub(imem_sec.src_start)
201                     .map(usize::from_safe_cast)
202                     .ok_or(EOVERFLOW)?
203             };
204 
205             let mut firmware_obj = KVVec::new();
206             firmware_obj.extend_with(align_padding, 0u8, GFP_KERNEL)?;
207             firmware_obj.extend_from_slice(firmware.ucode.0.as_slice(), GFP_KERNEL)?;
208 
209             (
210                 align_padding,
211                 DmaObject::from_data(dev, firmware_obj.as_slice())?,
212             )
213         };
214 
215         let dmem_desc = {
216             // Bootloader payload is in non-coherent system memory.
217             const FALCON_DMAIDX_PHYS_SYS_NCOH: u32 = 4;
218 
219             let imem_sec = firmware.imem_sec_load_params();
220             let imem_ns = firmware.imem_ns_load_params().ok_or(EINVAL)?;
221             let dmem = firmware.dmem_load_params();
222 
223             // The bootloader does not have a data destination offset field and copies the data at
224             // the start of DMEM, so it can only be used if the destination offset of the firmware
225             // is 0.
226             if dmem.dst_start != 0 {
227                 return Err(EINVAL);
228             }
229 
230             BootloaderDmemDescV2 {
231                 reserved: [0; 4],
232                 signature: [0; 4],
233                 ctx_dma: FALCON_DMAIDX_PHYS_SYS_NCOH,
234                 code_dma_base: firmware_dma.dma_handle(),
235                 // `dst_start` is also valid as the source offset since the firmware DMA object is
236                 // a mirror image of the target IMEM layout.
237                 non_sec_code_off: imem_ns.dst_start,
238                 non_sec_code_size: imem_ns.len,
239                 // `dst_start` is also valid as the source offset since the firmware DMA object is
240                 // a mirror image of the target IMEM layout.
241                 sec_code_off: imem_sec.dst_start,
242                 sec_code_size: imem_sec.len,
243                 code_entry_point: 0,
244                 // Start of data section is the added padding + the DMEM `src_start` field.
245                 data_dma_base: firmware_dma
246                     .dma_handle()
247                     .checked_add(u64::from_safe_cast(align_padding))
248                     .and_then(|offset| offset.checked_add(dmem.src_start.into()))
249                     .ok_or(EOVERFLOW)?,
250                 data_size: dmem.len,
251                 argc: 0,
252                 argv: 0,
253             }
254         };
255 
256         // The bootloader's code must be loaded in the area right below the first 64K of IMEM.
257         const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K;
258         let imem_dst_start = BOOTLOADER_LOAD_CEILING
259             .checked_sub(ucode.len())
260             .ok_or(EOVERFLOW)?;
261 
262         Ok(Self {
263             _firmware_dma: firmware_dma,
264             ucode,
265             dmem_desc,
266             brom_params: firmware.brom_params(),
267             imem_dst_start: u16::try_from(imem_dst_start)?,
268             start_tag: u16::try_from(desc.start_tag)?,
269         })
270     }
271 
272     /// Loads the bootloader into `falcon` and execute it.
273     ///
274     /// The bootloader will load the FWSEC firmware and then execute it. This function returns
275     /// after FWSEC has reached completion.
276     pub(crate) fn run(
277         &self,
278         dev: &Device<device::Bound>,
279         falcon: &Falcon<Gsp>,
280         bar: &Bar0,
281     ) -> Result<()> {
282         // Reset falcon, load the firmware, and run it.
283         falcon
284             .reset(bar)
285             .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?;
286         falcon
287             .pio_load(bar, self)
288             .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
289 
290         // Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory.
291         regs::NV_PFALCON_FBIF_TRANSCFG::try_update(
292             bar,
293             &Gsp::ID,
294             usize::from_safe_cast(self.dmem_desc.ctx_dma),
295             |v| {
296                 v.set_target(FalconFbifTarget::CoherentSysmem)
297                     .set_mem_type(FalconFbifMemType::Physical)
298             },
299         )?;
300 
301         let (mbox0, _) = falcon
302             .boot(bar, Some(0), None)
303             .inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?;
304         if mbox0 != 0 {
305             dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0);
306             Err(EIO)
307         } else {
308             Ok(())
309         }
310     }
311 }
312 
313 impl FalconFirmware for FwsecFirmwareWithBl {
314     type Target = Gsp;
315 
316     fn brom_params(&self) -> FalconBromParams {
317         self.brom_params.clone()
318     }
319 
320     fn boot_addr(&self) -> u32 {
321         // On V2 platforms, the boot address is extracted from the generic bootloader, because the
322         // gbl is what actually copies FWSEC into memory, so that is what needs to be booted.
323         u32::from(self.start_tag) << 8
324     }
325 }
326 
327 impl FalconPioLoadable for FwsecFirmwareWithBl {
328     fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
329         None
330     }
331 
332     fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
333         Some(FalconPioImemLoadTarget {
334             data: self.ucode.as_ref(),
335             dst_start: self.imem_dst_start,
336             secure: false,
337             start_tag: self.start_tag,
338         })
339     }
340 
341     fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
342         FalconPioDmemLoadTarget {
343             data: self.dmem_desc.as_bytes(),
344             dst_start: 0,
345         }
346     }
347 }
348