xref: /linux/drivers/gpu/drm/tyr/fw/parser.rs (revision 4e69c1856bfd9ffb7e9d335a25842fa211628929)
1*aa9ce820SDaniel Almeida // SPDX-License-Identifier: GPL-2.0 or MIT
2*aa9ce820SDaniel Almeida 
3*aa9ce820SDaniel Almeida //! Firmware binary parser for Mali CSF (Command Stream Frontend) GPU.
4*aa9ce820SDaniel Almeida //!
5*aa9ce820SDaniel Almeida //! This module implements a parser for the Mali GPU firmware binary format. The firmware
6*aa9ce820SDaniel Almeida //! file contains a header followed by a sequence of entries, each describing how to load
7*aa9ce820SDaniel Almeida //! firmware sections into the MCU (Microcontroller Unit) memory. The parser extracts section
8*aa9ce820SDaniel Almeida //! metadata including:
9*aa9ce820SDaniel Almeida //! - Virtual address ranges where sections should be mapped
10*aa9ce820SDaniel Almeida //! - Data ranges (byte offsets) within the firmware binary
11*aa9ce820SDaniel Almeida //! - Section flags (permissions, cache modes)
12*aa9ce820SDaniel Almeida 
13*aa9ce820SDaniel Almeida use core::{
14*aa9ce820SDaniel Almeida     mem::size_of,
15*aa9ce820SDaniel Almeida     ops::Range, //
16*aa9ce820SDaniel Almeida };
17*aa9ce820SDaniel Almeida 
18*aa9ce820SDaniel Almeida use kernel::{
19*aa9ce820SDaniel Almeida     bits::bit_u32,
20*aa9ce820SDaniel Almeida     device::Device,
21*aa9ce820SDaniel Almeida     prelude::*,
22*aa9ce820SDaniel Almeida     sizes::SZ_4K, //
23*aa9ce820SDaniel Almeida };
24*aa9ce820SDaniel Almeida 
25*aa9ce820SDaniel Almeida use crate::{
26*aa9ce820SDaniel Almeida     fw::{
27*aa9ce820SDaniel Almeida         CacheMode,
28*aa9ce820SDaniel Almeida         SectionFlags,
29*aa9ce820SDaniel Almeida         CSF_MCU_SHARED_REGION_START, //
30*aa9ce820SDaniel Almeida     },
31*aa9ce820SDaniel Almeida     vm::{
32*aa9ce820SDaniel Almeida         VmFlag,
33*aa9ce820SDaniel Almeida         VmMapFlags, //
34*aa9ce820SDaniel Almeida     }, //
35*aa9ce820SDaniel Almeida };
36*aa9ce820SDaniel Almeida 
37*aa9ce820SDaniel Almeida /// A parsed firmware section ready for loading into MCU memory.
38*aa9ce820SDaniel Almeida ///
39*aa9ce820SDaniel Almeida /// Represents a single firmware section extracted from the firmware binary, containing
40*aa9ce820SDaniel Almeida /// all information needed to map the section's data into the MCU's virtual address space.
41*aa9ce820SDaniel Almeida pub(super) struct ParsedSection {
42*aa9ce820SDaniel Almeida     /// Byte offset range within the firmware binary where this section's data resides.
43*aa9ce820SDaniel Almeida     pub(super) data_range: Range<u32>,
44*aa9ce820SDaniel Almeida     /// MCU virtual address range where this section should be mapped.
45*aa9ce820SDaniel Almeida     pub(super) va: Range<u32>,
46*aa9ce820SDaniel Almeida     /// Memory protection and caching flags for the mapping.
47*aa9ce820SDaniel Almeida     pub(super) vm_map_flags: VmMapFlags,
48*aa9ce820SDaniel Almeida }
49*aa9ce820SDaniel Almeida 
50*aa9ce820SDaniel Almeida /// A bare-bones `std::io::Cursor<[u8]>` clone to keep track of the current position in the
51*aa9ce820SDaniel Almeida /// firmware binary.
52*aa9ce820SDaniel Almeida ///
53*aa9ce820SDaniel Almeida /// Provides methods to sequentially read primitive types and byte arrays from the firmware
54*aa9ce820SDaniel Almeida /// binary while maintaining the current read position.
55*aa9ce820SDaniel Almeida struct Cursor<'a> {
56*aa9ce820SDaniel Almeida     dev: &'a Device,
57*aa9ce820SDaniel Almeida     data: &'a [u8],
58*aa9ce820SDaniel Almeida     pos: usize,
59*aa9ce820SDaniel Almeida }
60*aa9ce820SDaniel Almeida 
61*aa9ce820SDaniel Almeida impl<'a> Cursor<'a> {
62*aa9ce820SDaniel Almeida     fn new(dev: &'a Device, data: &'a [u8]) -> Self {
63*aa9ce820SDaniel Almeida         Self { dev, data, pos: 0 }
64*aa9ce820SDaniel Almeida     }
65*aa9ce820SDaniel Almeida 
66*aa9ce820SDaniel Almeida     fn len(&self) -> usize {
67*aa9ce820SDaniel Almeida         self.data.len()
68*aa9ce820SDaniel Almeida     }
69*aa9ce820SDaniel Almeida 
70*aa9ce820SDaniel Almeida     fn pos(&self) -> usize {
71*aa9ce820SDaniel Almeida         self.pos
72*aa9ce820SDaniel Almeida     }
73*aa9ce820SDaniel Almeida 
74*aa9ce820SDaniel Almeida     /// Returns a view into the cursor's data.
75*aa9ce820SDaniel Almeida     ///
76*aa9ce820SDaniel Almeida     /// This spawns a new cursor, leaving the current cursor unchanged.
77*aa9ce820SDaniel Almeida     fn view(&self, range: Range<usize>) -> Result<Cursor<'_>> {
78*aa9ce820SDaniel Almeida         if range.start < self.pos || range.end > self.data.len() {
79*aa9ce820SDaniel Almeida             dev_err!(
80*aa9ce820SDaniel Almeida                 self.dev,
81*aa9ce820SDaniel Almeida                 "Invalid cursor range {:?} for data of length {}",
82*aa9ce820SDaniel Almeida                 range,
83*aa9ce820SDaniel Almeida                 self.data.len()
84*aa9ce820SDaniel Almeida             );
85*aa9ce820SDaniel Almeida 
86*aa9ce820SDaniel Almeida             Err(EINVAL)
87*aa9ce820SDaniel Almeida         } else {
88*aa9ce820SDaniel Almeida             Ok(Self {
89*aa9ce820SDaniel Almeida                 dev: self.dev,
90*aa9ce820SDaniel Almeida                 data: &self.data[range],
91*aa9ce820SDaniel Almeida                 pos: 0,
92*aa9ce820SDaniel Almeida             })
93*aa9ce820SDaniel Almeida         }
94*aa9ce820SDaniel Almeida     }
95*aa9ce820SDaniel Almeida 
96*aa9ce820SDaniel Almeida     /// Reads a slice of bytes from the current position and advances the cursor.
97*aa9ce820SDaniel Almeida     ///
98*aa9ce820SDaniel Almeida     /// Returns an error if the read would exceed the data bounds.
99*aa9ce820SDaniel Almeida     fn read(&mut self, nbytes: usize) -> Result<&[u8]> {
100*aa9ce820SDaniel Almeida         let start = self.pos;
101*aa9ce820SDaniel Almeida         let end = start + nbytes;
102*aa9ce820SDaniel Almeida 
103*aa9ce820SDaniel Almeida         if end > self.data.len() {
104*aa9ce820SDaniel Almeida             dev_err!(
105*aa9ce820SDaniel Almeida                 self.dev,
106*aa9ce820SDaniel Almeida                 "Invalid firmware file: read of size {} at position {} is out of bounds",
107*aa9ce820SDaniel Almeida                 nbytes,
108*aa9ce820SDaniel Almeida                 start,
109*aa9ce820SDaniel Almeida             );
110*aa9ce820SDaniel Almeida             return Err(EINVAL);
111*aa9ce820SDaniel Almeida         }
112*aa9ce820SDaniel Almeida 
113*aa9ce820SDaniel Almeida         self.pos += nbytes;
114*aa9ce820SDaniel Almeida         Ok(&self.data[start..end])
115*aa9ce820SDaniel Almeida     }
116*aa9ce820SDaniel Almeida 
117*aa9ce820SDaniel Almeida     /// Reads a little-endian `u8` from the current position and advances the cursor.
118*aa9ce820SDaniel Almeida     fn read_u8(&mut self) -> Result<u8> {
119*aa9ce820SDaniel Almeida         let bytes = self.read(size_of::<u8>())?;
120*aa9ce820SDaniel Almeida         Ok(bytes[0])
121*aa9ce820SDaniel Almeida     }
122*aa9ce820SDaniel Almeida 
123*aa9ce820SDaniel Almeida     /// Reads a little-endian `u16` from the current position and advances the cursor.
124*aa9ce820SDaniel Almeida     fn read_u16(&mut self) -> Result<u16> {
125*aa9ce820SDaniel Almeida         let bytes: [u8; 2] = self
126*aa9ce820SDaniel Almeida             .read(size_of::<u16>())?
127*aa9ce820SDaniel Almeida             .try_into()
128*aa9ce820SDaniel Almeida             .map_err(|_| EINVAL)?;
129*aa9ce820SDaniel Almeida 
130*aa9ce820SDaniel Almeida         Ok(u16::from_le_bytes(bytes))
131*aa9ce820SDaniel Almeida     }
132*aa9ce820SDaniel Almeida 
133*aa9ce820SDaniel Almeida     /// Reads a little-endian `u32` from the current position and advances the cursor.
134*aa9ce820SDaniel Almeida     fn read_u32(&mut self) -> Result<u32> {
135*aa9ce820SDaniel Almeida         let bytes: [u8; 4] = self
136*aa9ce820SDaniel Almeida             .read(size_of::<u32>())?
137*aa9ce820SDaniel Almeida             .try_into()
138*aa9ce820SDaniel Almeida             .map_err(|_| EINVAL)?;
139*aa9ce820SDaniel Almeida 
140*aa9ce820SDaniel Almeida         Ok(u32::from_le_bytes(bytes))
141*aa9ce820SDaniel Almeida     }
142*aa9ce820SDaniel Almeida 
143*aa9ce820SDaniel Almeida     /// Advances the cursor position by the specified number of bytes.
144*aa9ce820SDaniel Almeida     ///
145*aa9ce820SDaniel Almeida     /// Returns an error if the advance would exceed the data bounds.
146*aa9ce820SDaniel Almeida     fn advance(&mut self, nbytes: usize) -> Result {
147*aa9ce820SDaniel Almeida         if self.pos + nbytes > self.data.len() {
148*aa9ce820SDaniel Almeida             dev_err!(
149*aa9ce820SDaniel Almeida                 self.dev,
150*aa9ce820SDaniel Almeida                 "Invalid firmware file: advance of size {} at position {} is out of bounds",
151*aa9ce820SDaniel Almeida                 nbytes,
152*aa9ce820SDaniel Almeida                 self.pos,
153*aa9ce820SDaniel Almeida             );
154*aa9ce820SDaniel Almeida             return Err(EINVAL);
155*aa9ce820SDaniel Almeida         }
156*aa9ce820SDaniel Almeida         self.pos += nbytes;
157*aa9ce820SDaniel Almeida         Ok(())
158*aa9ce820SDaniel Almeida     }
159*aa9ce820SDaniel Almeida }
160*aa9ce820SDaniel Almeida 
161*aa9ce820SDaniel Almeida /// Parser for Mali CSF GPU firmware binaries.
162*aa9ce820SDaniel Almeida ///
163*aa9ce820SDaniel Almeida /// Parses the firmware binary format, extracting section metadata including virtual
164*aa9ce820SDaniel Almeida /// address ranges, data offsets, and memory protection flags needed to load firmware
165*aa9ce820SDaniel Almeida /// into the MCU's memory.
166*aa9ce820SDaniel Almeida pub(super) struct FwParser<'a> {
167*aa9ce820SDaniel Almeida     cursor: Cursor<'a>,
168*aa9ce820SDaniel Almeida }
169*aa9ce820SDaniel Almeida 
170*aa9ce820SDaniel Almeida impl<'a> FwParser<'a> {
171*aa9ce820SDaniel Almeida     /// Creates a new firmware parser for the given firmware binary data.
172*aa9ce820SDaniel Almeida     pub(super) fn new(dev: &'a Device, data: &'a [u8]) -> Self {
173*aa9ce820SDaniel Almeida         Self {
174*aa9ce820SDaniel Almeida             cursor: Cursor::new(dev, data),
175*aa9ce820SDaniel Almeida         }
176*aa9ce820SDaniel Almeida     }
177*aa9ce820SDaniel Almeida 
178*aa9ce820SDaniel Almeida     /// Parses the firmware binary and returns a collection of parsed sections.
179*aa9ce820SDaniel Almeida     ///
180*aa9ce820SDaniel Almeida     /// This method validates the firmware header and iterates through all entries
181*aa9ce820SDaniel Almeida     /// in the binary, extracting section information needed for loading.
182*aa9ce820SDaniel Almeida     pub(super) fn parse(&mut self) -> Result<KVec<ParsedSection>> {
183*aa9ce820SDaniel Almeida         let fw_header = self.parse_fw_header()?;
184*aa9ce820SDaniel Almeida         let header_end = fw_header.size as usize;
185*aa9ce820SDaniel Almeida 
186*aa9ce820SDaniel Almeida         let mut parsed_sections = KVec::new();
187*aa9ce820SDaniel Almeida         while self.cursor.pos() < header_end {
188*aa9ce820SDaniel Almeida             let entry_section = self.parse_entry(header_end)?;
189*aa9ce820SDaniel Almeida 
190*aa9ce820SDaniel Almeida             if let Some(inner) = entry_section.inner {
191*aa9ce820SDaniel Almeida                 parsed_sections.push(inner, GFP_KERNEL)?;
192*aa9ce820SDaniel Almeida             }
193*aa9ce820SDaniel Almeida         }
194*aa9ce820SDaniel Almeida 
195*aa9ce820SDaniel Almeida         if parsed_sections.is_empty() {
196*aa9ce820SDaniel Almeida             dev_err!(self.cursor.dev, "Firmware contains no loadable sections");
197*aa9ce820SDaniel Almeida             return Err(EINVAL);
198*aa9ce820SDaniel Almeida         }
199*aa9ce820SDaniel Almeida 
200*aa9ce820SDaniel Almeida         Ok(parsed_sections)
201*aa9ce820SDaniel Almeida     }
202*aa9ce820SDaniel Almeida 
203*aa9ce820SDaniel Almeida     fn parse_fw_header(&mut self) -> Result<FirmwareHeader> {
204*aa9ce820SDaniel Almeida         let fw_header: FirmwareHeader = match FirmwareHeader::new(&mut self.cursor) {
205*aa9ce820SDaniel Almeida             Ok(fw_header) => fw_header,
206*aa9ce820SDaniel Almeida             Err(e) => {
207*aa9ce820SDaniel Almeida                 dev_err!(self.cursor.dev, "Invalid firmware file: {}", e.to_errno());
208*aa9ce820SDaniel Almeida                 return Err(e);
209*aa9ce820SDaniel Almeida             }
210*aa9ce820SDaniel Almeida         };
211*aa9ce820SDaniel Almeida 
212*aa9ce820SDaniel Almeida         if fw_header.size as usize > self.cursor.len() {
213*aa9ce820SDaniel Almeida             dev_err!(self.cursor.dev, "Firmware image is truncated");
214*aa9ce820SDaniel Almeida             return Err(EINVAL);
215*aa9ce820SDaniel Almeida         }
216*aa9ce820SDaniel Almeida         Ok(fw_header)
217*aa9ce820SDaniel Almeida     }
218*aa9ce820SDaniel Almeida 
219*aa9ce820SDaniel Almeida     fn parse_entry(&mut self, header_end: usize) -> Result<EntrySection> {
220*aa9ce820SDaniel Almeida         let entry_start = self.cursor.pos();
221*aa9ce820SDaniel Almeida 
222*aa9ce820SDaniel Almeida         let entry_header_end = entry_start
223*aa9ce820SDaniel Almeida             .checked_add(size_of::<EntryHeader>())
224*aa9ce820SDaniel Almeida             .ok_or(EINVAL)?;
225*aa9ce820SDaniel Almeida 
226*aa9ce820SDaniel Almeida         if entry_header_end > header_end {
227*aa9ce820SDaniel Almeida             dev_err!(
228*aa9ce820SDaniel Almeida                 self.cursor.dev,
229*aa9ce820SDaniel Almeida                 "Firmware entry header at {:#x} exceeds header region ending at {:#x}",
230*aa9ce820SDaniel Almeida                 entry_start,
231*aa9ce820SDaniel Almeida                 header_end
232*aa9ce820SDaniel Almeida             );
233*aa9ce820SDaniel Almeida             return Err(EINVAL);
234*aa9ce820SDaniel Almeida         }
235*aa9ce820SDaniel Almeida 
236*aa9ce820SDaniel Almeida         let entry_section = EntrySection {
237*aa9ce820SDaniel Almeida             entry_hdr: EntryHeader(self.cursor.read_u32()?),
238*aa9ce820SDaniel Almeida             inner: None,
239*aa9ce820SDaniel Almeida         };
240*aa9ce820SDaniel Almeida 
241*aa9ce820SDaniel Almeida         let firmware_size = self.cursor.len();
242*aa9ce820SDaniel Almeida         let entry_size = entry_section.entry_hdr.size() as usize;
243*aa9ce820SDaniel Almeida 
244*aa9ce820SDaniel Almeida         if self.cursor.pos() % size_of::<u32>() != 0
245*aa9ce820SDaniel Almeida             || entry_size % size_of::<u32>() != 0
246*aa9ce820SDaniel Almeida             || entry_size < size_of::<EntryHeader>()
247*aa9ce820SDaniel Almeida         {
248*aa9ce820SDaniel Almeida             dev_err!(
249*aa9ce820SDaniel Almeida                 self.cursor.dev,
250*aa9ce820SDaniel Almeida                 "Firmware entry isn't 32 bit aligned, offset={:#x} size={:#x}",
251*aa9ce820SDaniel Almeida                 self.cursor.pos() - size_of::<u32>(),
252*aa9ce820SDaniel Almeida                 entry_size
253*aa9ce820SDaniel Almeida             );
254*aa9ce820SDaniel Almeida             return Err(EINVAL);
255*aa9ce820SDaniel Almeida         }
256*aa9ce820SDaniel Almeida 
257*aa9ce820SDaniel Almeida         let entry_end = entry_start.checked_add(entry_size).ok_or(EINVAL)?;
258*aa9ce820SDaniel Almeida 
259*aa9ce820SDaniel Almeida         if entry_end > header_end {
260*aa9ce820SDaniel Almeida             dev_err!(
261*aa9ce820SDaniel Almeida                 self.cursor.dev,
262*aa9ce820SDaniel Almeida                 "Firmware entry at {:#x} extends beyond header region ending at {:#x}",
263*aa9ce820SDaniel Almeida                 entry_start,
264*aa9ce820SDaniel Almeida                 header_end
265*aa9ce820SDaniel Almeida             );
266*aa9ce820SDaniel Almeida             return Err(EINVAL);
267*aa9ce820SDaniel Almeida         }
268*aa9ce820SDaniel Almeida 
269*aa9ce820SDaniel Almeida         let section_hdr_size = entry_size - size_of::<EntryHeader>();
270*aa9ce820SDaniel Almeida 
271*aa9ce820SDaniel Almeida         let entry_section = {
272*aa9ce820SDaniel Almeida             let mut entry_cursor = self.cursor.view(self.cursor.pos()..entry_end)?;
273*aa9ce820SDaniel Almeida 
274*aa9ce820SDaniel Almeida             match entry_section.entry_hdr.entry_type() {
275*aa9ce820SDaniel Almeida                 Ok(EntryType::Iface) => Ok(EntrySection {
276*aa9ce820SDaniel Almeida                     entry_hdr: entry_section.entry_hdr,
277*aa9ce820SDaniel Almeida                     inner: Self::parse_section_entry(&mut entry_cursor, firmware_size)?,
278*aa9ce820SDaniel Almeida                 }),
279*aa9ce820SDaniel Almeida                 Ok(
280*aa9ce820SDaniel Almeida                     EntryType::Config
281*aa9ce820SDaniel Almeida                     | EntryType::FutfTest
282*aa9ce820SDaniel Almeida                     | EntryType::TraceBuffer
283*aa9ce820SDaniel Almeida                     | EntryType::TimelineMetadata
284*aa9ce820SDaniel Almeida                     | EntryType::BuildInfoMetadata,
285*aa9ce820SDaniel Almeida                 ) => Ok(entry_section),
286*aa9ce820SDaniel Almeida 
287*aa9ce820SDaniel Almeida                 Err(_) => {
288*aa9ce820SDaniel Almeida                     if entry_section.entry_hdr.optional() {
289*aa9ce820SDaniel Almeida                         Ok(entry_section)
290*aa9ce820SDaniel Almeida                     } else {
291*aa9ce820SDaniel Almeida                         dev_err!(
292*aa9ce820SDaniel Almeida                             self.cursor.dev,
293*aa9ce820SDaniel Almeida                             "Failed to handle firmware entry type: {}",
294*aa9ce820SDaniel Almeida                             entry_section.entry_hdr.entry_type_raw()
295*aa9ce820SDaniel Almeida                         );
296*aa9ce820SDaniel Almeida                         Err(EINVAL)
297*aa9ce820SDaniel Almeida                     }
298*aa9ce820SDaniel Almeida                 }
299*aa9ce820SDaniel Almeida             }
300*aa9ce820SDaniel Almeida         };
301*aa9ce820SDaniel Almeida 
302*aa9ce820SDaniel Almeida         if entry_section.is_ok() {
303*aa9ce820SDaniel Almeida             self.cursor.advance(section_hdr_size)?;
304*aa9ce820SDaniel Almeida         }
305*aa9ce820SDaniel Almeida 
306*aa9ce820SDaniel Almeida         entry_section
307*aa9ce820SDaniel Almeida     }
308*aa9ce820SDaniel Almeida 
309*aa9ce820SDaniel Almeida     fn parse_section_entry(
310*aa9ce820SDaniel Almeida         entry_cursor: &mut Cursor<'_>,
311*aa9ce820SDaniel Almeida         firmware_size: usize,
312*aa9ce820SDaniel Almeida     ) -> Result<Option<ParsedSection>> {
313*aa9ce820SDaniel Almeida         let section_hdr: SectionHeader = SectionHeader::new(entry_cursor)?;
314*aa9ce820SDaniel Almeida 
315*aa9ce820SDaniel Almeida         if section_hdr.data.end < section_hdr.data.start {
316*aa9ce820SDaniel Almeida             dev_err!(
317*aa9ce820SDaniel Almeida                 entry_cursor.dev,
318*aa9ce820SDaniel Almeida                 "Firmware corrupted, data.end < data.start (0x{:x} < 0x{:x})",
319*aa9ce820SDaniel Almeida                 section_hdr.data.end,
320*aa9ce820SDaniel Almeida                 section_hdr.data.start
321*aa9ce820SDaniel Almeida             );
322*aa9ce820SDaniel Almeida             return Err(EINVAL);
323*aa9ce820SDaniel Almeida         }
324*aa9ce820SDaniel Almeida 
325*aa9ce820SDaniel Almeida         if section_hdr.data.end as usize > firmware_size {
326*aa9ce820SDaniel Almeida             dev_err!(
327*aa9ce820SDaniel Almeida                 entry_cursor.dev,
328*aa9ce820SDaniel Almeida                 "Firmware data range {:#x}..{:#x} exceeds firmware size {:#x}",
329*aa9ce820SDaniel Almeida                 section_hdr.data.start,
330*aa9ce820SDaniel Almeida                 section_hdr.data.end,
331*aa9ce820SDaniel Almeida                 firmware_size,
332*aa9ce820SDaniel Almeida             );
333*aa9ce820SDaniel Almeida             return Err(EINVAL);
334*aa9ce820SDaniel Almeida         }
335*aa9ce820SDaniel Almeida 
336*aa9ce820SDaniel Almeida         if section_hdr.va.start as usize % SZ_4K != 0 || section_hdr.va.end as usize % SZ_4K != 0 {
337*aa9ce820SDaniel Almeida             dev_err!(
338*aa9ce820SDaniel Almeida                 entry_cursor.dev,
339*aa9ce820SDaniel Almeida                 "Firmware virtual address range {:#x}..{:#x} is not page aligned",
340*aa9ce820SDaniel Almeida                 section_hdr.va.start,
341*aa9ce820SDaniel Almeida                 section_hdr.va.end
342*aa9ce820SDaniel Almeida             );
343*aa9ce820SDaniel Almeida             return Err(EINVAL);
344*aa9ce820SDaniel Almeida         }
345*aa9ce820SDaniel Almeida 
346*aa9ce820SDaniel Almeida         if section_hdr.section_flags.prot() {
347*aa9ce820SDaniel Almeida             dev_dbg!(
348*aa9ce820SDaniel Almeida                 entry_cursor.dev,
349*aa9ce820SDaniel Almeida                 "Firmware protected mode entry not supported, ignoring"
350*aa9ce820SDaniel Almeida             );
351*aa9ce820SDaniel Almeida             return Ok(None);
352*aa9ce820SDaniel Almeida         }
353*aa9ce820SDaniel Almeida 
354*aa9ce820SDaniel Almeida         if section_hdr.va.start == CSF_MCU_SHARED_REGION_START
355*aa9ce820SDaniel Almeida             && !section_hdr.section_flags.shared()
356*aa9ce820SDaniel Almeida         {
357*aa9ce820SDaniel Almeida             dev_err!(
358*aa9ce820SDaniel Almeida                 entry_cursor.dev,
359*aa9ce820SDaniel Almeida                 "Interface at 0x{:x} must be shared",
360*aa9ce820SDaniel Almeida                 CSF_MCU_SHARED_REGION_START
361*aa9ce820SDaniel Almeida             );
362*aa9ce820SDaniel Almeida             return Err(EINVAL);
363*aa9ce820SDaniel Almeida         }
364*aa9ce820SDaniel Almeida 
365*aa9ce820SDaniel Almeida         if section_hdr.va.is_empty() {
366*aa9ce820SDaniel Almeida             return Ok(None);
367*aa9ce820SDaniel Almeida         }
368*aa9ce820SDaniel Almeida 
369*aa9ce820SDaniel Almeida         let mut vm_map_flags = VmMapFlags::empty();
370*aa9ce820SDaniel Almeida 
371*aa9ce820SDaniel Almeida         if !section_hdr.section_flags.write() {
372*aa9ce820SDaniel Almeida             vm_map_flags |= VmFlag::Readonly;
373*aa9ce820SDaniel Almeida         }
374*aa9ce820SDaniel Almeida 
375*aa9ce820SDaniel Almeida         if !section_hdr.section_flags.exec() {
376*aa9ce820SDaniel Almeida             vm_map_flags |= VmFlag::Noexec;
377*aa9ce820SDaniel Almeida         }
378*aa9ce820SDaniel Almeida 
379*aa9ce820SDaniel Almeida         // TODO: As in Panthor, map coherent firmware sections uncached until the VM
380*aa9ce820SDaniel Almeida         // supports a coherent mapping attribute.
381*aa9ce820SDaniel Almeida         if section_hdr.section_flags.cache_mode() != CacheMode::Cached {
382*aa9ce820SDaniel Almeida             vm_map_flags |= VmFlag::Uncached;
383*aa9ce820SDaniel Almeida         }
384*aa9ce820SDaniel Almeida 
385*aa9ce820SDaniel Almeida         Ok(Some(ParsedSection {
386*aa9ce820SDaniel Almeida             data_range: section_hdr.data.clone(),
387*aa9ce820SDaniel Almeida             va: section_hdr.va,
388*aa9ce820SDaniel Almeida             vm_map_flags,
389*aa9ce820SDaniel Almeida         }))
390*aa9ce820SDaniel Almeida     }
391*aa9ce820SDaniel Almeida }
392*aa9ce820SDaniel Almeida 
393*aa9ce820SDaniel Almeida /// Firmware binary header containing version and size information.
394*aa9ce820SDaniel Almeida ///
395*aa9ce820SDaniel Almeida /// The header is located at the beginning of the firmware binary and contains
396*aa9ce820SDaniel Almeida /// a magic value for validation, version information, and the total size of
397*aa9ce820SDaniel Almeida /// all structured headers that follow.
398*aa9ce820SDaniel Almeida #[expect(dead_code)]
399*aa9ce820SDaniel Almeida struct FirmwareHeader {
400*aa9ce820SDaniel Almeida     /// Magic value to check binary validity.
401*aa9ce820SDaniel Almeida     magic: u32,
402*aa9ce820SDaniel Almeida 
403*aa9ce820SDaniel Almeida     /// Minor firmware version.
404*aa9ce820SDaniel Almeida     minor: u8,
405*aa9ce820SDaniel Almeida 
406*aa9ce820SDaniel Almeida     /// Major firmware version.
407*aa9ce820SDaniel Almeida     major: u8,
408*aa9ce820SDaniel Almeida 
409*aa9ce820SDaniel Almeida     /// Padding. Must be set to zero.
410*aa9ce820SDaniel Almeida     _padding1: u16,
411*aa9ce820SDaniel Almeida 
412*aa9ce820SDaniel Almeida     /// Firmware version hash.
413*aa9ce820SDaniel Almeida     version_hash: u32,
414*aa9ce820SDaniel Almeida 
415*aa9ce820SDaniel Almeida     /// Padding. Must be set to zero.
416*aa9ce820SDaniel Almeida     _padding2: u32,
417*aa9ce820SDaniel Almeida 
418*aa9ce820SDaniel Almeida     /// Total size of all the structured data headers at beginning of firmware binary.
419*aa9ce820SDaniel Almeida     size: u32,
420*aa9ce820SDaniel Almeida }
421*aa9ce820SDaniel Almeida 
422*aa9ce820SDaniel Almeida impl FirmwareHeader {
423*aa9ce820SDaniel Almeida     const FW_BINARY_MAGIC: u32 = 0xc3f13a6e;
424*aa9ce820SDaniel Almeida     const FW_BINARY_MAJOR_MAX: u8 = 0;
425*aa9ce820SDaniel Almeida 
426*aa9ce820SDaniel Almeida     /// Reads and validates a firmware header from the cursor.
427*aa9ce820SDaniel Almeida     ///
428*aa9ce820SDaniel Almeida     /// Verifies the magic value, version compatibility, and padding fields.
429*aa9ce820SDaniel Almeida     fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
430*aa9ce820SDaniel Almeida         let magic = cursor.read_u32()?;
431*aa9ce820SDaniel Almeida         if magic != Self::FW_BINARY_MAGIC {
432*aa9ce820SDaniel Almeida             dev_err!(cursor.dev, "Invalid firmware magic");
433*aa9ce820SDaniel Almeida             return Err(EINVAL);
434*aa9ce820SDaniel Almeida         }
435*aa9ce820SDaniel Almeida 
436*aa9ce820SDaniel Almeida         let minor = cursor.read_u8()?;
437*aa9ce820SDaniel Almeida         let major = cursor.read_u8()?;
438*aa9ce820SDaniel Almeida 
439*aa9ce820SDaniel Almeida         if major > Self::FW_BINARY_MAJOR_MAX {
440*aa9ce820SDaniel Almeida             dev_err!(
441*aa9ce820SDaniel Almeida                 cursor.dev,
442*aa9ce820SDaniel Almeida                 "Unsupported firmware binary header version {}.{} (expected {}.x)",
443*aa9ce820SDaniel Almeida                 major,
444*aa9ce820SDaniel Almeida                 minor,
445*aa9ce820SDaniel Almeida                 Self::FW_BINARY_MAJOR_MAX
446*aa9ce820SDaniel Almeida             );
447*aa9ce820SDaniel Almeida             return Err(EINVAL);
448*aa9ce820SDaniel Almeida         }
449*aa9ce820SDaniel Almeida 
450*aa9ce820SDaniel Almeida         let padding1 = cursor.read_u16()?;
451*aa9ce820SDaniel Almeida         let version_hash = cursor.read_u32()?;
452*aa9ce820SDaniel Almeida         let padding2 = cursor.read_u32()?;
453*aa9ce820SDaniel Almeida         let size = cursor.read_u32()?;
454*aa9ce820SDaniel Almeida 
455*aa9ce820SDaniel Almeida         if padding1 != 0 || padding2 != 0 {
456*aa9ce820SDaniel Almeida             dev_err!(
457*aa9ce820SDaniel Almeida                 cursor.dev,
458*aa9ce820SDaniel Almeida                 "Invalid firmware file: header padding is not zero"
459*aa9ce820SDaniel Almeida             );
460*aa9ce820SDaniel Almeida             return Err(EINVAL);
461*aa9ce820SDaniel Almeida         }
462*aa9ce820SDaniel Almeida 
463*aa9ce820SDaniel Almeida         let fw_header = Self {
464*aa9ce820SDaniel Almeida             magic,
465*aa9ce820SDaniel Almeida             minor,
466*aa9ce820SDaniel Almeida             major,
467*aa9ce820SDaniel Almeida             _padding1: padding1,
468*aa9ce820SDaniel Almeida             version_hash,
469*aa9ce820SDaniel Almeida             _padding2: padding2,
470*aa9ce820SDaniel Almeida             size,
471*aa9ce820SDaniel Almeida         };
472*aa9ce820SDaniel Almeida 
473*aa9ce820SDaniel Almeida         Ok(fw_header)
474*aa9ce820SDaniel Almeida     }
475*aa9ce820SDaniel Almeida }
476*aa9ce820SDaniel Almeida 
477*aa9ce820SDaniel Almeida /// Firmware section header for loading binary sections into MCU memory.
478*aa9ce820SDaniel Almeida #[derive(Debug)]
479*aa9ce820SDaniel Almeida struct SectionHeader {
480*aa9ce820SDaniel Almeida     section_flags: SectionFlags,
481*aa9ce820SDaniel Almeida     /// MCU virtual range to map this binary section to.
482*aa9ce820SDaniel Almeida     va: Range<u32>,
483*aa9ce820SDaniel Almeida     /// References the data in the FW binary.
484*aa9ce820SDaniel Almeida     data: Range<u32>,
485*aa9ce820SDaniel Almeida }
486*aa9ce820SDaniel Almeida 
487*aa9ce820SDaniel Almeida impl SectionHeader {
488*aa9ce820SDaniel Almeida     /// Reads and validates a section header from the cursor.
489*aa9ce820SDaniel Almeida     ///
490*aa9ce820SDaniel Almeida     /// Parses section flags, virtual address range, and data range from the firmware binary.
491*aa9ce820SDaniel Almeida     fn new(cursor: &mut Cursor<'_>) -> Result<Self> {
492*aa9ce820SDaniel Almeida         let section_flags = SectionFlags::try_from_fw(cursor.read_u32()?)?;
493*aa9ce820SDaniel Almeida 
494*aa9ce820SDaniel Almeida         let va_start = cursor.read_u32()?;
495*aa9ce820SDaniel Almeida         let va_end = cursor.read_u32()?;
496*aa9ce820SDaniel Almeida 
497*aa9ce820SDaniel Almeida         let va = va_start..va_end;
498*aa9ce820SDaniel Almeida 
499*aa9ce820SDaniel Almeida         if va.end < va.start {
500*aa9ce820SDaniel Almeida             dev_err!(
501*aa9ce820SDaniel Almeida                 cursor.dev,
502*aa9ce820SDaniel Almeida                 "Invalid firmware file: VA end precedes start at pos {}",
503*aa9ce820SDaniel Almeida                 cursor.pos(),
504*aa9ce820SDaniel Almeida             );
505*aa9ce820SDaniel Almeida             return Err(EINVAL);
506*aa9ce820SDaniel Almeida         }
507*aa9ce820SDaniel Almeida 
508*aa9ce820SDaniel Almeida         let data_start = cursor.read_u32()?;
509*aa9ce820SDaniel Almeida         let data_end = cursor.read_u32()?;
510*aa9ce820SDaniel Almeida         let data = data_start..data_end;
511*aa9ce820SDaniel Almeida 
512*aa9ce820SDaniel Almeida         Ok(Self {
513*aa9ce820SDaniel Almeida             section_flags,
514*aa9ce820SDaniel Almeida             va,
515*aa9ce820SDaniel Almeida             data,
516*aa9ce820SDaniel Almeida         })
517*aa9ce820SDaniel Almeida     }
518*aa9ce820SDaniel Almeida }
519*aa9ce820SDaniel Almeida 
520*aa9ce820SDaniel Almeida /// A firmware entry containing a header and optional parsed section data.
521*aa9ce820SDaniel Almeida ///
522*aa9ce820SDaniel Almeida /// Represents a single entry in the firmware binary, which may contain loadable
523*aa9ce820SDaniel Almeida /// section data or metadata that doesn't require loading.
524*aa9ce820SDaniel Almeida struct EntrySection {
525*aa9ce820SDaniel Almeida     entry_hdr: EntryHeader,
526*aa9ce820SDaniel Almeida     inner: Option<ParsedSection>,
527*aa9ce820SDaniel Almeida }
528*aa9ce820SDaniel Almeida 
529*aa9ce820SDaniel Almeida /// Header for a firmware entry, packed into a single u32.
530*aa9ce820SDaniel Almeida ///
531*aa9ce820SDaniel Almeida /// The entry header encodes the entry type, size, and optional flag in a
532*aa9ce820SDaniel Almeida /// 32-bit value with the following layout:
533*aa9ce820SDaniel Almeida /// - Bits 0-7: Entry type
534*aa9ce820SDaniel Almeida /// - Bits 8-15: Size in bytes
535*aa9ce820SDaniel Almeida /// - Bit 31: Optional flag
536*aa9ce820SDaniel Almeida struct EntryHeader(u32);
537*aa9ce820SDaniel Almeida 
538*aa9ce820SDaniel Almeida impl EntryHeader {
539*aa9ce820SDaniel Almeida     fn entry_type_raw(&self) -> u8 {
540*aa9ce820SDaniel Almeida         (self.0 & 0xff) as u8
541*aa9ce820SDaniel Almeida     }
542*aa9ce820SDaniel Almeida 
543*aa9ce820SDaniel Almeida     fn entry_type(&self) -> Result<EntryType> {
544*aa9ce820SDaniel Almeida         let v = self.entry_type_raw();
545*aa9ce820SDaniel Almeida         EntryType::try_from(v)
546*aa9ce820SDaniel Almeida     }
547*aa9ce820SDaniel Almeida 
548*aa9ce820SDaniel Almeida     fn optional(&self) -> bool {
549*aa9ce820SDaniel Almeida         self.0 & bit_u32(31) != 0
550*aa9ce820SDaniel Almeida     }
551*aa9ce820SDaniel Almeida 
552*aa9ce820SDaniel Almeida     fn size(&self) -> u32 {
553*aa9ce820SDaniel Almeida         self.0 >> 8 & 0xff
554*aa9ce820SDaniel Almeida     }
555*aa9ce820SDaniel Almeida }
556*aa9ce820SDaniel Almeida 
557*aa9ce820SDaniel Almeida #[derive(Clone, Copy, Debug)]
558*aa9ce820SDaniel Almeida #[repr(u8)]
559*aa9ce820SDaniel Almeida enum EntryType {
560*aa9ce820SDaniel Almeida     /// Host <-> FW interface.
561*aa9ce820SDaniel Almeida     Iface = 0,
562*aa9ce820SDaniel Almeida     /// FW config.
563*aa9ce820SDaniel Almeida     Config = 1,
564*aa9ce820SDaniel Almeida     /// Unit tests.
565*aa9ce820SDaniel Almeida     FutfTest = 2,
566*aa9ce820SDaniel Almeida     /// Trace buffer interface.
567*aa9ce820SDaniel Almeida     TraceBuffer = 3,
568*aa9ce820SDaniel Almeida     /// Timeline metadata interface.
569*aa9ce820SDaniel Almeida     TimelineMetadata = 4,
570*aa9ce820SDaniel Almeida     /// Metadata about how the FW binary was built.
571*aa9ce820SDaniel Almeida     BuildInfoMetadata = 6,
572*aa9ce820SDaniel Almeida }
573*aa9ce820SDaniel Almeida 
574*aa9ce820SDaniel Almeida impl TryFrom<u8> for EntryType {
575*aa9ce820SDaniel Almeida     type Error = Error;
576*aa9ce820SDaniel Almeida 
577*aa9ce820SDaniel Almeida     fn try_from(value: u8) -> Result<Self, Self::Error> {
578*aa9ce820SDaniel Almeida         match value {
579*aa9ce820SDaniel Almeida             0 => Ok(EntryType::Iface),
580*aa9ce820SDaniel Almeida             1 => Ok(EntryType::Config),
581*aa9ce820SDaniel Almeida             2 => Ok(EntryType::FutfTest),
582*aa9ce820SDaniel Almeida             3 => Ok(EntryType::TraceBuffer),
583*aa9ce820SDaniel Almeida             4 => Ok(EntryType::TimelineMetadata),
584*aa9ce820SDaniel Almeida             6 => Ok(EntryType::BuildInfoMetadata),
585*aa9ce820SDaniel Almeida             _ => Err(EINVAL),
586*aa9ce820SDaniel Almeida         }
587*aa9ce820SDaniel Almeida     }
588*aa9ce820SDaniel Almeida }
589