use bitflags::bitflags;
use safe_mmio::{SharedMmioPointer, UniqueMmioPointer, field, field_shared, fields::ReadPureWrite};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use crate::{
for_in_slice,
utils::{concatenate_address_parts, get_bit, set_bit, split_address},
};
const REGION_ALIGNMENT_MASK: u64 = 0xfff;
#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(transparent)]
pub struct SecureAccess(u32);
bitflags! {
impl SecureAccess: u32 {
const NONE = 0;
const R = 1 << 30;
const W = 1 << 31;
const RW = Self::R.bits() | Self::W.bits();
}
}
#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(transparent)]
pub struct RegionAttributes(u32);
impl RegionAttributes {
pub const fn new(secure_access: SecureAccess, filters: &[usize]) -> Self {
let mut s = Self(secure_access.bits());
for_in_slice! {
f in filters,
s.set_filter_status(*f, true)
}
s
}
pub const fn secure_access(&self) -> SecureAccess {
SecureAccess::from_bits_truncate(self.0)
}
pub const fn set_secure_access(&mut self, secure_access: SecureAccess) {
self.0 &= !SecureAccess::RW.bits();
self.0 |= secure_access.bits();
}
pub const fn is_filter_enabled(&self, index: usize) -> bool {
assert!(index < 4);
get_bit(self.0, index)
}
pub const fn set_filter_status(&mut self, index: usize, enabled: bool) {
assert!(index < 4);
set_bit(&mut self.0, index, enabled);
}
}
#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(transparent)]
pub struct RegionIDAccess(u32);
impl RegionIDAccess {
pub const NONE: Self = Self::new(&[], &[]);
pub const fn new(writes: &[usize], reads: &[usize]) -> Self {
let mut s = Self(0);
for_in_slice! {
w in writes,
s.set_write_enabled(*w, true)
}
for_in_slice! {
r in reads,
s.set_read_enabled(*r, true)
}
s
}
pub const fn is_write_enabled(&self, index: usize) -> bool {
assert!(index < 16);
get_bit(self.0, index + 16)
}
pub const fn set_write_enabled(&mut self, index: usize, enabled: bool) {
assert!(index < 16);
set_bit(&mut self.0, index + 16, enabled);
}
pub const fn is_read_enabled(&self, index: usize) -> bool {
assert!(index < 16);
get_bit(self.0, index)
}
pub const fn set_read_enabled(&mut self, index: usize, enabled: bool) {
assert!(index < 16);
set_bit(&mut self.0, index, enabled);
}
}
#[derive(Debug, Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(C, align(4))]
pub(crate) struct RegionRegisters {
region_base_low: ReadPureWrite<u32>,
region_base_high: ReadPureWrite<u32>,
region_top_low: ReadPureWrite<u32>,
region_top_high: ReadPureWrite<u32>,
region_attributes: ReadPureWrite<RegionAttributes>,
region_id_access: ReadPureWrite<RegionIDAccess>,
reserved: [u32; 2],
}
pub struct TzcRegion<'regs> {
pub(crate) regs: SharedMmioPointer<'regs, RegionRegisters>,
pub(crate) address_width_mask: u64,
}
impl<'a> TzcRegion<'a> {
pub fn region_base_address(&self) -> u64 {
concatenate_address_parts(
field_shared!(self.regs, region_base_low).read(),
field_shared!(self.regs, region_base_high).read(),
) & self.address_width_mask }
pub fn region_top_address(&self) -> u64 {
concatenate_address_parts(
field_shared!(self.regs, region_top_low).read(),
field_shared!(self.regs, region_top_high).read(),
) & self.address_width_mask }
pub fn region_attributes(&self) -> RegionAttributes {
field_shared!(self.regs, region_attributes).read()
}
pub fn region_id_access(&self) -> RegionIDAccess {
field_shared!(self.regs, region_id_access).read()
}
}
macro_rules! redeclare_fn {
($name:ident, $ret:ty) => {
#[doc=concat!("See [TzcRegion::", stringify!($name), "].")]
pub fn $name(&self) -> $ret {
self.as_shared().$name()
}
};
}
pub struct TzcRegionMut<'regs> {
pub(crate) regs: UniqueMmioPointer<'regs, RegionRegisters>,
pub(crate) addresses_writable: bool,
pub(crate) address_width_mask: u64,
}
impl<'a> TzcRegionMut<'a> {
#[inline(always)]
fn as_shared(&self) -> TzcRegion<'a> {
TzcRegion {
regs: *self.regs,
address_width_mask: self.address_width_mask,
}
}
redeclare_fn!(region_base_address, u64);
redeclare_fn!(region_top_address, u64);
redeclare_fn!(region_attributes, RegionAttributes);
redeclare_fn!(region_id_access, RegionIDAccess);
pub fn set_region_base_address(&mut self, address: u64) {
assert_eq!(address & self.address_width_mask, address);
assert_eq!(address & REGION_ALIGNMENT_MASK, 0);
assert!(self.addresses_writable);
let (low, high) = split_address(address);
field!(self.regs, region_base_low).write(low);
field!(self.regs, region_base_high).write(high);
}
pub fn set_region_top_address(&mut self, address: u64) {
assert_eq!(address & self.address_width_mask, address);
assert_eq!(address & REGION_ALIGNMENT_MASK, REGION_ALIGNMENT_MASK);
assert!(self.addresses_writable);
let (low, high) = split_address(address);
field!(self.regs, region_top_low).write(low);
field!(self.regs, region_top_high).write(high);
}
pub fn set_region_attributes(&mut self, value: RegionAttributes) {
field!(self.regs, region_attributes).write(value);
}
pub fn set_region_id_access(&mut self, value: RegionIDAccess) {
field!(self.regs, region_id_access).write(value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{region::REGION_ALIGNMENT_MASK, tests::FakeTZCRegisters};
#[test]
fn region_0_address() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x100, 0xbeef_0000);
regs.reg_write(0x104, 0xdead);
regs.reg_write(0x108, 0xdaed_0000);
regs.reg_write(0x10c, 0xfeeb);
let tzc = regs.tzc_for_test();
assert_eq!(
tzc.region(0).unwrap().region_base_address(),
0xdead_beef_0000
);
assert_eq!(
tzc.region(0).unwrap().region_top_address(),
0xfeeb_daed_0000
);
}
#[test]
fn region_n_address() {
let mut regs = FakeTZCRegisters::new();
for i in 1..=8 {
regs.reg_write(0x100 + i * 0x20, 0xabcd_0000);
regs.reg_write(0x104 + i * 0x20, i as u32);
regs.reg_write(0x108 + i * 0x20, 0xeffe_0000);
regs.reg_write(0x10c + i * 0x20, i as u32 + 8);
}
let tzc = regs.tzc_for_test();
for i in 1..=8 {
assert_eq!(
tzc.region(i).unwrap().region_base_address(),
(i as u64) << 32 | 0xabcd_0000
);
assert_eq!(
tzc.region(i).unwrap().region_top_address(),
(i as u64 + 8) << 32 | 0xeffe_0000
);
}
}
#[test]
fn region_n_address_mask() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x120, 0xffff_f000);
regs.reg_write(0x124, u32::MAX);
regs.reg_write(0x128, u32::MAX);
regs.reg_write(0x12c, u32::MAX);
let tzc = regs.tzc_for_test();
assert_eq!(
tzc.region(1).unwrap().region_base_address(),
0x0000_ffff_ffff_f000
);
assert_eq!(
tzc.region(1).unwrap().region_top_address(),
0x0000_ffff_ffff_ffff
);
}
#[test]
fn region_n_write_address() {
let mut regs = FakeTZCRegisters::new();
let mut tzc = regs.tzc_for_test();
for i in 1..=8 {
let mut reg = tzc.region_mut(i).unwrap();
reg.set_region_base_address((i << 32 | (i << 16)) as u64);
reg.set_region_top_address((i << 36 | (i << 24)) as u64 | REGION_ALIGNMENT_MASK);
}
for i in 1..=8 {
assert_eq!(regs.reg_read(0x100 + i * 0x20), (i as u32) << 16);
assert_eq!(regs.reg_read(0x104 + i * 0x20), i as u32);
assert_eq!(
regs.reg_read(0x108 + i * 0x20),
(i as u32) << 24 | (REGION_ALIGNMENT_MASK as u32)
);
assert_eq!(regs.reg_read(0x10c + i * 0x20), (i << 4) as u32);
}
}
#[test]
#[should_panic]
fn region_0_write_base_address() {
let mut regs = FakeTZCRegisters::new();
let mut tzc = regs.tzc_for_test();
tzc.region_mut(0)
.unwrap()
.set_region_base_address(0xdead_beef_0000);
}
#[test]
#[should_panic]
fn region_0_write_top_address() {
let mut regs = FakeTZCRegisters::new();
let mut tzc = regs.tzc_for_test();
tzc.region_mut(0)
.unwrap()
.set_region_top_address(0xdead_beef_0000);
}
#[test]
fn region_attributes() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x110, 0b0000_0000_0000_0000_0000_0000_0000_0101);
regs.reg_write(0x130, 0b0100_0000_0000_0000_0000_0000_0000_0010);
regs.reg_write(0x150, 0b1000_0000_0000_0000_0000_0000_0000_1111);
regs.reg_write(0x170, 0b1100_0000_0000_0000_0000_0000_0000_0000);
let tzc = regs.tzc_for_test();
let att0 = tzc.region(0).unwrap().region_attributes();
let att1 = tzc.region(1).unwrap().region_attributes();
let att2 = tzc.region(2).unwrap().region_attributes();
let att3 = tzc.region(3).unwrap().region_attributes();
assert_eq!(SecureAccess::NONE, att0.secure_access());
assert!(att0.is_filter_enabled(0));
assert!(!att0.is_filter_enabled(1));
assert!(att0.is_filter_enabled(2));
assert!(!att0.is_filter_enabled(3));
assert_eq!(SecureAccess::R, att1.secure_access());
assert!(!att1.is_filter_enabled(0));
assert!(att1.is_filter_enabled(1));
assert!(!att1.is_filter_enabled(2));
assert!(!att1.is_filter_enabled(3));
assert_eq!(SecureAccess::W, att2.secure_access());
assert!(att2.is_filter_enabled(0));
assert!(att2.is_filter_enabled(1));
assert!(att2.is_filter_enabled(2));
assert!(att2.is_filter_enabled(3));
assert_eq!(SecureAccess::RW, att3.secure_access());
assert!(!att3.is_filter_enabled(0));
assert!(!att3.is_filter_enabled(1));
assert!(!att3.is_filter_enabled(2));
assert!(!att3.is_filter_enabled(3));
}
#[test]
fn set_region_attributes() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x110, 0b0000_0000_0000_0000_0000_0000_0000_0101);
regs.reg_write(0x130, 0b0100_0000_0000_0000_0000_0000_0000_0010);
let mut tzc = regs.tzc_for_test();
let mut reg0 = tzc.region_mut(0).unwrap();
let mut att0 = reg0.region_attributes();
att0.set_secure_access(SecureAccess::W);
att0.set_filter_status(0, false);
reg0.set_region_attributes(att0);
let mut reg1 = tzc.region_mut(1).unwrap();
let mut att1 = reg1.region_attributes();
att1.set_secure_access(SecureAccess::NONE);
att1.set_filter_status(1, false);
reg1.set_region_attributes(att1);
let att2 = RegionAttributes::new(SecureAccess::RW, &[0, 1, 2, 3]);
tzc.region_mut(2).unwrap().set_region_attributes(att2);
assert_eq!(
regs.reg_read(0x110),
0b1000_0000_0000_0000_0000_0000_0000_0100
);
assert_eq!(
regs.reg_read(0x130),
0b0000_0000_0000_0000_0000_0000_0000_0000
);
assert_eq!(
regs.reg_read(0x150),
0b1100_0000_0000_0000_0000_0000_0000_1111
);
}
#[test]
fn region_id_access() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x114, 0b1000_0000_0000_0001_0000_0000_0011_1000);
regs.reg_write(0x134, 0b1111_1111_1111_1111_0000_0000_0000_0000);
let tzc = regs.tzc_for_test();
let rid0 = tzc.region(0).unwrap().region_id_access();
let rid1 = tzc.region(1).unwrap().region_id_access();
for i in 0..16 {
assert_eq!(rid0.is_read_enabled(i), (3..=5).contains(&i));
assert_eq!(rid0.is_write_enabled(i), [0, 15].contains(&i));
assert!(!rid1.is_read_enabled(i));
assert!(rid1.is_write_enabled(i));
}
}
#[test]
fn set_region_id_access() {
let mut regs = FakeTZCRegisters::new();
regs.reg_write(0x134, 0b1000_0000_0000_0001_0000_0000_0011_1000);
let mut tzc = regs.tzc_for_test();
let mut rid0 = tzc.region(0).unwrap().region_id_access();
rid0.set_read_enabled(13, true);
rid0.set_read_enabled(14, true);
rid0.set_read_enabled(15, true);
rid0.set_write_enabled(0, true);
rid0.set_write_enabled(1, true);
tzc.region_mut(0).unwrap().set_region_id_access(rid0);
let mut rid1 = tzc.region(1).unwrap().region_id_access();
rid1.set_read_enabled(3, false);
rid1.set_read_enabled(4, false);
rid1.set_read_enabled(6, true);
rid1.set_write_enabled(0, false);
tzc.region_mut(1).unwrap().set_region_id_access(rid1);
let rid2 = RegionIDAccess::new(&[1, 2, 3], &[14, 15]);
tzc.region_mut(2).unwrap().set_region_id_access(rid2);
assert_eq!(
regs.reg_read(0x114),
0b0000_0000_0000_0011_1110_0000_0000_0000
);
assert_eq!(
regs.reg_read(0x134),
0b1000_0000_0000_0000_0000_0000_0110_0000
);
assert_eq!(
regs.reg_read(0x154),
0b0000_0000_0000_1110_1100_0000_0000_0000,
);
}
#[test]
#[should_panic]
fn region_attributes_oob_filter_read() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_attributes()
.is_filter_enabled(4);
}
#[test]
#[should_panic]
fn region_attributes_oob_filter_write() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_attributes()
.set_filter_status(4, true);
}
#[test]
#[should_panic]
fn region_id_access_oob_get_read() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_id_access()
.is_read_enabled(17);
}
#[test]
#[should_panic]
fn region_id_access_oob_set_read() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_id_access()
.set_read_enabled(17, false);
}
#[test]
#[should_panic]
fn region_id_access_oob_get_write() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_id_access()
.is_write_enabled(17);
}
#[test]
#[should_panic]
fn region_id_access_oob_set_write() {
let mut regs = FakeTZCRegisters::new();
let tzc = regs.tzc_for_test();
tzc.region(0)
.unwrap()
.region_id_access()
.set_write_enabled(17, false);
}
}