use alloc::fmt;
use crate::core::sync::{AtomicU32, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CpOp {
pub cp: u8,
pub opc1: u8,
pub crd: u8,
pub crn: u8,
pub crm: u8,
pub opc2: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CpTransfer {
pub cp: u8,
pub crd: u8,
pub long: bool,
pub option: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CpFault {
Undefined,
}
pub type CpResult<T = ()> = core::result::Result<T, CpFault>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct CpEffect {
pub halt: bool,
}
impl CpEffect {
pub const NONE: CpEffect = CpEffect { halt: false };
pub const HALT: CpEffect = CpEffect { halt: true };
}
pub trait Coprocessor: Send + Sync + fmt::Debug {
fn mcr(&self, op: CpOp, value: u32) -> CpResult<CpEffect> {
let _ = (op, value);
Err(CpFault::Undefined)
}
fn mrc(&self, op: CpOp) -> CpResult<u32> {
let _ = op;
Err(CpFault::Undefined)
}
fn cdp(&self, op: CpOp) -> CpResult<CpEffect> {
let _ = op;
Err(CpFault::Undefined)
}
fn mcrr(&self, cp: u8, opc: u8, crm: u8, value: u64) -> CpResult<CpEffect> {
let _ = (cp, opc, crm, value);
Err(CpFault::Undefined)
}
fn mrrc(&self, cp: u8, opc: u8, crm: u8) -> CpResult<u64> {
let _ = (cp, opc, crm);
Err(CpFault::Undefined)
}
fn transfer_len(&self, op: CpTransfer) -> CpResult<u8> {
let _ = op;
Err(CpFault::Undefined)
}
fn write_word(&self, op: CpTransfer, index: u8, value: u32) -> CpResult {
let _ = (op, index, value);
Err(CpFault::Undefined)
}
fn read_word(&self, op: CpTransfer, index: u8) -> CpResult<u32> {
let _ = (op, index);
Err(CpFault::Undefined)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AccessKind {
Fetch,
Read,
Write,
}
impl AccessKind {
#[must_use]
pub const fn is_fetch(self) -> bool {
matches!(self, AccessKind::Fetch)
}
#[inline]
const fn slot(self) -> usize {
match self {
AccessKind::Fetch => 0,
AccessKind::Read => 1,
AccessKind::Write => 2,
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Va(pub u32);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pa(pub u32);
impl fmt::Display for Va {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#010x}", self.0)
}
}
impl fmt::Display for Pa {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#010x}", self.0)
}
}
pub trait PhysMem {
fn read_u32(&self, at: Pa) -> Option<u32>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Regime {
pub generation: u32,
pub translating: bool,
pub high_vectors: bool,
pub alignment_faults: bool,
}
impl Regime {
pub const FLAT: Regime = Regime {
generation: 0,
translating: false,
high_vectors: false,
alignment_faults: false,
};
}
impl Default for Regime {
fn default() -> Regime {
Regime::FLAT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Fault {
pub status: u8,
pub domain: u8,
}
impl Fault {
pub const ALIGNMENT: Fault = Fault {
status: 0b0001,
domain: 0,
};
pub const TRANSLATION_SECTION: Fault = Fault {
status: 0b0101,
domain: 0,
};
pub const TRANSLATION_PAGE: Fault = Fault {
status: 0b0111,
domain: 0,
};
pub const DOMAIN_SECTION: Fault = Fault {
status: 0b1001,
domain: 0,
};
pub const DOMAIN_PAGE: Fault = Fault {
status: 0b1011,
domain: 0,
};
pub const EXTERNAL_L1: Fault = Fault {
status: 0b1100,
domain: 0,
};
pub const EXTERNAL_L2: Fault = Fault {
status: 0b1110,
domain: 0,
};
pub const EXTERNAL: Fault = Fault {
status: 0b1000,
domain: 0,
};
pub const PERMISSION_SECTION: Fault = Fault {
status: 0b1101,
domain: 0,
};
pub const PERMISSION_PAGE: Fault = Fault {
status: 0b1111,
domain: 0,
};
#[must_use]
pub const fn in_domain(mut self, domain: u8) -> Fault {
self.domain = domain;
self
}
#[must_use]
pub const fn to_fsr(self) -> u32 {
(((self.domain & 0xf) as u32) << 4) | ((self.status & 0xf) as u32)
}
}
pub trait Mmu: Send + Sync + fmt::Debug {
fn regime(&self) -> Regime {
Regime::FLAT
}
fn translate(
&self,
mem: &dyn PhysMem,
va: Va,
kind: AccessKind,
privileged: bool,
) -> Result<Pa, Fault>;
fn translate_debug(&self, mem: &dyn PhysMem, va: Va) -> Result<Pa, Fault> {
self.translate(mem, va, AccessKind::Read, true)
}
fn report_abort(&self, va: Va, fault: Fault, kind: AccessKind) {
let _ = (va, fault, kind);
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FlatMmu {
pub high_vectors: bool,
pub alignment_faults: bool,
}
impl FlatMmu {
#[must_use]
pub const fn new() -> FlatMmu {
FlatMmu {
high_vectors: false,
alignment_faults: false,
}
}
}
impl Mmu for FlatMmu {
fn regime(&self) -> Regime {
Regime {
high_vectors: self.high_vectors,
alignment_faults: self.alignment_faults,
..Regime::FLAT
}
}
fn translate(
&self,
_mem: &dyn PhysMem,
va: Va,
_kind: AccessKind,
_privileged: bool,
) -> Result<Pa, Fault> {
Ok(Pa(va.0))
}
}
#[derive(Debug)]
pub struct Cp15Stub {
main_id: u32,
cache_type: u32,
control: AtomicU32,
}
impl Cp15Stub {
#[must_use]
pub const fn new(main_id: u32) -> Cp15Stub {
Cp15Stub {
main_id,
cache_type: 0,
control: AtomicU32::new(0),
}
}
pub const ARM926EJS_ID: u32 = 0x4106_9265;
#[must_use]
pub const fn with_cache_type(mut self, cache_type: u32) -> Cp15Stub {
self.cache_type = cache_type;
self
}
#[must_use]
pub fn control(&self) -> u32 {
self.control.load(Ordering::Acquire)
}
}
impl Default for Cp15Stub {
fn default() -> Cp15Stub {
Cp15Stub::new(Cp15Stub::ARM926EJS_ID)
}
}
impl Coprocessor for Cp15Stub {
fn mrc(&self, op: CpOp) -> CpResult<u32> {
if op.cp != 15 || op.opc1 != 0 {
return Err(CpFault::Undefined);
}
match (op.crn, op.crm, op.opc2) {
(0, 0, 0) => Ok(self.main_id),
(0, 0, 1) => Ok(self.cache_type),
(1, 0, 0) => Ok(self.control()),
_ => Ok(0),
}
}
fn mcr(&self, op: CpOp, value: u32) -> CpResult<CpEffect> {
if op.cp != 15 || op.opc1 != 0 {
return Err(CpFault::Undefined);
}
match (op.crn, op.crm, op.opc2) {
(1, 0, 0) => {
self.control.store(value, Ordering::Release);
Ok(CpEffect::NONE)
}
(7, 0, 4) => Ok(CpEffect::HALT),
_ => Ok(CpEffect::NONE),
}
}
}
impl Mmu for Cp15Stub {
fn regime(&self) -> Regime {
let control = self.control();
Regime {
high_vectors: control & (1 << 13) != 0,
alignment_faults: control & (1 << 1) != 0,
..Regime::FLAT
}
}
fn translate(
&self,
_mem: &dyn PhysMem,
va: Va,
_kind: AccessKind,
_privileged: bool,
) -> Result<Pa, Fault> {
Ok(Pa(va.0))
}
}
pub const TLB_ENTRIES: usize = 256;
pub const TLB_PAGE_BITS: u32 = 10;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Entry {
tag: u64,
base: u32,
valid: bool,
}
#[derive(Debug)]
pub struct Tlb {
slots: [[Entry; TLB_ENTRIES]; 3],
seen: (u32, u64),
epoch: u64,
hits: u64,
misses: u64,
}
impl Default for Tlb {
fn default() -> Tlb {
Tlb::new()
}
}
impl Tlb {
#[must_use]
pub fn new() -> Tlb {
Tlb {
slots: [[Entry::default(); TLB_ENTRIES]; 3],
seen: (0, 0),
epoch: 1,
hits: 0,
misses: 0,
}
}
pub fn flush(&mut self) {
self.epoch = self.epoch.wrapping_add(1);
self.hits = 0;
self.misses = 0;
}
#[inline]
pub fn sync(&mut self, generation: u32, topology: u64) {
if self.seen != (generation, topology) {
self.seen = (generation, topology);
self.epoch = self.epoch.wrapping_add(1);
}
}
#[must_use]
pub fn stats(&self) -> (u64, u64) {
(self.hits, self.misses)
}
#[inline]
fn tag(&self, page: u32, privileged: bool) -> u64 {
self.epoch.wrapping_mul(0xff51_afd7_ed55_8ccd)
^ u64::from(page).wrapping_mul(0x9e37_79b9_7f4a_7c15)
^ u64::from(privileged)
}
#[inline]
pub fn lookup(&mut self, kind: AccessKind, va: Va, privileged: bool) -> Option<Pa> {
let page = va.0 >> TLB_PAGE_BITS;
let slot = &self.slots[kind.slot()][(page as usize) & (TLB_ENTRIES - 1)];
if slot.valid && slot.tag == self.tag(page, privileged) {
let base = slot.base;
self.hits += 1;
Some(Pa(base | (va.0 & ((1 << TLB_PAGE_BITS) - 1))))
} else {
self.misses += 1;
None
}
}
#[inline]
pub fn insert(&mut self, kind: AccessKind, va: Va, privileged: bool, pa: Pa) {
let page = va.0 >> TLB_PAGE_BITS;
let tag = self.tag(page, privileged);
self.slots[kind.slot()][(page as usize) & (TLB_ENTRIES - 1)] = Entry {
tag,
base: pa.0 & !((1 << TLB_PAGE_BITS) - 1),
valid: true,
};
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_stub_answers_the_id_registers_and_remembers_control() {
let cp = Cp15Stub::default();
let id = CpOp {
cp: 15,
opc1: 0,
crd: 0,
crn: 0,
crm: 0,
opc2: 0,
};
assert_eq!(cp.mrc(id), Ok(Cp15Stub::ARM926EJS_ID));
let control = CpOp { crn: 1, ..id };
assert_eq!(cp.mcr(control, 1 << 13), Ok(CpEffect::NONE));
assert_eq!(cp.mrc(control), Ok(1 << 13));
assert!(cp.regime().high_vectors);
assert!(
!cp.regime().translating,
"the stub is honest about having no page tables"
);
}
#[test]
fn wait_for_interrupt_asks_the_core_to_halt() {
let cp = Cp15Stub::default();
let wfi = CpOp {
cp: 15,
opc1: 0,
crd: 0,
crn: 7,
crm: 0,
opc2: 4,
};
assert_eq!(cp.mcr(wfi, 0), Ok(CpEffect::HALT));
}
#[test]
fn another_coprocessor_number_is_not_this_ones_business() {
let cp = Cp15Stub::default();
let op = CpOp {
cp: 14,
opc1: 0,
crd: 0,
crn: 0,
crm: 0,
opc2: 0,
};
assert_eq!(cp.mrc(op), Err(CpFault::Undefined));
}
#[derive(Debug)]
struct NoMem;
impl PhysMem for NoMem {
fn read_u32(&self, _at: Pa) -> Option<u32> {
None
}
}
#[test]
fn a_flat_mmu_translates_nothing_and_faults_never() {
let mmu = FlatMmu::new();
assert_eq!(
mmu.translate(&NoMem, Va(0xdead_beef), AccessKind::Read, false),
Ok(Pa(0xdead_beef))
);
assert_eq!(mmu.regime(), Regime::FLAT);
}
#[test]
fn a_flat_mmu_carries_the_cores_two_straps() {
let mmu = FlatMmu {
high_vectors: true,
alignment_faults: true,
};
let regime = mmu.regime();
assert!(regime.high_vectors);
assert!(regime.alignment_faults);
assert!(!regime.translating, "a strap is not a page table");
}
#[test]
fn fault_status_packs_the_domain_above_the_status() {
assert_eq!(Fault::EXTERNAL.to_fsr(), 0b1000);
assert_eq!(Fault::PERMISSION_PAGE.in_domain(3).to_fsr(), 0x3f);
assert_eq!(Fault::DOMAIN_SECTION.in_domain(15).to_fsr(), 0xf9);
}
#[test]
fn the_tlb_answers_what_it_was_told_and_only_that() {
let mut tlb = Tlb::new();
tlb.insert(AccessKind::Read, Va(0x0001_2345), true, Pa(0x8000_0345));
assert_eq!(
tlb.lookup(AccessKind::Read, Va(0x0001_2345), true),
Some(Pa(0x8000_0345))
);
assert_eq!(
tlb.lookup(AccessKind::Read, Va(0x0001_2000), true),
Some(Pa(0x8000_0000)),
"another byte of the same kibibyte translates through the same entry"
);
assert_eq!(tlb.lookup(AccessKind::Write, Va(0x0001_2345), true), None);
assert_eq!(tlb.lookup(AccessKind::Read, Va(0x0001_2345), false), None);
assert_eq!(tlb.lookup(AccessKind::Read, Va(0x0001_2800), true), None);
}
#[test]
fn a_tiny_page_does_not_answer_for_its_neighbours() {
let mut tlb = Tlb::new();
tlb.insert(AccessKind::Read, Va(0x0000_0000), true, Pa(0x1000_0000));
assert_eq!(tlb.lookup(AccessKind::Read, Va(0x0000_0400), true), None);
}
#[test]
fn either_generation_moving_empties_the_tlb() {
for (generation, topology) in [(1u32, 0u64), (0, 1)] {
let mut tlb = Tlb::new();
tlb.sync(0, 0);
tlb.insert(AccessKind::Fetch, Va(0x4000), true, Pa(0x9000));
assert!(tlb.lookup(AccessKind::Fetch, Va(0x4000), true).is_some());
tlb.sync(generation, topology);
assert_eq!(
tlb.lookup(AccessKind::Fetch, Va(0x4000), true),
None,
"generation {generation}, topology {topology} left a stale entry"
);
tlb.insert(AccessKind::Fetch, Va(0x4000), true, Pa(0x9000));
tlb.sync(generation, topology);
assert!(tlb.lookup(AccessKind::Fetch, Va(0x4000), true).is_some());
}
}
#[test]
fn the_tlb_counts_its_hits_and_misses() {
let mut tlb = Tlb::new();
assert_eq!(tlb.lookup(AccessKind::Read, Va(0), true), None);
tlb.insert(AccessKind::Read, Va(0), true, Pa(0));
assert!(tlb.lookup(AccessKind::Read, Va(0), true).is_some());
assert_eq!(tlb.stats(), (1, 1));
tlb.flush();
assert_eq!(tlb.stats(), (0, 0));
assert_eq!(tlb.lookup(AccessKind::Read, Va(0), true), None);
}
}