pub mod pte {
pub const PRESENT: u64 = 1 << 0;
pub const WRITABLE: u64 = 1 << 1;
pub const USER: u64 = 1 << 2;
pub const PWT: u64 = 1 << 3;
pub const PCD: u64 = 1 << 4;
pub const ACCESSED: u64 = 1 << 5;
pub const DIRTY: u64 = 1 << 6;
pub const PAGE_SIZE: u64 = 1 << 7;
pub const GLOBAL: u64 = 1 << 8;
pub const NX: u64 = 1 << 63;
pub const FRAME32: u64 = 0xffff_f000;
pub const FRAME64: u64 = 0x000f_ffff_ffff_f000;
pub const FRAME_4M: u64 = 0xffc0_0000;
}
pub mod pf {
pub const PROTECTION: u32 = 1 << 0;
pub const WRITE: u32 = 1 << 1;
pub const USER: u32 = 1 << 2;
pub const RESERVED: u32 = 1 << 3;
pub const FETCH: u32 = 1 << 4;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Mode {
Off,
Legacy,
Pae,
Ia32e,
}
impl Mode {
#[must_use]
pub const fn levels(self) -> u8 {
match self {
Mode::Off => 0,
Mode::Legacy => 2,
Mode::Pae => 3,
Mode::Ia32e => 4,
}
}
#[must_use]
pub const fn entry_bytes(self) -> u8 {
match self {
Mode::Legacy => 4,
_ => 8,
}
}
#[must_use]
pub const fn top_index_bits(self) -> u32 {
match self {
Mode::Off => 0,
Mode::Legacy => 10,
Mode::Pae => 2,
Mode::Ia32e => 9,
}
}
#[must_use]
pub const fn index_bits(self) -> u32 {
match self {
Mode::Legacy => 10,
_ => 9,
}
}
#[must_use]
pub const fn cr3_mask(self) -> u64 {
match self {
Mode::Off => 0,
Mode::Legacy => pte::FRAME32,
Mode::Pae => 0xffff_ffe0,
Mode::Ia32e => pte::FRAME64,
}
}
#[must_use]
pub const fn level_perms(self, level: u8) -> bool {
!(matches!(self, Mode::Pae) && level == 0)
}
}
pub const TLB_ENTRIES: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TlbEntry {
pub page: u64,
pub frame: u64,
pub user: bool,
pub writable: bool,
pub no_execute: bool,
pub dirty: bool,
}
impl TlbEntry {
pub const EMPTY: u64 = u64::MAX;
#[must_use]
pub const fn empty() -> TlbEntry {
TlbEntry {
page: TlbEntry::EMPTY,
frame: 0,
user: false,
writable: false,
no_execute: 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: u64) {
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: u64) -> 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
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Access {
pub write: bool,
pub user: bool,
pub fetch: bool,
}
impl Access {
pub const SYSTEM: Access = Access {
write: false,
user: false,
fetch: false,
};
#[must_use]
pub const fn data(write: bool, user: bool) -> Access {
Access {
write,
user,
fetch: false,
}
}
#[must_use]
pub const fn fetch(user: bool) -> Access {
Access {
write: false,
user,
fetch: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tables {
pub mode: Mode,
pub cr3: u64,
pub pse: bool,
pub nxe: bool,
pub wp: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Walk {
pub phys: u64,
pub user: bool,
pub writable: bool,
pub no_execute: bool,
pub global: bool,
pub entries: [(u64, u64); 4],
pub depth: u8,
}
impl Walk {
#[must_use]
pub const fn leaf(&self) -> (u64, u64) {
self.entries[(self.depth as usize) - 1]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WalkFail {
NotPresent,
Reserved,
}
pub fn walk<R>(t: Tables, linear: u64, read: &mut R) -> Result<Walk, WalkFail>
where
R: FnMut(u64, u8) -> u64,
{
let levels = t.mode.levels();
assert!(levels > 0, "Mode::Off has no walk");
let width = t.mode.entry_bytes();
let mut table = t.cr3 & t.mode.cr3_mask();
let mut user = true;
let mut writable = true;
let mut no_execute = false;
let mut entries = [(0u64, 0u64); 4];
let mut shift = 12 + t.mode.index_bits() * u32::from(levels - 1);
for level in 0..levels {
let bits = if level == 0 {
t.mode.top_index_bits()
} else {
t.mode.index_bits()
};
let index = (linear >> shift) & ((1u64 << bits) - 1);
let addr = table.wrapping_add(index * u64::from(width));
let entry = read(addr, width);
entries[level as usize] = (addr, entry);
if entry & pte::PRESENT == 0 {
return Err(WalkFail::NotPresent);
}
if reserved_set(t, entry, level, levels) {
return Err(WalkFail::Reserved);
}
if t.mode.level_perms(level) {
user &= entry & pte::USER != 0;
writable &= entry & pte::WRITABLE != 0;
if t.nxe {
no_execute |= entry & pte::NX != 0;
}
}
let last = level + 1 == levels;
let large = !last && entry & pte::PAGE_SIZE != 0 && large_allowed(t, level);
if last || large {
let frame_mask = if width == 4 {
if large { pte::FRAME_4M } else { pte::FRAME32 }
} else {
pte::FRAME64
};
let page_shift = if large { shift } else { 12 };
let page_mask = (1u64 << page_shift) - 1;
return Ok(Walk {
phys: ((entry & frame_mask) & !page_mask) | (linear & page_mask),
user,
writable,
no_execute,
global: entry & pte::GLOBAL != 0,
entries,
depth: level + 1,
});
}
table = entry
& if width == 4 {
pte::FRAME32
} else {
pte::FRAME64
};
shift -= t.mode.index_bits();
}
unreachable!("the last level always returns")
}
const fn reserved_set(t: Tables, entry: u64, level: u8, levels: u8) -> bool {
if !t.nxe && t.mode.entry_bytes() == 8 && entry & pte::NX != 0 {
return true;
}
if entry & pte::PAGE_SIZE != 0 && level + 1 < levels && !large_allowed(t, level) {
return true;
}
false
}
const fn large_allowed(t: Tables, level: u8) -> bool {
match t.mode {
Mode::Off => false,
Mode::Legacy => t.pse && level == 0,
Mode::Pae => level == 1,
Mode::Ia32e => level == 1 || level == 2,
}
}
use super::exec::{Ex, Exec, Fault, VEC_PF};
use crate::core::device::DebugTranslation;
use crate::core::space::{AddressSpace, MemAttrs};
use crate::core::value::Width;
impl Exec<'_> {
pub(super) fn translate_access(&mut self, linear: u64, access: Access) -> Ex<u64> {
let generation = self.mem.generation();
self.state.tlb.sync(generation);
let t = self.state.sys.tables(self.cfg.features);
let write = access.write;
let user = access.user;
if let Some(entry) = self.state.tlb.get(linear) {
let allowed = (!user || entry.user)
&& (!write || write_allowed(entry.writable, user, t.wp))
&& !(access.fetch && entry.no_execute);
if allowed && (!write || entry.dirty) {
return Ok(entry.frame | (linear & 0xfff));
}
if !allowed {
return Err(self.page_fault(linear, access, true, entry.no_execute));
}
}
let walked = {
let mut read = |addr: u64, width: u8| -> u64 { self.phys_read(addr, width) };
match walk(t, linear, &mut read) {
Ok(w) => w,
Err(fail) => {
let present = matches!(fail, WalkFail::Reserved);
let mut fault = self.page_fault(linear, access, present, false);
if matches!(fail, WalkFail::Reserved) {
fault.error = fault.error.map(|e| e | pf::RESERVED);
}
return Err(fault);
}
}
};
if (user && !walked.user)
|| (write && !write_allowed(walked.writable, user, t.wp))
|| (access.fetch && walked.no_execute)
{
return Err(self.page_fault(linear, access, true, walked.no_execute));
}
let width = t.mode.entry_bytes();
let depth = usize::from(walked.depth);
for (level, (addr, value)) in walked.entries.iter().copied().enumerate().take(depth) {
let mut want = pte::ACCESSED;
if level + 1 == depth && write {
want |= pte::DIRTY;
}
if value & want != want {
self.phys_write(addr, width, value | want);
}
}
let (_, leaf) = walked.leaf();
self.state.tlb.insert(TlbEntry {
page: linear >> 12,
frame: walked.phys & !0xfff,
user: walked.user,
writable: walked.writable,
no_execute: walked.no_execute,
dirty: leaf & pte::DIRTY != 0 || write,
});
Ok(walked.phys)
}
pub(super) fn translate(&mut self, linear: u64, write: bool, user: bool) -> Ex<u64> {
self.translate_access(linear, Access::data(write, user))
}
fn page_fault(&mut self, linear: u64, access: Access, present: bool, nx: bool) -> Fault {
self.state.sys.cr2 = linear;
let mut code = fault_code(present, access.write, access.user);
if access.fetch && nx {
code |= pf::FETCH;
}
Fault::coded(VEC_PF, code)
}
}
pub(super) fn debug_translate(
sys: &super::prot::Sys,
features: super::Features,
space: &AddressSpace,
linear: u64,
) -> DebugTranslation {
let t = sys.tables(features);
if matches!(t.mode, Mode::Off) {
return DebugTranslation::Identity;
}
let mut failed = false;
let mut read = |addr: u64, width: u8| -> u64 {
let w = if width == 4 { Width::U32 } else { Width::U64 };
match space.read(addr, w, MemAttrs::DEBUG) {
Ok(value) => value,
Err(_) => {
failed = true;
0
}
}
};
match walk(t, linear, &mut read) {
Ok(w) if !failed => DebugTranslation::Mapped(w.phys),
_ => DebugTranslation::Unmapped,
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[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,
no_execute: false,
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 u64) * 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,
no_execute: false,
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());
}
fn walk_over(t: Tables, linear: u64, table: &[(u64, u64)]) -> Result<Walk, WalkFail> {
let mut reads: Vec<u64> = Vec::new();
let mut read = |addr: u64, _w: u8| -> u64 {
reads.push(addr);
table
.iter()
.find(|(a, _)| *a == addr)
.map_or(0, |(_, v)| *v)
};
walk(t, linear, &mut read)
}
fn tables(mode: Mode, cr3: u64) -> Tables {
Tables {
mode,
cr3,
pse: true,
nxe: true,
wp: false,
}
}
#[test]
fn a_legacy_walk_indexes_ten_bits_then_ten_then_twelve() {
let t = tables(Mode::Legacy, 0x1000);
let flags = pte::PRESENT | pte::WRITABLE | pte::USER;
let w = walk_over(
t,
0x0080_4123,
&[
(0x1000 + 2 * 4, 0x2000 | flags),
(0x2000 + 4 * 4, 0x9000 | flags),
],
)
.expect("both levels present");
assert_eq!(w.phys, 0x9123);
assert_eq!(w.depth, 2);
assert!(w.user && w.writable);
}
#[test]
fn a_legacy_directory_entry_with_ps_maps_four_megabytes() {
let t = tables(Mode::Legacy, 0x1000);
let w = walk_over(
t,
0x0080_4123,
&[(
0x1000 + 2 * 4,
0x0080_0000 | pte::PRESENT | pte::WRITABLE | pte::PAGE_SIZE,
)],
)
.expect("the directory entry maps the page itself");
assert_eq!(w.phys, 0x0080_0000 | 0x4123);
assert_eq!(w.depth, 1);
}
#[test]
fn a_directory_entry_with_ps_and_no_cr4_pse_is_a_reserved_bit_fault() {
let mut t = tables(Mode::Legacy, 0x1000);
t.pse = false;
let err = walk_over(
t,
0x0080_4123,
&[(0x1000 + 2 * 4, 0x0080_0000 | pte::PRESENT | pte::PAGE_SIZE)],
)
.expect_err("PS without CR4.PSE is reserved");
assert_eq!(err, WalkFail::Reserved);
}
#[test]
fn a_pae_pointer_table_has_four_entries_and_no_permission_bits() {
let t = tables(Mode::Pae, 0xff20);
let flags = pte::PRESENT | pte::WRITABLE | pte::USER;
let w = walk_over(
t,
0xc040_2123,
&[
(0xff20 + 3 * 8, 0x2_0000 | pte::PRESENT),
(0x2_0000 + 2 * 8, 0x3_0000 | flags),
(0x3_0000 + 2 * 8, 0x4_0000 | flags),
],
)
.expect("three levels present");
assert_eq!(w.phys, 0x4_0123);
assert_eq!(w.depth, 3);
assert!(w.user && w.writable);
}
#[test]
fn an_ia32e_walk_descends_four_levels_of_nine_bits() {
let t = tables(Mode::Ia32e, 0x1000);
let linear = 0x0000_7f80_1234_5678u64;
let i = |shift: u32| ((linear >> shift) & 0x1ff) * 8;
let flags = pte::PRESENT | pte::WRITABLE | pte::USER;
let w = walk_over(
t,
linear,
&[
(0x1000 + i(39), 0x2000 | flags),
(0x2000 + i(30), 0x3000 | flags),
(0x3000 + i(21), 0x4000 | flags),
(0x4000 + i(12), 0x5000 | flags),
],
)
.expect("four levels present");
assert_eq!(w.phys, 0x5678);
assert_eq!(w.depth, 4);
}
#[test]
fn an_ia32e_directory_entry_with_ps_maps_two_megabytes() {
let t = tables(Mode::Ia32e, 0x1000);
let linear = 0x0000_0000_0020_3456u64;
let i = |shift: u32| ((linear >> shift) & 0x1ff) * 8;
let flags = pte::PRESENT | pte::WRITABLE | pte::USER;
let w = walk_over(
t,
linear,
&[
(0x1000 + i(39), 0x2000 | flags),
(0x2000 + i(30), 0x3000 | flags),
(0x3000 + i(21), 0x20_0000 | flags | pte::PAGE_SIZE),
],
)
.expect("the directory entry maps the page");
assert_eq!(w.phys, 0x20_0000 | 0x3456);
assert_eq!(w.depth, 3);
}
#[test]
fn an_ia32e_pointer_entry_with_ps_maps_a_gigabyte() {
let t = tables(Mode::Ia32e, 0x1000);
let linear = 0x0000_0000_4020_3456u64;
let i = |shift: u32| ((linear >> shift) & 0x1ff) * 8;
let flags = pte::PRESENT | pte::WRITABLE | pte::USER;
let w = walk_over(
t,
linear,
&[
(0x1000 + i(39), 0x2000 | flags),
(0x2000 + i(30), 0x4000_0000 | flags | pte::PAGE_SIZE),
],
)
.expect("the pointer entry maps the page");
assert_eq!(w.phys, 0x4000_0000 | 0x0020_3456);
assert_eq!(w.depth, 2);
}
#[test]
fn a_pml4_entry_may_not_set_ps() {
let t = tables(Mode::Ia32e, 0x1000);
let err = walk_over(t, 0, &[(0x1000, 0x2000 | pte::PRESENT | pte::PAGE_SIZE)])
.expect_err("PS is reserved in a PML4 entry");
assert_eq!(err, WalkFail::Reserved);
}
#[test]
fn the_execute_disable_bit_is_reserved_without_efer_nxe() {
let mut t = tables(Mode::Ia32e, 0x1000);
t.nxe = false;
let err = walk_over(t, 0, &[(0x1000, 0x2000 | pte::PRESENT | pte::NX)])
.expect_err("bit 63 is reserved with NXE clear");
assert_eq!(err, WalkFail::Reserved);
}
#[test]
fn permissions_are_the_conjunction_of_every_level() {
let t = tables(Mode::Ia32e, 0x1000);
let w = walk_over(
t,
0,
&[
(0x1000, 0x2000 | pte::PRESENT | pte::WRITABLE),
(0x2000, 0x3000 | pte::PRESENT | pte::WRITABLE | pte::USER),
(0x3000, 0x4000 | pte::PRESENT | pte::USER),
(0x4000, 0x5000 | pte::PRESENT | pte::WRITABLE | pte::USER),
],
)
.expect("present at every level");
assert!(!w.user);
assert!(!w.writable);
}
#[test]
fn a_missing_entry_reports_which_kind_of_failure_it_was() {
let t = tables(Mode::Ia32e, 0x1000);
assert_eq!(walk_over(t, 0, &[]), Err(WalkFail::NotPresent));
}
}