use alloc::fmt;
use crate::core::error::Result;
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU32, Ordering};
use crate::core::value::Endian;
use super::Config;
use super::cp::{
AccessKind, Coprocessor, CpEffect, CpFault, CpOp, CpResult, Fault, Mmu, Pa, PhysMem, Regime, Va,
};
pub mod control {
pub const M: u32 = 1 << 0;
pub const A: u32 = 1 << 1;
pub const C: u32 = 1 << 2;
pub const W: u32 = 1 << 3;
pub const B: u32 = 1 << 7;
pub const S: u32 = 1 << 8;
pub const R: u32 = 1 << 9;
pub const I: u32 = 1 << 12;
pub const V: u32 = 1 << 13;
pub const READ_AS_ONE: u32 = 0b111 << 4;
pub const WRITABLE: u32 = 0xffff;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Allow {
None,
Read,
Write,
}
impl Allow {
const fn permits(self, kind: AccessKind) -> bool {
match kind {
AccessKind::Write => matches!(self, Allow::Write),
_ => matches!(self, Allow::Read | Allow::Write),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Checks {
Guest(AccessKind, bool),
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Level {
Section,
Page,
}
impl Level {
const fn domain_fault(self) -> Fault {
match self {
Level::Section => Fault::DOMAIN_SECTION,
Level::Page => Fault::DOMAIN_PAGE,
}
}
const fn permission_fault(self) -> Fault {
match self {
Level::Section => Fault::PERMISSION_SECTION,
Level::Page => Fault::PERMISSION_PAGE,
}
}
}
#[derive(Debug)]
pub struct Cp15 {
main_id: u32,
cache_type: u32,
tcm_status: u32,
reset_control: u32,
control: AtomicU32,
ttbr: AtomicU32,
domains: AtomicU32,
dfsr: AtomicU32,
ifsr: AtomicU32,
far: AtomicU32,
c9: [AtomicU32; 4],
fcse_pid: AtomicU32,
generation: AtomicU32,
}
impl Cp15 {
pub const ARM926EJS_ID: u32 = 0x4106_9265;
pub const ARM926EJS_CACHE_TYPE: u32 = 0x1d15_2152;
#[must_use]
pub fn arm926ejs(cfg: &Config) -> Cp15 {
let mut control = control::READ_AS_ONE;
if cfg.high_vectors {
control |= control::V;
}
if cfg.alignment_faults {
control |= control::A;
}
if cfg.endian == Endian::Big {
control |= control::B;
}
Cp15 {
main_id: Cp15::ARM926EJS_ID,
cache_type: Cp15::ARM926EJS_CACHE_TYPE,
tcm_status: 0,
reset_control: control,
control: AtomicU32::new(control),
ttbr: AtomicU32::new(0),
domains: AtomicU32::new(0),
dfsr: AtomicU32::new(0),
ifsr: AtomicU32::new(0),
far: AtomicU32::new(0),
c9: [
AtomicU32::new(0),
AtomicU32::new(0),
AtomicU32::new(0),
AtomicU32::new(0),
],
fcse_pid: AtomicU32::new(0),
generation: AtomicU32::new(0),
}
}
pub fn reset(&self) {
self.control.store(self.reset_control, Ordering::Release);
self.ttbr.store(0, Ordering::Release);
self.domains.store(0, Ordering::Release);
self.dfsr.store(0, Ordering::Release);
self.ifsr.store(0, Ordering::Release);
self.far.store(0, Ordering::Release);
for reg in &self.c9 {
reg.store(0, Ordering::Release);
}
self.fcse_pid.store(0, Ordering::Release);
self.invalidate();
}
#[must_use]
pub fn control(&self) -> u32 {
self.control.load(Ordering::Acquire)
}
#[must_use]
pub fn ttbr(&self) -> u32 {
self.ttbr.load(Ordering::Acquire)
}
#[must_use]
pub fn domains(&self) -> u32 {
self.domains.load(Ordering::Acquire)
}
#[must_use]
pub fn fault_status(&self) -> (u32, u32) {
(
self.dfsr.load(Ordering::Acquire),
self.ifsr.load(Ordering::Acquire),
)
}
#[must_use]
pub fn fault_address(&self) -> u32 {
self.far.load(Ordering::Acquire)
}
#[must_use]
pub fn fcse_pid(&self) -> u32 {
self.fcse_pid.load(Ordering::Acquire)
}
#[must_use]
pub fn mmu_enabled(&self) -> bool {
self.control() & control::M != 0
}
fn invalidate(&self) {
self.generation.fetch_add(1, Ordering::AcqRel);
}
#[inline]
fn mva(&self, va: Va) -> u32 {
let pid = self.fcse_pid.load(Ordering::Acquire) & 0xfe00_0000;
if pid != 0 && va.0 < 0x0200_0000 {
va.0 | pid
} else {
va.0
}
}
fn domain_check(
&self,
domain: u8,
ap: u32,
checks: Checks,
level: Level,
) -> core::result::Result<(), Fault> {
let Checks::Guest(kind, privileged) = checks else {
return Ok(());
};
let access = (self.domains() >> (2 * u32::from(domain))) & 0b11;
match access {
0b11 => Ok(()),
0b01 => {
if self.permits(ap, kind, privileged) {
Ok(())
} else {
Err(level.permission_fault().in_domain(domain))
}
}
_ => Err(level.domain_fault().in_domain(domain)),
}
}
fn permits(&self, ap: u32, kind: AccessKind, privileged: bool) -> bool {
let control = self.control();
let (privileged_allow, user_allow) = match ap & 0b11 {
0b00 => match (control & control::S != 0, control & control::R != 0) {
(false, false) => (Allow::None, Allow::None),
(true, false) => (Allow::Read, Allow::None),
(false, true) => (Allow::Read, Allow::Read),
(true, true) => (Allow::None, Allow::None),
},
0b01 => (Allow::Write, Allow::None),
0b10 => (Allow::Write, Allow::Read),
_ => (Allow::Write, Allow::Write),
};
let allow = if privileged {
privileged_allow
} else {
user_allow
};
allow.permits(kind)
}
fn second_level(
&self,
descriptor: u32,
mva: u32,
domain: u8,
checks: Checks,
) -> core::result::Result<Pa, Fault> {
let subpage = |index: u32| (descriptor >> (4 + 2 * index)) & 0b11;
let (ap, base, offset_mask) = match descriptor & 0b11 {
0b00 => return Err(Fault::TRANSLATION_PAGE.in_domain(domain)),
0b01 => (
subpage((mva >> 14) & 0b11),
descriptor & 0xffff_0000,
0x0000_ffff,
),
0b10 => (
subpage((mva >> 10) & 0b11),
descriptor & 0xffff_f000,
0x0000_0fff,
),
_ => (subpage(0), descriptor & 0xffff_fc00, 0x0000_03ff),
};
self.domain_check(domain, ap, checks, Level::Page)?;
Ok(Pa(base | (mva & offset_mask)))
}
}
impl Coprocessor for Cp15 {
fn mrc(&self, op: CpOp) -> CpResult<u32> {
if op.cp != 15 || op.opc1 != 0 {
return Err(CpFault::Undefined);
}
Ok(match (op.crn, op.crm, op.opc2) {
(0, 0, 1) => self.cache_type,
(0, 0, 2) => self.tcm_status,
(0, 0, _) => self.main_id,
(1, 0, 0) => self.control(),
(2, 0, 0) => self.ttbr(),
(3, 0, 0) => self.domains(),
(5, 0, 0) => self.dfsr.load(Ordering::Acquire),
(5, 0, 1) => self.ifsr.load(Ordering::Acquire),
(6, 0, 0) => self.far.load(Ordering::Acquire),
(7, 10 | 14, 3) => 0x4000_0000,
(9, 0, 0) => self.c9[0].load(Ordering::Acquire),
(9, 0, 1) => self.c9[1].load(Ordering::Acquire),
(9, 1, 0) => self.c9[2].load(Ordering::Acquire),
(9, 1, 1) => self.c9[3].load(Ordering::Acquire),
(13, 0, 0) => self.fcse_pid(),
_ => 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 & control::WRITABLE) | control::READ_AS_ONE,
Ordering::Release,
);
self.invalidate();
}
(2, 0, 0) => {
self.ttbr.store(value & 0xffff_c000, Ordering::Release);
self.invalidate();
}
(3, 0, 0) => {
self.domains.store(value, Ordering::Release);
self.invalidate();
}
(5, 0, 0) => self.dfsr.store(value & 0xff, Ordering::Release),
(5, 0, 1) => self.ifsr.store(value & 0xff, Ordering::Release),
(6, 0, 0) => self.far.store(value, Ordering::Release),
(7, 0, 4) => return Ok(CpEffect::HALT),
(7, _, _) => {}
(8, _, _) => self.invalidate(),
(9, 0, 0) => self.c9[0].store(value, Ordering::Release),
(9, 0, 1) => self.c9[1].store(value, Ordering::Release),
(9, 1, 0) => self.c9[2].store(value, Ordering::Release),
(9, 1, 1) => self.c9[3].store(value, Ordering::Release),
(13, 0, 0) => {
self.fcse_pid.store(value & 0xfe00_0000, Ordering::Release);
self.invalidate();
}
_ => {}
}
Ok(CpEffect::NONE)
}
}
impl Mmu for Cp15 {
fn regime(&self) -> Regime {
let control = self.control();
Regime {
generation: self.generation.load(Ordering::Acquire),
translating: control & control::M != 0 || self.fcse_pid() != 0,
high_vectors: control & control::V != 0,
alignment_faults: control & control::A != 0,
}
}
fn translate(
&self,
mem: &dyn PhysMem,
va: Va,
kind: AccessKind,
privileged: bool,
) -> core::result::Result<Pa, Fault> {
self.walk(mem, va, Checks::Guest(kind, privileged))
}
fn translate_debug(&self, mem: &dyn PhysMem, va: Va) -> core::result::Result<Pa, Fault> {
self.walk(mem, va, Checks::None)
}
fn report_abort(&self, va: Va, fault: Fault, kind: AccessKind) {
if kind.is_fetch() {
self.ifsr.store(fault.to_fsr(), Ordering::Release);
} else {
self.dfsr.store(fault.to_fsr(), Ordering::Release);
self.far.store(self.mva(va), Ordering::Release);
}
}
}
impl Cp15 {
fn walk(&self, mem: &dyn PhysMem, va: Va, checks: Checks) -> core::result::Result<Pa, Fault> {
let mva = self.mva(va);
if self.control() & control::M == 0 {
return Ok(Pa(mva));
}
let first = self.ttbr() | ((mva >> 20) << 2);
let descriptor = mem.read_u32(Pa(first)).ok_or(Fault::EXTERNAL_L1)?;
let domain = ((descriptor >> 5) & 0xf) as u8;
match descriptor & 0b11 {
0b00 => Err(Fault::TRANSLATION_SECTION),
0b01 => {
let second = (descriptor & 0xffff_fc00) | (((mva >> 12) & 0xff) << 2);
let entry = mem
.read_u32(Pa(second))
.ok_or_else(|| Fault::EXTERNAL_L2.in_domain(domain))?;
self.second_level(entry, mva, domain, checks)
}
0b10 => {
let ap = (descriptor >> 10) & 0b11;
self.domain_check(domain, ap, checks, Level::Section)?;
Ok(Pa((descriptor & 0xfff0_0000) | (mva & 0x000f_ffff)))
}
_ => {
let second = (descriptor & 0xffff_f000) | (((mva >> 10) & 0x3ff) << 2);
let entry = mem
.read_u32(Pa(second))
.ok_or_else(|| Fault::EXTERNAL_L2.in_domain(domain))?;
self.second_level(entry, mva, domain, checks)
}
}
}
}
impl Cp15 {
pub fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_u32(self.control())?;
w.write_u32(self.ttbr())?;
w.write_u32(self.domains())?;
w.write_u32(self.dfsr.load(Ordering::Acquire))?;
w.write_u32(self.ifsr.load(Ordering::Acquire))?;
w.write_u32(self.far.load(Ordering::Acquire))?;
for reg in &self.c9 {
w.write_u32(reg.load(Ordering::Acquire))?;
}
w.write_u32(self.fcse_pid())?;
Ok(())
}
pub fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
self.control
.store(r.read_u32()? | control::READ_AS_ONE, Ordering::Release);
self.ttbr.store(r.read_u32()?, Ordering::Release);
self.domains.store(r.read_u32()?, Ordering::Release);
self.dfsr.store(r.read_u32()?, Ordering::Release);
self.ifsr.store(r.read_u32()?, Ordering::Release);
self.far.store(r.read_u32()?, Ordering::Release);
for reg in &self.c9 {
reg.store(r.read_u32()?, Ordering::Release);
}
self.fcse_pid.store(r.read_u32()?, Ordering::Release);
self.invalidate();
Ok(())
}
}
impl fmt::Display for Cp15 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (dfsr, ifsr) = self.fault_status();
write!(
f,
"cp15 c1={:#010x} c2={:#010x} c3={:#010x} dfsr={dfsr:#04x} ifsr={ifsr:#04x} \
far={:#010x}",
self.control(),
self.ttbr(),
self.domains(),
self.fault_address(),
)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use super::*;
#[derive(Debug)]
struct Ram(Vec<u32>);
impl Ram {
fn new() -> Ram {
Ram(vec![0; 0x1_0000])
}
fn put(&mut self, at: u32, value: u32) {
self.0[(at / 4) as usize] = value;
}
}
impl PhysMem for Ram {
fn read_u32(&self, at: Pa) -> Option<u32> {
self.0.get((at.0 / 4) as usize).copied()
}
}
fn cp15() -> Cp15 {
Cp15::arm926ejs(&Config::ARM926EJS)
}
fn op(crn: u8, crm: u8, opc2: u8) -> CpOp {
CpOp {
cp: 15,
opc1: 0,
crd: 0,
crn,
crm,
opc2,
}
}
fn enable(cp: &Cp15, ttbr: u32) {
cp.mcr(op(2, 0, 0), ttbr).unwrap();
cp.mcr(op(3, 0, 0), 0x5555_5555).unwrap();
cp.mcr(op(1, 0, 0), cp.control() | control::M).unwrap();
}
#[test]
fn it_identifies_itself_as_an_arm926ejs() {
let cp = cp15();
assert_eq!(cp.mrc(op(0, 0, 0)), Ok(Cp15::ARM926EJS_ID));
assert_eq!(cp.mrc(op(0, 0, 1)), Ok(Cp15::ARM926EJS_CACHE_TYPE));
assert_eq!(cp.mrc(op(0, 0, 2)), Ok(0));
assert_eq!(
cp.mrc(CpOp {
cp: 14,
..op(0, 0, 0)
}),
Err(CpFault::Undefined)
);
}
#[test]
fn the_control_register_keeps_its_should_be_one_bits() {
let cp = cp15();
assert_eq!(cp.control(), control::READ_AS_ONE);
cp.mcr(op(1, 0, 0), 0).unwrap();
assert_eq!(
cp.control(),
control::READ_AS_ONE,
"writing zero must not clear bits the architecture reads as one"
);
cp.mcr(op(1, 0, 0), 0xffff_ffff).unwrap();
assert_eq!(
cp.control(),
control::WRITABLE,
"the reserved half of the register reads as zero"
);
}
#[test]
fn the_straps_become_the_reset_value_of_c1() {
let cfg = Config {
high_vectors: true,
alignment_faults: true,
endian: Endian::Big,
..Config::ARM926EJS
};
let cp = Cp15::arm926ejs(&cfg);
assert_eq!(
cp.control(),
control::READ_AS_ONE | control::V | control::A | control::B
);
let regime = cp.regime();
assert!(regime.high_vectors);
assert!(regime.alignment_faults);
assert!(!regime.translating, "a strap does not enable the MMU");
cp.mcr(op(1, 0, 0), cp.control() & !control::V).unwrap();
assert!(!cp.regime().high_vectors);
}
#[test]
fn a_section_translates_and_keeps_the_offset() {
let mut ram = Ram::new();
ram.put(4, 0x0400_0000 | (0b11 << 10) | 0b10);
let cp = cp15();
enable(&cp, 0);
assert_eq!(
cp.translate(&ram, Va(0x0010_1234), AccessKind::Read, false),
Ok(Pa(0x0400_1234))
);
}
#[test]
fn an_unmapped_megabyte_is_a_section_translation_fault() {
let ram = Ram::new();
let cp = cp15();
enable(&cp, 0);
assert_eq!(
cp.translate(&ram, Va(0x0010_0000), AccessKind::Read, false),
Err(Fault::TRANSLATION_SECTION)
);
}
#[test]
fn a_small_page_translates_through_a_coarse_table() {
let mut ram = Ram::new();
ram.put(8, 0x0000_8000 | (3 << 5) | 0b01);
ram.put(0x8000 + 5 * 4, 0x0000_2000 | 0xff0 | 0b10);
let cp = cp15();
enable(&cp, 0);
assert_eq!(
cp.translate(&ram, Va(0x0020_5abc), AccessKind::Write, true),
Ok(Pa(0x0000_2abc))
);
assert_eq!(
cp.translate(&ram, Va(0x0020_6000), AccessKind::Read, true),
Err(Fault::TRANSLATION_PAGE.in_domain(3))
);
}
#[test]
fn a_large_page_selects_its_subpage_from_bits_15_and_14() {
let mut ram = Ram::new();
ram.put(0, 0x0000_8000 | 0b01);
let descriptor = 0x0001_0000 | (0b10 << 4) | (0b11 << 6) | 0b01;
for index in 0..16 {
ram.put(0x8000 + index * 4, descriptor);
}
let cp = cp15();
enable(&cp, 0);
assert_eq!(
cp.translate(&ram, Va(0x0000_0004), AccessKind::Read, false),
Ok(Pa(0x0001_0004))
);
assert_eq!(
cp.translate(&ram, Va(0x0000_0004), AccessKind::Write, false),
Err(Fault::PERMISSION_PAGE)
);
assert_eq!(
cp.translate(&ram, Va(0x0000_4004), AccessKind::Write, false),
Ok(Pa(0x0001_4004))
);
}
#[test]
fn a_tiny_page_has_one_permission_field_and_a_kibibyte_of_reach() {
let mut ram = Ram::new();
ram.put(0, 0x0000_9000 | 0b11);
ram.put(0x9000 + 2 * 4, 0x0000_3000 | (0b01 << 4) | 0b11);
let cp = cp15();
enable(&cp, 0);
assert_eq!(
cp.translate(&ram, Va(0x0000_0801), AccessKind::Write, true),
Ok(Pa(0x0000_3001))
);
assert_eq!(
cp.translate(&ram, Va(0x0000_0801), AccessKind::Read, false),
Err(Fault::PERMISSION_PAGE),
"AP 0b01 gives an unprivileged access nothing"
);
assert_eq!(
cp.translate(&ram, Va(0x0000_0c00), AccessKind::Read, true),
Err(Fault::TRANSLATION_PAGE)
);
}
#[test]
fn a_domain_with_no_access_refuses_what_its_permissions_would_allow() {
let mut ram = Ram::new();
ram.put(0, (0b11 << 10) | (5 << 5) | 0b10);
let cp = cp15();
enable(&cp, 0);
assert!(cp.translate(&ram, Va(0), AccessKind::Write, true).is_ok());
cp.mcr(op(3, 0, 0), 0x5555_5555 & !(0b11 << 10)).unwrap();
assert_eq!(
cp.translate(&ram, Va(0), AccessKind::Write, true),
Err(Fault::DOMAIN_SECTION.in_domain(5))
);
cp.mcr(op(3, 0, 0), 0xffff_ffff).unwrap();
ram.put(0, (5 << 5) | 0b10);
assert!(cp.translate(&ram, Va(0), AccessKind::Write, false).is_ok());
}
#[test]
fn the_s_and_r_bits_reinterpret_every_ap_zero_descriptor() {
let mut ram = Ram::new();
ram.put(0, 0b10);
let cp = cp15();
enable(&cp, 0);
assert!(cp.translate(&ram, Va(0), AccessKind::Read, true).is_err());
cp.mcr(op(1, 0, 0), cp.control() | control::S).unwrap();
assert!(cp.translate(&ram, Va(0), AccessKind::Read, true).is_ok());
assert!(cp.translate(&ram, Va(0), AccessKind::Read, false).is_err());
assert!(cp.translate(&ram, Va(0), AccessKind::Write, true).is_err());
cp.mcr(op(1, 0, 0), (cp.control() & !control::S) | control::R)
.unwrap();
assert!(cp.translate(&ram, Va(0), AccessKind::Read, false).is_ok());
assert!(cp.translate(&ram, Va(0), AccessKind::Write, true).is_err());
cp.mcr(op(1, 0, 0), cp.control() | control::S).unwrap();
assert!(cp.translate(&ram, Va(0), AccessKind::Read, true).is_err());
}
#[test]
fn a_table_the_bus_refuses_is_an_external_abort_naming_its_level() {
let mut ram = Ram::new();
let cp = cp15();
enable(&cp, 0xffff_c000);
assert_eq!(
cp.translate(&ram, Va(0), AccessKind::Read, true),
Err(Fault::EXTERNAL_L1)
);
enable(&cp, 0);
ram.put(0, 0xfff0_0000 | (7 << 5) | 0b01);
assert_eq!(
cp.translate(&ram, Va(0), AccessKind::Read, true),
Err(Fault::EXTERNAL_L2.in_domain(7))
);
}
#[test]
fn the_fcse_relocates_the_bottom_thirty_two_megabytes() {
let ram = Ram::new();
let cp = cp15();
assert!(!cp.regime().translating, "pid 0 translates nothing");
cp.mcr(op(13, 0, 0), 0x0400_0000).unwrap();
assert_eq!(cp.fcse_pid(), 0x0400_0000);
assert!(
cp.regime().translating,
"a non-zero pid relocates even with the MMU off"
);
assert_eq!(
cp.translate(&ram, Va(0x0000_1000), AccessKind::Read, true),
Ok(Pa(0x0400_1000))
);
assert_eq!(
cp.translate(&ram, Va(0x0200_0000), AccessKind::Read, true),
Ok(Pa(0x0200_0000))
);
}
#[test]
fn every_invalidating_write_moves_the_generation() {
let cp = cp15();
let mut last = cp.regime().generation;
for (crn, crm, opc2, value) in [
(1u8, 0u8, 0u8, 1u32),
(2, 0, 0, 0x4000),
(3, 0, 0, 1),
(8, 7, 0, 0),
(8, 5, 0, 0),
(8, 6, 1, 0),
(13, 0, 0, 0x0200_0000),
] {
cp.mcr(op(crn, crm, opc2), value).unwrap();
let now = cp.regime().generation;
assert_ne!(now, last, "c{crn} c{crm} {opc2} did not invalidate");
last = now;
}
cp.mcr(op(9, 0, 0), 0xffff).unwrap();
assert_eq!(cp.regime().generation, last);
}
#[test]
fn an_abort_latches_the_status_and_the_address() {
let cp = cp15();
cp.report_abort(
Va(0xdead_beef),
Fault::PERMISSION_PAGE.in_domain(2),
AccessKind::Write,
);
assert_eq!(cp.fault_status().0, 0x2f);
assert_eq!(cp.fault_address(), 0xdead_beef);
cp.report_abort(Va(0x1000), Fault::TRANSLATION_SECTION, AccessKind::Fetch);
assert_eq!(cp.fault_status(), (0x2f, 0x05));
assert_eq!(cp.fault_address(), 0xdead_beef);
}
#[test]
fn wait_for_interrupt_asks_the_core_to_halt() {
assert_eq!(cp15().mcr(op(7, 0, 4), 0), Ok(CpEffect::HALT));
}
#[test]
fn test_and_clean_reports_the_cache_already_clean() {
let cp = cp15();
assert_eq!(cp.mrc(op(7, 10, 3)), Ok(0x4000_0000));
assert_eq!(cp.mrc(op(7, 14, 3)), Ok(0x4000_0000));
}
#[test]
fn reset_puts_the_straps_back_and_nothing_else() {
let cp = cp15();
enable(&cp, 0x1_0000);
cp.mcr(op(13, 0, 0), 0x0200_0000).unwrap();
cp.report_abort(Va(1), Fault::EXTERNAL, AccessKind::Read);
cp.reset();
assert_eq!(cp.control(), control::READ_AS_ONE);
assert_eq!(cp.ttbr(), 0);
assert_eq!(cp.domains(), 0);
assert_eq!(cp.fcse_pid(), 0);
assert_eq!(cp.fault_status(), (0, 0));
assert_eq!(cp.fault_address(), 0);
assert!(!cp.mmu_enabled());
}
}