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