Path: blob/master/drivers/gpu/nova-core/firmware/gsp.rs
50683 views
// SPDX-License-Identifier: GPL-2.012use kernel::{3device,4dma::{5DataDirection,6DmaAddress, //7},8kvec,9prelude::*,10scatterlist::{11Owned,12SGTable, //13},14};1516use crate::{17dma::DmaObject,18firmware::riscv::RiscvFirmware,19gpu::{20Architecture,21Chipset, //22},23gsp::GSP_PAGE_SIZE,24num::FromSafeCast,25};2627/// Ad-hoc and temporary module to extract sections from ELF images.28///29/// Some firmware images are currently packaged as ELF files, where sections names are used as keys30/// to specific and related bits of data. Future firmware versions are scheduled to move away from31/// that scheme before nova-core becomes stable, which means this module will eventually be32/// removed.33mod elf {34use kernel::{35bindings,36prelude::*,37transmute::FromBytes, //38};3940/// Newtype to provide a [`FromBytes`] implementation.41#[repr(transparent)]42struct Elf64Hdr(bindings::elf64_hdr);43// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.44unsafe impl FromBytes for Elf64Hdr {}4546#[repr(transparent)]47struct Elf64SHdr(bindings::elf64_shdr);48// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.49unsafe impl FromBytes for Elf64SHdr {}5051/// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.52pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {53let hdr = &elf54.get(0..size_of::<bindings::elf64_hdr>())55.and_then(Elf64Hdr::from_bytes)?56.0;5758// Get all the section headers.59let mut shdr = {60let shdr_num = usize::from(hdr.e_shnum);61let shdr_start = usize::try_from(hdr.e_shoff).ok()?;62let shdr_end = shdr_num63.checked_mul(size_of::<Elf64SHdr>())64.and_then(|v| v.checked_add(shdr_start))?;6566elf.get(shdr_start..shdr_end)67.map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?68};6970// Get the strings table.71let strhdr = shdr72.clone()73.nth(usize::from(hdr.e_shstrndx))74.and_then(Elf64SHdr::from_bytes)?;7576// Find the section which name matches `name` and return it.77shdr.find(|&sh| {78let Some(hdr) = Elf64SHdr::from_bytes(sh) else {79return false;80};8182let Some(name_idx) = strhdr83.084.sh_offset85.checked_add(u64::from(hdr.0.sh_name))86.and_then(|idx| usize::try_from(idx).ok())87else {88return false;89};9091// Get the start of the name.92elf.get(name_idx..)93// Stop at the first `0`.94.and_then(|nstr| nstr.get(0..=nstr.iter().position(|b| *b == 0)?))95// Convert into CStr. This should never fail because of the line above.96.and_then(|nstr| CStr::from_bytes_with_nul(nstr).ok())97// Convert into str.98.and_then(|c_str| c_str.to_str().ok())99// Check that the name matches.100.map(|str| str == name)101.unwrap_or(false)102})103// Return the slice containing the section.104.and_then(|sh| {105let hdr = Elf64SHdr::from_bytes(sh)?;106let start = usize::try_from(hdr.0.sh_offset).ok()?;107let end = usize::try_from(hdr.0.sh_size)108.ok()109.and_then(|sh_size| start.checked_add(sh_size))?;110111elf.get(start..end)112})113}114}115116/// GSP firmware with 3-level radix page tables for the GSP bootloader.117///118/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address119/// space:120///121/// ```text122/// Level 0: 1 page, 1 entry -> points to first level 1 page123/// Level 1: Multiple pages/entries -> each entry points to a level 2 page124/// Level 2: Multiple pages/entries -> each entry points to a firmware page125/// ```126///127/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).128/// Also known as "Radix3" firmware.129#[pin_data]130pub(crate) struct GspFirmware {131/// The GSP firmware inside a [`VVec`], device-mapped via a SG table.132#[pin]133fw: SGTable<Owned<VVec<u8>>>,134/// Level 2 page table whose entries contain DMA addresses of firmware pages.135#[pin]136level2: SGTable<Owned<VVec<u8>>>,137/// Level 1 page table whose entries contain DMA addresses of level 2 pages.138#[pin]139level1: SGTable<Owned<VVec<u8>>>,140/// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.141level0: DmaObject,142/// Size in bytes of the firmware contained in [`Self::fw`].143pub(crate) size: usize,144/// Device-mapped GSP signatures matching the GPU's [`Chipset`].145pub(crate) signatures: DmaObject,146/// GSP bootloader, verifies the GSP firmware before loading and running it.147pub(crate) bootloader: RiscvFirmware,148}149150impl GspFirmware {151/// Loads the GSP firmware binaries, map them into `dev`'s address-space, and creates the page152/// tables expected by the GSP bootloader to load it.153pub(crate) fn new<'a, 'b>(154dev: &'a device::Device<device::Bound>,155chipset: Chipset,156ver: &'b str,157) -> Result<impl PinInit<Self, Error> + 'a> {158let fw = super::request_firmware(dev, chipset, "gsp", ver)?;159160let fw_section = elf::elf64_section(fw.data(), ".fwimage").ok_or(EINVAL)?;161162let sigs_section = match chipset.arch() {163Architecture::Ampere => ".fwsignature_ga10x",164Architecture::Ada => ".fwsignature_ad10x",165_ => return Err(ENOTSUPP),166};167let signatures = elf::elf64_section(fw.data(), sigs_section)168.ok_or(EINVAL)169.and_then(|data| DmaObject::from_data(dev, data))?;170171let size = fw_section.len();172173// Move the firmware into a vmalloc'd vector and map it into the device address174// space.175let fw_vvec = VVec::with_capacity(fw_section.len(), GFP_KERNEL)176.and_then(|mut v| {177v.extend_from_slice(fw_section, GFP_KERNEL)?;178Ok(v)179})180.map_err(|_| ENOMEM)?;181182let bl = super::request_firmware(dev, chipset, "bootloader", ver)?;183let bootloader = RiscvFirmware::new(dev, &bl)?;184185Ok(try_pin_init!(Self {186fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),187level2 <- {188// Allocate the level 2 page table, map the firmware onto it, and map it into the189// device address space.190VVec::<u8>::with_capacity(191fw.iter().count() * core::mem::size_of::<u64>(),192GFP_KERNEL,193)194.map_err(|_| ENOMEM)195.and_then(|level2| map_into_lvl(&fw, level2))196.map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?197},198level1 <- {199// Allocate the level 1 page table, map the level 2 page table onto it, and map it200// into the device address space.201VVec::<u8>::with_capacity(202level2.iter().count() * core::mem::size_of::<u64>(),203GFP_KERNEL,204)205.map_err(|_| ENOMEM)206.and_then(|level1| map_into_lvl(&level2, level1))207.map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?208},209level0: {210// Allocate the level 0 page table as a device-visible DMA object, and map the211// level 1 page table onto it.212213// Level 0 page table data.214let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?;215216// Fill level 1 page entry.217let level1_entry = level1.iter().next().ok_or(EINVAL)?;218let level1_entry_addr = level1_entry.dma_address();219let dst = &mut level0_data[..size_of_val(&level1_entry_addr)];220dst.copy_from_slice(&level1_entry_addr.to_le_bytes());221222// Turn the level0 page table into a [`DmaObject`].223DmaObject::from_data(dev, &level0_data)?224},225size,226signatures,227bootloader,228}))229}230231/// Returns the DMA handle of the radix3 level 0 page table.232pub(crate) fn radix3_dma_handle(&self) -> DmaAddress {233self.level0.dma_handle()234}235}236237/// Build a page table from a scatter-gather list.238///239/// Takes each DMA-mapped region from `sg_table` and writes page table entries240/// for all 4KB pages within that region. For example, a 16KB SG entry becomes241/// 4 consecutive page table entries.242fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {243for sg_entry in sg_table.iter() {244// Number of pages we need to map.245let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);246247for i in 0..num_pages {248let entry = sg_entry.dma_address()249+ (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));250dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;251}252}253254Ok(dst)255}256257258