use crate::core::cpu::CpuCore;
use crate::core::memory::{AddressBus, BusFaultKind};
use super::{MmuFault, MmuFaultCause, MmuFaultKind, MmuResult};
fn buserr(address: u32) -> MmuFault {
MmuFault {
kind: MmuFaultKind::BusError,
address,
cause: MmuFaultCause::TableWalkBusError,
}
}
fn access_fault(address: u32) -> MmuFault {
access_fault_cause(address, MmuFaultCause::PageFault)
}
fn access_fault_cause(address: u32, cause: MmuFaultCause) -> MmuFault {
MmuFault {
kind: MmuFaultKind::AccessLevelViolation,
address,
cause,
}
}
fn config_fault(address: u32) -> MmuFault {
MmuFault {
kind: MmuFaultKind::ConfigurationError,
address,
cause: MmuFaultCause::PageFault,
}
}
fn read_u32_phys<B: AddressBus>(bus: &mut B, addr: u32) -> MmuResult<u32> {
bus.try_read_long(addr).map_err(|f| {
if matches!(f.kind, BusFaultKind::BusError) {
buserr(f.address)
} else {
buserr(addr)
}
})
}
pub fn translate<B: AddressBus>(
cpu: &mut CpuCore,
bus: &mut B,
logical: u32,
write: bool,
supervisor: bool,
instruction: bool,
) -> MmuResult<u32> {
if !cpu.pmmu_enabled || !cpu.has_pmmu {
return Ok(logical);
}
let (supervisor, instruction) = match cpu.mmu_fc_override {
Some(fc) => ((fc & 4) != 0, (fc & 3) == 2),
None => (supervisor, instruction),
};
if let Some(phys) = super::ttr::check_transparent_translation(cpu, logical, write, instruction)
{
return Ok(phys);
}
if cpu.is_040() || cpu.is_060() {
return translate_040(cpu, bus, logical, write, supervisor, instruction);
}
translate_030(cpu, bus, logical, write, supervisor, instruction)
}
struct Desc030 {
dt: u32,
field: u32,
wp: bool,
sup: bool,
}
fn read_desc_030<B: AddressBus>(
bus: &mut B,
base: u32,
index: u32,
long: bool,
) -> MmuResult<Desc030> {
if long {
let at = base.wrapping_add(index * 8);
let hi = read_u32_phys(bus, at)?;
let lo = read_u32_phys(bus, at.wrapping_add(4))?;
Ok(Desc030 {
dt: hi & 3,
field: lo,
wp: hi & 0x0000_0004 != 0,
sup: hi & 0x0000_0100 != 0,
})
} else {
let at = base.wrapping_add(index * 4);
let e = read_u32_phys(bus, at)?;
Ok(Desc030 {
dt: e & 3,
field: e,
wp: e & 0x0000_0004 != 0,
sup: false,
})
}
}
enum Walk030End {
Page { field: u32, consumed: u32 },
Invalid { indirect: bool },
LevelStop,
BusErr { at: u32 },
Config,
}
struct Walk030 {
end: Walk030End,
wp: bool,
sup: bool,
levels: u32,
desc_addr: u32,
}
fn walk_030<B: AddressBus>(
cpu: &CpuCore,
bus: &mut B,
logical: u32,
fc: u32,
max_levels: u32,
) -> Walk030 {
let supervisor = fc & 4 != 0;
let use_srp = (cpu.mmu_tc & 0x0200_0000) != 0 && supervisor;
let (root_aptr, root_limit) = if use_srp {
(cpu.mmu_srp_aptr, cpu.mmu_srp_limit)
} else {
(cpu.mmu_crp_aptr, cpu.mmu_crp_limit)
};
let fcl = (cpu.mmu_tc & 0x0100_0000) != 0;
let is = (cpu.mmu_tc >> 16) & 0xF;
let ti = [
(cpu.mmu_tc >> 12) & 0xF, (cpu.mmu_tc >> 8) & 0xF, (cpu.mmu_tc >> 4) & 0xF, cpu.mmu_tc & 0xF, ];
#[inline]
fn top_index(addr: u32, left_shift: u32, bits: u32) -> u32 {
addr.wrapping_shl(left_shift) >> (32 - bits)
}
let mut w = Walk030 {
end: Walk030End::Config,
wp: false,
sup: false,
levels: 0,
desc_addr: root_aptr,
};
let mut long = match root_limit & 3 {
2 => false,
3 => true,
_ => return w,
};
let mut ptr = root_aptr & 0xFFFF_FFF0;
let mut consumed = is;
let n_addr = ti.iter().take_while(|&&b| b != 0).count();
let n = n_addr + fcl as usize;
if n == 0 {
return w;
}
for li in 0..n {
if w.levels >= max_levels {
w.end = Walk030End::LevelStop;
return w;
}
let index = if fcl && li == 0 {
fc
} else {
let bits = ti[li - fcl as usize];
let idx = top_index(logical, consumed, bits);
consumed += bits;
idx
};
w.desc_addr = ptr.wrapping_add(index * if long { 8 } else { 4 });
let d = match read_desc_030(bus, ptr, index, long) {
Ok(d) => d,
Err(f) => {
w.end = Walk030End::BusErr { at: f.address };
return w;
}
};
w.levels += 1;
match d.dt {
0 => {
w.end = Walk030End::Invalid { indirect: false };
return w;
}
1 => {
w.wp |= d.wp;
w.sup |= d.sup;
w.end = Walk030End::Page {
field: d.field,
consumed,
};
return w;
}
_ => {
if li + 1 < n {
w.wp |= d.wp;
w.sup |= d.sup;
ptr = d.field & 0xFFFF_FFF0;
long = d.dt == 3;
} else {
w.desc_addr = d.field & 0xFFFF_FFFC;
let t = match read_desc_030(bus, w.desc_addr, 0, d.dt == 3) {
Ok(t) => t,
Err(f) => {
w.end = Walk030End::BusErr { at: f.address };
return w;
}
};
w.wp |= t.wp;
w.sup |= t.sup;
w.end = if t.dt == 1 {
Walk030End::Page {
field: t.field,
consumed,
}
} else {
Walk030End::Invalid { indirect: true }
};
return w;
}
}
}
}
unreachable!("the last level always terminates or indirects");
}
#[inline]
fn low_bits(addr: u32, shift: u32) -> u32 {
if shift >= 32 {
0
} else {
addr.wrapping_shl(shift) >> shift
}
}
fn translate_030<B: AddressBus>(
cpu: &mut CpuCore,
bus: &mut B,
logical: u32,
write: bool,
supervisor: bool,
instruction: bool,
) -> MmuResult<u32> {
let fc = if supervisor {
if instruction { 6 } else { 5 }
} else if instruction {
2
} else {
1
};
let w = walk_030(cpu, bus, logical, fc, u32::MAX);
match w.end {
Walk030End::Page { field, consumed } => {
if w.sup && !supervisor {
return Err(access_fault_cause(
logical,
MmuFaultCause::SupervisorProtect,
));
}
if write && w.wp {
return Err(access_fault_cause(logical, MmuFaultCause::WriteProtect));
}
let base = field & 0xFFFF_FF00;
Ok(low_bits(logical, consumed).wrapping_add(base))
}
Walk030End::Invalid { indirect: false } => Err(access_fault(logical)),
Walk030End::Invalid { indirect: true } => {
Err(access_fault_cause(logical, MmuFaultCause::Indirect))
}
Walk030End::BusErr { at } => Err(buserr(at)),
Walk030End::Config => Err(config_fault(logical)),
Walk030End::LevelStop => Err(config_fault(logical)),
}
}
pub(crate) fn ptest_030<B: AddressBus>(
cpu: &CpuCore,
bus: &mut B,
logical: u32,
fc: u32,
write: bool,
level: u32,
) -> (u16, u32) {
if level == 0 {
let fc8 = fc as u8;
let t = super::ttr::ttr_matches(cpu.mmu_tt0, logical, fc8, write)
|| super::ttr::ttr_matches(cpu.mmu_tt1, logical, fc8, write);
return (if t { 0x0040 } else { 0 }, 0);
}
let w = walk_030(cpu, bus, logical, fc, level);
let n = (w.levels & 7) as u16;
let mut sr = n;
if w.wp {
sr |= 0x0800; }
if w.sup && fc & 4 == 0 {
sr |= 0x2000; }
match w.end {
Walk030End::Page { .. } | Walk030End::LevelStop => {}
Walk030End::Invalid { .. } => sr |= 0x0400, Walk030End::BusErr { .. } | Walk030End::Config => sr |= 0x8000, }
(sr, w.desc_addr)
}
fn translate_040<B: AddressBus>(
cpu: &mut CpuCore,
bus: &mut B,
logical: u32,
write: bool,
supervisor: bool,
_instruction: bool,
) -> MmuResult<u32> {
let invalid = |logical: u32, cause: MmuFaultCause| -> MmuResult<u32> {
Err(access_fault_cause(logical, cause))
};
let page_bits = if cpu.mmu_tc & 0x0000_4000 != 0 {
13
} else {
12
};
let page_mask = (1u32 << page_bits) - 1;
let page_frame = logical >> page_bits;
if let Some(phys_page) = cpu.atc.lookup(page_frame, supervisor, write) {
return Ok(phys_page | (logical & page_mask));
}
let root = if supervisor {
cpu.mmu_srp_aptr
} else {
cpu.mmu_crp_aptr
};
let root_idx = (logical >> 25) & 0x7F;
let root_desc = read_u32_phys(bus, (root & 0xFFFF_FE00).wrapping_add(root_idx * 4))?;
if root_desc & 3 < 2 {
return invalid(logical, MmuFaultCause::PointerA);
}
let ptr_table = root_desc & 0xFFFF_FE00;
let ptr_idx = (logical >> 18) & 0x7F;
let ptr_desc = read_u32_phys(bus, ptr_table.wrapping_add(ptr_idx * 4))?;
if ptr_desc & 3 < 2 {
return invalid(logical, MmuFaultCause::PointerB);
}
let (page_table_mask, page_idx) = if page_bits == 13 {
(0xFFFF_FF80u32, (logical >> 13) & 0x1F)
} else {
(0xFFFF_FF00u32, (logical >> 12) & 0x3F)
};
let page_table = ptr_desc & page_table_mask;
let mut page_desc = read_u32_phys(bus, page_table.wrapping_add(page_idx * 4))?;
let was_indirect = page_desc & 3 == 2;
if was_indirect {
page_desc = read_u32_phys(bus, page_desc & 0xFFFF_FFFC)?;
}
if page_desc & 3 == 0 {
return invalid(
logical,
if was_indirect {
MmuFaultCause::Indirect
} else {
MmuFaultCause::PageFault
},
);
}
let write_protected = (root_desc | ptr_desc | page_desc) & 0x0000_0004 != 0;
let supervisor_only = page_desc & 0x0000_0080 != 0;
if write && write_protected {
return Err(access_fault_cause(logical, MmuFaultCause::WriteProtect));
}
if !supervisor && supervisor_only {
return Err(access_fault_cause(
logical,
MmuFaultCause::SupervisorProtect,
));
}
let phys_page = page_desc & !page_mask;
cpu.atc.insert(
page_frame,
supervisor,
phys_page,
write_protected,
supervisor_only,
);
Ok(phys_page | (logical & page_mask))
}