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)
}
}
#[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 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 translate(&self, va: u32, kind: AccessKind, privileged: bool) -> Result<u32, Fault>;
fn high_vectors(&self) -> bool {
false
}
fn report_abort(&self, va: u32, fault: Fault, kind: AccessKind) {
let _ = (va, fault, kind);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FlatMmu;
impl Mmu for FlatMmu {
fn translate(&self, va: u32, _kind: AccessKind, _privileged: bool) -> Result<u32, Fault> {
Ok(va)
}
}
#[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 translate(&self, va: u32, _kind: AccessKind, _privileged: bool) -> Result<u32, Fault> {
Ok(va)
}
fn high_vectors(&self) -> bool {
self.control() & (1 << 13) != 0
}
}
#[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.high_vectors());
}
#[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));
}
#[test]
fn a_flat_mmu_translates_nothing_and_faults_never() {
let mmu = FlatMmu;
assert_eq!(
mmu.translate(0xdead_beef, AccessKind::Read, false),
Ok(0xdead_beef)
);
assert!(!mmu.high_vectors());
}
#[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);
}
}