use crate::arch::csr::Csrs;
use crate::arch::translation::TranslationResult;
use crate::common::{AccessType, PhysAddr, VirtAddr};
use crate::isa::csr;
use crate::isa::privileged::{PagingMode, PrivilegeMode, Trap};
use crate::sim::memory::GlobalMemory;
use crate::soc::uncore::Uncore;
use crate::tests::support::harness::TestContext;
use crate::uarch::mmu::tlb::TlbGeometry;
use crate::uarch::mmu::{Mmu, TranslateOutcome};
fn translate_sync(
mmu: &mut Mmu,
vaddr: VirtAddr,
access: AccessType,
privilege: PrivilegeMode,
csrs: &Csrs,
uncore: &Uncore,
) -> TranslationResult {
let mut outcome = mmu.translate_async(vaddr, access, privilege, csrs, None);
loop {
match outcome {
TranslateOutcome::Ready(r) => return r,
TranslateOutcome::NeedPte { pte_addr, state } => {
let raw_pte = uncore.memory.read(pte_addr, 8).unwrap_or(0);
let cycles = uncore.bus.calculate_transit_time(8);
outcome = mmu.continue_walk(state, raw_pte, csrs, None, cycles);
}
}
}
}
const ROOT_PPN: u64 = 0x80000; const MEM_BASE: u64 = 0x8000_0000;
const MEM_SIZE: usize = 0x1000_0000;
const V: u64 = 1 << 0;
const R: u64 = 1 << 1;
const W: u64 = 1 << 2;
const X: u64 = 1 << 3;
const U: u64 = 1 << 4;
#[allow(dead_code)]
const G: u64 = 1 << 5;
const A: u64 = 1 << 6;
const D: u64 = 1 << 7;
fn make_pte(ppn: u64, perms: u64) -> u64 {
(ppn << 10) | perms | V
}
fn setup_mmu() -> (Mmu, Csrs, TestContext) {
let mmu = Mmu::new(
TlbGeometry { entries: 4, ways: 0 },
TlbGeometry { entries: 4, ways: 4 },
4,
PagingMode::Sv57,
); let mut csrs = Csrs::default();
csrs.write(csr::MENVCFG, csr::MENVCFG_ADUE);
let satp_val = (csr::SATP_MODE_SV39 << 60) | ROOT_PPN;
csrs.write(csr::SATP, satp_val);
csrs.write(csr::SSTATUS, (1 << 18) | (1 << 19));
let tc = TestContext::new().with_memory(MEM_SIZE, MEM_BASE);
(mmu, csrs, tc)
}
fn write_pte(memory: &mut GlobalMemory, base_ppn: u64, vpn_index: u64, pte: u64) {
let addr = PhysAddr::new((base_ppn << 12) + (vpn_index * 8));
assert!(
memory.ram().is_some_and(|ram| ram.contains(addr, 8)),
"PTE address must lie inside RAM"
);
memory.load(addr, &pte.to_le_bytes());
}
#[test]
fn bare_mode_bypass() {
let (mut mmu, mut csrs, tc) = setup_mmu();
csrs.write(csr::SATP, 0);
let vaddr = VirtAddr::new(0x1234_5678);
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Read,
PrivilegeMode::Supervisor,
&csrs,
&tc.cpu().uncore,
);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), 0x1234_5678);
}
#[test]
fn machine_mode_bypass() {
let (mut mmu, csrs, tc) = setup_mmu();
let vaddr = VirtAddr::new(0x1234_5678);
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Read,
PrivilegeMode::Machine,
&csrs,
&tc.cpu().uncore,
);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), 0x1234_5678);
}
#[test]
fn sv39_4kb_page_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4000_1234);
let l2_idx = (0x4000_1234 >> 30) & 0x1FF; let l1_idx = (0x4000_1234 >> 21) & 0x1FF; let l0_idx = (0x4000_1234 >> 12) & 0x1FF;
let l1_table_ppn = ROOT_PPN + 1;
let l0_table_ppn = ROOT_PPN + 2;
let target_ppn = ROOT_PPN + 10;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(l1_table_ppn, 0)); write_pte(&mut uncore.memory, l1_table_ppn, l1_idx, make_pte(l0_table_ppn, 0));
write_pte(&mut uncore.memory, l0_table_ppn, l0_idx, make_pte(target_ppn, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), (target_ppn << 12) | 0x234);
}
#[test]
fn sv39_megapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4020_0000);
let l2_idx = (0x4020_0000 >> 30) & 0x1FF; let l1_idx = (0x4020_0000 >> 21) & 0x1FF;
let l1_table_ppn = ROOT_PPN + 1;
let target_ppn = ROOT_PPN + 0x200;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(l1_table_ppn, 0));
write_pte(&mut uncore.memory, l1_table_ppn, l1_idx, make_pte(target_ppn, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), target_ppn << 12);
}
#[test]
fn sv39_gigapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000); let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), target_ppn << 12);
}
#[test]
fn invalid_pte_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x1000);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn pointer_at_level_0_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x1000);
let l2_idx = 0;
let l1_idx = 0;
let l0_idx = 1;
let l1_ppn = ROOT_PPN + 1;
let l0_ppn = ROOT_PPN + 2;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(l1_ppn, 0));
write_pte(&mut uncore.memory, l1_ppn, l1_idx, make_pte(l0_ppn, 0));
write_pte(&mut uncore.memory, l0_ppn, l0_idx, make_pte(ROOT_PPN + 10, 0));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn misaligned_superpage_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4000_0000);
let l2_idx = (0x4000_0000 >> 30) & 0x1FF;
let l1_idx = 0;
let l1_ppn = ROOT_PPN + 1;
let misaligned_target_ppn = (ROOT_PPN + 100) | 0x1;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(l1_ppn, 0));
write_pte(
&mut uncore.memory,
l1_ppn,
l1_idx,
make_pte(misaligned_target_ppn, R | W | X | A | D),
);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn write_to_clean_page_sets_dirty() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
let pte_val = make_pte(target_ppn, R | W | X | A);
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, pte_val);
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Write,
PrivilegeMode::Supervisor,
&csrs,
uncore,
);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
let upd = res.dirty_update.expect("a store to a clean page sets D when it retires");
assert_eq!(upd.set_bits, A | D);
}
#[test]
fn read_from_unaccessed_page_sets_accessed() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
let pte_val = make_pte(target_ppn, R | W | X);
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, pte_val);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
let upd = res.accessed_update.expect("the walker sets A on an unaccessed page");
assert_eq!(upd.set_bits, A);
assert!(res.dirty_update.is_none(), "a load never sets D");
}
#[test]
fn write_permission_check() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | A | D));
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Write,
PrivilegeMode::Supervisor,
&csrs,
uncore,
);
assert!(matches!(res.trap, Some(Trap::StorePageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn execute_permission_check() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | W | A | D));
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Fetch,
PrivilegeMode::Supervisor,
&csrs,
uncore,
);
assert!(matches!(res.trap, Some(Trap::InstructionPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn user_cannot_access_supervisor_page() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | W | X | A | D));
let res = translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::User, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn supervisor_access_user_page_needs_sum() {
let (mut mmu, mut csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | W | X | U | A | D));
csrs.write(csr::SSTATUS, 0);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
csrs.write(csr::SSTATUS, 1 << 18);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
}
#[test]
fn supervisor_cannot_fetch_user_page() {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l2_idx = (0x8000_0000 >> 30) & 0x1FF;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l2_idx, make_pte(target_ppn, R | X | U | A | D));
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Fetch,
PrivilegeMode::Supervisor,
&csrs,
uncore,
);
assert!(matches!(res.trap, Some(Trap::InstructionPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn non_canonical_address_faults() {
let (mut mmu, csrs, tc) = setup_mmu();
let non_canon = VirtAddr::new(1 << 38);
let res = translate_sync(
&mut mmu,
non_canon,
AccessType::Read,
PrivilegeMode::Supervisor,
&csrs,
&tc.cpu().uncore,
);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
fn setup_mmu_with_mode(mode: u64) -> (Mmu, Csrs, TestContext) {
let mmu = Mmu::new(
TlbGeometry { entries: 4, ways: 0 },
TlbGeometry { entries: 4, ways: 4 },
4,
PagingMode::Sv57,
);
let mut csrs = Csrs::default();
csrs.write(csr::MENVCFG, csr::MENVCFG_ADUE);
let satp_val = (mode << 60) | ROOT_PPN;
csrs.write(csr::SATP, satp_val);
csrs.write(csr::SSTATUS, (1 << 18) | (1 << 19));
let tc = TestContext::new().with_memory(MEM_SIZE, MEM_BASE);
(mmu, csrs, tc)
}
fn vpn_index(va: u64, level: u32) -> u64 {
(va >> (12 + 9 * level)) & 0x1FF
}
#[test]
fn sv48_4kb_page_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4000_1234);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l0 = vpn_index(vaddr.val(), 0);
let l2_table = ROOT_PPN + 1;
let l1_table = ROOT_PPN + 2;
let l0_table = ROOT_PPN + 3;
let target = ROOT_PPN + 10;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(l0_table, 0));
write_pte(&mut uncore.memory, l0_table, l0, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), (target << 12) | 0x234);
}
#[test]
fn sv48_megapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4020_0000);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l2_table = ROOT_PPN + 1;
let l1_table = ROOT_PPN + 2;
let target = ROOT_PPN + 0x200;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), target << 12);
}
#[test]
fn sv48_gigapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x8000_0000);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l2_table = ROOT_PPN + 1;
let target = ROOT_PPN + 0x40000;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), target << 12);
}
#[test]
fn sv48_terapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x10_0000_1000);
let l3 = vpn_index(vaddr.val(), 3);
let target = 1u64 << 27;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
let offset_mask = (1u64 << (12 + 9 * 3)) - 1;
assert_eq!(res.paddr.val(), (target << 12) | (vaddr.val() & offset_mask));
}
#[test]
fn sv48_misaligned_superpage_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4020_0000);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l2_table = ROOT_PPN + 1;
let l1_table = ROOT_PPN + 2;
let misaligned = (ROOT_PPN + 100) | 0x1;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(misaligned, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv48_pointer_at_level_0_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x1000);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l0 = vpn_index(vaddr.val(), 0);
let l2_table = ROOT_PPN + 1;
let l1_table = ROOT_PPN + 2;
let l0_table = ROOT_PPN + 3;
write_pte(&mut uncore.memory, ROOT_PPN, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(l0_table, 0));
write_pte(&mut uncore.memory, l0_table, l0, make_pte(ROOT_PPN + 10, 0));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv48_non_canonical_address_faults() {
let (mut mmu, csrs, tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let non_canon_low = VirtAddr::new(1u64 << 47);
let res = translate_sync(
&mut mmu,
non_canon_low,
AccessType::Read,
PrivilegeMode::Supervisor,
&csrs,
&tc.cpu().uncore,
);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
let non_canon_hi = VirtAddr::new(1u64 << 50);
let res = translate_sync(
&mut mmu,
non_canon_hi,
AccessType::Read,
PrivilegeMode::Supervisor,
&csrs,
&tc.cpu().uncore,
);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv48_invalid_pte_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV48);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x1000);
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv57_4kb_page_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV57);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4000_1234);
let l4 = vpn_index(vaddr.val(), 4);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l0 = vpn_index(vaddr.val(), 0);
let l3_table = ROOT_PPN + 1;
let l2_table = ROOT_PPN + 2;
let l1_table = ROOT_PPN + 3;
let l0_table = ROOT_PPN + 4;
let target = ROOT_PPN + 20;
write_pte(&mut uncore.memory, ROOT_PPN, l4, make_pte(l3_table, 0));
write_pte(&mut uncore.memory, l3_table, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(l0_table, 0));
write_pte(&mut uncore.memory, l0_table, l0, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
assert_eq!(res.paddr.val(), (target << 12) | 0x234);
}
#[test]
fn sv57_petapage_walk() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV57);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x10_0000_1000);
let l4 = vpn_index(vaddr.val(), 4);
let target = 1u64 << 36;
write_pte(&mut uncore.memory, ROOT_PPN, l4, make_pte(target, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(res.trap.is_none(), "Trap: {:?}", res.trap);
let offset_mask = (1u64 << (12 + 9 * 4)) - 1;
assert_eq!(res.paddr.val(), (target << 12) | (vaddr.val() & offset_mask));
}
#[test]
fn sv57_misaligned_superpage_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV57);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x10_0000_1000);
let l4 = vpn_index(vaddr.val(), 4);
let misaligned = (1u64 << 36) | 0x1;
write_pte(&mut uncore.memory, ROOT_PPN, l4, make_pte(misaligned, R | W | X | A | D));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv57_pointer_at_level_0_causes_fault() {
let (mut mmu, csrs, mut tc) = setup_mmu_with_mode(csr::SATP_MODE_SV57);
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x1000);
let l4 = vpn_index(vaddr.val(), 4);
let l3 = vpn_index(vaddr.val(), 3);
let l2 = vpn_index(vaddr.val(), 2);
let l1 = vpn_index(vaddr.val(), 1);
let l0 = vpn_index(vaddr.val(), 0);
let l3_table = ROOT_PPN + 1;
let l2_table = ROOT_PPN + 2;
let l1_table = ROOT_PPN + 3;
let l0_table = ROOT_PPN + 4;
write_pte(&mut uncore.memory, ROOT_PPN, l4, make_pte(l3_table, 0));
write_pte(&mut uncore.memory, l3_table, l3, make_pte(l2_table, 0));
write_pte(&mut uncore.memory, l2_table, l2, make_pte(l1_table, 0));
write_pte(&mut uncore.memory, l1_table, l1, make_pte(l0_table, 0));
write_pte(&mut uncore.memory, l0_table, l0, make_pte(ROOT_PPN + 10, 0));
let res =
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn sv57_non_canonical_address_faults() {
let (mut mmu, csrs, tc) = setup_mmu_with_mode(csr::SATP_MODE_SV57);
let non_canon = VirtAddr::new(1u64 << 56);
let res = translate_sync(
&mut mmu,
non_canon,
AccessType::Read,
PrivilegeMode::Supervisor,
&csrs,
&tc.cpu().uncore,
);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
fn walk_with(pointer_extra: u64, leaf_extra: u64) -> TranslationResult {
let (mut mmu, csrs, mut tc) = setup_mmu();
let uncore = &mut tc.cpu_mut().uncore;
let vaddr = VirtAddr::new(0x4000_0000);
let l1_ppn = ROOT_PPN + 1;
let leaf_ppn = ROOT_PPN + 0x200;
write_pte(
&mut uncore.memory,
ROOT_PPN,
(0x4000_0000 >> 30) & 0x1FF,
make_pte(l1_ppn, 0) | pointer_extra,
);
write_pte(&mut uncore.memory, l1_ppn, 0, make_pte(leaf_ppn, R | W | X | A | D) | leaf_extra);
translate_sync(&mut mmu, vaddr, AccessType::Read, PrivilegeMode::Supervisor, &csrs, uncore)
}
#[test]
fn a_walk_through_clean_ptes_succeeds() {
assert!(walk_with(0, 0).trap.is_none());
}
#[test]
fn a_leaf_with_a_reserved_high_bit_set_raises_a_page_fault() {
for bit in [54, 60, 61, 62, 63] {
let res = walk_with(0, 1 << bit);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "bit {bit}: {:?}", res.trap);
}
}
#[test]
fn a_pointer_with_a_reserved_high_bit_set_raises_a_page_fault() {
let res = walk_with(1 << 54, 0);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "Trap: {:?}", res.trap);
}
#[test]
fn a_pointer_with_d_a_or_u_set_raises_a_page_fault() {
for bit in [D, A, U] {
let res = walk_with(bit, 0);
assert!(matches!(res.trap, Some(Trap::LoadPageFault(_))), "{bit:#x}: {:?}", res.trap);
}
}
#[test]
fn with_adue_clear_a_write_to_a_clean_page_raises_a_page_fault() {
let (mut mmu, mut csrs, mut tc) = setup_mmu();
csrs.write(csr::MENVCFG, 0);
let uncore = &mut tc.cpu_mut().uncore;
let target_ppn = ROOT_PPN + 0x40000;
write_pte(
&mut uncore.memory,
ROOT_PPN,
(0x8000_0000 >> 30) & 0x1FF,
make_pte(target_ppn, R | W | X | A),
);
let vaddr = VirtAddr::new(0x8000_0000);
let res = translate_sync(
&mut mmu,
vaddr,
AccessType::Write,
PrivilegeMode::Supervisor,
&csrs,
uncore,
);
assert!(matches!(res.trap, Some(Trap::StorePageFault(_))), "Trap: {:?}", res.trap);
}