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