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