use crate::core::sync::{AtomicBool, AtomicU32, Ordering};
use super::isa::Endian;
pub mod status {
pub const IEC: u32 = 1 << 0;
pub const KUC: u32 = 1 << 1;
pub const IEP: u32 = 1 << 2;
pub const KUP: u32 = 1 << 3;
pub const IEO: u32 = 1 << 4;
pub const KUO: u32 = 1 << 5;
pub const STACK: u32 = 0x3f;
pub const IM: u32 = 0xff << 8;
pub const IM_SHIFT: u32 = 8;
pub const ISC: u32 = 1 << 16;
pub const SWC: u32 = 1 << 17;
pub const PZ: u32 = 1 << 18;
pub const CM: u32 = 1 << 19;
pub const PE: u32 = 1 << 20;
pub const TS: u32 = 1 << 21;
pub const BEV: u32 = 1 << 22;
pub const RE: u32 = 1 << 25;
pub const CU0: u32 = 1 << 28;
pub const CU_SHIFT: u32 = 28;
pub const WRITABLE: u32 = 0xf257_ff3f;
}
pub mod cause_bits {
pub const EXC_CODE: u32 = 0x1f << 2;
pub const EXC_SHIFT: u32 = 2;
pub const IP: u32 = 0xff << 8;
pub const IP_SHIFT: u32 = 8;
pub const SW: u32 = 0x3 << 8;
pub const HW: u32 = 0x3f << 10;
pub const HW_SHIFT: u32 = 10;
pub const CE: u32 = 0x3 << 28;
pub const CE_SHIFT: u32 = 28;
pub const BD: u32 = 1 << 31;
pub const WRITABLE: u32 = SW;
}
pub mod exc {
pub const INT: u32 = 0;
pub const MOD: u32 = 1;
pub const TLBL: u32 = 2;
pub const TLBS: u32 = 3;
pub const ADEL: u32 = 4;
pub const ADES: u32 = 5;
pub const IBE: u32 = 6;
pub const DBE: u32 = 7;
pub const SYS: u32 = 8;
pub const BP: u32 = 9;
pub const RI: u32 = 10;
pub const CPU: u32 = 11;
pub const OV: u32 = 12;
#[must_use]
pub const fn name(code: u32) -> &'static str {
match code {
INT => "Int",
MOD => "Mod",
TLBL => "TLBL",
TLBS => "TLBS",
ADEL => "AdEL",
ADES => "AdES",
IBE => "IBE",
DBE => "DBE",
SYS => "Sys",
BP => "Bp",
RI => "RI",
CPU => "CpU",
OV => "Ov",
_ => "reserved",
}
}
}
pub mod reg {
pub const INDEX: u32 = 0;
pub const RANDOM: u32 = 1;
pub const ENTRY_LO: u32 = 2;
pub const BPC: u32 = 3;
pub const CONTEXT: u32 = 4;
pub const BDA: u32 = 5;
pub const JUMP_DEST: u32 = 6;
pub const DCIC: u32 = 7;
pub const BAD_VADDR: u32 = 8;
pub const BDAM: u32 = 9;
pub const ENTRY_HI: u32 = 10;
pub const BPCM: u32 = 11;
pub const STATUS: u32 = 12;
pub const CAUSE: u32 = 13;
pub const EPC: u32 = 14;
pub const PRID: u32 = 15;
#[must_use]
pub const fn name(n: u32) -> Option<&'static str> {
Some(match n {
INDEX => "index",
RANDOM => "random",
ENTRY_LO => "entrylo",
BPC => "bpc",
CONTEXT => "context",
BDA => "bda",
JUMP_DEST => "jumpdest",
DCIC => "dcic",
BAD_VADDR => "badvaddr",
BDAM => "bdam",
ENTRY_HI => "entryhi",
BPCM => "bpcm",
STATUS => "sr",
CAUSE => "cause",
EPC => "epc",
PRID => "prid",
_ => return None,
})
}
}
pub const RESET_VECTOR: u32 = 0xbfc0_0000;
pub const REFILL_VECTOR: u32 = 0x8000_0000;
pub const GENERAL_VECTOR: u32 = 0x8000_0080;
pub const REFILL_VECTOR_BEV: u32 = 0xbfc0_0100;
pub const GENERAL_VECTOR_BEV: u32 = 0xbfc0_0180;
pub const TLB_ENTRIES: usize = 64;
pub const TLB_WIRED: u32 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Segment {
Kuseg,
Kseg0,
Kseg1,
Kseg2,
}
impl Segment {
#[inline]
#[must_use]
pub const fn of(vaddr: u32) -> Segment {
match vaddr >> 29 {
0..=3 => Segment::Kuseg,
4 => Segment::Kseg0,
5 => Segment::Kseg1,
_ => Segment::Kseg2,
}
}
#[inline]
#[must_use]
pub const fn user_accessible(self) -> bool {
matches!(self, Segment::Kuseg)
}
#[inline]
#[must_use]
pub const fn mapped(self) -> bool {
matches!(self, Segment::Kuseg | Segment::Kseg2)
}
#[inline]
#[must_use]
pub const fn uses_refill_vector(self) -> bool {
matches!(self, Segment::Kuseg)
}
#[inline]
#[must_use]
pub const fn unmapped_phys(vaddr: u32) -> u32 {
vaddr & 0x1fff_ffff
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TlbEntry {
pub hi: u32,
pub lo: u32,
}
impl TlbEntry {
#[inline]
#[must_use]
pub const fn vpn(self) -> u32 {
self.hi & 0xffff_f000
}
#[inline]
#[must_use]
pub const fn asid(self) -> u32 {
(self.hi >> 6) & 0x3f
}
#[inline]
#[must_use]
pub const fn pfn(self) -> u32 {
self.lo & 0xffff_f000
}
#[inline]
#[must_use]
pub const fn global(self) -> bool {
self.lo & (1 << 8) != 0
}
#[inline]
#[must_use]
pub const fn valid(self) -> bool {
self.lo & (1 << 9) != 0
}
#[inline]
#[must_use]
pub const fn writable(self) -> bool {
self.lo & (1 << 10) != 0
}
#[inline]
#[must_use]
pub const fn noncacheable(self) -> bool {
self.lo & (1 << 11) != 0
}
}
#[derive(Debug, Clone)]
pub struct Tlb {
entries: [TlbEntry; TLB_ENTRIES],
}
impl Default for Tlb {
fn default() -> Self {
Tlb::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Lookup {
Hit {
pfn: u32,
writable: bool,
noncacheable: bool,
},
Miss,
Invalid,
Conflict,
}
impl Tlb {
#[must_use]
pub const fn new() -> Tlb {
Tlb {
entries: [TlbEntry { hi: 0, lo: 0 }; TLB_ENTRIES],
}
}
#[inline]
#[must_use]
pub fn entry(&self, index: u32) -> TlbEntry {
self.entries[(index as usize) % TLB_ENTRIES]
}
pub fn set_entry(&mut self, index: u32, entry: TlbEntry) {
self.entries[(index as usize) % TLB_ENTRIES] = entry;
}
#[must_use]
pub fn entries(&self) -> &[TlbEntry; TLB_ENTRIES] {
&self.entries
}
#[must_use]
pub fn lookup(&self, vaddr: u32, asid: u32) -> Lookup {
let vpn = vaddr & 0xffff_f000;
let mut found: Option<TlbEntry> = None;
for entry in &self.entries {
if entry.vpn() != vpn {
continue;
}
if !entry.global() && entry.asid() != asid {
continue;
}
if found.is_some() {
return Lookup::Conflict;
}
found = Some(*entry);
}
match found {
None => Lookup::Miss,
Some(e) if !e.valid() => Lookup::Invalid,
Some(e) => Lookup::Hit {
pfn: e.pfn(),
writable: e.writable(),
noncacheable: e.noncacheable(),
},
}
}
#[must_use]
pub fn probe(&self, hi: u32) -> Option<u32> {
let vpn = hi & 0xffff_f000;
let asid = (hi >> 6) & 0x3f;
self.entries
.iter()
.position(|e| e.vpn() == vpn && (e.global() || e.asid() == asid))
.map(|i| i as u32)
}
}
#[derive(Debug, Default)]
pub struct Lines {
hw: AtomicU32,
reset: AtomicBool,
}
impl Lines {
pub fn set_hw(&self, pin: u32, asserted: bool) {
if pin >= 6 {
return;
}
let bit = 1u32 << pin;
let mut cur = self.hw.load(Ordering::Relaxed);
loop {
let next = if asserted { cur | bit } else { cur & !bit };
match self
.hw
.compare_exchange_weak(cur, next, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) => return,
Err(seen) => cur = seen,
}
}
}
#[must_use]
pub fn hw(&self) -> u32 {
self.hw.load(Ordering::Relaxed) & 0x3f
}
pub fn set_all_hw(&self, level: u32) {
self.hw.store(level & 0x3f, Ordering::Relaxed);
}
pub fn request_reset(&self) {
self.reset.store(true, Ordering::Relaxed);
}
pub fn take_reset_request(&self) -> bool {
self.reset.swap(false, Ordering::Relaxed)
}
}
#[derive(Debug, Clone)]
pub struct Cp0 {
pub index: u32,
pub random: u32,
pub entry_lo: u32,
pub context: u32,
pub bad_vaddr: u32,
pub entry_hi: u32,
pub status: u32,
pub cause: u32,
pub epc: u32,
pub prid: u32,
pub debug: [u32; 6],
}
impl Cp0 {
#[must_use]
pub fn new(prid: u32) -> Cp0 {
Cp0 {
index: 0,
random: (TLB_ENTRIES - 1) as u32,
entry_lo: 0,
context: 0,
bad_vaddr: 0,
entry_hi: 0,
status: status::BEV,
cause: 0,
epc: 0,
prid,
debug: [0; 6],
}
}
#[inline]
#[must_use]
pub const fn kernel_mode(&self) -> bool {
self.status & status::KUC == 0
}
#[inline]
#[must_use]
pub const fn interrupts_enabled(&self) -> bool {
self.status & status::IEC != 0
}
#[inline]
#[must_use]
pub const fn asid(&self) -> u32 {
(self.entry_hi >> 6) & 0x3f
}
#[inline]
#[must_use]
pub const fn coprocessor_usable(&self, n: u32) -> bool {
if n == 0 && self.kernel_mode() {
return true;
}
self.status & (1 << (status::CU_SHIFT + n)) != 0
}
#[inline]
#[must_use]
pub fn data_endian(&self, pin: Endian) -> Endian {
if !self.kernel_mode() && self.status & status::RE != 0 {
match pin {
Endian::Big => Endian::Little,
Endian::Little => Endian::Big,
}
} else {
pin
}
}
#[must_use]
pub fn cause_with(&self, hw: u32) -> u32 {
(self.cause & !cause_bits::HW) | ((hw & 0x3f) << cause_bits::HW_SHIFT)
}
#[must_use]
pub fn ready_interrupts(&self, hw: u32) -> u32 {
let pending = (self.cause_with(hw) & cause_bits::IP) >> cause_bits::IP_SHIFT;
let mask = (self.status & status::IM) >> status::IM_SHIFT;
pending & mask
}
pub fn push_mode(&mut self) {
let stack = self.status & status::STACK;
self.status = (self.status & !status::STACK) | ((stack << 2) & status::STACK);
}
pub fn pop_mode(&mut self) {
let stack = self.status & status::STACK;
let popped = (stack >> 2) | (stack & (status::KUO | status::IEO));
self.status = (self.status & !status::STACK) | popped;
}
pub fn set_context_vpn(&mut self, vaddr: u32) {
self.context = (self.context & 0xffe0_0000) | ((vaddr & 0x7fff_f000) >> 10);
}
pub fn tick_random(&mut self) {
self.random = if self.random <= TLB_WIRED {
(TLB_ENTRIES - 1) as u32
} else {
self.random - 1
};
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_mode_stack_is_three_deep_and_rfe_pops_it() {
let mut cp0 = Cp0::new(0);
cp0.status = status::KUC | status::IEC;
cp0.push_mode();
assert_eq!(
cp0.status & status::STACK,
status::KUP | status::IEP,
"the current pair moved to previous and the new current is kernel \
mode with interrupts off"
);
assert!(cp0.kernel_mode());
assert!(!cp0.interrupts_enabled());
cp0.pop_mode();
assert_eq!(cp0.status & status::STACK, status::KUC | status::IEC);
assert!(!cp0.kernel_mode());
assert!(cp0.interrupts_enabled());
}
#[test]
fn a_third_nested_exception_loses_the_outermost_level() {
let mut cp0 = Cp0::new(0);
cp0.status = status::KUC | status::IEC;
cp0.push_mode();
cp0.push_mode();
assert_eq!(cp0.status & status::STACK, status::KUO | status::IEO);
cp0.push_mode();
assert_eq!(cp0.status & status::STACK, 0, "the outermost pair is gone");
}
#[test]
fn rfe_leaves_the_old_pair_in_place() {
let mut cp0 = Cp0::new(0);
cp0.status = status::KUO | status::IEO;
cp0.pop_mode();
assert_eq!(
cp0.status & status::STACK,
status::KUO | status::IEO | status::KUP | status::IEP
);
cp0.pop_mode();
assert_eq!(
cp0.status & status::STACK,
status::KUO | status::IEO | status::KUP | status::IEP | status::KUC | status::IEC
);
}
#[test]
fn the_segments_are_where_the_manual_puts_them() {
assert_eq!(Segment::of(0x0000_0000), Segment::Kuseg);
assert_eq!(Segment::of(0x7fff_ffff), Segment::Kuseg);
assert_eq!(Segment::of(0x8000_0000), Segment::Kseg0);
assert_eq!(Segment::of(0x9fff_ffff), Segment::Kseg0);
assert_eq!(Segment::of(0xa000_0000), Segment::Kseg1);
assert_eq!(Segment::of(0xbfff_ffff), Segment::Kseg1);
assert_eq!(Segment::of(0xc000_0000), Segment::Kseg2);
assert_eq!(Segment::of(0xffff_ffff), Segment::Kseg2);
assert_eq!(Segment::unmapped_phys(0x8000_1234), 0x0000_1234);
assert_eq!(Segment::unmapped_phys(0xa000_1234), 0x0000_1234);
assert_eq!(Segment::unmapped_phys(0xbfc0_0000), 0x1fc0_0000);
}
#[test]
fn only_kuseg_takes_the_refill_vector() {
assert!(Segment::Kuseg.uses_refill_vector());
assert!(!Segment::Kseg2.uses_refill_vector());
assert!(Segment::Kseg2.mapped());
assert!(!Segment::Kseg0.mapped());
}
#[test]
fn context_holds_the_faulting_page_where_the_r3000_puts_it() {
let mut cp0 = Cp0::new(0);
cp0.context = 0x1234_5678;
cp0.set_context_vpn(0x0abc_d123);
assert_eq!(cp0.context & 0xffe0_0000, 0x1234_5678 & 0xffe0_0000);
assert_eq!((cp0.context >> 2) & 0x7ffff, 0x0abcd);
assert_eq!(cp0.context & 3, 0);
}
#[test]
fn random_counts_down_and_never_reaches_the_wired_entries() {
let mut cp0 = Cp0::new(0);
assert_eq!(cp0.random, 63);
let mut seen_low = u32::MAX;
for _ in 0..200 {
cp0.tick_random();
seen_low = seen_low.min(cp0.random);
}
assert_eq!(seen_low, TLB_WIRED, "TLBWR must never pick entries 0..7");
assert!(cp0.random <= 63);
}
#[test]
fn a_tlb_entry_matches_on_asid_unless_it_is_global() {
let mut tlb = Tlb::new();
tlb.set_entry(
0,
TlbEntry {
hi: 0x1000_0000 | (5 << 6),
lo: 0x0020_0000 | (1 << 9) | (1 << 10),
},
);
assert!(matches!(tlb.lookup(0x1000_0abc, 5), Lookup::Hit { .. }));
assert_eq!(tlb.lookup(0x1000_0abc, 6), Lookup::Miss);
tlb.set_entry(
0,
TlbEntry {
hi: 0x1000_0000 | (5 << 6),
lo: 0x0020_0000 | (1 << 8) | (1 << 9) | (1 << 10),
},
);
assert!(matches!(tlb.lookup(0x1000_0abc, 6), Lookup::Hit { .. }));
}
#[test]
fn an_entry_with_v_clear_is_invalid_rather_than_a_miss() {
let mut tlb = Tlb::new();
tlb.set_entry(
3,
TlbEntry {
hi: 0x2000_0000,
lo: 0x0030_0000 | (1 << 8),
},
);
assert_eq!(tlb.lookup(0x2000_0000, 0), Lookup::Invalid);
}
#[test]
fn two_matching_entries_are_a_conflict() {
let mut tlb = Tlb::new();
let e = TlbEntry {
hi: 0x3000_0000,
lo: 0x0040_0000 | (1 << 8) | (1 << 9),
};
tlb.set_entry(1, e);
tlb.set_entry(2, e);
assert_eq!(tlb.lookup(0x3000_0000, 0), Lookup::Conflict);
}
#[test]
fn probe_finds_the_index_tlbp_should_report() {
let mut tlb = Tlb::new();
tlb.set_entry(
17,
TlbEntry {
hi: 0x4000_0000 | (9 << 6),
lo: (1 << 9),
},
);
assert_eq!(tlb.probe(0x4000_0000 | (9 << 6)), Some(17));
assert_eq!(tlb.probe(0x4000_0000 | (8 << 6)), None);
}
#[test]
fn cop0_is_usable_from_kernel_mode_without_cu0() {
let mut cp0 = Cp0::new(0);
cp0.status = 0; assert!(cp0.coprocessor_usable(0));
assert!(!cp0.coprocessor_usable(2));
cp0.status = status::KUC;
assert!(!cp0.coprocessor_usable(0));
cp0.status = status::KUC | status::CU0;
assert!(cp0.coprocessor_usable(0));
}
#[test]
fn the_hardware_interrupt_bits_come_from_the_pins() {
let mut cp0 = Cp0::new(0);
cp0.cause = 1 << 8;
let merged = cp0.cause_with(0b000_100);
assert_eq!(merged & cause_bits::IP, (1 << 8) | (1 << 12));
assert_eq!(cp0.ready_interrupts(0b000_100), 0);
cp0.status = status::IM;
assert_eq!(cp0.ready_interrupts(0b000_100), 0b0001_0001);
}
#[test]
fn reverse_endianness_applies_only_to_user_mode() {
let mut cp0 = Cp0::new(0);
cp0.status = status::RE; assert_eq!(cp0.data_endian(Endian::Big), Endian::Big);
cp0.status = status::RE | status::KUC;
assert_eq!(cp0.data_endian(Endian::Big), Endian::Little);
cp0.status = status::KUC;
assert_eq!(cp0.data_endian(Endian::Big), Endian::Big);
}
#[test]
fn the_lines_hold_six_pins_and_a_reset_request() {
let lines = Lines::default();
assert_eq!(lines.hw(), 0);
lines.set_hw(0, true);
lines.set_hw(5, true);
assert_eq!(lines.hw(), 0b10_0001);
lines.set_hw(0, false);
assert_eq!(lines.hw(), 0b10_0000);
lines.set_hw(6, true);
assert_eq!(lines.hw(), 0b10_0000);
assert!(!lines.take_reset_request());
lines.request_reset();
assert!(lines.take_reset_request());
assert!(!lines.take_reset_request());
}
}