use crate::arch::csr::Csrs;
use crate::arch::pmp::{Pmp, PmpResult};
use crate::arch::translation::{PteUpdate, TranslationResult};
use crate::common::{AccessType, Asid, PAGE_SHIFT, PhysAddr, Ppn, VPN_MASK, VirtAddr, Vpn};
use crate::isa::csr::{
MENVCFG_ADUE, MSTATUS_MXR, MSTATUS_SUM, SATP_ASID_MASK, SATP_ASID_SHIFT, SATP_PPN_MASK,
};
use crate::isa::privileged::PagingMode;
use crate::isa::privileged::{PrivilegeMode, Trap};
use crate::uarch::mmu::Mmu;
use crate::uarch::mmu::tlb::PageSize;
const PTE_VALID_BIT: u64 = 1;
const PTE_READ_BIT: u64 = 1 << 1;
const PTE_WRITE_BIT: u64 = 1 << 2;
const PTE_EXEC_BIT: u64 = 1 << 3;
const PTE_USER_BIT: u64 = 1 << 4;
const PTE_ACCESSED_BIT: u64 = 1 << 6;
const PTE_DIRTY_BIT: u64 = 1 << 7;
const PTE_PPN_SHIFT: u64 = 10;
const VPN_BITS_PER_LEVEL: u64 = 9;
const VPN_ENTRY_MASK: u64 = 0x1FF;
const PTE_SIZE: u64 = 8;
const PTE_RESERVED_BITS: u64 = u64::MAX << 54;
const PTE_POINTER_RESERVED_BITS: u64 = PTE_DIRTY_BIT | PTE_ACCESSED_BIT | PTE_USER_BIT;
const PTE_UPDATE_CYCLES: u64 = 10;
#[derive(Clone, Copy, Debug)]
struct PageTableEntry(u64);
impl PageTableEntry {
const fn new(val: u64) -> Self {
Self(val)
}
const fn raw(self) -> u64 {
self.0
}
const fn is_valid(self) -> bool {
self.0 & PTE_VALID_BIT != 0
}
const fn can_read(self) -> bool {
self.0 & PTE_READ_BIT != 0
}
const fn can_write(self) -> bool {
self.0 & PTE_WRITE_BIT != 0
}
const fn can_exec(self) -> bool {
self.0 & PTE_EXEC_BIT != 0
}
const fn is_user(self) -> bool {
self.0 & PTE_USER_BIT != 0
}
const fn is_accessed(self) -> bool {
self.0 & PTE_ACCESSED_BIT != 0
}
const fn is_dirty(self) -> bool {
self.0 & PTE_DIRTY_BIT != 0
}
const fn ppn(self) -> Ppn {
Ppn::new((self.0 >> PTE_PPN_SHIFT) & SATP_PPN_MASK)
}
const fn ppn_raw(self) -> u64 {
(self.0 >> PTE_PPN_SHIFT) & SATP_PPN_MASK
}
const fn is_pointer(self) -> bool {
!self.can_read() && !self.can_write() && !self.can_exec()
}
const fn has_reserved_bits(self) -> bool {
self.0 & PTE_RESERVED_BITS != 0
|| (self.is_pointer() && self.0 & PTE_POINTER_RESERVED_BITS != 0)
}
const fn with_accessed(self) -> Self {
Self(self.0 | PTE_ACCESSED_BIT)
}
}
#[derive(Clone, Copy, Debug)]
pub struct WalkRequest {
pub access: AccessType,
pub privilege: PrivilegeMode,
pub mode: PagingMode,
}
#[derive(Clone, Debug)]
pub struct WalkState {
pub vaddr: VirtAddr,
pub access: AccessType,
pub privilege: PrivilegeMode,
pub level: u32,
pub ppn_raw: u64,
pub asid: Asid,
pub cycles: u64,
}
#[derive(Clone, Debug)]
pub enum WalkStep {
Done(TranslationResult),
NeedPte {
pte_addr: PhysAddr,
state: WalkState,
},
}
pub fn start_walk(
request: WalkRequest,
vaddr: VirtAddr,
csrs: &Csrs,
pmp: Option<&Pmp>,
) -> WalkStep {
let satp = csrs.satp;
let ppn_raw = satp & SATP_PPN_MASK;
let asid = Asid::new(((satp >> SATP_ASID_SHIFT) & SATP_ASID_MASK) as u16);
let state = WalkState {
vaddr,
access: request.access,
privilege: request.privilege,
level: (request.mode.levels() as u32) - 1,
ppn_raw,
asid,
cycles: 0,
};
request_pte(state, pmp)
}
pub fn continue_walk(
mut state: WalkState,
raw_pte: u64,
mmu: &mut Mmu,
csrs: &Csrs,
pmp: Option<&Pmp>,
bus_transit_cycles: u64,
) -> WalkStep {
state.cycles += bus_transit_cycles;
let pte = PageTableEntry::new(raw_pte);
if !pte.is_valid() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
if pte.has_reserved_bits() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
if pte.is_pointer() {
if state.level == 0 {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
state.level -= 1;
state.ppn_raw = pte.ppn_raw();
return request_pte(state, pmp);
}
if pte.can_write() && !pte.can_read() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
if state.level > 0 {
let ppn_mask = (1u64 << (u64::from(state.level) * VPN_BITS_PER_LEVEL)) - 1;
if (pte.ppn_raw() & ppn_mask) != 0 {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
}
if check_permissions(pte, state.access, state.privilege, csrs).is_err() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
if csrs.menvcfg & MENVCFG_ADUE == 0 {
if !pte.is_accessed() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
if state.access == AccessType::Write && !pte.is_dirty() {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
}
}
let (accessed_update, dirty_update) = access_bit_updates(&state, pte);
if accessed_update.is_some() || dirty_update.is_some() {
state.cycles += PTE_UPDATE_CYCLES;
}
let final_ppn = pte.ppn();
let vpn_shift = PAGE_SHIFT + u64::from(state.level) * VPN_BITS_PER_LEVEL;
let offset_mask = (1u64 << vpn_shift) - 1;
let final_paddr = final_ppn.to_addr() | (state.vaddr.val() & offset_mask);
let specific_4kb_ppn = Ppn::new(final_paddr >> PAGE_SHIFT);
let vpn = Vpn::new((state.vaddr.val() >> PAGE_SHIFT) & VPN_MASK);
let pte_raw = pte.raw();
let Some(size) = PageSize::from_level(state.level) else {
return WalkStep::Done(TranslationResult::fault(
Trap::page_fault(state.access, state.vaddr.val()),
state.cycles,
));
};
if state.access == AccessType::Fetch {
mmu.itlb.insert(vpn, specific_4kb_ppn, pte_raw, state.asid, size);
} else {
mmu.dtlb.insert(vpn, specific_4kb_ppn, pte_raw, state.asid, size);
}
mmu.l2_tlb.insert(vpn, specific_4kb_ppn, pte_raw, state.asid, size);
let result = TranslationResult {
paddr: PhysAddr::new(final_paddr),
cycles: state.cycles,
trap: None,
dirty_update,
accessed_update,
};
WalkStep::Done(result)
}
fn request_pte(state: WalkState, pmp: Option<&Pmp>) -> WalkStep {
let vpn_shift = PAGE_SHIFT + u64::from(state.level) * VPN_BITS_PER_LEVEL;
let vpn_i = (state.vaddr.val() >> vpn_shift) & VPN_ENTRY_MASK;
let pte_addr = (state.ppn_raw << PAGE_SHIFT) + (vpn_i * PTE_SIZE);
if let Some(pmp_unit) = pmp {
let pmp_result = pmp_unit.check(pte_addr, 8, true, false, false, false);
if pmp_result != PmpResult::Allow {
let trap = match state.access {
AccessType::Fetch => Trap::InstructionAccessFault(state.vaddr.val()),
AccessType::Read => Trap::LoadAccessFault(state.vaddr.val()),
AccessType::Write => Trap::StoreAccessFault(state.vaddr.val()),
};
return WalkStep::Done(TranslationResult::fault(trap, state.cycles));
}
}
WalkStep::NeedPte { pte_addr: PhysAddr::new(pte_addr), state }
}
fn check_permissions(
pte: PageTableEntry,
access: AccessType,
privilege: PrivilegeMode,
csrs: &Csrs,
) -> Result<(), ()> {
if access == AccessType::Write && !pte.can_write() {
return Err(());
}
if access == AccessType::Fetch && !pte.can_exec() {
return Err(());
}
let mxr = csrs.mstatus & MSTATUS_MXR != 0;
if access == AccessType::Read && !(pte.can_read() || (pte.can_exec() && mxr)) {
return Err(());
}
if privilege == PrivilegeMode::User && !pte.is_user() {
return Err(());
}
if privilege == PrivilegeMode::Supervisor && pte.is_user() {
let sum = csrs.mstatus & MSTATUS_SUM != 0;
if !sum {
return Err(());
}
if access == AccessType::Fetch {
return Err(());
}
}
Ok(())
}
fn access_bit_updates(
state: &WalkState,
pte: PageTableEntry,
) -> (Option<PteUpdate>, Option<PteUpdate>) {
let vpn_shift = PAGE_SHIFT + u64::from(state.level) * VPN_BITS_PER_LEVEL;
let vpn_i = (state.vaddr.val() >> vpn_shift) & VPN_ENTRY_MASK;
let pte_addr = PhysAddr::new((state.ppn_raw << PAGE_SHIFT) + (vpn_i * PTE_SIZE));
let accessed = (!pte.is_accessed()).then_some(PteUpdate {
pte_addr,
walked_pte: pte.raw(),
set_bits: PTE_ACCESSED_BIT,
});
let dirty = (state.access == AccessType::Write && !pte.is_dirty()).then_some(PteUpdate {
pte_addr,
walked_pte: pte.with_accessed().raw(),
set_bits: PTE_ACCESSED_BIT | PTE_DIRTY_BIT,
});
(accessed, dirty)
}