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