blob: c4d0ad1d7899db40b8a84d6f33a1a6d845a46e79 [file] [edit]
// SPDX-License-Identifier: GPL-2.0 or MIT
//! Firmware binary parser for Mali CSF (Command Stream Frontend) GPU.
//!
//! This module implements a parser for the Mali GPU firmware binary format. The firmware
//! file contains a header followed by a sequence of entries, each describing how to load
//! firmware sections into the MCU (Microcontroller Unit) memory. The parser extracts section
//! metadata including:
//! - Virtual address ranges where sections should be mapped
//! - Data ranges (byte offsets) within the firmware binary
//! - Section flags (permissions, cache modes)
use core::{
mem::size_of,
ops::Range, //
};
use kernel::{
bits::bit_u32,
device::Device,
prelude::*,
sizes::SZ_4K, //
};
use crate::{
fw::{
CacheMode,
SectionFlags,
CSF_MCU_SHARED_REGION_START, //
},
vm::{
VmFlag,
VmMapFlags, //
}, //
};
/// A parsed firmware section ready for loading into MCU memory.
///
/// Represents a single firmware section extracted from the firmware binary, containing
/// all information needed to map the section's data into the MCU's virtual address space.
pub(super) struct ParsedSection {
/// Byte offset range within the firmware binary where this section's data resides.
pub(super) data_range: Range<u32>,
/// MCU virtual address range where this section should be mapped.
pub(super) va: Range<u32>,
/// Memory protection and caching flags for the mapping.
pub(super) vm_map_flags: VmMapFlags,
}
/// A bare-bones `std::io::Cursor<[u8]>` clone to keep track of the current position in the
/// firmware binary.
///
/// Provides methods to sequentially read primitive types and byte arrays from the firmware
/// binary while maintaining the current read position.
struct Cursor<'a> {
dev: &'a Device,
data: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(dev: &'a Device, data: &'a [u8]) -> Self {
Self { dev, data, pos: 0 }
}
fn len(&self) -> usize {
self.data.len()
}
fn pos(&self) -> usize {
self.pos
}
/// Returns a view into the cursor's data.
///
/// This spawns a new cursor, leaving the current cursor unchanged.
fn view(&self, range: Range<usize>) -> Result<Cursor<'_>> {
if range.start < self.pos || range.end > self.data.len() {
dev_err!(
self.dev,
"Invalid cursor range {:?} for data of length {}",
range,
self.data.len()
);
Err(EINVAL)
} else {
Ok(Self {
dev: self.dev,
data: &self.data[range],
pos: 0,
})
}
}
/// Reads a slice of bytes from the current position and advances the cursor.
///
/// Returns an error if the read would exceed the data bounds.
fn read(&mut self, nbytes: usize) -> Result<&[u8]> {
let start = self.pos;
let end = start + nbytes;
if end > self.data.len() {
dev_err!(
self.dev,
"Invalid firmware file: read of size {} at position {} is out of bounds",
nbytes,
start,
);
return Err(EINVAL);
}
self.pos += nbytes;
Ok(&self.data[start..end])
}
/// Reads a little-endian `u8` from the current position and advances the cursor.
fn read_u8(&mut self) -> Result<u8> {
let bytes = self.read(size_of::<u8>())?;
Ok(bytes[0])
}
/// Reads a little-endian `u16` from the current position and advances the cursor.
fn read_u16(&mut self) -> Result<u16> {
let bytes: [u8; 2] = self
.read(size_of::<u16>())?
.try_into()
.map_err(|_| EINVAL)?;
Ok(u16::from_le_bytes(bytes))
}
/// Reads a little-endian `u32` from the current position and advances the cursor.
fn read_u32(&mut self) -> Result<u32> {
let bytes: [u8; 4] = self
.read(size_of::<u32>())?
.try_into()
.map_err(|_| EINVAL)?;
Ok(u32::from_le_bytes(bytes))
}
/// Advances the cursor position by the specified number of bytes.
///
/// Returns an error if the advance would exceed the data bounds.
fn advance(&mut self, nbytes: usize) -> Result {
if self.pos + nbytes > self.data.len() {
dev_err!(
self.dev,
"Invalid firmware file: advance of size {} at position {} is out of bounds",
nbytes,
self.pos,
);
return Err(EINVAL);
}
self.pos += nbytes;
Ok(())
}
}
/// Parser for Mali CSF GPU firmware binaries.
///
/// Parses the firmware binary format, extracting section metadata including virtual
/// address ranges, data offsets, and memory protection flags needed to load firmware
/// into the MCU's memory.
pub(super) struct FwParser<'a> {
cursor: Cursor<'a>,
}
impl<'a> FwParser<'a> {
/// Creates a new firmware parser for the given firmware binary data.
pub(super) fn new(dev: &'a Device, data: &'a [u8]) -> Self {
Self {
cursor: Cursor::new(dev, data),
}
}
/// Parses the firmware binary and returns a collection of parsed sections.
///
/// This method validates the firmware header and iterates through all entries
/// in the binary, extracting section information needed for loading.
pub(super) fn parse(&mut self) -> Result<KVec<ParsedSection>> {
let fw_header = self.parse_fw_header()?;
let header_end = fw_header.size as usize;
let mut parsed_sections = KVec::new();
while self.cursor.pos() < header_end {
let entry_section = self.parse_entry(header_end)?;
if let Some(inner) = entry_section.inner {
parsed_sections.push(inner, GFP_KERNEL)?;
}
}
if parsed_sections.is_empty() {
dev_err!(self.cursor.dev, "Firmware contains no loadable sections");
return Err(EINVAL);
}
Ok(parsed_sections)
}
fn parse_fw_header(&mut self) -> Result<FirmwareHeader> {
let fw_header: FirmwareHeader = match FirmwareHeader::new(&mut self.cursor) {
Ok(fw_header) => fw_header,
Err(e) => {
dev_err!(self.cursor.dev, "Invalid firmware file: {}", e.to_errno());
return Err(e);
}
};
if fw_header.size as usize > self.cursor.len() {
dev_err!(self.cursor.dev, "Firmware image is truncated");
return Err(EINVAL);
}
Ok(fw_header)
}
fn parse_entry(&mut self, header_end: usize) -> Result<EntrySection> {
let entry_start = self.cursor.pos();
let entry_header_end = entry_start
.checked_add(size_of::<EntryHeader>())
.ok_or(EINVAL)?;
if entry_header_end > header_end {
dev_err!(
self.cursor.dev,
"Firmware entry header at {:#x} exceeds header region ending at {:#x}",
entry_start,
header_end
);
return Err(EINVAL);
}
let entry_section = EntrySection {
entry_hdr: EntryHeader(self.cursor.read_u32()?),
inner: None,
};
let firmware_size = self.cursor.len();
let entry_size = entry_section.entry_hdr.size() as usize;
if self.cursor.pos() % size_of::<u32>() != 0
|| entry_size % size_of::<u32>() != 0
|| entry_size < size_of::<EntryHeader>()
{
dev_err!(
self.cursor.dev,
"Firmware entry isn't 32 bit aligned, offset={:#x} size={:#x}",
self.cursor.pos() - size_of::<u32>(),
entry_size
);
return Err(EINVAL);
}
let entry_end = entry_start.checked_add(entry_size).ok_or(EINVAL)?;
if entry_end > header_end {
dev_err!(
self.cursor.dev,
"Firmware entry at {:#x} extends beyond header region ending at {:#x}",
entry_start,
header_end
);
return Err(EINVAL);
}
let section_hdr_size = entry_size - size_of::<EntryHeader>();
let entry_section = {
let mut entry_cursor = self.cursor.view(self.cursor.pos()..entry_end)?;
match entry_section.entry_hdr.entry_type() {
Ok(EntryType::Iface) => Ok(EntrySection {
entry_hdr: entry_section.entry_hdr,
inner: Self::parse_section_entry(&mut entry_cursor, firmware_size)?,
}),
Ok(
EntryType::Config
| EntryType::FutfTest
| EntryType::TraceBuffer
| EntryType::TimelineMetadata
| EntryType::BuildInfoMetadata,
) => Ok(entry_section),
Err(_) => {
if entry_section.entry_hdr.optional() {
Ok(entry_section)
} else {
dev_err!(
self.cursor.dev,
"Failed to handle firmware entry type: {}",
entry_section.entry_hdr.entry_type_raw()
);
Err(EINVAL)
}
}
}
};
if entry_section.is_ok() {
self.cursor.advance(section_hdr_size)?;
}
entry_section
}
fn parse_section_entry(
entry_cursor: &mut Cursor<'_>,
firmware_size: usize,
) -> Result<Option<ParsedSection>> {
let section_hdr: SectionHeader = SectionHeader::new(entry_cursor)?;
if section_hdr.data.end < section_hdr.data.start {
dev_err!(
entry_cursor.dev,
"Firmware corrupted, data.end < data.start (0x{:x} < 0x{:x})",
section_hdr.data.end,
section_hdr.data.start
);
return Err(EINVAL);
}
if section_hdr.data.end as usize > firmware_size {
dev_err!(
entry_cursor.dev,
"Firmware data range {:#x}..{:#x} exceeds firmware size {:#x}",
section_hdr.data.start,
section_hdr.data.end,
firmware_size,
);
return Err(EINVAL);
}
if section_hdr.va.start as usize % SZ_4K != 0 || section_hdr.va.end as usize % SZ_4K != 0 {
dev_err!(
entry_cursor.dev,
"Firmware virtual address range {:#x}..{:#x} is not page aligned",
section_hdr.va.start,
section_hdr.va.end
);
return Err(EINVAL);
}
if section_hdr.section_flags.prot() {
dev_dbg!(
entry_cursor.dev,
"Firmware protected mode entry not supported, ignoring"
);
return Ok(None);
}
if section_hdr.va.start == CSF_MCU_SHARED_REGION_START
&& !section_hdr.section_flags.shared()
{
dev_err!(
entry_cursor.dev,
"Interface at 0x{:x} must be shared",
CSF_MCU_SHARED_REGION_START
);
return Err(EINVAL);
}
if section_hdr.va.is_empty() {
return Ok(None);
}
let mut vm_map_flags = VmMapFlags::empty();
if !section_hdr.section_flags.write() {
vm_map_flags |= VmFlag::Readonly;
}
if !section_hdr.section_flags.exec() {
vm_map_flags |= VmFlag::Noexec;
}
// TODO: As in Panthor, map coherent firmware sections uncached until the VM
// supports a coherent mapping attribute.
if section_hdr.section_flags.cache_mode() != CacheMode::Cached {
vm_map_flags |= VmFlag::Uncached;
}
Ok(Some(ParsedSection {
data_range: section_hdr.data.clone(),
va: section_hdr.va,
vm_map_flags,
}))
}
}
/// Firmware binary header containing version and size information.
///
/// The header is located at the beginning of the firmware binary and contains
/// a magic value for validation, version information, and the total size of
/// all structured headers that follow.
#[expect(dead_code)]
struct FirmwareHeader {
/// Magic value to check binary validity.
magic: u32,
/// Minor firmware version.
minor: u8,
/// Major firmware version.
major: u8,
/// Padding. Must be set to zero.
_padding1: u16,
/// Firmware version hash.
version_hash: u32,
/// Padding. Must be set to zero.
_padding2: u32,
/// Total size of all the structured data headers at beginning of firmware binary.
size: u32,
}
impl FirmwareHeader {
const FW_BINARY_MAGIC: u32 = 0xc3f13a6e;
const FW_BINARY_MAJOR_MAX: u8 = 0;
/// Reads and validates a firmware header from the cursor.
///
/// Verifies the magic value, version compatibility, and padding fields.
fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
let magic = cursor.read_u32()?;
if magic != Self::FW_BINARY_MAGIC {
dev_err!(cursor.dev, "Invalid firmware magic");
return Err(EINVAL);
}
let minor = cursor.read_u8()?;
let major = cursor.read_u8()?;
if major > Self::FW_BINARY_MAJOR_MAX {
dev_err!(
cursor.dev,
"Unsupported firmware binary header version {}.{} (expected {}.x)",
major,
minor,
Self::FW_BINARY_MAJOR_MAX
);
return Err(EINVAL);
}
let padding1 = cursor.read_u16()?;
let version_hash = cursor.read_u32()?;
let padding2 = cursor.read_u32()?;
let size = cursor.read_u32()?;
if padding1 != 0 || padding2 != 0 {
dev_err!(
cursor.dev,
"Invalid firmware file: header padding is not zero"
);
return Err(EINVAL);
}
let fw_header = Self {
magic,
minor,
major,
_padding1: padding1,
version_hash,
_padding2: padding2,
size,
};
Ok(fw_header)
}
}
/// Firmware section header for loading binary sections into MCU memory.
#[derive(Debug)]
struct SectionHeader {
section_flags: SectionFlags,
/// MCU virtual range to map this binary section to.
va: Range<u32>,
/// References the data in the FW binary.
data: Range<u32>,
}
impl SectionHeader {
/// Reads and validates a section header from the cursor.
///
/// Parses section flags, virtual address range, and data range from the firmware binary.
fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
let section_flags = SectionFlags::try_from_fw(cursor.read_u32()?)?;
let va_start = cursor.read_u32()?;
let va_end = cursor.read_u32()?;
let va = va_start..va_end;
if va.end < va.start {
dev_err!(
cursor.dev,
"Invalid firmware file: VA end precedes start at pos {}",
cursor.pos(),
);
return Err(EINVAL);
}
let data_start = cursor.read_u32()?;
let data_end = cursor.read_u32()?;
let data = data_start..data_end;
Ok(Self {
section_flags,
va,
data,
})
}
}
/// A firmware entry containing a header and optional parsed section data.
///
/// Represents a single entry in the firmware binary, which may contain loadable
/// section data or metadata that doesn't require loading.
struct EntrySection {
entry_hdr: EntryHeader,
inner: Option<ParsedSection>,
}
/// Header for a firmware entry, packed into a single u32.
///
/// The entry header encodes the entry type, size, and optional flag in a
/// 32-bit value with the following layout:
/// - Bits 0-7: Entry type
/// - Bits 8-15: Size in bytes
/// - Bit 31: Optional flag
struct EntryHeader(u32);
impl EntryHeader {
fn entry_type_raw(&self) -> u8 {
(self.0 & 0xff) as u8
}
fn entry_type(&self) -> Result<EntryType> {
let v = self.entry_type_raw();
EntryType::try_from(v)
}
fn optional(&self) -> bool {
self.0 & bit_u32(31) != 0
}
fn size(&self) -> u32 {
self.0 >> 8 & 0xff
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum EntryType {
/// Host <-> FW interface.
Iface = 0,
/// FW config.
Config = 1,
/// Unit tests.
FutfTest = 2,
/// Trace buffer interface.
TraceBuffer = 3,
/// Timeline metadata interface.
TimelineMetadata = 4,
/// Metadata about how the FW binary was built.
BuildInfoMetadata = 6,
}
impl TryFrom<u8> for EntryType {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(EntryType::Iface),
1 => Ok(EntryType::Config),
2 => Ok(EntryType::FutfTest),
3 => Ok(EntryType::TraceBuffer),
4 => Ok(EntryType::TimelineMetadata),
6 => Ok(EntryType::BuildInfoMetadata),
_ => Err(EINVAL),
}
}
}