150b3e0c7STimur Tabi // SPDX-License-Identifier: GPL-2.0 250b3e0c7STimur Tabi 350b3e0c7STimur Tabi //! Bootloader support for the FWSEC firmware. 450b3e0c7STimur Tabi //! 550b3e0c7STimur Tabi //! On Turing, the FWSEC firmware is not loaded directly, but is instead loaded through a small 650b3e0c7STimur Tabi //! bootloader program that performs the required DMA operations. This bootloader itself needs to 750b3e0c7STimur Tabi //! be loaded using PIO. 850b3e0c7STimur Tabi 950b3e0c7STimur Tabi use kernel::{ 1050b3e0c7STimur Tabi alloc::KVec, 1150b3e0c7STimur Tabi device::{ 1250b3e0c7STimur Tabi self, 1350b3e0c7STimur Tabi Device, // 1450b3e0c7STimur Tabi }, 15*1f9283afSAlexandre Courbot dma::Coherent, 1638f7e545SAlexandre Courbot io::{ 1738f7e545SAlexandre Courbot register::WithBase, // 1838f7e545SAlexandre Courbot Io, 1938f7e545SAlexandre Courbot }, 2050b3e0c7STimur Tabi prelude::*, 2150b3e0c7STimur Tabi ptr::{ 2250b3e0c7STimur Tabi Alignable, 2350b3e0c7STimur Tabi Alignment, // 2450b3e0c7STimur Tabi }, 2550b3e0c7STimur Tabi sizes, 2650b3e0c7STimur Tabi transmute::{ 2750b3e0c7STimur Tabi AsBytes, 2850b3e0c7STimur Tabi FromBytes, // 2950b3e0c7STimur Tabi }, 3050b3e0c7STimur Tabi }; 3150b3e0c7STimur Tabi 3250b3e0c7STimur Tabi use crate::{ 3350b3e0c7STimur Tabi driver::Bar0, 3450b3e0c7STimur Tabi falcon::{ 3550b3e0c7STimur Tabi self, 3650b3e0c7STimur Tabi gsp::Gsp, 3750b3e0c7STimur Tabi Falcon, 3850b3e0c7STimur Tabi FalconBromParams, 3950b3e0c7STimur Tabi FalconDmaLoadable, 4050b3e0c7STimur Tabi FalconFbifMemType, 4150b3e0c7STimur Tabi FalconFbifTarget, 4250b3e0c7STimur Tabi FalconFirmware, 4350b3e0c7STimur Tabi FalconPioDmemLoadTarget, 4450b3e0c7STimur Tabi FalconPioImemLoadTarget, 4550b3e0c7STimur Tabi FalconPioLoadable, // 4650b3e0c7STimur Tabi }, 4750b3e0c7STimur Tabi firmware::{ 4850b3e0c7STimur Tabi fwsec::FwsecFirmware, 4950b3e0c7STimur Tabi request_firmware, 5050b3e0c7STimur Tabi BinHdr, 5150b3e0c7STimur Tabi FIRMWARE_VERSION, // 5250b3e0c7STimur Tabi }, 5350b3e0c7STimur Tabi gpu::Chipset, 5450b3e0c7STimur Tabi num::FromSafeCast, 5550b3e0c7STimur Tabi regs, 5650b3e0c7STimur Tabi }; 5750b3e0c7STimur Tabi 5850b3e0c7STimur Tabi /// Descriptor used by RM to figure out the requirements of the boot loader. 5950b3e0c7STimur Tabi /// 6050b3e0c7STimur Tabi /// Most of its fields appear to be legacy and carry incorrect values, so they are left unused. 6150b3e0c7STimur Tabi #[repr(C)] 6250b3e0c7STimur Tabi #[derive(Debug, Clone)] 6350b3e0c7STimur Tabi struct BootloaderDesc { 6450b3e0c7STimur Tabi /// Starting tag of bootloader. 6550b3e0c7STimur Tabi start_tag: u32, 6650b3e0c7STimur Tabi /// DMEM load offset - unused here as we always load at offset `0`. 6750b3e0c7STimur Tabi _dmem_load_off: u32, 6850b3e0c7STimur Tabi /// Offset of code section in the image. Unused as there is only one section in the bootloader 6950b3e0c7STimur Tabi /// binary. 7050b3e0c7STimur Tabi _code_off: u32, 7150b3e0c7STimur Tabi /// Size of code section in the image. 7250b3e0c7STimur Tabi code_size: u32, 7350b3e0c7STimur Tabi /// Offset of data section in the image. Unused as we build the data section ourselves. 7450b3e0c7STimur Tabi _data_off: u32, 7550b3e0c7STimur Tabi /// Size of data section in the image. Unused as we build the data section ourselves. 7650b3e0c7STimur Tabi _data_size: u32, 7750b3e0c7STimur Tabi } 7850b3e0c7STimur Tabi // SAFETY: any byte sequence is valid for this struct. 7950b3e0c7STimur Tabi unsafe impl FromBytes for BootloaderDesc {} 8050b3e0c7STimur Tabi 8150b3e0c7STimur Tabi /// Structure used by the boot-loader to load the rest of the code. 8250b3e0c7STimur Tabi /// 8350b3e0c7STimur Tabi /// This has to be filled by the GPU driver and copied into DMEM at offset 8450b3e0c7STimur Tabi /// [`BootloaderDesc.dmem_load_off`]. 8550b3e0c7STimur Tabi #[repr(C, packed)] 8650b3e0c7STimur Tabi #[derive(Debug, Clone)] 8750b3e0c7STimur Tabi struct BootloaderDmemDescV2 { 8850b3e0c7STimur Tabi /// Reserved, should always be first element. 8950b3e0c7STimur Tabi reserved: [u32; 4], 9050b3e0c7STimur Tabi /// 16B signature for secure code, 0s if no secure code. 9150b3e0c7STimur Tabi signature: [u32; 4], 9250b3e0c7STimur Tabi /// DMA context used by the bootloader while loading code/data. 9350b3e0c7STimur Tabi ctx_dma: u32, 9450b3e0c7STimur Tabi /// 256B-aligned physical FB address where code is located. 9550b3e0c7STimur Tabi code_dma_base: u64, 9650b3e0c7STimur Tabi /// Offset from `code_dma_base` where the non-secure code is located. 9750b3e0c7STimur Tabi /// 9850b3e0c7STimur Tabi /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is 9950b3e0c7STimur Tabi /// expected to be a mirror image of its loaded state. 10050b3e0c7STimur Tabi /// 10150b3e0c7STimur Tabi /// Must be multiple of 256. 10250b3e0c7STimur Tabi non_sec_code_off: u32, 10350b3e0c7STimur Tabi /// Size of the non-secure code part. 10450b3e0c7STimur Tabi non_sec_code_size: u32, 10550b3e0c7STimur Tabi /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256). 10650b3e0c7STimur Tabi /// 10750b3e0c7STimur Tabi /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected 10850b3e0c7STimur Tabi /// to be a mirror image of its loaded state. 10950b3e0c7STimur Tabi /// 11050b3e0c7STimur Tabi /// Must be multiple of 256. 11150b3e0c7STimur Tabi sec_code_off: u32, 11250b3e0c7STimur Tabi /// Size of the secure code part. 11350b3e0c7STimur Tabi sec_code_size: u32, 11450b3e0c7STimur Tabi /// Code entry point invoked by the bootloader after code is loaded. 11550b3e0c7STimur Tabi code_entry_point: u32, 11650b3e0c7STimur Tabi /// 256B-aligned physical FB address where data is located. 11750b3e0c7STimur Tabi data_dma_base: u64, 11850b3e0c7STimur Tabi /// Size of data block (should be multiple of 256B). 11950b3e0c7STimur Tabi data_size: u32, 12050b3e0c7STimur Tabi /// Number of arguments to be passed to the target firmware being loaded. 12150b3e0c7STimur Tabi argc: u32, 12250b3e0c7STimur Tabi /// Arguments to be passed to the target firmware being loaded. 12350b3e0c7STimur Tabi argv: u32, 12450b3e0c7STimur Tabi } 12550b3e0c7STimur Tabi // SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability. 12650b3e0c7STimur Tabi unsafe impl AsBytes for BootloaderDmemDescV2 {} 12750b3e0c7STimur Tabi 12850b3e0c7STimur Tabi /// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load 12950b3e0c7STimur Tabi /// operation. 13050b3e0c7STimur Tabi pub(crate) struct FwsecFirmwareWithBl { 13150b3e0c7STimur Tabi /// DMA object the bootloader will copy the firmware from. 132*1f9283afSAlexandre Courbot _firmware_dma: Coherent<[u8]>, 13350b3e0c7STimur Tabi /// Code of the bootloader to be loaded into non-secure IMEM. 13450b3e0c7STimur Tabi ucode: KVec<u8>, 13550b3e0c7STimur Tabi /// Descriptor to be loaded into DMEM for the bootloader to read. 13650b3e0c7STimur Tabi dmem_desc: BootloaderDmemDescV2, 13750b3e0c7STimur Tabi /// Range-validated start offset of the firmware code in IMEM. 13850b3e0c7STimur Tabi imem_dst_start: u16, 13950b3e0c7STimur Tabi /// BROM parameters of the loaded firmware. 14050b3e0c7STimur Tabi brom_params: FalconBromParams, 14150b3e0c7STimur Tabi /// Range-validated `desc.start_tag`. 14250b3e0c7STimur Tabi start_tag: u16, 14350b3e0c7STimur Tabi } 14450b3e0c7STimur Tabi 14550b3e0c7STimur Tabi impl FwsecFirmwareWithBl { 14650b3e0c7STimur Tabi /// Loads the bootloader firmware for `dev` and `chipset`, and wrap `firmware` so it can be 14750b3e0c7STimur Tabi /// loaded using it. 14850b3e0c7STimur Tabi pub(crate) fn new( 14950b3e0c7STimur Tabi firmware: FwsecFirmware, 15050b3e0c7STimur Tabi dev: &Device<device::Bound>, 15150b3e0c7STimur Tabi chipset: Chipset, 15250b3e0c7STimur Tabi ) -> Result<Self> { 15350b3e0c7STimur Tabi let fw = request_firmware(dev, chipset, "gen_bootloader", FIRMWARE_VERSION)?; 15450b3e0c7STimur Tabi let hdr = fw 15550b3e0c7STimur Tabi .data() 15650b3e0c7STimur Tabi .get(0..size_of::<BinHdr>()) 15750b3e0c7STimur Tabi .and_then(BinHdr::from_bytes_copy) 15850b3e0c7STimur Tabi .ok_or(EINVAL)?; 15950b3e0c7STimur Tabi 16050b3e0c7STimur Tabi let desc = { 16150b3e0c7STimur Tabi let desc_offset = usize::from_safe_cast(hdr.header_offset); 16250b3e0c7STimur Tabi 16350b3e0c7STimur Tabi fw.data() 16450b3e0c7STimur Tabi .get(desc_offset..) 16550b3e0c7STimur Tabi .and_then(BootloaderDesc::from_bytes_copy_prefix) 16650b3e0c7STimur Tabi .ok_or(EINVAL)? 16750b3e0c7STimur Tabi .0 16850b3e0c7STimur Tabi }; 16950b3e0c7STimur Tabi 17050b3e0c7STimur Tabi let ucode = { 17150b3e0c7STimur Tabi let ucode_start = usize::from_safe_cast(hdr.data_offset); 17250b3e0c7STimur Tabi let code_size = usize::from_safe_cast(desc.code_size); 17350b3e0c7STimur Tabi // Align to falcon block size (256 bytes). 17450b3e0c7STimur Tabi let aligned_code_size = code_size 17550b3e0c7STimur Tabi .align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>()) 17650b3e0c7STimur Tabi .ok_or(EINVAL)?; 17750b3e0c7STimur Tabi 17850b3e0c7STimur Tabi let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?; 17950b3e0c7STimur Tabi ucode.extend_from_slice( 18050b3e0c7STimur Tabi fw.data() 18150b3e0c7STimur Tabi .get(ucode_start..ucode_start + code_size) 18250b3e0c7STimur Tabi .ok_or(EINVAL)?, 18350b3e0c7STimur Tabi GFP_KERNEL, 18450b3e0c7STimur Tabi )?; 18550b3e0c7STimur Tabi ucode.resize(aligned_code_size, 0, GFP_KERNEL)?; 18650b3e0c7STimur Tabi 18750b3e0c7STimur Tabi ucode 18850b3e0c7STimur Tabi }; 18950b3e0c7STimur Tabi 19050b3e0c7STimur Tabi // `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and 19150b3e0c7STimur Tabi // uses the same offset parameter for both. 19250b3e0c7STimur Tabi // 19350b3e0c7STimur Tabi // Thus, the start of the source object needs to be padded with the difference between the 19450b3e0c7STimur Tabi // destination and source offsets. 19550b3e0c7STimur Tabi // 19650b3e0c7STimur Tabi // In practice, this is expected to always be zero but is required for code correctness. 19750b3e0c7STimur Tabi let (align_padding, firmware_dma) = { 19850b3e0c7STimur Tabi let align_padding = { 19950b3e0c7STimur Tabi let imem_sec = firmware.imem_sec_load_params(); 20050b3e0c7STimur Tabi 20150b3e0c7STimur Tabi imem_sec 20250b3e0c7STimur Tabi .dst_start 20350b3e0c7STimur Tabi .checked_sub(imem_sec.src_start) 20450b3e0c7STimur Tabi .map(usize::from_safe_cast) 20550b3e0c7STimur Tabi .ok_or(EOVERFLOW)? 20650b3e0c7STimur Tabi }; 20750b3e0c7STimur Tabi 20850b3e0c7STimur Tabi let mut firmware_obj = KVVec::new(); 20950b3e0c7STimur Tabi firmware_obj.extend_with(align_padding, 0u8, GFP_KERNEL)?; 21050b3e0c7STimur Tabi firmware_obj.extend_from_slice(firmware.ucode.0.as_slice(), GFP_KERNEL)?; 21150b3e0c7STimur Tabi 21250b3e0c7STimur Tabi ( 21350b3e0c7STimur Tabi align_padding, 214*1f9283afSAlexandre Courbot Coherent::from_slice(dev, firmware_obj.as_slice(), GFP_KERNEL)?, 21550b3e0c7STimur Tabi ) 21650b3e0c7STimur Tabi }; 21750b3e0c7STimur Tabi 21850b3e0c7STimur Tabi let dmem_desc = { 21950b3e0c7STimur Tabi // Bootloader payload is in non-coherent system memory. 22050b3e0c7STimur Tabi const FALCON_DMAIDX_PHYS_SYS_NCOH: u32 = 4; 22150b3e0c7STimur Tabi 22250b3e0c7STimur Tabi let imem_sec = firmware.imem_sec_load_params(); 22350b3e0c7STimur Tabi let imem_ns = firmware.imem_ns_load_params().ok_or(EINVAL)?; 22450b3e0c7STimur Tabi let dmem = firmware.dmem_load_params(); 22550b3e0c7STimur Tabi 22650b3e0c7STimur Tabi // The bootloader does not have a data destination offset field and copies the data at 22750b3e0c7STimur Tabi // the start of DMEM, so it can only be used if the destination offset of the firmware 22850b3e0c7STimur Tabi // is 0. 22950b3e0c7STimur Tabi if dmem.dst_start != 0 { 23050b3e0c7STimur Tabi return Err(EINVAL); 23150b3e0c7STimur Tabi } 23250b3e0c7STimur Tabi 23350b3e0c7STimur Tabi BootloaderDmemDescV2 { 23450b3e0c7STimur Tabi reserved: [0; 4], 23550b3e0c7STimur Tabi signature: [0; 4], 23650b3e0c7STimur Tabi ctx_dma: FALCON_DMAIDX_PHYS_SYS_NCOH, 23750b3e0c7STimur Tabi code_dma_base: firmware_dma.dma_handle(), 23850b3e0c7STimur Tabi // `dst_start` is also valid as the source offset since the firmware DMA object is 23950b3e0c7STimur Tabi // a mirror image of the target IMEM layout. 24050b3e0c7STimur Tabi non_sec_code_off: imem_ns.dst_start, 24150b3e0c7STimur Tabi non_sec_code_size: imem_ns.len, 24250b3e0c7STimur Tabi // `dst_start` is also valid as the source offset since the firmware DMA object is 24350b3e0c7STimur Tabi // a mirror image of the target IMEM layout. 24450b3e0c7STimur Tabi sec_code_off: imem_sec.dst_start, 24550b3e0c7STimur Tabi sec_code_size: imem_sec.len, 24650b3e0c7STimur Tabi code_entry_point: 0, 24750b3e0c7STimur Tabi // Start of data section is the added padding + the DMEM `src_start` field. 24850b3e0c7STimur Tabi data_dma_base: firmware_dma 24950b3e0c7STimur Tabi .dma_handle() 25050b3e0c7STimur Tabi .checked_add(u64::from_safe_cast(align_padding)) 25150b3e0c7STimur Tabi .and_then(|offset| offset.checked_add(dmem.src_start.into())) 25250b3e0c7STimur Tabi .ok_or(EOVERFLOW)?, 25350b3e0c7STimur Tabi data_size: dmem.len, 25450b3e0c7STimur Tabi argc: 0, 25550b3e0c7STimur Tabi argv: 0, 25650b3e0c7STimur Tabi } 25750b3e0c7STimur Tabi }; 25850b3e0c7STimur Tabi 25950b3e0c7STimur Tabi // The bootloader's code must be loaded in the area right below the first 64K of IMEM. 26050b3e0c7STimur Tabi const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K; 26150b3e0c7STimur Tabi let imem_dst_start = BOOTLOADER_LOAD_CEILING 26250b3e0c7STimur Tabi .checked_sub(ucode.len()) 26350b3e0c7STimur Tabi .ok_or(EOVERFLOW)?; 26450b3e0c7STimur Tabi 26550b3e0c7STimur Tabi Ok(Self { 26650b3e0c7STimur Tabi _firmware_dma: firmware_dma, 26750b3e0c7STimur Tabi ucode, 26850b3e0c7STimur Tabi dmem_desc, 26950b3e0c7STimur Tabi brom_params: firmware.brom_params(), 27050b3e0c7STimur Tabi imem_dst_start: u16::try_from(imem_dst_start)?, 27150b3e0c7STimur Tabi start_tag: u16::try_from(desc.start_tag)?, 27250b3e0c7STimur Tabi }) 27350b3e0c7STimur Tabi } 27450b3e0c7STimur Tabi 27550b3e0c7STimur Tabi /// Loads the bootloader into `falcon` and execute it. 27650b3e0c7STimur Tabi /// 27750b3e0c7STimur Tabi /// The bootloader will load the FWSEC firmware and then execute it. This function returns 27850b3e0c7STimur Tabi /// after FWSEC has reached completion. 27950b3e0c7STimur Tabi pub(crate) fn run( 28050b3e0c7STimur Tabi &self, 28150b3e0c7STimur Tabi dev: &Device<device::Bound>, 28250b3e0c7STimur Tabi falcon: &Falcon<Gsp>, 28350b3e0c7STimur Tabi bar: &Bar0, 28450b3e0c7STimur Tabi ) -> Result<()> { 28550b3e0c7STimur Tabi // Reset falcon, load the firmware, and run it. 28650b3e0c7STimur Tabi falcon 28750b3e0c7STimur Tabi .reset(bar) 28850b3e0c7STimur Tabi .inspect_err(|e| dev_err!(dev, "Failed to reset GSP falcon: {:?}\n", e))?; 28950b3e0c7STimur Tabi falcon 29050b3e0c7STimur Tabi .pio_load(bar, self) 29150b3e0c7STimur Tabi .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?; 29250b3e0c7STimur Tabi 29350b3e0c7STimur Tabi // Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory. 29438f7e545SAlexandre Courbot bar.update( 29538f7e545SAlexandre Courbot regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>() 29638f7e545SAlexandre Courbot .try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma)) 29738f7e545SAlexandre Courbot .ok_or(EINVAL)?, 29850b3e0c7STimur Tabi |v| { 29938f7e545SAlexandre Courbot v.with_target(FalconFbifTarget::CoherentSysmem) 30038f7e545SAlexandre Courbot .with_mem_type(FalconFbifMemType::Physical) 30150b3e0c7STimur Tabi }, 30238f7e545SAlexandre Courbot ); 30350b3e0c7STimur Tabi 30450b3e0c7STimur Tabi let (mbox0, _) = falcon 30550b3e0c7STimur Tabi .boot(bar, Some(0), None) 30650b3e0c7STimur Tabi .inspect_err(|e| dev_err!(dev, "Failed to boot FWSEC firmware: {:?}\n", e))?; 30750b3e0c7STimur Tabi if mbox0 != 0 { 30850b3e0c7STimur Tabi dev_err!(dev, "FWSEC firmware returned error {}\n", mbox0); 30950b3e0c7STimur Tabi Err(EIO) 31050b3e0c7STimur Tabi } else { 31150b3e0c7STimur Tabi Ok(()) 31250b3e0c7STimur Tabi } 31350b3e0c7STimur Tabi } 31450b3e0c7STimur Tabi } 31550b3e0c7STimur Tabi 31650b3e0c7STimur Tabi impl FalconFirmware for FwsecFirmwareWithBl { 31750b3e0c7STimur Tabi type Target = Gsp; 31850b3e0c7STimur Tabi 31950b3e0c7STimur Tabi fn brom_params(&self) -> FalconBromParams { 32050b3e0c7STimur Tabi self.brom_params.clone() 32150b3e0c7STimur Tabi } 32250b3e0c7STimur Tabi 32350b3e0c7STimur Tabi fn boot_addr(&self) -> u32 { 32450b3e0c7STimur Tabi // On V2 platforms, the boot address is extracted from the generic bootloader, because the 32550b3e0c7STimur Tabi // gbl is what actually copies FWSEC into memory, so that is what needs to be booted. 32650b3e0c7STimur Tabi u32::from(self.start_tag) << 8 32750b3e0c7STimur Tabi } 32850b3e0c7STimur Tabi } 32950b3e0c7STimur Tabi 33050b3e0c7STimur Tabi impl FalconPioLoadable for FwsecFirmwareWithBl { 33150b3e0c7STimur Tabi fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> { 33250b3e0c7STimur Tabi None 33350b3e0c7STimur Tabi } 33450b3e0c7STimur Tabi 33550b3e0c7STimur Tabi fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> { 33650b3e0c7STimur Tabi Some(FalconPioImemLoadTarget { 33750b3e0c7STimur Tabi data: self.ucode.as_ref(), 33850b3e0c7STimur Tabi dst_start: self.imem_dst_start, 33950b3e0c7STimur Tabi secure: false, 34050b3e0c7STimur Tabi start_tag: self.start_tag, 34150b3e0c7STimur Tabi }) 34250b3e0c7STimur Tabi } 34350b3e0c7STimur Tabi 34450b3e0c7STimur Tabi fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> { 34550b3e0c7STimur Tabi FalconPioDmemLoadTarget { 34650b3e0c7STimur Tabi data: self.dmem_desc.as_bytes(), 34750b3e0c7STimur Tabi dst_start: 0, 34850b3e0c7STimur Tabi } 34950b3e0c7STimur Tabi } 35050b3e0c7STimur Tabi } 351