| // 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), |
| } |
| } |
| } |