use core::arch::asm;
use aarch32_cpu::{
asm::{dsb, isb},
mmu::{CachePolicy, MemoryRegionAttributes},
register::{SysReg, SysRegRead, SysRegWrite},
};
use arbitrary_int::*;
use bitbybit::{bitenum, bitfield};
#[bitfield(u32, debug)]
pub struct TranslationTableBaseControlRegister {
#[bit(5, rw)]
tt1_walk_disable: bool,
#[bit(4, rw)]
tt0_walk_disable: bool,
#[bits(0..=2, rw)]
tt0_boundary: u3,
}
impl SysReg for TranslationTableBaseControlRegister {
const CP: u32 = 15;
const OP1: u32 = 0;
const CRN: u32 = 2;
const CRM: u32 = 0;
const OP2: u32 = 2;
}
impl SysRegRead for TranslationTableBaseControlRegister {}
impl SysRegWrite for TranslationTableBaseControlRegister {}
impl TranslationTableBaseControlRegister {
pub fn read() -> Self {
Self::new_with_raw_value(Self::read_raw())
}
pub fn virtual_addr_uses_tt0(self, addr: u32) -> bool {
let n = self.tt0_boundary().value() as u32;
addr.leading_zeros() >= n
}
}
#[bitfield(u32, debug, introspect)]
pub struct TranslationTableBaseRegister {
#[bits(7..=31, rw)]
upper_base_addr: u25,
#[bit(5, rw)]
not_outer_shareable: bool,
#[bits(3..=4, rw)]
outer_cache_attrs: CachePolicy,
#[bit(1, rw)]
sharable: bool,
#[bit(0, rw)]
cacheable: bool,
}
impl TranslationTableBaseRegister {
pub fn read_tt0() -> Self {
let ttbr0: u32;
unsafe {
asm!(
"mrc p15, 0, {}, c2, c0, 0",
out(reg) ttbr0,
options(nomem, nostack, preserves_flags)
);
}
Self::new_with_raw_value(ttbr0)
}
pub fn read_tt1() -> Self {
let ttbr1: u32;
unsafe {
asm!(
"mrc p15, 0, {}, c2, c0, 1",
out(reg) ttbr1,
options(nomem, nostack, preserves_flags)
);
}
Self::new_with_raw_value(ttbr1)
}
pub fn base_ptr(self, tt_boundary: u8) -> *mut L1Descriptor {
let last_addr_bit = 14 - tt_boundary;
let mask = !0u32 << last_addr_bit;
let addr = self.raw_value() & mask & Self::upper_base_addr_mask();
addr as *mut L1Descriptor
}
}
pub struct TranslationTable {
tt_boundary: u8,
base_ptr: *mut L1Descriptor,
num_entries: usize,
}
impl TranslationTable {
pub fn for_addr(addr: u32) -> Self {
let ctrl = TranslationTableBaseControlRegister::read();
let is_tt0 = ctrl.virtual_addr_uses_tt0(addr);
let tt_boundary = if is_tt0 {
ctrl.tt0_boundary().value()
} else {
0 };
let ttbr = if is_tt0 {
TranslationTableBaseRegister::read_tt0()
} else {
TranslationTableBaseRegister::read_tt1()
};
let section_inner_bits = 20;
let num_entries = 2usize.pow(32 - section_inner_bits - tt_boundary as u32);
Self {
base_ptr: ttbr.base_ptr(tt_boundary),
tt_boundary,
num_entries,
}
}
pub fn base_ptr(&self) -> *const L1Descriptor {
self.base_ptr
}
pub fn lookup_l1(&self, addr: u32) -> *mut L1Descriptor {
assert!(addr.leading_zeros() >= self.tt_boundary as u32);
let section_index = addr as usize >> 20; assert!(section_index < self.num_entries);
unsafe { self.base_ptr.add(section_index) }
}
}
#[bitfield(u32)]
pub struct L1Descriptor {
#[bits(0..=1, r)]
variant: Option<L1DescriptorType>,
}
impl L1Descriptor {
pub fn as_section(self) -> Option<SectionDescriptor> {
if self.variant() == Ok(L1DescriptorType::Section) {
Some(SectionDescriptor::new_with_raw_value(self.raw_value))
} else {
None
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[bitenum(u2, exhaustive = false)]
pub enum L1DescriptorType {
Invalid = 0b00,
PageTable = 0b01,
Section = 0b10,
}
#[bitfield(u32, debug)]
pub struct SectionDescriptor {
#[bits(20..=31, rw)]
physical_address: u12,
#[bit(19, rw)]
non_secure: bool,
#[bit(17, rw)]
not_global: bool,
#[bits(12..=14, rw)]
tex: u3,
#[bits([10..=11, 15], rw)]
access_permissions: Option<AccessPermissions>,
#[bits(5..=8, rw)]
domain_id: u4,
#[bit(4, rw)]
no_execute: bool,
#[bit(3, rw)]
c: bool,
#[bit(2, rw)]
b: bool,
}
impl SectionDescriptor {
pub fn mem_attrs(self) -> Option<MemoryRegionAttributes> {
let tex = self.tex().value();
let c = self.c();
let b = self.b();
Some(match (tex, c, b) {
(0b000, false, false) => MemoryRegionAttributes::StronglyOrdered,
(0b000, false, true) => MemoryRegionAttributes::ShareableDevice,
(0b000, true, false) => MemoryRegionAttributes::OuterAndInnerWriteThroughNoWriteAlloc,
(0b000, true, true) => MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc,
(0b001, false, false) => MemoryRegionAttributes::OuterAndInnerNonCacheable,
(0b001, true, true) => MemoryRegionAttributes::OuterAndInnerWriteBackWriteAlloc,
(0b010, false, false) => MemoryRegionAttributes::NonShareableDevice,
(tex, c, b) if (tex >> 2 == 1) => {
let bb = tex & 0b11;
let aa = ((c as u8) << 1) | b as u8;
MemoryRegionAttributes::CacheableMemory {
inner: CachePolicy::new_with_raw_value(u2::new(aa)),
outer: CachePolicy::new_with_raw_value(u2::new(bb)),
}
}
_ => return None,
})
}
}
#[derive(Debug, PartialEq, Eq)]
#[bitenum(u3, exhaustive = false)]
pub enum AccessPermissions {
NoAccess = 0b000,
PL1AccessOnly = 0b001,
PL1AccessUserRead = 0b010,
AllAccess = 0b011,
ReadOnlyPL1AccessOnly = 0b101,
ReadOnlyPL1AccessUserRead = 0b110,
ReadOnlyAllAccess = 0b111,
}
impl AccessPermissions {
#[must_use]
pub const fn test_write(self, pl1: bool) -> bool {
match self {
Self::AllAccess => true,
Self::PL1AccessOnly | Self::PL1AccessUserRead if pl1 => true,
_ => false,
}
}
#[must_use]
pub const fn test_read(self, pl1: bool) -> bool {
if self.test_write(pl1) {
return true;
}
match self {
Self::ReadOnlyAllAccess | Self::ReadOnlyPL1AccessUserRead | Self::PL1AccessUserRead => {
true
}
Self::ReadOnlyPL1AccessOnly if pl1 => true,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DomainAccessControlRegister(pub u32);
impl SysReg for DomainAccessControlRegister {
const CP: u32 = 15;
const OP1: u32 = 0;
const CRN: u32 = 3;
const CRM: u32 = 0;
const OP2: u32 = 0;
}
impl SysRegRead for DomainAccessControlRegister {}
impl SysRegWrite for DomainAccessControlRegister {}
impl DomainAccessControlRegister {
#[must_use]
pub fn read() -> Self {
Self(<Self as SysRegRead>::read_raw())
}
#[must_use]
pub const fn set_all(mut self, permission: DomainPermission) -> Self {
let bits = permission.raw_value().value() as u32;
self.0 = 0;
let mut offset = 0;
while offset < 32 {
self.0 |= bits << offset;
offset += 2;
}
self
}
pub fn write(self) {
unsafe {
Self::write_raw(self.0);
}
}
}
#[bitenum(u2, exhaustive = false)]
pub enum DomainPermission {
NoPermission = 0b00,
Client = 0b01,
Manager = 0b11,
}
#[inline]
pub fn with_manager_domain_access<T>(inner: impl FnOnce() -> T) -> T {
let domain_access = DomainAccessControlRegister::read();
domain_access.set_all(DomainPermission::Manager).write();
isb();
let res = inner();
dsb();
domain_access.write();
isb();
res
}