Path: blob/master/drivers/gpu/nova-core/firmware/gsp.rs
29281 views
// SPDX-License-Identifier: GPL-2.012use core::mem::size_of_val;34use kernel::device;5use kernel::dma::{DataDirection, DmaAddress};6use kernel::kvec;7use kernel::prelude::*;8use kernel::scatterlist::{Owned, SGTable};910use crate::dma::DmaObject;11use crate::firmware::riscv::RiscvFirmware;12use crate::gpu::{Architecture, Chipset};13use crate::gsp::GSP_PAGE_SIZE;1415/// Ad-hoc and temporary module to extract sections from ELF images.16///17/// Some firmware images are currently packaged as ELF files, where sections names are used as keys18/// to specific and related bits of data. Future firmware versions are scheduled to move away from19/// that scheme before nova-core becomes stable, which means this module will eventually be20/// removed.21mod elf {22use core::mem::size_of;2324use kernel::bindings;25use kernel::str::CStr;26use kernel::transmute::FromBytes;2728/// Newtype to provide a [`FromBytes`] implementation.29#[repr(transparent)]30struct Elf64Hdr(bindings::elf64_hdr);31// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.32unsafe impl FromBytes for Elf64Hdr {}3334#[repr(transparent)]35struct Elf64SHdr(bindings::elf64_shdr);36// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.37unsafe impl FromBytes for Elf64SHdr {}3839/// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.40pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {41let hdr = &elf42.get(0..size_of::<bindings::elf64_hdr>())43.and_then(Elf64Hdr::from_bytes)?44.0;4546// Get all the section headers.47let mut shdr = {48let shdr_num = usize::from(hdr.e_shnum);49let shdr_start = usize::try_from(hdr.e_shoff).ok()?;50let shdr_end = shdr_num51.checked_mul(size_of::<Elf64SHdr>())52.and_then(|v| v.checked_add(shdr_start))?;5354elf.get(shdr_start..shdr_end)55.map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?56};5758// Get the strings table.59let strhdr = shdr60.clone()61.nth(usize::from(hdr.e_shstrndx))62.and_then(Elf64SHdr::from_bytes)?;6364// Find the section which name matches `name` and return it.65shdr.find(|&sh| {66let Some(hdr) = Elf64SHdr::from_bytes(sh) else {67return false;68};6970let Some(name_idx) = strhdr71.072.sh_offset73.checked_add(u64::from(hdr.0.sh_name))74.and_then(|idx| usize::try_from(idx).ok())75else {76return false;77};7879// Get the start of the name.80elf.get(name_idx..)81// Stop at the first `0`.82.and_then(|nstr| nstr.get(0..=nstr.iter().position(|b| *b == 0)?))83// Convert into CStr. This should never fail because of the line above.84.and_then(|nstr| CStr::from_bytes_with_nul(nstr).ok())85// Convert into str.86.and_then(|c_str| c_str.to_str().ok())87// Check that the name matches.88.map(|str| str == name)89.unwrap_or(false)90})91// Return the slice containing the section.92.and_then(|sh| {93let hdr = Elf64SHdr::from_bytes(sh)?;94let start = usize::try_from(hdr.0.sh_offset).ok()?;95let end = usize::try_from(hdr.0.sh_size)96.ok()97.and_then(|sh_size| start.checked_add(sh_size))?;9899elf.get(start..end)100})101}102}103104/// GSP firmware with 3-level radix page tables for the GSP bootloader.105///106/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address107/// space:108///109/// ```text110/// Level 0: 1 page, 1 entry -> points to first level 1 page111/// Level 1: Multiple pages/entries -> each entry points to a level 2 page112/// Level 2: Multiple pages/entries -> each entry points to a firmware page113/// ```114///115/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).116/// Also known as "Radix3" firmware.117#[pin_data]118pub(crate) struct GspFirmware {119/// The GSP firmware inside a [`VVec`], device-mapped via a SG table.120#[pin]121fw: SGTable<Owned<VVec<u8>>>,122/// Level 2 page table whose entries contain DMA addresses of firmware pages.123#[pin]124level2: SGTable<Owned<VVec<u8>>>,125/// Level 1 page table whose entries contain DMA addresses of level 2 pages.126#[pin]127level1: SGTable<Owned<VVec<u8>>>,128/// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.129level0: DmaObject,130/// Size in bytes of the firmware contained in [`Self::fw`].131size: usize,132/// Device-mapped GSP signatures matching the GPU's [`Chipset`].133signatures: DmaObject,134/// GSP bootloader, verifies the GSP firmware before loading and running it.135bootloader: RiscvFirmware,136}137138impl GspFirmware {139/// Loads the GSP firmware binaries, map them into `dev`'s address-space, and creates the page140/// tables expected by the GSP bootloader to load it.141pub(crate) fn new<'a, 'b>(142dev: &'a device::Device<device::Bound>,143chipset: Chipset,144ver: &'b str,145) -> Result<impl PinInit<Self, Error> + 'a> {146let fw = super::request_firmware(dev, chipset, "gsp", ver)?;147148let fw_section = elf::elf64_section(fw.data(), ".fwimage").ok_or(EINVAL)?;149150let sigs_section = match chipset.arch() {151Architecture::Ampere => ".fwsignature_ga10x",152_ => return Err(ENOTSUPP),153};154let signatures = elf::elf64_section(fw.data(), sigs_section)155.ok_or(EINVAL)156.and_then(|data| DmaObject::from_data(dev, data))?;157158let size = fw_section.len();159160// Move the firmware into a vmalloc'd vector and map it into the device address161// space.162let fw_vvec = VVec::with_capacity(fw_section.len(), GFP_KERNEL)163.and_then(|mut v| {164v.extend_from_slice(fw_section, GFP_KERNEL)?;165Ok(v)166})167.map_err(|_| ENOMEM)?;168169let bl = super::request_firmware(dev, chipset, "bootloader", ver)?;170let bootloader = RiscvFirmware::new(dev, &bl)?;171172Ok(try_pin_init!(Self {173fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),174level2 <- {175// Allocate the level 2 page table, map the firmware onto it, and map it into the176// device address space.177VVec::<u8>::with_capacity(178fw.iter().count() * core::mem::size_of::<u64>(),179GFP_KERNEL,180)181.map_err(|_| ENOMEM)182.and_then(|level2| map_into_lvl(&fw, level2))183.map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?184},185level1 <- {186// Allocate the level 1 page table, map the level 2 page table onto it, and map it187// into the device address space.188VVec::<u8>::with_capacity(189level2.iter().count() * core::mem::size_of::<u64>(),190GFP_KERNEL,191)192.map_err(|_| ENOMEM)193.and_then(|level1| map_into_lvl(&level2, level1))194.map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?195},196level0: {197// Allocate the level 0 page table as a device-visible DMA object, and map the198// level 1 page table onto it.199200// Level 0 page table data.201let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?;202203// Fill level 1 page entry.204#[allow(clippy::useless_conversion)]205let level1_entry = u64::from(level1.iter().next().unwrap().dma_address());206let dst = &mut level0_data[..size_of_val(&level1_entry)];207dst.copy_from_slice(&level1_entry.to_le_bytes());208209// Turn the level0 page table into a [`DmaObject`].210DmaObject::from_data(dev, &level0_data)?211},212size,213signatures,214bootloader,215}))216}217218#[expect(unused)]219/// Returns the DMA handle of the radix3 level 0 page table.220pub(crate) fn radix3_dma_handle(&self) -> DmaAddress {221self.level0.dma_handle()222}223}224225/// Build a page table from a scatter-gather list.226///227/// Takes each DMA-mapped region from `sg_table` and writes page table entries228/// for all 4KB pages within that region. For example, a 16KB SG entry becomes229/// 4 consecutive page table entries.230fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {231for sg_entry in sg_table.iter() {232// Number of pages we need to map.233let num_pages = (sg_entry.dma_len() as usize).div_ceil(GSP_PAGE_SIZE);234235for i in 0..num_pages {236let entry = sg_entry.dma_address() + (i as u64 * GSP_PAGE_SIZE as u64);237dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;238}239}240241Ok(dst)242}243244245