curiOS/kernel/src/x86_64/paging.rs
2026-08-03 18:33:30 +02:00

234 lines
6.6 KiB
Rust

use core::{
borrow::Borrow,
fmt::Debug,
hint::unlikely,
ops::{Deref, Index},
};
use bit_field::BitField;
use crate::{
memory::{PhyAddr, VirtAddr, VirtAddrTranslationExt},
x86_64::{VirtAddrExt, registers::Cr4},
};
#[repr(C, align(4096))]
pub struct PageTable {
entries: [PageTableEntry; 512],
}
impl PageTable {
pub fn get(&self, index: u16) -> Option<PageTableEntry> {
if index < 512 {
let entry = self.entries[index as usize];
if entry.present() { Some(entry) } else { None }
} else {
None
}
}
pub fn get_unchecked(&self, index: u16) -> PageTableEntry {
assert!(index < 512, "Page table index out of bounds");
self.entries[index as usize]
}
}
impl Index<u16> for PageTable {
type Output = PageTableEntry;
fn index(&self, index: u16) -> &Self::Output {
assert!(index < 512, "Page table index out of bounds");
&self.entries[index as usize]
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PageTableEntry(PageTableEntryFlags);
impl Debug for PageTableEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PageTableEntry")
.field(
"flags",
&PageTableEntryFlags::from_bits_truncate(self.as_raw()),
)
.field("phy", &self.phy())
.field("pk", &self.pk())
.field("pat_index", &self.pat_index())
.field("free_bits", &format_args!("{:#b}", self.free_bits()))
.finish()
}
}
impl AsRef<PageTableEntryFlags> for PageTableEntry {
fn as_ref(&self) -> &PageTableEntryFlags {
&self.0
}
}
impl Borrow<PageTableEntryFlags> for PageTableEntry {
fn borrow(&self) -> &PageTableEntryFlags {
&self.0
}
}
impl Deref for PageTableEntry {
type Target = PageTableEntryFlags;
fn deref(&self) -> &Self::Target {
&self.0
}
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageTableEntryFlags: u64 {
const PRESENT = 1 << 0;
const WRITABLE = 1 << 1;
const USER_ACCESSIBLE = 1 << 2;
const WRITE_THROUGH = 1 << 3; // PWT
const NO_CACHE = 1 << 4; // PCD
const ACCESSED = 1 << 5;
const DIRTY = 1 << 6;
const HUGE_PAGE = 1 << 7;
const GLOBAL = 1 << 8;
const NO_EXECUTE = 1 << 63;
const PAT_4K = 1 << 7;
const PAT_HUGE = 1 << 12;
}
}
impl PageTableEntry {
pub fn from_raw(bits: u64) -> Self {
Self(PageTableEntryFlags::from_bits_retain(bits))
}
pub fn as_raw(&self) -> u64 {
self.0.bits()
}
pub fn as_mut_raw(&mut self) -> &mut u64 {
self.0.0.bits_mut()
}
pub fn present(&self) -> bool {
self.contains(PageTableEntryFlags::PRESENT)
}
pub fn phy(&self) -> PhyAddr {
PhyAddr(self.as_raw().get_bits(12..52) << 12)
}
pub fn try_as_page_table(&self) -> Option<&PageTable> {
if !self.contains(PageTableEntryFlags::PRESENT) {
return None;
}
if self.contains(PageTableEntryFlags::HUGE_PAGE) {
return None;
}
unsafe {
Some(
self.phy()
.as_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked(),
)
}
}
pub unsafe fn as_page_table(&self) -> &PageTable {
// entry must be present
assert!(self.contains(PageTableEntryFlags::PRESENT));
// if the entry is a huge page, it does not point to a deeper page table.
assert!(!self.contains(PageTableEntryFlags::HUGE_PAGE));
unsafe {
self.phy()
.as_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked()
}
}
pub fn pk(&self) -> u8 {
self.as_raw().get_bits(59..63) as u8
}
pub fn pat_index(&self) -> u8 {
let pat = if self.contains(PageTableEntryFlags::HUGE_PAGE) {
self.as_raw().get_bit(12) as u8
} else {
self.as_raw().get_bit(7) as u8
};
self.as_raw().get_bits(3..=4) as u8 | (pat << 2)
}
/// Returns the free bits in the page table entry, which are bits 9-11 and
/// 52-58, combined into a single 10-bit value.
pub fn free_bits(&self) -> u16 {
let low = self.bits().get_bits(9..12) as u16;
let high = self.bits().get_bits(52..59) as u16;
low | (high << 3)
}
pub fn set_free_bits(&mut self, value: u16) {
let low = (value & 0b111) as u64;
let high = (value >> 3) as u64;
self.as_mut_raw().set_bits(9..12, low);
self.as_mut_raw().set_bits(52..59, high);
}
}
impl VirtAddrTranslationExt for VirtAddr {
fn into_phy_addr(self) -> Option<PhyAddr> {
get_physical_addr(self)
}
}
pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
let cr3 = crate::x86_64::registers::Cr3::read();
let cr4 = crate::x86_64::registers::Cr4::read();
let frame = cr3.phy();
let page_table = unsafe {
frame
.as_hhdm_virt()
.as_ptr::<PageTable>()
.as_ref()
.unwrap_unchecked()
};
let pml4_entry = if unlikely(cr4.contains(Cr4::LA57)) {
let l5_entry = page_table[virt.page_table_index::<{ VirtAddr::PML5 }>()];
l5_entry
.try_as_page_table()?
.get_unchecked(virt.page_table_index::<{ VirtAddr::PML4 }>())
} else {
page_table[virt.page_table_index::<{ VirtAddr::PML4 }>()]
};
let pdpt_entry = pml4_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PDPT }>()];
if pdpt_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_1G: u64 = !((1 << 30) - 1);
let phys_addr = (pdpt_entry.phy().0 & PHY_MASK_1G) + virt.offset_1g() as u64;
return Some(PhyAddr(phys_addr));
}
let pd_entry = pdpt_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PD }>()];
if pd_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
const PHY_MASK_2M: u64 = !((1 << 21) - 1);
let phys_addr = (pd_entry.phy().0 & PHY_MASK_2M) + virt.offset_2m() as u64;
return Some(PhyAddr(phys_addr));
}
let pt_entry = pd_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PT }>()];
if !pt_entry.contains(PageTableEntryFlags::PRESENT) {
return None;
}
let phy_addr = pt_entry.phy().as_hhdm_virt().0 + virt.offset_4k() as u64;
Some(PhyAddr(phy_addr))
}