pub mod pte {
pub const PRESENT: u32 = 1 << 0;
pub const WRITABLE: u32 = 1 << 1;
pub const USER: u32 = 1 << 2;
pub const PWT: u32 = 1 << 3;
pub const PCD: u32 = 1 << 4;
pub const ACCESSED: u32 = 1 << 5;
pub const DIRTY: u32 = 1 << 6;
pub const FRAME: u32 = 0xffff_f000;
}
pub mod pf {
pub const PROTECTION: u32 = 1 << 0;
pub const WRITE: u32 = 1 << 1;
pub const USER: u32 = 1 << 2;
}
pub const TLB_ENTRIES: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TlbEntry {
pub page: u32,
pub frame: u32,
pub user: bool,
pub writable: bool,
pub dirty: bool,
}
impl TlbEntry {
pub const EMPTY: u32 = u32::MAX;
#[must_use]
pub const fn empty() -> TlbEntry {
TlbEntry {
page: TlbEntry::EMPTY,
frame: 0,
user: false,
writable: false,
dirty: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Tlb {
entries: [TlbEntry; TLB_ENTRIES],
generation: u64,
}
impl Tlb {
#[must_use]
pub const fn new() -> Tlb {
Tlb {
entries: [TlbEntry::empty(); TLB_ENTRIES],
generation: 0,
}
}
pub const fn flush(&mut self) {
let mut i = 0;
while i < TLB_ENTRIES {
self.entries[i] = TlbEntry::empty();
i += 1;
}
}
pub const fn invalidate(&mut self, linear: u32) {
let page = linear >> 12;
let slot = (page as usize) % TLB_ENTRIES;
if self.entries[slot].page == page {
self.entries[slot] = TlbEntry::empty();
}
}
pub const fn sync(&mut self, generation: u64) {
if self.generation != generation {
self.generation = generation;
self.flush();
}
}
#[inline]
#[must_use]
pub const fn get(&self, linear: u32) -> Option<TlbEntry> {
let page = linear >> 12;
let entry = self.entries[(page as usize) % TLB_ENTRIES];
if entry.page == page {
Some(entry)
} else {
None
}
}
pub const fn insert(&mut self, entry: TlbEntry) {
self.entries[(entry.page as usize) % TLB_ENTRIES] = entry;
}
#[must_use]
pub fn occupancy(&self) -> usize {
self.entries
.iter()
.filter(|e| e.page != TlbEntry::EMPTY)
.count()
}
}
impl Default for Tlb {
fn default() -> Self {
Tlb::new()
}
}
#[must_use]
pub const fn fault_code(present: bool, write: bool, user: bool) -> u32 {
let mut code = 0;
if present {
code |= pf::PROTECTION;
}
if write {
code |= pf::WRITE;
}
if user {
code |= pf::USER;
}
code
}
#[must_use]
pub const fn write_allowed(writable: bool, user_access: bool, wp: bool) -> bool {
if writable {
return true;
}
!user_access && !wp
}
use super::exec::{Ex, Exec, Fault, VEC_PF};
use super::prot::cr0;
impl Exec<'_> {
pub(super) fn translate(&mut self, linear: u32, write: bool, user: bool) -> Ex<u32> {
let generation = self.mem.generation();
self.state.tlb.sync(generation);
let wp = self.variant().has_486_extras() && self.state.sys.cr0 & cr0::WP != 0;
if let Some(entry) = self.state.tlb.get(linear) {
let allowed =
(!user || entry.user) && (!write || write_allowed(entry.writable, user, wp));
if allowed && (!write || entry.dirty) {
return Ok(entry.frame | (linear & 0xfff));
}
if !allowed {
self.state.sys.cr2 = linear;
return Err(Fault::coded(VEC_PF, fault_code(true, write, user)));
}
}
let dir_base = self.state.sys.cr3 & pte::FRAME;
let dir_addr = dir_base.wrapping_add((linear >> 22) * 4);
let dir = self.phys_read(dir_addr, 4);
if dir & pte::PRESENT == 0 {
self.state.sys.cr2 = linear;
return Err(Fault::coded(VEC_PF, fault_code(false, write, user)));
}
let table_addr = (dir & pte::FRAME).wrapping_add(((linear >> 12) & 0x3ff) * 4);
let table = self.phys_read(table_addr, 4);
if table & pte::PRESENT == 0 {
self.state.sys.cr2 = linear;
return Err(Fault::coded(VEC_PF, fault_code(false, write, user)));
}
let user_ok = dir & pte::USER != 0 && table & pte::USER != 0;
let writable = dir & pte::WRITABLE != 0 && table & pte::WRITABLE != 0;
if (user && !user_ok) || (write && !write_allowed(writable, user, wp)) {
self.state.sys.cr2 = linear;
return Err(Fault::coded(VEC_PF, fault_code(true, write, user)));
}
if dir & pte::ACCESSED == 0 {
self.phys_write(dir_addr, 4, dir | pte::ACCESSED);
}
let mut table_bits = table;
let mut want = pte::ACCESSED;
if write {
want |= pte::DIRTY;
}
if table_bits & want != want {
table_bits |= want;
self.phys_write(table_addr, 4, table_bits);
}
self.state.tlb.insert(TlbEntry {
page: linear >> 12,
frame: table_bits & pte::FRAME,
user: user_ok,
writable,
dirty: table_bits & pte::DIRTY != 0,
});
Ok((table_bits & pte::FRAME) | (linear & 0xfff))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_error_code_names_the_three_things_a_handler_branches_on() {
assert_eq!(fault_code(false, false, false), 0);
assert_eq!(fault_code(true, false, false), 1);
assert_eq!(fault_code(false, true, false), 2);
assert_eq!(fault_code(true, true, true), 7);
}
#[test]
fn the_kernel_may_write_a_read_only_page_only_without_write_protect() {
assert!(write_allowed(false, false, false));
assert!(!write_allowed(false, false, true));
assert!(!write_allowed(false, true, false));
assert!(write_allowed(true, true, true));
}
#[test]
fn a_lookup_misses_after_a_flush_and_after_an_invalidate() {
let mut tlb = Tlb::new();
let entry = TlbEntry {
page: 0x1_2345,
frame: 0x9000_0000,
user: true,
writable: true,
dirty: true,
};
tlb.insert(entry);
assert_eq!(tlb.get(0x1234_5678).map(|e| e.frame), Some(0x9000_0000));
assert!(
tlb.get(0x1234_5678 + (TLB_ENTRIES as u32) * 0x1000)
.is_none()
);
tlb.invalidate(0x1234_5000);
assert!(tlb.get(0x1234_5678).is_none());
tlb.insert(entry);
assert_eq!(tlb.occupancy(), 1);
tlb.flush();
assert_eq!(tlb.occupancy(), 0);
}
#[test]
fn a_topology_change_invalidates_every_translation() {
let mut tlb = Tlb::new();
tlb.sync(7);
tlb.insert(TlbEntry {
page: 4,
frame: 0x4000,
user: false,
writable: true,
dirty: false,
});
assert!(tlb.get(0x4000).is_some());
tlb.sync(7);
assert!(tlb.get(0x4000).is_some());
tlb.sync(8);
assert!(tlb.get(0x4000).is_none());
}
}