use crate::common::{Asid, PAGE_SHIFT, Ppn, Vpn};
const VPN_BITS_PER_LEVEL: u32 = 9;
const PTE_R: u64 = 1 << 1;
const PTE_W: u64 = 1 << 2;
const PTE_X: u64 = 1 << 3;
const PTE_U: u64 = 1 << 4;
const PTE_G: u64 = 1 << 5;
const PTE_D: u64 = 1 << 7;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PageSize {
Kib4,
Mib2,
Gib1,
Gib512,
Tib256,
}
impl PageSize {
pub const ALL: [Self; 5] = [Self::Kib4, Self::Mib2, Self::Gib1, Self::Gib512, Self::Tib256];
#[must_use]
pub const fn from_level(level: u32) -> Option<Self> {
match level {
0 => Some(Self::Kib4),
1 => Some(Self::Mib2),
2 => Some(Self::Gib1),
3 => Some(Self::Gib512),
4 => Some(Self::Tib256),
_ => None,
}
}
const fn vpn_offset_bits(self) -> u32 {
let level = match self {
Self::Kib4 => 0,
Self::Mib2 => 1,
Self::Gib1 => 2,
Self::Gib512 => 3,
Self::Tib256 => 4,
};
level * VPN_BITS_PER_LEVEL
}
const fn vpn_offset_mask(self) -> u64 {
(1u64 << self.vpn_offset_bits()) - 1
}
#[must_use]
pub const fn bytes(self) -> u64 {
1u64 << (PAGE_SHIFT as u32 + self.vpn_offset_bits())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Mapping {
vpn: Vpn,
ppn: Ppn,
size: PageSize,
pte: u64,
asid: Asid,
}
impl Mapping {
const fn new(vpn: Vpn, ppn: Ppn, pte: u64, asid: Asid, size: PageSize) -> Self {
let offset = vpn.val() & size.vpn_offset_mask();
Self {
vpn: Vpn::new(vpn.val() - offset),
ppn: Ppn::new(ppn.val() - offset),
size,
pte,
asid,
}
}
#[must_use]
pub const fn size(&self) -> PageSize {
self.size
}
const fn is_global(&self) -> bool {
self.pte & PTE_G != 0
}
const fn covers(&self, vpn: Vpn) -> bool {
vpn.val() & !self.size.vpn_offset_mask() == self.vpn.val()
}
const fn translates(&self, vpn: Vpn, asid: Asid) -> bool {
self.covers(vpn) && (self.is_global() || self.asid.val() == asid.val())
}
const fn hit(self, vpn: Vpn) -> TlbHit {
let pte = self.pte;
TlbHit {
ppn: Ppn::new(self.ppn.val() + (vpn.val() - self.vpn.val())),
r: pte & PTE_R != 0,
w: pte & PTE_W != 0,
x: pte & PTE_X != 0,
u: pte & PTE_U != 0,
d: pte & PTE_D != 0,
mapping: self,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct TlbHit {
pub ppn: Ppn,
pub r: bool,
pub w: bool,
pub x: bool,
pub u: bool,
pub d: bool,
pub mapping: Mapping,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TlbGeometry {
pub entries: usize,
pub ways: usize,
}
#[derive(Clone, Copy, Debug, Default)]
struct Slot {
mapping: Option<Mapping>,
last_use: u64,
}
#[derive(Debug)]
pub struct Tlb {
slots: Vec<Slot>,
ways: usize,
set_mask: usize,
next_use: u64,
}
impl Tlb {
#[must_use]
pub fn new(geometry: TlbGeometry) -> Self {
let TlbGeometry { entries, ways } = geometry;
if entries == 0 {
return Self { slots: Vec::new(), ways: 0, set_mask: 0, next_use: 0 };
}
let ways = if ways == 0 || ways >= entries { entries } else { ways };
let num_sets = entries.div_ceil(ways).next_power_of_two();
Self {
slots: vec![Slot::default(); num_sets * ways],
ways,
set_mask: num_sets - 1,
next_use: 0,
}
}
const fn set_of(&self, vpn: Vpn, size: PageSize) -> std::ops::Range<usize> {
let page_number = vpn.val() >> size.vpn_offset_bits();
let base = ((page_number as usize) & self.set_mask) * self.ways;
base..base + self.ways
}
fn find(&self, vpn: Vpn, asid: Asid) -> Option<usize> {
if self.slots.is_empty() {
return None;
}
PageSize::ALL.into_iter().find_map(|size| {
self.set_of(vpn, size).find(|&i| {
self.slots[i].mapping.is_some_and(|m| m.size == size && m.translates(vpn, asid))
})
})
}
pub fn lookup(&mut self, vpn: Vpn, asid: Asid) -> Option<TlbHit> {
let index = self.find(vpn, asid)?;
self.next_use += 1;
self.slots[index].last_use = self.next_use;
self.slots[index].mapping.map(|m| m.hit(vpn))
}
#[must_use]
pub fn peek(&self, vpn: Vpn, asid: Asid) -> Option<TlbHit> {
self.find(vpn, asid).and_then(|index| self.slots[index].mapping).map(|m| m.hit(vpn))
}
pub fn insert(&mut self, vpn: Vpn, ppn: Ppn, pte: u64, asid: Asid, size: PageSize) {
self.insert_mapping(Mapping::new(vpn, ppn, pte, asid, size));
}
pub fn insert_mapping(&mut self, mapping: Mapping) {
if self.slots.is_empty() {
return;
}
let set = self.set_of(mapping.vpn, mapping.size);
let same_page =
|m: Mapping| m.vpn == mapping.vpn && m.size == mapping.size && m.asid == mapping.asid;
let index = set
.clone()
.find(|&i| self.slots[i].mapping.is_some_and(same_page))
.or_else(|| set.clone().find(|&i| self.slots[i].mapping.is_none()))
.or_else(|| set.clone().min_by_key(|&i| self.slots[i].last_use))
.unwrap_or(set.start);
self.next_use += 1;
self.slots[index] = Slot { mapping: Some(mapping), last_use: self.next_use };
}
fn remove_if(&mut self, doomed: impl Fn(&Mapping) -> bool) {
for slot in &mut self.slots {
if slot.mapping.as_ref().is_some_and(&doomed) {
slot.mapping = None;
}
}
}
pub fn invalidate(&mut self, vpn: Vpn) {
self.remove_if(|m| m.covers(vpn));
}
pub fn flush(&mut self) {
self.remove_if(|_| true);
}
pub fn flush_vaddr(&mut self, vpn: Vpn) {
self.remove_if(|m| m.covers(vpn));
}
pub fn flush_asid(&mut self, asid: Asid) {
self.remove_if(|m| !m.is_global() && m.asid == asid);
}
pub fn flush_vaddr_asid(&mut self, vpn: Vpn, asid: Asid) {
self.remove_if(|m| m.covers(vpn) && !m.is_global() && m.asid == asid);
}
}