// 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, /// MCU virtual address range where this section should be mapped. pub(super) va: Range, /// 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) -> Result> { 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 { let bytes = self.read(size_of::())?; Ok(bytes[0]) } /// Reads a little-endian `u16` from the current position and advances the cursor. fn read_u16(&mut self) -> Result { let bytes: [u8; 2] = self .read(size_of::())? .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 { let bytes: [u8; 4] = self .read(size_of::())? .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> { 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 { 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 { let entry_start = self.cursor.pos(); let entry_header_end = entry_start .checked_add(size_of::()) .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::() != 0 || entry_size % size_of::() != 0 || entry_size < size_of::() { dev_err!( self.cursor.dev, "Firmware entry isn't 32 bit aligned, offset={:#x} size={:#x}", self.cursor.pos() - size_of::(), 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::(); 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> { 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 { 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, /// References the data in the FW binary. data: Range, } 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 { 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, } /// 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 { 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 for EntryType { type Error = Error; fn try_from(value: u8) -> Result { 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), } } }