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