diff --git a/sample-objects/arm64-main-r5f0-0-fw.armhf b/sample-objects/arm64-main-r5f0-0-fw.armhf new file mode 100644 index 0000000..392b493 Binary files /dev/null and b/sample-objects/arm64-main-r5f0-0-fw.armhf differ diff --git a/src/abi.rs b/src/abi.rs index 3ffcfc3..00d3020 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -3146,6 +3146,23 @@ pub const DT_MIPS_DIRECT: u32 = 0x70000032; /// The dynamic symbol entry of a callback function pub const DT_MIPS_RLD_OBJ_UPDATE: u32 = 0x70000033; +// resource_type in FirmwareResource +/// Request for allocation of a physically contiguous memory region. +pub const RSC_CARVEOUT: u32 = 0; +/// Request to iommu_map a memory-based peripheral. +pub const RSC_DEVMEM: u32 = 1; +/// Announces the availability of a trace buffer into which the remote processor will be writing logs. +pub const RSC_TRACE: u32 = 2; +/// Declare support for a virtio device, and serve as its virtio header. +pub const RSC_VDEV: u32 = 3; +/// Start of the vendor specific resource types range +pub const RSC_VENDOR_START: u32 = 128; +/// End of the vendor specific resource types range +pub const RSC_VENDOR_END: u32 = 512; + +/// Tells that the firmware does not care about the location of the resource +pub const FW_RSC_ADDR_ANY: u32 = u32::MAX; + /// None pub const RHF_NONE: u32 = 0x00000000; /// Use runtime loading shortcuts if possible diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 6444e76..601a8f0 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -10,6 +10,7 @@ use crate::hash::{GnuHashTable, SysVHashTable}; use crate::note::NoteIterator; use crate::parse::{ParseAt, ParseError, ReadBytesExt}; use crate::relocation::{RelIterator, RelaIterator}; +use crate::resource_table::ResourceTable; use crate::section::{SectionHeader, SectionHeaderTable}; use crate::segment::{ProgramHeader, SegmentTable}; use crate::string_table::StringTable; @@ -549,6 +550,28 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { )) } + /// Get the section data for a given [SectionHeader], and interpret it as a + /// [ResourceTable](crate::resource_table::ResourceTable) + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_PROGBITS](abi::SHT_PROGBITS). + pub fn section_data_as_resource_table( + &self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_PROGBITS { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_PROGBITS, + ))); + } + + let (buf, _) = self.section_data(shdr)?; + let mut offset = 0; + ResourceTable::parse_at(self.ehdr.endianness, self.ehdr.class, &mut offset, buf) + } + /// Internal helper to get the section data for an SHT_DYNAMIC section as a .dynamic section table. /// See [ElfBytes::dynamic] or [ElfBytes::find_common_data] for the public interface fn section_data_as_dynamic( @@ -828,7 +851,10 @@ impl<'data, E: EndianParse> ElfBytes<'data, E> { #[cfg(test)] mod interface_tests { use super::*; - use crate::abi::{SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB}; + use crate::abi::{ + FW_RSC_ADDR_ANY, SHT_GNU_HASH, SHT_NOBITS, SHT_NOTE, SHT_NULL, SHT_REL, SHT_RELA, + SHT_STRTAB, + }; use crate::endian::AnyEndian; use crate::hash::sysv_hash; use crate::note::{Note, NoteGnuAbiTag, NoteGnuBuildId}; @@ -1200,6 +1226,77 @@ mod interface_tests { assert!(notes.next().is_none()); } + #[test] + fn section_data_as_resource_table() { + use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring}; + let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let shdr = file + .section_headers() + .expect("File should have a section table") + .get(24) + .expect("Failed to get .resource_table shdr"); + + let resource_table = file + .section_data_as_resource_table(&shdr) + .expect("Failed to read .resource_table section"); + + let mut it = (&resource_table).into_iter(); + if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { + assert_eq!(vdev.id, 7); + assert_eq!(vdev.notify_id, 0); + assert_eq!(vdev.dfeatures, 1); + assert_eq!(vdev.num_of_vrings, 2); + let mut it = (&vdev).into_iter(); + + let vdev_vring1 = it.next().unwrap(); + assert_eq!( + vdev_vring1, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 1, + pa: 0 + } + ); + + let vdev_vring2 = it.next().unwrap(); + assert_eq!( + vdev_vring2, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 2, + pa: 0 + } + ); + assert!(it.next().is_none()); + } else { + panic!(".resource_table parsed incorrectly"); + } + + if let FirmwareResource::Trace(trace) = it.next().unwrap() { + assert_eq!(trace.da, 0xA0106080); + assert_eq!(trace.len, 0x1000); + let null_pos = trace + .name + .iter() + .position(|&c| c == 0) + .unwrap_or(trace.name.len()); + assert_eq!( + String::from_utf8_lossy(&trace.name[..null_pos]), + "trace:r5fss0_0" + ); + } else { + panic!(".resource_table parsed incorrectly"); + } + } + #[test] fn segment_data_as_notes() { let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); diff --git a/src/elf_stream.rs b/src/elf_stream.rs index 026c885..70cd950 100644 --- a/src/elf_stream.rs +++ b/src/elf_stream.rs @@ -13,6 +13,7 @@ use crate::gnu_symver::{ use crate::note::NoteIterator; use crate::parse::{ParseAt, ParseError}; use crate::relocation::{RelIterator, RelaIterator}; +use crate::resource_table::ResourceTable; use crate::section::{SectionHeader, SectionHeaderTable}; use crate::segment::ProgramHeader; use crate::segment::SegmentTable; @@ -646,6 +647,30 @@ impl ElfStream { )) } + /// Read the section data for the given + /// [SectionHeader](SectionHeader) and interpret it in-place as a + /// [ResourceTable](crate::resource_table::ResourceTable) + /// + /// Returns a [ParseError] if the + /// [sh_type](SectionHeader#structfield.sh_type) is not + /// [SHT_PROGBITS](abi::SHT_PROGBITS). + pub fn section_data_as_resource_table( + &mut self, + shdr: &SectionHeader, + ) -> Result, ParseError> { + if shdr.sh_type != abi::SHT_PROGBITS { + return Err(ParseError::UnexpectedSectionType(( + shdr.sh_type, + abi::SHT_PROGBITS, + ))); + } + + let (start, end) = shdr.get_data_range()?; + let buf = self.reader.read_bytes(start, end)?; + let mut offset = 0; + ResourceTable::parse_at(self.ehdr.endianness, self.ehdr.class, &mut offset, buf) + } + /// Read the segment data for the given [Segment](ProgramHeader). pub fn segment_data(&mut self, phdr: &ProgramHeader) -> Result<&[u8], ParseError> { let (start, end) = phdr.get_file_data_range()?; @@ -739,6 +764,7 @@ impl CachingReader { #[cfg(test)] mod interface_tests { use super::*; + use crate::abi::FW_RSC_ADDR_ANY; use crate::dynamic::Dyn; use crate::endian::AnyEndian; use crate::hash::SysVHashTable; @@ -1112,6 +1138,73 @@ mod interface_tests { assert!(notes.next().is_none()); } + #[test] + fn section_data_as_resource_table() { + use crate::resource_table::{FirmwareResource, FirmwareResourceVdevVring}; + let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); + let io = std::fs::File::open(path).expect("Could not open file."); + let mut file = ElfStream::::open_stream(io).expect("Open test1"); + + let shdrs = file.section_headers(); + let shdr = shdrs[24]; + + let resource_table = file + .section_data_as_resource_table(&shdr) + .expect("Failed to read .resource_table section"); + + let mut it = (&resource_table).into_iter(); + if let FirmwareResource::Vdev(vdev) = it.next().unwrap() { + assert_eq!(vdev.id, 7); + assert_eq!(vdev.notify_id, 0); + assert_eq!(vdev.dfeatures, 1); + assert_eq!(vdev.num_of_vrings, 2); + let mut it = (&vdev).into_iter(); + + let vdev_vring1 = it.next().unwrap(); + assert_eq!( + vdev_vring1, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 1, + pa: 0 + } + ); + + let vdev_vring2 = it.next().unwrap(); + assert_eq!( + vdev_vring2, + FirmwareResourceVdevVring { + da: FW_RSC_ADDR_ANY, + align: 0x1000, + num: 256, + notify_id: 2, + pa: 0 + } + ); + assert!(it.next().is_none()); + } else { + panic!(".resource_table parsed incorrectly"); + } + + if let FirmwareResource::Trace(trace) = it.next().unwrap() { + assert_eq!(trace.da, 0xA0106080); + assert_eq!(trace.len, 0x1000); + let null_pos = trace + .name + .iter() + .position(|&c| c == 0) + .unwrap_or(trace.name.len()); + assert_eq!( + String::from_utf8_lossy(&trace.name[..null_pos]), + "trace:r5fss0_0" + ); + } else { + panic!(".resource_table parsed incorrectly"); + } + } + #[test] fn symbol_version_table() { let path = std::path::PathBuf::from("sample-objects/symver.x86_64.so"); diff --git a/src/lib.rs b/src/lib.rs index 75cfad8..cc19800 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,7 @@ pub mod gnu_symver; pub mod hash; pub mod note; pub mod relocation; +pub mod resource_table; pub mod section; pub mod segment; pub mod string_table; diff --git a/src/resource_table.rs b/src/resource_table.rs new file mode 100644 index 0000000..5c5c414 --- /dev/null +++ b/src/resource_table.rs @@ -0,0 +1,622 @@ +//! Parsing remoteproc resource_table: `.resource_table` +//! +//! Example for getting the trace information +//! ``` +//! use elf::ElfBytes; +//! use elf::endian::AnyEndian; +//! use elf::note::Note; +//! use elf::note::NoteGnuAbiTag; +//! use elf::resource_table::FirmwareResource; +//! +//! let path = std::path::PathBuf::from("sample-objects/arm64-main-r5f0-0-fw.armhf"); +//! let file_data = std::fs::read(path).expect("Could not read file."); +//! let slice = file_data.as_slice(); +//! let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); +//! +//! let shdr = file +//! .section_header_by_name(".resource_table") +//! .expect("section table should be parseable") +//! .expect("file should have a .resource_table section"); +//! +//! let resource_table = file +//! .section_data_as_resource_table(&shdr) +//! .expect("Should be able to get .resource_table section data"); +//! let resources: Vec<_> = resource_table +//! .into_iter() +//! .collect(); +//! if let FirmwareResource::Trace(trace) = resources[1] { +//! assert_eq!(trace.da, 0xA0106080); +//! assert_eq!(trace.len, 0x1000); +//! let null_pos = trace.name.iter().position(|&c| c == 0).unwrap_or(trace.name.len()); +//! assert_eq!(String::from_utf8_lossy(&trace.name[..null_pos]), "trace:r5fss0_0"); +//! } +//! ``` +use crate::{ + abi::{RSC_CARVEOUT, RSC_DEVMEM, RSC_TRACE, RSC_VDEV}, + endian::EndianParse, + file::Class, + parse::{ParseAt, ReadBytesExt}, + ParseError, +}; + +/// Firmware resource table header +/// The offsets and entries are left as raw bytes due to the dynamic nature of the possible +/// variants and lengths, this means each entry needs to be parsed when iterating +/// The entire table is parsed when constructed with `parse_at` so that parsing errors are not +/// encountered during iterating, which has no error state +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ResourceTable<'data, E: EndianParse> { + /// Version number + pub version: u32, + /// Number of resource entries + pub num_resources: u32, + /// Array of offsets pointing at the various resource entries + /// These offsets are from the start of the section, so you must subtract + /// (size_of::() * 4) + resource_offsets.len() to index into the resource_entries + /// These are u32s in the endian of the elf, so they must be parsed as they are needed + pub resource_offsets: &'data [u8], + /// The rest of the section, to be parsed into FirmwareResource as needed + pub resource_entries: &'data [u8], + + /// Used to create the iterator later + endian: E, + /// Used to create the iterator later + class: Class, +} + +impl<'data, E: EndianParse> ResourceTable<'data, E> { + pub fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let version = endian.parse_u32_at(offset, data)?; + let num_resources = endian.parse_u32_at(offset, data)?; + + let _reserved = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + + let resource_offsets = { + let resource_offsets_start = *offset; + let resource_offsets_end = + resource_offsets_start + (size_of::() * num_resources as usize); + *offset = resource_offsets_end; + &data.get_bytes(resource_offsets_start..resource_offsets_end)? + }; + + let resource_entries = { + let resource_entries_start = *offset; + let resource_entries_end = data.len(); + *offset = resource_entries_end; + &data.get_bytes(resource_entries_start..resource_entries_end)? + }; + // Parse all resource entries + // The itererator cannot return an error state so the checks are done here + let resource_table = ResourceTable { + version, + num_resources, + resource_offsets, + resource_entries, + endian, + class, + }; + for i in 0..num_resources { + let _ = resource_table.get(i as usize)?; + } + Ok(resource_table) + } + fn get(&self, entry: usize) -> Result, ParseError> { + if entry >= self.num_resources as usize { + return Err(ParseError::BadOffset(entry as u64)); + } + + // offset into the resource_entries + let mut resource_offsets_offset: usize = entry * size_of::(); + + // offset into the resource_entries + let mut resource_entry_offset = self + .endian + .parse_u32_at(&mut resource_offsets_offset, self.resource_offsets)? + as usize; + // Subtract the header size + resource_entry_offset = + resource_entry_offset - ((size_of::() * 4) + self.resource_offsets.len()) as usize; + + FirmwareResource::parse_at( + self.endian, + self.class, + &mut resource_entry_offset, + self.resource_entries, + ) + } + pub fn to_iter(&'data self) -> FirmwareResourceIterator<'data, E> { + FirmwareResourceIterator { + idx: 0, + resource_table: self, + } + } +} + +impl<'data, E: EndianParse> IntoIterator for &'data ResourceTable<'data, E> { + type Item = FirmwareResource<'data, E>; + type IntoIter = FirmwareResourceIterator<'data, E>; + fn into_iter(self) -> Self::IntoIter { + self.to_iter() + } +} + +impl<'data, E: EndianParse> Iterator for FirmwareResourceIterator<'data, E> { + type Item = FirmwareResource<'data, E>; + fn next(&mut self) -> Option { + let out = self.resource_table.get(self.idx).ok(); + self.idx += 1; + out + } +} + +#[derive(Debug)] +pub struct FirmwareResourceIterator<'data, E: EndianParse> { + idx: usize, + resource_table: &'data ResourceTable<'data, E>, +} + +/// This enum contains parsed firmware resource variants that can be matched on +#[derive(Debug, PartialEq, Eq)] +pub enum FirmwareResource<'data, E: EndianParse> { + Carveout(FirmwareResourceCarveout<'data>), + Devmem(FirmwareResourceDevmem<'data>), + Trace(FirmwareResourceTrace<'data>), + Vdev(FirmwareResourceVdev<'data, E>), + /// Represents Resource with an unsupported type + /// These are usually vendor defined resources + Unknown(u32), +} + +impl<'data, E: EndianParse> FirmwareResource<'data, E> { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + // Can break this out to enum if needed + let resource_type = endian.parse_u32_at(offset, data)?; + match resource_type { + RSC_CARVEOUT => Ok(FirmwareResource::Carveout( + FirmwareResourceCarveout::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceCarveout::size_for(class)], + )?, + )), + RSC_DEVMEM => Ok(FirmwareResource::Devmem(FirmwareResourceDevmem::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceDevmem::size_for(class)], + )?)), + RSC_TRACE => Ok(FirmwareResource::Trace(FirmwareResourceTrace::parse_at( + endian, + class, + offset, + &data[..*offset + FirmwareResourceTrace::size_for(class)], + )?)), + RSC_VDEV => Ok(FirmwareResource::Vdev(FirmwareResourceVdev::parse_at( + endian, class, offset, &data, + )?)), + _ => Ok(FirmwareResource::Unknown(resource_type)), + } + } +} + +/// physically contiguous memory request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceCarveout<'data> { + /// Device address + pub da: u32, + /// Physical address + pub pa: u32, + /// Length in bytes + pub len: u32, + /// iommu protection flags + pub flags: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} +impl<'data> FirmwareResourceCarveout<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 5) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let flags = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceCarveout { + da, + pa, + len, + flags, + name, + }) + } +} + +/// iommu mapping request +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceDevmem<'data> { + /// Device address + pub da: u32, + /// Physical address + pub pa: u32, + /// Length in bytes + pub len: u32, + /// iommu protection flags + pub flags: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} + +impl<'data> FirmwareResourceDevmem<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 5) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let flags = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceDevmem { + da, + pa, + len, + flags, + name, + }) + } +} + +/// trace buffer declaration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceTrace<'data> { + /// Device address + pub da: u32, + /// Length in bytes + pub len: u32, + /// Human-readable name of the requested memory region + pub name: &'data [u8; 32], +} + +impl<'data> FirmwareResourceTrace<'data> { + fn size_for(_class: Class) -> usize { + (size_of::() * 3) + 32 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let len = endian.parse_u32_at(offset, data)?; + let _reserved = endian.parse_u32_at(offset, data)?; + let name_start = *offset; + let name_end = name_start + 32; + let name = data.get_bytes(name_start..name_end)?.try_into()?; + *offset = name_end; + + Ok(FirmwareResourceTrace { da, len, name }) + } +} + +/// vring descriptor entry +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceVdevVring { + /// Device address + pub da: u32, + /// The alignment between the consumer and producer parts of the vring + pub align: u32, + /// Number of buffers supported by this vring (must be power of two) + pub num: u32, + /// A unique rproc-wide notify index for this vring. This notify + /// index is used when kicking a remote processor, to let it know that this + /// vring is triggered + pub notify_id: u32, + /// physical address + pub pa: u32, +} + +impl ParseAt for FirmwareResourceVdevVring { + fn size_for(_class: Class) -> usize { + size_of::() * 5 + } + fn parse_at( + endian: E, + _class: Class, + offset: &mut usize, + data: &[u8], + ) -> Result { + let da = endian.parse_u32_at(offset, data)?; + let align = endian.parse_u32_at(offset, data)?; + let num = endian.parse_u32_at(offset, data)?; + let notify_id = endian.parse_u32_at(offset, data)?; + let pa = endian.parse_u32_at(offset, data)?; + + Ok(FirmwareResourceVdevVring { + da, + align, + num, + notify_id, + pa, + }) + } +} + +impl<'data, E: EndianParse> IntoIterator for &'data FirmwareResourceVdev<'data, E> { + type Item = FirmwareResourceVdevVring; + type IntoIter = FirmwareResourceVdevVringIterator<'data, E>; + fn into_iter(self) -> Self::IntoIter { + self.to_iter() + } +} + +impl<'data, E: EndianParse> Iterator for FirmwareResourceVdevVringIterator<'data, E> { + type Item = FirmwareResourceVdevVring; + fn next(&mut self) -> Option { + let out = self.firmware_resource_vdev.get(self.idx).ok(); + self.idx += 1; + out + } +} + +#[derive(Debug)] +pub struct FirmwareResourceVdevVringIterator<'data, E: EndianParse> { + idx: usize, + firmware_resource_vdev: &'data FirmwareResourceVdev<'data, E>, +} + +/// virtio device header +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FirmwareResourceVdev<'data, E: EndianParse> { + /// virtio device id (as in virtio_ids.h) + pub id: u32, + /// A unique rproc-wide notify index for this vdev. This notify + /// index is used when kicking a remote processor, to let it know that the + /// status/features of this vdev have changes. + pub notify_id: u32, + /// Specifies the virtio device features supported by the firmware + pub dfeatures: u32, + /// A place holder used by the host to write back the + /// negotiated features that are supported by both sides. + pub gfeatures: u32, + /// A place holder where the host will indicate its virtio progress. + pub status: u8, + /// Indicates how many vrings are described in this vdev header + pub num_of_vrings: u8, + /// An array of num_of_vrings entries + pub vrings: &'data [u8], + /// virtio config space for this vdev (which is specific to the vdev; for more info, read the virtio spec). + pub config: &'data [u8], + + /// Used to create the iterator later + endian: E, + /// Used to create the iterator later + class: Class, +} + +impl<'data, E: EndianParse> FirmwareResourceVdev<'data, E> { + fn parse_at( + endian: E, + class: Class, + offset: &mut usize, + data: &'data [u8], + ) -> Result { + let id = endian.parse_u32_at(offset, data)?; + let notify_id = endian.parse_u32_at(offset, data)?; + let dfeatures = endian.parse_u32_at(offset, data)?; + let gfeatures = endian.parse_u32_at(offset, data)?; + let config_len = endian.parse_u32_at(offset, data)?; + let status = endian.parse_u8_at(offset, data)?; + let num_of_vrings = endian.parse_u8_at(offset, data)?; + let _reserved = endian.parse_u8_at(offset, data)?; + let _reserved = endian.parse_u8_at(offset, data)?; + let vrings = data.get_bytes( + *offset + ..*offset + + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)) + as usize, + )?; + *offset = *offset + (num_of_vrings as usize * FirmwareResourceVdevVring::size_for(class)); + let config = data.get_bytes(*offset..*offset + config_len as usize)?; + *offset = *offset + config_len as usize; + Ok(FirmwareResourceVdev { + id, + notify_id, + dfeatures, + gfeatures, + num_of_vrings, + status, + vrings, + config, + endian, + class, + }) + } + pub fn to_iter(&'data self) -> FirmwareResourceVdevVringIterator<'data, E> { + FirmwareResourceVdevVringIterator { + idx: 0, + firmware_resource_vdev: self, + } + } + fn get(&self, entry: usize) -> Result { + if entry >= self.num_of_vrings as usize { + return Err(ParseError::BadOffset(entry as u64)); + } + + let mut offset = 0; + FirmwareResourceVdevVring::parse_at( + self.endian, + self.class, + &mut offset, + &self.vrings[entry * FirmwareResourceVdevVring::size_for(self.class) + ..(entry + 1) * FirmwareResourceVdevVring::size_for(self.class)], + ) + } +} + +#[cfg(test)] +mod parse_tests { + use super::*; + use crate::endian::{BigEndian, LittleEndian}; + + #[test] + fn parse_resource_table_hdr() { + #[rustfmt::skip] + let data = [ + 0x00, 0x00, 0x00, 0x01, // version + 0x00, 0x00, 0x00, 0x00, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + ]; + let mut offset = 0; + + let resource_table = ResourceTable::parse_at(BigEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 0); + assert_eq!(resource_table.resource_offsets.len(), 0); + } + + #[test] + fn parse_resource_table_fw_rsc_carveout() { + #[rustfmt::skip] + let data = [ + 0x00, 0x00, 0x00, 0x01, // version + 0x00, 0x00, 0x00, 0x01, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x14, // resource_offsets[0] == 20 + + 0x00, 0x00, 0x00, 0x00, // RSC_CARVEOUT == 0 + 0xDE, 0xAD, 0xBE, 0xEF, // da == 0xDEADBEEF + 0xAA, 0xBB, 0xCC, 0xDD, // pa == 0xAABBCCDD + 0xAA, 0xAA, 0xAA, 0xAA, // len == 0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, // flags == 0x00000000 + 0x00, 0x00, 0x00, 0x00, // reserved + 0x40, 0x00, 0x00, 0x00, // name: [u8;32] == "@" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let mut offset = 0; + + let resource_table = ResourceTable::parse_at(BigEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 1); + assert_eq!(resource_table.resource_offsets.len(), 1 * size_of::()); + + let resource_carveout = resource_table + .to_iter() + .next() + .expect("Could not get resource carveout"); + assert!(matches!( + resource_carveout, + FirmwareResource::Carveout(FirmwareResourceCarveout { + da: 0xDEADBEEF, + pa: 0xAABBCCDD, + len: 0xAAAAAAAA, + flags: 0x00000000, + name: _ + }) + )); + + if let FirmwareResource::Carveout(resource_carveout) = resource_carveout { + assert_eq!(resource_carveout.name[0], b'@'); + } + } + #[test] + fn parse_resource_table_fw_rsc_devmem() { + #[rustfmt::skip] + let data = [ + 0x01, 0x00, 0x00, 0x00, // version + 0x01, 0x00, 0x00, 0x00, // num_resources + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x14, 0x00, 0x00, 0x00, // resource_offsets[0] == 20 + + 0x01, 0x00, 0x00, 0x00, // RSC_DEVMEM == 1 + 0xEF, 0xBE, 0xAD, 0xDE, // da == 0xDEADBEEF + 0xDD, 0xCC, 0xBB, 0xAA, // pa == 0xAABBCCDD + 0xAA, 0xAA, 0xAA, 0xAA, // len == 0xAAAAAAAA + 0x00, 0x00, 0x00, 0x00, // flags == 0x00000000 + 0x00, 0x00, 0x00, 0x00, // reserved + 0x40, 0x00, 0x00, 0x00, // name: [u8;32] == "@" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let mut offset = 0; + + let resource_table = + ResourceTable::parse_at(LittleEndian, Class::ELF32, &mut offset, &data) + .expect("Failed to parse"); + + assert_eq!(resource_table.version, 1); + assert_eq!(resource_table.num_resources, 1); + assert_eq!(resource_table.resource_offsets.len(), 1 * size_of::()); + + let resource_devmem = resource_table + .to_iter() + .next() + .expect("Could not get resource carveout"); + assert!(matches!( + resource_devmem, + FirmwareResource::Devmem(FirmwareResourceDevmem { + da: 0xDEADBEEF, + pa: 0xAABBCCDD, + len: 0xAAAAAAAA, + flags: 0x00000000, + name: _ + }) + )); + + if let FirmwareResource::Devmem(resource_devmem) = resource_devmem { + assert_eq!(resource_devmem.name[0], b'@'); + } + } +}