projects/linux·blob master · refs

309 lines · 10.6 KiB

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
9use 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
25use 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)]
58struct 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.
97unsafe impl AsBytes for BootloaderDmemDescV2 {}
98
99/// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load
100/// operation.
101pub(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
116impl 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
275impl 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
289impl 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}