blob: aadd1dc285701322b930696c296527247581e1ba [file]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! ARM CoreSight ROM table registers and tree walker, mirroring
//! `<dev/coresight/rom_table.h>`.
use core::ffi::{c_char, c_void};
use core::mem::{align_of, size_of};
use bitrs::{bitfield_repr, layout};
use regio::{AccessRestrictsTo, Accessible, MmioBank, MmioPtr, Offset, Ro};
use zr::static_assert;
use crate::component::{CIDR1, ClassId, DEVARCH, MINIMUM_COMPONENT_SIZE, arm};
layout!({
/// [CS] D6.4.4
/// Class `0x1` ROM table entry register.
pub struct Class0x1RomEntry(u32);
{
let offset @ 31..12;
let __ @ 11..9;
let powerid @ 8..4;
let __ @ 3;
let powerid_valid @ 2;
let format @ 1;
let present @ 0;
}
});
impl Class0x1RomEntry {
/// Returns the byte offset of entry `n` within a Class `0x1` ROM table.
#[inline]
pub const fn offset_of(n: u32) -> Offset<Self, Ro> {
Offset::new((n as usize) * size_of::<u32>())
}
}
layout!({
/// [CS] D7.5.17
/// Class `0x9` 32-bit ROM table entry register.
pub struct Class0x9Rom32BitEntry(u32);
{
let offset @ 31..12;
let __ @ 11..9;
let powerid @ 8..4;
let __ @ 3;
let powerid_valid @ 2;
let present @ 1..0;
}
});
impl Class0x9Rom32BitEntry {
/// Returns the byte offset of entry `n` within a Class `0x9` 32-bit ROM table.
#[inline]
pub const fn offset_of(n: u32) -> Offset<Self, Ro> {
Offset::new((n as usize) * size_of::<u32>())
}
}
layout!({
/// [CS] D7.5.17
/// Class `0x9` 64-bit ROM table entry register.
pub struct Class0x9Rom64BitEntry(u64);
{
let offset @ 63..12;
let __ @ 11..9;
let powerid @ 8..4;
let __ @ 3;
let powerid_valid @ 2;
let present @ 1..0;
}
});
impl Class0x9Rom64BitEntry {
/// Returns the byte offset of entry `n` within a Class `0x9` 64-bit ROM table.
#[inline]
pub const fn offset_of(n: u32) -> Offset<Self, Ro> {
Offset::new((n as usize) * size_of::<u64>())
}
}
/// Entry format encoded in `DEVID.FORMAT` (`Class0x9RomDeviceIdRegister::Format`).
#[bitfield_repr(u8)]
#[derive(Copy, Clone)]
pub enum Class0x9RomEntryFormat {
/// 32-bit ROM table entries (`0`).
Bits32 = 0,
/// 64-bit ROM table entries (`1`).
Bits64 = 1,
}
static_assert!(size_of::<Class0x9RomEntryFormat>() == size_of::<u8>());
static_assert!(align_of::<Class0x9RomEntryFormat>() == align_of::<u8>());
layout!({
/// [CS] D7.5.10
/// Class `0x9` ROM table device ID register (`DEVID`).
pub struct Class0x9RomDeviceIdRegister(u32);
{
let __ @ 31..6;
let prr @ 5;
let sysmem @ 4;
let format @ 3..0: Class0x9RomEntryFormat;
}
});
/// Byte offset of [`Class0x9RomDeviceIdRegister`] (`DEVID`) within a component page.
pub const DEVID: Offset<Class0x9RomDeviceIdRegister, Ro> = Offset::new(0xfc8);
/// Represents an error that occurred while walking the table.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct WalkError {
/// Human-readable description of the walk error.
pub reason: &'static str,
/// Byte offset from the base ROM table at which the error occurred.
pub offset: u32,
}
/// There are several types of ROM table entry registers; this struct serves
/// as unified front-end for accessing their contents.
struct EntryContents {
value: u64,
offset: u32,
present: bool,
}
/// [CS] D5
/// A ROM table is a basic CoreSight component that provides pointers to other
/// components (including other ROM tables) in its lower registers via offsets
/// from its base address. It is an organizational structure that can be used to
/// find all CoreSight components - possibly as well as legacy or
/// vendor-specific ones - on an SoC. Thought of as a tree, the leaves are the
/// system's CoreSight components and the root is typically referred to as the
/// "base ROM table" (or, more plainly, "the ROM table").
pub struct RomTable;
impl RomTable {
// [CS] D6.2.1, D7.2.1
// The maximum number of ROM table entries, for various types.
/// Maximum number of entries in a Class `0x1` ROM table.
const MAX_0X1_ROM_ENTRIES: u32 = 960;
/// Maximum number of entries in a Class `0x9` 32-bit ROM table.
const MAX_0X9_ROM_32BIT_ENTRIES: u32 = 512;
/// Maximum number of entries in a Class `0x9` 64-bit ROM table.
const MAX_0X9_ROM_64BIT_ENTRIES: u32 = 256;
/// Walks the underlying tree of components with no dynamic allocation,
/// calling `callback` on the offset from the table's base address (implicitly
/// encoded in `base`) of each component found. The (`base`, `max_offset`)
/// together implicitly give the aperture to walk.
///
/// The walk will visit and access the first page of memory of each found
/// component. Unfortunately, however, there is no canonical means to
/// determine how large a region of memory this entails. The determination of
/// the maximum visited offset - or at least something deemed large enough -
/// is left to the caller. The offset must at least be
/// `MINIMUM_COMPONENT_SIZE`, which is the size of the base table proper.
///
/// # Safety
///
/// `base` must point to a valid, mapped MMIO aperture of at least `max_offset` bytes that
/// remains mapped and safe to read for the duration of the call (with 8-byte alignment if
/// 64-bit Class `0x9` ROM tables are present in the aperture).
pub unsafe fn walk<Access, F>(
base: MmioPtr<u32, Access>,
max_offset: u32,
mut callback: F,
) -> Result<(), WalkError>
where
Access: Accessible + AccessRestrictsTo<Ro>,
F: FnMut(u32),
{
const { assert!(size_of::<u32>() == 4) };
assert!((max_offset as usize) >= MINIMUM_COMPONENT_SIZE);
let base = base.restrict::<Ro>();
// SAFETY: Caller guarantees `base` points to a mapped MMIO aperture of at least
// `max_offset` bytes.
unsafe { Self::walk_from(base, max_offset, &mut callback, 0) }
}
#[inline]
fn is_table(classid: Option<ClassId>, architect: u16, archid: u16) -> bool {
if classid == Some(ClassId::RomTable0x1) {
return true;
}
if classid == Some(ClassId::CoreSight)
&& architect == arm::ARCHITECT
&& archid == arm::archid::ROM_TABLE
{
return true;
}
false
}
/// # Safety
///
/// `base` must point to a valid, mapped MMIO aperture of at least `max_offset` bytes.
unsafe fn walk_from<F>(
base: MmioPtr<u32, Ro>,
max_offset: u32,
callback: &mut F,
offset: u32,
) -> Result<(), WalkError>
where
F: FnMut(u32),
{
if max_offset - (MINIMUM_COMPONENT_SIZE as u32) < offset {
return Err(WalkError { reason: "component exceeds aperture", offset });
}
// Treats I/O as rooted at the current offset (instead of rooted at the base ROM table,
// which would complicate register access).
// SAFETY: The check above guarantees `[offset, offset + MINIMUM_COMPONENT_SIZE)` lies
// within the `max_offset`-byte aperture starting at `base`, `offset` is a multiple of
// `MINIMUM_COMPONENT_SIZE` (4096) so alignment is preserved, and `CIDR1` and `DEVARCH`
// lie within the 4 KiB `bank32` component region.
let (comp_ptr, bank32, cidr1, arch_reg) = unsafe {
let comp_ptr = MmioPtr::<u32, Ro>::new(base.as_ptr().byte_add(offset as usize));
let bank32 = MmioBank::new(comp_ptr, MINIMUM_COMPONENT_SIZE);
let cidr1 = bank32.at(CIDR1).read();
let arch_reg = bank32.at(DEVARCH).read();
(comp_ptr, bank32, cidr1, arch_reg)
};
let classid = cidr1.try_classid().ok();
let architect = arch_reg.architect();
let archid = arch_reg.archid();
if Self::is_table(classid, architect, archid) {
let classid = classid.unwrap();
let max_entries: u32;
let mut format: Option<Class0x9RomEntryFormat> = None;
if classid == ClassId::RomTable0x1 {
max_entries = Self::MAX_0X1_ROM_ENTRIES;
} else {
// If not a class 0x1 table, then a class 0x9.
debug_assert_eq!(classid, ClassId::CoreSight);
// SAFETY: `DEVID` lies within the 4 KiB `bank32` component region.
let devid = unsafe { bank32.at(DEVID) }.read();
match devid.try_format() {
Ok(fmt @ Class0x9RomEntryFormat::Bits32) => {
format = Some(fmt);
max_entries = Self::MAX_0X9_ROM_32BIT_ENTRIES;
}
Ok(fmt @ Class0x9RomEntryFormat::Bits64) => {
format = Some(fmt);
max_entries = Self::MAX_0X9_ROM_64BIT_ENTRIES;
}
Err(_) => {
return Err(WalkError { reason: "bad format value", offset });
}
}
}
for i in 0..max_entries {
// SAFETY: `comp_ptr` covers the current 4 KiB component page, and
// `i < max_entries`.
let contents = unsafe { Self::read_entry(comp_ptr, i, classid, format) }
.map_err(|reason| WalkError { reason, offset })?;
if contents.value == 0 {
break; // Signals that the walk is over if identically zero.
}
if !contents.present {
continue;
}
// [CS] D5.4
// the offset provided by the ROM table entry requires a shift of 12 bits.
let new_offset = offset.wrapping_add(contents.offset.wrapping_shl(12));
// SAFETY: `base` covers `max_offset` bytes, and `walk_from` validates bounds for
// `new_offset` on entry.
unsafe { Self::walk_from(base, max_offset, callback, new_offset) }?;
}
return Ok(());
}
// There should be a ROM table at offset zero.
if offset == 0 {
return Err(WalkError { reason: "not a ROM table", offset: 0 });
}
callback(offset);
Ok(())
}
/// # Safety
///
/// `comp_ptr` must cover a valid 4 KiB component MMIO region (8-byte aligned if `format` is
/// `Some(Class0x9RomEntryFormat::Bits64)`), and `n` must be less than the table's maximum
/// entry count.
unsafe fn read_entry(
comp_ptr: MmioPtr<u32, Ro>,
n: u32,
classid: ClassId,
format: Option<Class0x9RomEntryFormat>,
) -> Result<EntryContents, &'static str> {
let bank32 = MmioBank::new(comp_ptr, MINIMUM_COMPONENT_SIZE);
if classid == ClassId::RomTable0x1 {
// SAFETY: `n < MAX_0X1_ROM_ENTRIES` (960), so `n * 4 < MINIMUM_COMPONENT_SIZE` (4096),
// which lies within `bank32`.
let entry = unsafe { bank32.at(Class0x1RomEntry::offset_of(n)) }.read();
return Ok(EntryContents {
value: u64::from(entry.bits()),
offset: entry.offset(),
present: entry.present(),
});
}
// If not a class 0x1 table, then a class 0x9.
debug_assert_eq!(classid, ClassId::CoreSight);
debug_assert!(format.is_some());
match format {
Some(Class0x9RomEntryFormat::Bits32) => {
// SAFETY: `n < MAX_0X9_ROM_32BIT_ENTRIES` (512), so
// `n * 4 < MINIMUM_COMPONENT_SIZE` (4096), which lies within `bank32`.
let entry = unsafe { bank32.at(Class0x9Rom32BitEntry::offset_of(n)) }.read();
Ok(EntryContents {
value: u64::from(entry.bits()),
offset: entry.offset(),
// [CS] D7.5.17: only a value of 0b11 for present() signifies presence.
present: (entry.present() & 0b11) != 0,
})
}
Some(Class0x9RomEntryFormat::Bits64) => {
// SAFETY: Caller guarantees the component MMIO region is 8-byte aligned when
// 64-bit ROM tables are present and covers `MINIMUM_COMPONENT_SIZE` bytes;
// `n < MAX_0X9_ROM_64BIT_ENTRIES` (256), so `n * 8 < MINIMUM_COMPONENT_SIZE`.
let entry = unsafe {
let comp_ptr64 = MmioPtr::<u64, Ro>::new(comp_ptr.as_ptr().cast::<u64>());
let bank64 = MmioBank::new(comp_ptr64, MINIMUM_COMPONENT_SIZE);
bank64.at(Class0x9Rom64BitEntry::offset_of(n))
}
.read();
let u32_offset = entry.offset() & 0xffff_ffff;
if entry.offset() != u32_offset {
return Err(
"a simplifying assumption is made that a ROM table entry's offset only \
contains 32 bits of information. If this is no longer true, please file \
a bug.",
);
}
Ok(EntryContents {
value: entry.bits(),
offset: u32_offset as u32,
present: (entry.present() & 0b11) != 0,
})
}
None => Err("bad format value"),
}
}
}
type WalkCallback = unsafe extern "C" fn(*mut c_void, u32);
/// FFI trampoline for `coresight::RomTable::Walk`.
///
/// Returns `true` on success, or `false` on error after populating `out_error_reason_ptr`,
/// `out_error_reason_len`, and `out_error_offset`.
///
/// # Safety
///
/// - `base` must point to a valid, mapped MMIO aperture of at least `max_offset` bytes that
/// remains mapped and safe to read for the duration of the call (with 8-byte alignment if
/// 64-bit Class `0x9` ROM tables are present).
/// - `callback` must be a valid function pointer safe to invoke with `cb_ctx` for the duration of
/// the call.
/// - `out_error_reason_ptr`, `out_error_reason_len`, and `out_error_offset` must be valid, aligned
/// pointers writable for the duration of the call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_coresight_rom_table_walk(
base: *const u32,
max_offset: u32,
cb_ctx: *mut c_void,
callback: WalkCallback,
out_error_reason_ptr: *mut *const c_char,
out_error_reason_len: *mut usize,
out_error_offset: *mut u32,
) -> bool {
// SAFETY: Caller guarantees `base` points to a mapped MMIO aperture of at least `max_offset`
// bytes, `callback` is safe to invoke with `cb_ctx`, and the output pointers are valid,
// aligned, and writable.
unsafe {
let ptr = MmioPtr::<u32, Ro>::new(base);
match RomTable::walk(ptr, max_offset, |offset| {
callback(cb_ctx, offset);
}) {
Ok(()) => true,
Err(err) => {
out_error_reason_ptr.write(err.reason.as_ptr().cast::<c_char>());
out_error_reason_len.write(err.reason.len());
out_error_offset.write(err.offset);
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const CLASS_0X1_ROM_COMPONENT_ID_REG: u32 = 0x0000_0010;
const CLASS_0X9_ROM_COMPONENT_ID_REG: u32 = 0x0000_0090;
const DEV_ID_REG: u32 = 0x0000_0000;
const EMPTY_DEV_ARCH_REG: u32 = 0x0000_0000;
const CLASS_0X9_ROM_TABLE_DEV_ARCH_REG: u32 = 0x4760_0af7;
const EMPTY_ROM_ENTRY_REG: u32 = 0x0000_0000;
const OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_1001;
const OFFSET_0X2000_CLASS_0X9_ROM_ENTRY_REG: u32 = 0x0000_2011;
const OFFSET_0X2000_NOT_PRESENT_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_2000;
const OFFSET_0X3000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_3001;
const OFFSET_0X5000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_5001;
const OFFSET_0X6000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_6001;
const OFFSET_0XA000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0x0000_a001;
const OFFSET_0XFFFFF000_CLASS_0X1_ROM_ENTRY_REG: u32 = 0xffff_f001;
const MAX_APERTURE_BYTES: usize = 0xb000;
#[repr(C, align(8))]
struct FakeAperture {
words: [u32; MAX_APERTURE_BYTES / size_of::<u32>()],
}
impl FakeAperture {
const fn new() -> Self {
Self { words: [0; MAX_APERTURE_BYTES / size_of::<u32>()] }
}
fn clear(&mut self) {
self.words.fill(0);
}
fn write32(&mut self, value: u32, offset: u32) -> &mut Self {
let idx = (offset as usize) / size_of::<u32>();
self.words[idx] = value;
self
}
fn write64(&mut self, value: u64, offset: u32) -> &mut Self {
let idx = (offset as usize) / size_of::<u32>();
let bytes = value.to_ne_bytes();
self.words[idx] = u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
self.words[idx + 1] = u32::from_ne_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
self
}
fn walk<F>(&self, max_offset: u32, callback: F) -> Result<(), WalkError>
where
F: FnMut(u32),
{
assert!((max_offset as usize) <= MAX_APERTURE_BYTES);
// SAFETY: `self.words` is a valid, 8-byte aligned aperture of `MAX_APERTURE_BYTES`
// (>= `max_offset`) bytes alive for the duration of the borrow.
unsafe {
let ptr = MmioPtr::<u32, Ro>::new(self.words.as_ptr());
RomTable::walk(ptr, max_offset, callback)
}
}
fn as_ptr(&self) -> *const u32 {
self.words.as_ptr()
}
}
struct VisitedList {
offsets: [u32; 8],
len: usize,
}
impl VisitedList {
const fn new() -> Self {
Self { offsets: [0; 8], len: 0 }
}
fn push(&mut self, offset: u32) {
self.offsets[self.len] = offset;
self.len += 1;
}
fn as_slice(&self) -> &[u32] {
&self.offsets[..self.len]
}
}
#[test]
fn empty_0x1_table() {
let end_offset = 0x0000 + (MINIMUM_COMPONENT_SIZE as u32);
let mut aperture = FakeAperture::new();
aperture
// Visit: Table (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0x0000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x0000 + 0xfbc)
// Read: Entry0 of Table (empty and last)
.write32(EMPTY_ROM_ENTRY_REG, 0);
let result = aperture.walk(end_offset, |offset| {
panic!("unexpected component found at offset {offset:#x}");
});
assert_eq!(result, Ok(()));
}
#[test]
fn empty_0x9_table() {
let end_offset = 0x0000 + (MINIMUM_COMPONENT_SIZE as u32);
let mut aperture = FakeAperture::new();
aperture
// Visit: Table (class 0x9)
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x0000 + 0xff4)
.write32(CLASS_0X9_ROM_TABLE_DEV_ARCH_REG, 0x0000 + 0xfbc)
.write32(DEV_ID_REG, 0x0000 + 0xfc8)
// Read: Entry0 of Table (empty and last)
.write32(EMPTY_ROM_ENTRY_REG, 0);
let result = aperture.walk(end_offset, |offset| {
panic!("unexpected component found at offset {offset:#x}");
});
assert_eq!(result, Ok(()));
}
#[test]
fn depth_one_references() {
let mut aperture = FakeAperture::new();
aperture
// Visit: Table (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0x0000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x0000 + 0xfbc)
// Read: Entry0 of Table -> Component0
.write32(OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG, 0x0000)
// Visit: Component0
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x1000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0xfbc)
// Read: Entry1 of Table (not present)
.write32(
OFFSET_0X2000_NOT_PRESENT_CLASS_0X1_ROM_ENTRY_REG,
0x0000 + (size_of::<u32>() as u32),
)
// Read: Entry2 of Table -> Component2
.write32(OFFSET_0X3000_CLASS_0X1_ROM_ENTRY_REG, 0x0000 + 2 * (size_of::<u32>() as u32))
// Visit: Component2
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x3000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x3000 + 0xfbc)
// Read: Entry3 of Table (empty and last).
.write32(EMPTY_ROM_ENTRY_REG, 0x0000 + 3 * (size_of::<u32>() as u32));
let end_offset = 0x3000 + (MINIMUM_COMPONENT_SIZE as u32);
let mut visited = VisitedList::new();
let result = aperture.walk(end_offset, |offset| visited.push(offset));
assert_eq!(result, Ok(()));
assert_eq!(visited.as_slice(), &[0x1000, 0x3000]);
}
#[test]
fn depth_two_references() {
let u32_size = size_of::<u32>() as u32;
let mut aperture = FakeAperture::new();
aperture
// Visit: Table (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0x0000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x0000 + 0xfbc)
// Read: Entry0 of Table -> Subtable0
.write32(OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG, 0x0000)
// Visit: Subtable0 (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0x1000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0xfbc)
// Read: Entry0 of Subtable0 -> Component00
.write32(OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG, 0x1000)
// Visit: Component00
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x1000 + 0x1000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0x1000 + 0xfbc)
// Read: Entry1 of Subtable0 (not present)
.write32(OFFSET_0X2000_NOT_PRESENT_CLASS_0X1_ROM_ENTRY_REG, 0x1000 + u32_size)
// Read: Entry2 of Subtable0 -> Component02
.write32(OFFSET_0X3000_CLASS_0X1_ROM_ENTRY_REG, 0x1000 + 2 * u32_size)
// Visit: Component02
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x1000 + 0x3000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0x3000 + 0xfbc)
// Read: Entry3 of Subtable0 (empty and last).
.write32(EMPTY_ROM_ENTRY_REG, 0x1000 + 3 * u32_size)
// Read: Entry1 of Table -> Component1
.write32(OFFSET_0X5000_CLASS_0X1_ROM_ENTRY_REG, 0x0000 + u32_size)
// Visit: Component1
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x5000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x5000 + 0xfbc)
// Read: Entry2 of Table -> Subtable2
.write32(OFFSET_0X6000_CLASS_0X1_ROM_ENTRY_REG, 0x0000 + 2 * u32_size)
// Visit: Subtable2 (class 0x9)
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x6000 + 0xff4)
.write32(CLASS_0X9_ROM_TABLE_DEV_ARCH_REG, 0x6000 + 0xfbc)
.write32(DEV_ID_REG, 0x6000 + 0xfc8)
// Read: Entry0 of Subtable2 -> Component20
.write32(OFFSET_0X2000_CLASS_0X9_ROM_ENTRY_REG, 0x6000)
// Visit: Component20
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x6000 + 0x2000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x6000 + 0x2000 + 0xfbc)
// Read: Entry1 of Subtable2 (empty and last)
.write32(EMPTY_ROM_ENTRY_REG, 0x6000 + u32_size)
// Read: Entry3 of Table (empty and last).
.write32(EMPTY_ROM_ENTRY_REG, 3 * u32_size);
let end_offset = 0x8000 + (MINIMUM_COMPONENT_SIZE as u32);
let mut visited = VisitedList::new();
let result = aperture.walk(end_offset, |offset| visited.push(offset));
assert_eq!(result, Ok(()));
assert_eq!(visited.as_slice(), &[0x2000, 0x4000, 0x5000, 0x8000]);
}
#[test]
fn negative_offset() {
// An encoded -4096 in two's complement form.
// const COMP00_ENCODED_OFFSET: u32 = !1u32 + 1;
let u32_size = size_of::<u32>() as u32;
let mut aperture = FakeAperture::new();
aperture
// Visit: Table (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0x0000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x0000 + 0xfbc)
// Read: Entry0 of Table -> Subtable0
.write32(OFFSET_0XA000_CLASS_0X1_ROM_ENTRY_REG, 0x0000)
// Visit: Subtable0 (class 0x1)
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0xa000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0xa000 + 0xfbc)
// Read: Entry0 of Subtable0 -> Component00
.write32(OFFSET_0XFFFFF000_CLASS_0X1_ROM_ENTRY_REG, 0xa000)
// Visit: Component00
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x9000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x9000 + 0xfbc)
// Read: Entry1 of Subtable0 (empty and last).
.write32(EMPTY_ROM_ENTRY_REG, 0xa000 + u32_size)
// Read: Entry1 of Table (empty and last).
.write32(EMPTY_ROM_ENTRY_REG, u32_size);
let end_offset = 0xa000 + (MINIMUM_COMPONENT_SIZE as u32);
let mut visited = VisitedList::new();
let result = aperture.walk(end_offset, |offset| visited.push(offset));
assert_eq!(result, Ok(()));
assert_eq!(visited.as_slice(), &[0x9000]);
}
#[test]
fn class_0x9_64bit_entries_and_errors() {
// 64-bit Class 0x9 table with one valid entry at offset 0x1000.
let mut aperture = FakeAperture::new();
aperture
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0xff4)
.write32(CLASS_0X9_ROM_TABLE_DEV_ARCH_REG, 0xfbc)
.write32(0x0000_0001, 0xfc8) // Format::Bits64
.write64(0x0000_0000_0000_1003, 0)
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x1000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0xfbc)
.write64(0, 8);
let mut visited = VisitedList::new();
assert_eq!(
aperture.walk(0x1000 + (MINIMUM_COMPONENT_SIZE as u32), |offset| visited.push(offset)),
Ok(())
);
assert_eq!(visited.as_slice(), &[0x1000]);
// Not a ROM table at offset 0.
aperture.clear();
aperture.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0xff4).write32(EMPTY_DEV_ARCH_REG, 0xfbc);
assert_eq!(
aperture.walk(MINIMUM_COMPONENT_SIZE as u32, |_| {}),
Err(WalkError { reason: "not a ROM table", offset: 0 })
);
}
#[test]
fn ffi_trampolines_and_register_setters() {
assert!(RomTable::is_table(Some(ClassId::RomTable0x1), 0, 0));
assert!(RomTable::is_table(
Some(ClassId::CoreSight),
arm::ARCHITECT,
arm::archid::ROM_TABLE,
));
assert!(!RomTable::is_table(Some(ClassId::CoreSight), arm::ARCHITECT, arm::archid::ETM4));
assert!(!RomTable::is_table(None, arm::ARCHITECT, arm::archid::ROM_TABLE));
let mut e0x1 = Class0x1RomEntry::default();
e0x1.set_offset(1)
.set_powerid(0)
.set_powerid_valid(false)
.set_format(false)
.set_present(true);
assert_eq!(e0x1.bits(), OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG);
let mut e0x9_32 = Class0x9Rom32BitEntry::default();
e0x9_32.set_offset(2).set_powerid(1).set_powerid_valid(false).set_present(1);
assert_eq!(e0x9_32.bits(), OFFSET_0X2000_CLASS_0X9_ROM_ENTRY_REG);
let mut e0x9_64 = Class0x9Rom64BitEntry::default();
e0x9_64.set_offset(1).set_powerid(0).set_powerid_valid(false).set_present(0b11);
assert_eq!(e0x9_64.bits(), 0x0000_0000_0000_1003);
let mut devid = Class0x9RomDeviceIdRegister::default();
devid.set_prr(true).set_sysmem(true).set_format(Class0x9RomEntryFormat::Bits64);
assert!(devid.prr());
assert!(devid.sysmem());
assert_eq!(devid.try_format().ok(), Some(Class0x9RomEntryFormat::Bits64));
/// # Safety
///
/// `ctx` must point to a valid `VisitedList`.
unsafe extern "C" fn visit_cb(ctx: *mut c_void, offset: u32) {
// SAFETY: `ctx` is a valid pointer to `VisitedList`.
unsafe { (*ctx.cast::<VisitedList>()).push(offset) };
}
let mut aperture = FakeAperture::new();
aperture
.write32(CLASS_0X1_ROM_COMPONENT_ID_REG, 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0xfbc)
.write32(OFFSET_0X1000_CLASS_0X1_ROM_ENTRY_REG, 0)
.write32(CLASS_0X9_ROM_COMPONENT_ID_REG, 0x1000 + 0xff4)
.write32(EMPTY_DEV_ARCH_REG, 0x1000 + 0xfbc)
.write32(EMPTY_ROM_ENTRY_REG, 4);
let mut visited = VisitedList::new();
let mut err_ptr: *const c_char = core::ptr::null();
let mut err_len: usize = 0;
let mut err_offset: u32 = 0;
// SAFETY: `aperture` and `visited` outlive the call and match the callback cast above.
let ok = unsafe {
rust_coresight_rom_table_walk(
aperture.as_ptr(),
0x1000 + (MINIMUM_COMPONENT_SIZE as u32),
(&raw mut visited).cast::<c_void>(),
visit_cb,
&mut err_ptr,
&mut err_len,
&mut err_offset,
)
};
assert!(ok);
assert_eq!(visited.as_slice(), &[0x1000]);
}
}