Commit b3d24269 authored by John Hubbard's avatar John Hubbard Committed by Alexandre Courbot
Browse files

gpu: nova-core: firmware: move firmware image parsing code to firmware.rs



Up until now, only the GSP required parsing of its firmware headers.
However, upcoming support for Hopper/Blackwell+ adds another firmware
image (FMC), along with another format (ELF32).

Therefore, the current ELF64 section parsing support needs to be moved
up a level, so that both of the above can use it.

There are no functional changes. This is pure code movement.

Reviewed-by: default avatarGary Guo <gary@garyguo.net>
Signed-off-by: default avatarJohn Hubbard <jhubbard@nvidia.com>
Acked-by: default avatarDanilo Krummrich <dakr@kernel.org>
Link: https://patch.msgid.link/20260326013902.588242-8-jhubbard@nvidia.com


[acourbot: use fuller prefix in commit message.]
Signed-off-by: default avatarAlexandre Courbot <acourbot@nvidia.com>
parent e10dcb9d
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
@@ -457,3 +457,91 @@ pub(crate) const fn create(
        this.0
    }
}

/// Ad-hoc and temporary module to extract sections from ELF images.
///
/// Some firmware images are currently packaged as ELF files, where sections names are used as keys
/// to specific and related bits of data. Future firmware versions are scheduled to move away from
/// that scheme before nova-core becomes stable, which means this module will eventually be
/// removed.
mod elf {
    use core::mem::size_of;

    use kernel::{
        bindings,
        str::CStr,
        transmute::FromBytes, //
    };

    /// Newtype to provide a [`FromBytes`] implementation.
    #[repr(transparent)]
    struct Elf64Hdr(bindings::elf64_hdr);
    // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
    unsafe impl FromBytes for Elf64Hdr {}

    #[repr(transparent)]
    struct Elf64SHdr(bindings::elf64_shdr);
    // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
    unsafe impl FromBytes for Elf64SHdr {}

    /// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
    pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {
        let hdr = &elf
            .get(0..size_of::<bindings::elf64_hdr>())
            .and_then(Elf64Hdr::from_bytes)?
            .0;

        // Get all the section headers.
        let mut shdr = {
            let shdr_num = usize::from(hdr.e_shnum);
            let shdr_start = usize::try_from(hdr.e_shoff).ok()?;
            let shdr_end = shdr_num
                .checked_mul(size_of::<Elf64SHdr>())
                .and_then(|v| v.checked_add(shdr_start))?;

            elf.get(shdr_start..shdr_end)
                .map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?
        };

        // Get the strings table.
        let strhdr = shdr
            .clone()
            .nth(usize::from(hdr.e_shstrndx))
            .and_then(Elf64SHdr::from_bytes)?;

        // Find the section which name matches `name` and return it.
        shdr.find(|&sh| {
            let Some(hdr) = Elf64SHdr::from_bytes(sh) else {
                return false;
            };

            let Some(name_idx) = strhdr
                .0
                .sh_offset
                .checked_add(u64::from(hdr.0.sh_name))
                .and_then(|idx| usize::try_from(idx).ok())
            else {
                return false;
            };

            // Get the start of the name.
            elf.get(name_idx..)
                .and_then(|nstr| CStr::from_bytes_until_nul(nstr).ok())
                // Convert into str.
                .and_then(|c_str| c_str.to_str().ok())
                // Check that the name matches.
                .map(|str| str == name)
                .unwrap_or(false)
        })
        // Return the slice containing the section.
        .and_then(|sh| {
            let hdr = Elf64SHdr::from_bytes(sh)?;
            let start = usize::try_from(hdr.0.sh_offset).ok()?;
            let end = usize::try_from(hdr.0.sh_size)
                .ok()
                .and_then(|sh_size| start.checked_add(sh_size))?;

            elf.get(start..end)
        })
    }
}
+4 −87
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@
};

use crate::{
    firmware::riscv::RiscvFirmware,
    firmware::{
        elf,
        riscv::RiscvFirmware, //
    },
    gpu::{
        Architecture,
        Chipset, //
@@ -25,92 +28,6 @@
    num::FromSafeCast,
};

/// Ad-hoc and temporary module to extract sections from ELF images.
///
/// Some firmware images are currently packaged as ELF files, where sections names are used as keys
/// to specific and related bits of data. Future firmware versions are scheduled to move away from
/// that scheme before nova-core becomes stable, which means this module will eventually be
/// removed.
mod elf {
    use kernel::{
        bindings,
        prelude::*,
        transmute::FromBytes, //
    };

    /// Newtype to provide a [`FromBytes`] implementation.
    #[repr(transparent)]
    struct Elf64Hdr(bindings::elf64_hdr);
    // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
    unsafe impl FromBytes for Elf64Hdr {}

    #[repr(transparent)]
    struct Elf64SHdr(bindings::elf64_shdr);
    // SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
    unsafe impl FromBytes for Elf64SHdr {}

    /// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
    pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {
        let hdr = &elf
            .get(0..size_of::<bindings::elf64_hdr>())
            .and_then(Elf64Hdr::from_bytes)?
            .0;

        // Get all the section headers.
        let mut shdr = {
            let shdr_num = usize::from(hdr.e_shnum);
            let shdr_start = usize::try_from(hdr.e_shoff).ok()?;
            let shdr_end = shdr_num
                .checked_mul(size_of::<Elf64SHdr>())
                .and_then(|v| v.checked_add(shdr_start))?;

            elf.get(shdr_start..shdr_end)
                .map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?
        };

        // Get the strings table.
        let strhdr = shdr
            .clone()
            .nth(usize::from(hdr.e_shstrndx))
            .and_then(Elf64SHdr::from_bytes)?;

        // Find the section which name matches `name` and return it.
        shdr.find(|&sh| {
            let Some(hdr) = Elf64SHdr::from_bytes(sh) else {
                return false;
            };

            let Some(name_idx) = strhdr
                .0
                .sh_offset
                .checked_add(u64::from(hdr.0.sh_name))
                .and_then(|idx| usize::try_from(idx).ok())
            else {
                return false;
            };

            // Get the start of the name.
            elf.get(name_idx..)
                .and_then(|nstr| CStr::from_bytes_until_nul(nstr).ok())
                // Convert into str.
                .and_then(|c_str| c_str.to_str().ok())
                // Check that the name matches.
                .map(|str| str == name)
                .unwrap_or(false)
        })
        // Return the slice containing the section.
        .and_then(|sh| {
            let hdr = Elf64SHdr::from_bytes(sh)?;
            let start = usize::try_from(hdr.0.sh_offset).ok()?;
            let end = usize::try_from(hdr.0.sh_size)
                .ok()
                .and_then(|sh_size| start.checked_add(sh_size))?;

            elf.get(start..end)
        })
    }
}

/// GSP firmware with 3-level radix page tables for the GSP bootloader.
///
/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address