use super::csr::{Csrs, Priv, status};
use super::isa::Xlen;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Access {
Fetch,
Load,
Store,
}
impl Access {
#[inline]
const fn slot(self) -> usize {
match self {
Access::Fetch => 0,
Access::Load => 1,
Access::Store => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fault {
Page,
Access,
}
pub mod pte {
pub const V: u64 = 1 << 0;
pub const R: u64 = 1 << 1;
pub const W: u64 = 1 << 2;
pub const X: u64 = 1 << 3;
pub const U: u64 = 1 << 4;
pub const G: u64 = 1 << 5;
pub const A: u64 = 1 << 6;
pub const D: u64 = 1 << 7;
}
pub const PAGE_BITS: u32 = 12;
pub const PAGE_SIZE: u64 = 1 << PAGE_BITS;
pub trait ReadPte {
fn read_pte(&mut self, addr: u64, bytes: u32) -> Option<u64>;
}
pub trait PhysMem: ReadPte {
fn write_pte(&mut self, addr: u64, bytes: u32, value: u64) -> Option<()>;
}
struct Scheme {
levels: u32,
vpn_bits: u32,
pte_bytes: u32,
va_bits: u32,
ppn_bits: u32,
}
const SV32: Scheme = Scheme {
levels: 2,
vpn_bits: 10,
pte_bytes: 4,
va_bits: 32,
ppn_bits: 22,
};
const SV39: Scheme = Scheme {
levels: 3,
vpn_bits: 9,
pte_bytes: 8,
va_bits: 39,
ppn_bits: 44,
};
const fn scheme(xlen: Xlen) -> &'static Scheme {
match xlen {
Xlen::Rv32 => &SV32,
Xlen::Rv64 => &SV39,
}
}
const fn mask(bits: u32) -> u64 {
(1u64 << bits) - 1
}
#[must_use]
pub fn translation_active(csrs: &Csrs, mode: Priv) -> bool {
if mode == Priv::Machine {
return false;
}
match csrs.xlen {
Xlen::Rv32 => csrs.satp >> 31 != 0,
Xlen::Rv64 => csrs.satp >> 60 == 8,
}
}
#[must_use]
pub fn asid(csrs: &Csrs) -> u64 {
match csrs.xlen {
Xlen::Rv32 => (csrs.satp >> 22) & 0x1ff,
Xlen::Rv64 => (csrs.satp >> 44) & 0xffff,
}
}
fn root(csrs: &Csrs) -> u64 {
(csrs.satp & mask(scheme(csrs.xlen).ppn_bits)) << PAGE_BITS
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Walked {
phys: u64,
update: Option<(u64, u32, u64)>,
}
pub fn translate<M: PhysMem>(
csrs: &Csrs,
mem: &mut M,
addr: u64,
kind: Access,
mode: Priv,
) -> Result<u64, Fault> {
let walked = walk(csrs, mem, addr, Some(kind), mode)?;
if let Some((at, bytes, value)) = walked.update {
if !pmp_allows(csrs, at, u64::from(bytes), Access::Store, mode) {
return Err(Fault::Access);
}
mem.write_pte(at, bytes, value).ok_or(Fault::Access)?;
}
Ok(walked.phys)
}
pub fn translate_debug<M: ReadPte>(
csrs: &Csrs,
mem: &mut M,
addr: u64,
mode: Priv,
) -> Result<u64, Fault> {
Ok(walk(csrs, mem, addr, None, mode)?.phys)
}
fn walk<M: ReadPte>(
csrs: &Csrs,
mem: &mut M,
addr: u64,
kind: Option<Access>,
mode: Priv,
) -> Result<Walked, Fault> {
if !translation_active(csrs, mode) {
return Ok(Walked {
phys: addr,
update: None,
});
}
let s = scheme(csrs.xlen);
if csrs.xlen == Xlen::Rv64 {
let shift = 64 - s.va_bits;
if ((addr as i64) << shift) >> shift != addr as i64 {
return Err(Fault::Page);
}
}
let mut table = root(csrs);
let mut level = s.levels;
loop {
level -= 1;
let shift = PAGE_BITS + s.vpn_bits * level;
let index = (addr >> shift) & ((1 << s.vpn_bits) - 1);
let entry_addr = table + index * u64::from(s.pte_bytes);
if kind.is_some()
&& !pmp_allows(csrs, entry_addr, u64::from(s.pte_bytes), Access::Load, mode)
{
return Err(Fault::Access);
}
let pte = mem.read_pte(entry_addr, s.pte_bytes).ok_or(Fault::Access)?;
if pte & pte::V == 0 || (pte & pte::R == 0 && pte & pte::W != 0) {
return Err(Fault::Page);
}
if csrs.xlen == Xlen::Rv64 && pte >> (10 + s.ppn_bits) != 0 {
return Err(Fault::Page);
}
let ppn = (pte >> 10) & mask(s.ppn_bits);
if pte & (pte::R | pte::X) == 0 {
if level == 0 {
return Err(Fault::Page);
}
table = ppn << PAGE_BITS;
continue;
}
if let Some(kind) = kind
&& !permitted(csrs, pte, kind, mode)
{
return Err(Fault::Page);
}
if level > 0 && ppn & ((1 << (s.vpn_bits * level)) - 1) != 0 {
return Err(Fault::Page);
}
let update = kind.and_then(|kind| {
let need = pte::A | if kind == Access::Store { pte::D } else { 0 };
if pte & need == need {
None
} else {
Some((entry_addr, s.pte_bytes, pte | need))
}
});
let low_bits = PAGE_BITS + s.vpn_bits * level;
let phys = (ppn << PAGE_BITS) & !((1u64 << low_bits) - 1);
return Ok(Walked {
phys: phys | (addr & ((1u64 << low_bits) - 1)),
update,
});
}
}
fn permitted(csrs: &Csrs, pte: u64, kind: Access, mode: Priv) -> bool {
let user_page = pte & pte::U != 0;
match mode {
Priv::User => {
if !user_page {
return false;
}
}
Priv::Supervisor => {
if user_page {
if kind == Access::Fetch || csrs.mstatus & status::SUM == 0 {
return false;
}
}
}
Priv::Machine => {}
}
match kind {
Access::Fetch => pte & pte::X != 0,
Access::Load => pte & pte::R != 0 || (csrs.mstatus & status::MXR != 0 && pte & pte::X != 0),
Access::Store => pte & pte::W != 0,
}
}
#[must_use]
pub fn pmp_allows(csrs: &Csrs, addr: u64, len: u64, kind: Access, mode: Priv) -> bool {
let last = addr.wrapping_add(len.saturating_sub(1));
let mut matched = None;
for i in 0..csrs.pmp_count {
let cfg = csrs.pmpcfg[i];
let a = (cfg >> 3) & 3;
if a == 0 {
continue;
}
let (lo, hi) = match a {
1 => {
let lo = if i == 0 { 0 } else { csrs.pmpaddr[i - 1] << 2 };
(lo, csrs.pmpaddr[i] << 2)
}
2 => {
let base = csrs.pmpaddr[i] << 2;
(base, base + 4)
}
_ => napot(csrs.pmpaddr[i]),
};
if hi <= lo {
continue;
}
if addr >= lo && addr < hi {
if last >= hi {
return false;
}
matched = Some(cfg);
break;
}
if last >= lo && last < hi {
return false;
}
}
match matched {
Some(cfg) => {
let locked = cfg & 0x80 != 0;
if mode == Priv::Machine && !locked {
return true;
}
let bit = match kind {
Access::Load => 0b001,
Access::Store => 0b010,
Access::Fetch => 0b100,
};
cfg & bit != 0
}
None => mode == Priv::Machine || csrs.pmp_count == 0,
}
}
fn napot(addr: u64) -> (u64, u64) {
let ones = (!addr).trailing_zeros();
if ones >= 62 {
return (0, u64::MAX);
}
let size_bits = ones + 3;
let base = (addr & !((1u64 << ones) - 1)) << 2;
(base, base + (1u64 << size_bits))
}
pub const TLB_ENTRIES: usize = 256;
#[derive(Debug, Clone, Copy, Default)]
struct Entry {
tag: u64,
base: u64,
valid: bool,
}
#[derive(Debug)]
pub struct Tlb {
slots: [[Entry; TLB_ENTRIES]; 3],
hits: u64,
misses: u64,
}
impl Default for Tlb {
fn default() -> Self {
Tlb::new()
}
}
impl Tlb {
#[must_use]
pub fn new() -> Tlb {
Tlb {
slots: [[Entry::default(); TLB_ENTRIES]; 3],
hits: 0,
misses: 0,
}
}
pub fn flush(&mut self) {
self.slots = [[Entry::default(); TLB_ENTRIES]; 3];
}
#[must_use]
pub fn stats(&self) -> (u64, u64) {
(self.hits, self.misses)
}
#[inline]
fn tag(vpn: u64, asid: u64, mode: Priv, generation: u64) -> u64 {
(generation << 40) ^ (vpn.wrapping_mul(0x9e37_79b9_7f4a_7c15)) ^ (asid << 2) ^ mode.bits()
}
#[inline]
pub fn lookup(
&mut self,
kind: Access,
vpn: u64,
asid: u64,
mode: Priv,
generation: u64,
) -> Option<u64> {
let tag = Self::tag(vpn, asid, mode, generation);
let slot = &self.slots[kind.slot()][(vpn as usize) & (TLB_ENTRIES - 1)];
if slot.valid && slot.tag == tag {
self.hits += 1;
Some(slot.base)
} else {
self.misses += 1;
None
}
}
#[inline]
pub fn insert(
&mut self,
kind: Access,
vpn: u64,
asid: u64,
mode: Priv,
generation: u64,
base: u64,
) {
self.slots[kind.slot()][(vpn as usize) & (TLB_ENTRIES - 1)] = Entry {
tag: Self::tag(vpn, asid, mode, generation),
base,
valid: true,
};
}
}
#[cfg(test)]
mod tests {
use super::super::csr::{Extensions, PMP_ENTRIES, num};
use super::*;
use alloc::vec;
use alloc::vec::Vec;
struct Ram(Vec<u8>);
impl ReadPte for Ram {
fn read_pte(&mut self, addr: u64, bytes: u32) -> Option<u64> {
let at = addr as usize;
let end = at + bytes as usize;
let slice = self.0.get(at..end)?;
let mut v = 0u64;
for (i, b) in slice.iter().enumerate() {
v |= u64::from(*b) << (8 * i);
}
Some(v)
}
}
impl PhysMem for Ram {
fn write_pte(&mut self, addr: u64, bytes: u32, value: u64) -> Option<()> {
let at = addr as usize;
for i in 0..bytes as usize {
*self.0.get_mut(at + i)? = (value >> (8 * i)) as u8;
}
Some(())
}
}
fn sv39_machine(perms: u64) -> (Csrs, Ram) {
let mut ram = Ram(vec![0; 0x8000]);
ram.write_pte(0x1000, 8, ((0x2000 >> 12) << 10) | pte::V)
.unwrap();
ram.write_pte(0x2000, 8, ((0x3000 >> 12) << 10) | pte::V)
.unwrap();
ram.write_pte(0x3000, 8, ((0x4000 >> 12) << 10) | pte::V | perms)
.unwrap();
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, 0);
csrs.satp = (8 << 60) | (0x1000 >> 12);
csrs.priv_mode = Priv::Supervisor;
(csrs, ram)
}
#[test]
fn machine_mode_is_never_translated() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W | pte::A | pte::D);
assert_eq!(
translate(&csrs, &mut ram, 0x1234, Access::Load, Priv::Machine),
Ok(0x1234)
);
}
#[test]
fn a_three_level_walk_finds_the_leaf() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W | pte::A | pte::D);
assert_eq!(
translate(&csrs, &mut ram, 0x0123, Access::Load, Priv::Supervisor),
Ok(0x4123)
);
}
#[test]
fn permissions_are_enforced_per_access_type() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::A);
assert!(translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor).is_ok());
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Store, Priv::Supervisor),
Err(Fault::Page)
);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Fetch, Priv::Supervisor),
Err(Fault::Page)
);
}
#[test]
fn the_user_bit_and_sum_decide_supervisor_access() {
let (mut csrs, mut ram) = sv39_machine(pte::R | pte::U | pte::A);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor),
Err(Fault::Page),
"a supervisor needs SUM to read a user page"
);
csrs.mstatus |= status::SUM;
assert!(translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor).is_ok());
let (mut csrs, mut ram) = sv39_machine(pte::X | pte::U | pte::A);
csrs.mstatus |= status::SUM;
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Fetch, Priv::Supervisor),
Err(Fault::Page)
);
let (csrs, mut ram) = sv39_machine(pte::R | pte::A);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::User),
Err(Fault::Page)
);
}
#[test]
fn mxr_makes_an_execute_only_page_readable() {
let (mut csrs, mut ram) = sv39_machine(pte::X | pte::A);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor),
Err(Fault::Page)
);
csrs.mstatus |= status::MXR;
assert!(translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor).is_ok());
}
#[test]
fn the_accessed_and_dirty_bits_are_set_by_the_walk() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W);
translate(&csrs, &mut ram, 0, Access::Store, Priv::Supervisor).unwrap();
let leaf = ram.read_pte(0x3000, 8).unwrap();
assert_ne!(leaf & pte::A, 0);
assert_ne!(leaf & pte::D, 0);
let (csrs, mut ram) = sv39_machine(pte::R | pte::W);
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor).unwrap();
let leaf = ram.read_pte(0x3000, 8).unwrap();
assert_ne!(leaf & pte::A, 0);
assert_eq!(leaf & pte::D, 0);
}
#[test]
fn a_debug_walk_sets_no_accessed_or_dirty_bit() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W);
let before = ram.read_pte(0x3000, 8).unwrap();
assert_eq!(before & (pte::A | pte::D), 0, "the fixture starts clean");
assert_eq!(
translate_debug(&csrs, &mut ram, 0, Priv::Supervisor),
Ok(0x4000)
);
assert_eq!(
ram.read_pte(0x3000, 8).unwrap(),
before,
"a debug walk wrote to the page table"
);
}
#[test]
fn a_debug_walk_resolves_a_page_the_permissions_would_hide() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W | pte::U | pte::A | pte::D);
assert_eq!(
translate(&csrs, &mut ram, 0x40, Access::Load, Priv::Supervisor),
Err(Fault::Page)
);
assert_eq!(
translate_debug(&csrs, &mut ram, 0x40, Priv::Supervisor),
Ok(0x4040)
);
}
#[test]
fn a_debug_walk_of_an_unmapped_address_still_faults() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::W | pte::A | pte::D);
assert_eq!(
translate_debug(&csrs, &mut ram, 0x4000_0000, Priv::Supervisor),
Err(Fault::Page)
);
}
#[test]
fn a_non_canonical_sv39_address_faults_without_a_walk() {
let (csrs, mut ram) = sv39_machine(pte::R | pte::A);
assert_eq!(
translate(
&csrs,
&mut ram,
0x0000_8000_0000_0000,
Access::Load,
Priv::Supervisor
),
Err(Fault::Page)
);
assert_eq!(
translate(&csrs, &mut ram, !0xfffu64, Access::Load, Priv::Supervisor),
Err(Fault::Page)
);
}
#[test]
fn a_misaligned_superpage_faults() {
let mut ram = Ram(vec![0; 0x8000]);
ram.write_pte(0x1000, 8, ((0x2000 >> 12) << 10) | pte::V)
.unwrap();
ram.write_pte(0x2000, 8, ((0x4001) << 10) | pte::V | pte::R | pte::A)
.unwrap();
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, 0);
csrs.satp = (8 << 60) | (0x1000 >> 12);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor),
Err(Fault::Page)
);
}
#[test]
fn a_superpage_carries_the_low_virtual_bits_through() {
let mut ram = Ram(vec![0; 0x8000]);
ram.write_pte(0x1000, 8, ((0x2000 >> 12) << 10) | pte::V)
.unwrap();
ram.write_pte(
0x2000,
8,
((0x40_0000u64 >> 12) << 10) | pte::V | pte::R | pte::A,
)
.unwrap();
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, 0);
csrs.satp = (8 << 60) | (0x1000 >> 12);
assert_eq!(
translate(&csrs, &mut ram, 0x1_2345, Access::Load, Priv::Supervisor),
Ok(0x41_2345)
);
}
#[test]
fn the_reserved_write_without_read_encoding_faults() {
let (_, mut ram) = sv39_machine(0);
ram.write_pte(0x3000, 8, ((0x4000 >> 12) << 10) | pte::V | pte::W)
.unwrap();
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, 0);
csrs.satp = (8 << 60) | (0x1000 >> 12);
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor),
Err(Fault::Page)
);
}
#[test]
fn pmp_lets_machine_mode_through_when_nothing_is_configured() {
let csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
assert!(pmp_allows(
&csrs,
0x8000_0000,
4,
Access::Load,
Priv::Machine
));
assert!(
!pmp_allows(&csrs, 0x8000_0000, 4, Access::Load, Priv::Supervisor),
"an unmatched supervisor access is refused"
);
}
#[test]
fn a_napot_entry_covers_its_declared_range() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
csrs.pmpaddr[0] = (0x1000 >> 2) | 1;
csrs.pmpcfg[0] = 0b0001_1001; assert!(pmp_allows(&csrs, 0x1000, 4, Access::Load, Priv::Supervisor));
assert!(pmp_allows(&csrs, 0x100c, 4, Access::Load, Priv::Supervisor));
assert!(!pmp_allows(
&csrs,
0x1010,
4,
Access::Load,
Priv::Supervisor
));
assert!(!pmp_allows(
&csrs,
0x1000,
4,
Access::Store,
Priv::Supervisor
));
assert!(!pmp_allows(
&csrs,
0x100e,
4,
Access::Load,
Priv::Supervisor
));
}
#[test]
fn the_all_ones_napot_entry_covers_everything() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
csrs.pmpaddr[0] = u64::MAX >> 10;
csrs.pmpcfg[0] = 0b0001_1111;
for mode in [Priv::User, Priv::Supervisor, Priv::Machine] {
assert!(pmp_allows(&csrs, 0x8000_0000, 8, Access::Store, mode));
}
}
#[test]
fn a_tor_entry_uses_the_previous_address_as_its_base() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
csrs.pmpaddr[0] = 0x1000 >> 2;
csrs.pmpaddr[1] = 0x2000 >> 2;
csrs.pmpcfg[1] = 0b0000_1001; assert!(!pmp_allows(&csrs, 0x0fff, 1, Access::Load, Priv::User));
assert!(pmp_allows(&csrs, 0x1000, 1, Access::Load, Priv::User));
assert!(!pmp_allows(&csrs, 0x2000, 1, Access::Load, Priv::User));
}
#[test]
fn a_locked_entry_constrains_machine_mode_too() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
csrs.pmpaddr[0] = u64::MAX >> 10;
csrs.pmpcfg[0] = 0x80 | 0b0001_1001; assert!(pmp_allows(&csrs, 0x1000, 4, Access::Load, Priv::Machine));
assert!(!pmp_allows(&csrs, 0x1000, 4, Access::Store, Priv::Machine));
}
#[test]
fn the_tlb_hits_only_on_an_exact_tag() {
let mut tlb = Tlb::new();
tlb.insert(Access::Load, 0x1234, 7, Priv::Supervisor, 3, 0x4000);
assert_eq!(
tlb.lookup(Access::Load, 0x1234, 7, Priv::Supervisor, 3),
Some(0x4000)
);
assert_eq!(
tlb.lookup(Access::Store, 0x1234, 7, Priv::Supervisor, 3),
None
);
assert_eq!(
tlb.lookup(Access::Load, 0x1234, 8, Priv::Supervisor, 3),
None
);
assert_eq!(tlb.lookup(Access::Load, 0x1234, 7, Priv::User, 3), None);
assert_eq!(
tlb.lookup(Access::Load, 0x1234, 7, Priv::Supervisor, 4),
None
);
tlb.flush();
assert_eq!(
tlb.lookup(Access::Load, 0x1234, 7, Priv::Supervisor, 3),
None
);
}
#[test]
fn a_non_zero_asid_does_not_move_the_root_page_table() {
let (mut csrs, mut ram) = sv39_machine(pte::R | pte::A);
let bare = translate(&csrs, &mut ram, 0x0123, Access::Load, Priv::Supervisor);
assert_eq!(bare, Ok(0x4123));
csrs.satp |= 0xffff << 44;
assert_eq!(root(&csrs), 0x1000, "ASID is not part of the root address");
assert_eq!(
translate(&csrs, &mut ram, 0x0123, Access::Load, Priv::Supervisor),
bare,
"an all-ones ASID must not move the page tables"
);
}
#[test]
fn every_asid_bit_sticks_and_none_of_them_reaches_the_ppn() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
csrs.write(num::SATP, (8 << 60) | (0xffff << 44) | 0x81fcb, 0)
.unwrap();
assert_eq!(asid(&csrs), 0xffff);
assert_eq!(root(&csrs), 0x81fcb << 12);
let mut csrs = Csrs::new(Xlen::Rv32, Extensions::GC, 0, PMP_ENTRIES);
csrs.write(num::SATP, (1 << 31) | (0x1ff << 22) | 0x123, 0)
.unwrap();
assert_eq!(asid(&csrs), 0x1ff);
assert_eq!(root(&csrs), 0x123 << 12);
}
#[test]
fn a_reserved_pte_bit_faults_rather_than_moving_the_page() {
for bit in [54, 61, 62, 63] {
let (csrs, mut ram) = sv39_machine(pte::R | pte::A);
let leaf = ram.read_pte(0x3000, 8).unwrap();
ram.write_pte(0x3000, 8, leaf | (1 << bit)).unwrap();
assert_eq!(
translate(&csrs, &mut ram, 0, Access::Load, Priv::Supervisor),
Err(Fault::Page),
"bit {bit} is reserved"
);
}
}
#[test]
fn a_satp_write_invalidates_the_whole_tlb_by_generation() {
let mut csrs = Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES);
let before = csrs.translation_gen;
csrs.write(num::SATP, 8 << 60, 0).unwrap();
assert_ne!(csrs.translation_gen, before);
}
}