Split kernel mode and user mode page table

This commit is contained in:
Yuke Peng
2023-10-15 17:48:13 +08:00
committed by Tate, Hongliang Tian
parent c26eac757a
commit aeea333945
8 changed files with 175 additions and 59 deletions

View File

@ -6,7 +6,6 @@ use pod::Pod;
use crate::{
bus::pci::PciDeviceLocation,
config::PAGE_SIZE,
vm::{
page_table::{PageTableConfig, PageTableError},
Paddr, PageTable, Vaddr, VmAllocOptions, VmFrame, VmFrameVec, VmIo,
@ -61,14 +60,14 @@ impl RootTable {
&mut self,
device: PciDeviceLocation,
vaddr: Vaddr,
paddr: Paddr,
frame: &VmFrame,
) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
}
self.get_or_create_context_table(device)
.map(device, vaddr, paddr & !(PAGE_SIZE - 1))?;
.map(device, vaddr, frame)?;
Ok(())
}
@ -262,7 +261,7 @@ impl ContextTable {
if !bus_entry.present() {
let table: PageTable<PageTableEntry> = PageTable::new(PageTableConfig {
address_width: crate::vm::page_table::AddressWidth::Level3PageTable,
address_width: crate::vm::page_table::AddressWidth::Level3,
});
let address = table.root_paddr();
self.page_tables.insert(address, table);
@ -286,7 +285,7 @@ impl ContextTable {
&mut self,
device: PciDeviceLocation,
vaddr: Vaddr,
paddr: Paddr,
frame: &VmFrame,
) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
@ -294,7 +293,7 @@ impl ContextTable {
self.get_or_create_page_table(device)
.map(
vaddr,
paddr,
frame,
PageTableFlags::WRITABLE | PageTableFlags::READABLE | PageTableFlags::LAST_PAGE,
)
.map_err(ContextTableError::ModificationError)

View File

@ -3,7 +3,7 @@ mod fault;
mod remapping;
mod second_stage;
use crate::sync::Mutex;
use crate::{sync::Mutex, vm::VmFrame};
use log::info;
use spin::Once;
@ -12,7 +12,7 @@ use crate::{
bus::pci::PciDeviceLocation,
vm::{
page_table::{PageTableConfig, PageTableError},
Paddr, PageTable, Vaddr,
PageTable, Vaddr,
},
};
@ -27,7 +27,7 @@ pub enum IommuError {
/// # Safety
///
/// Mapping an incorrect address may lead to a kernel data leak.
pub(crate) unsafe fn map(vaddr: Vaddr, paddr: Paddr) -> Result<(), IommuError> {
pub(crate) unsafe fn map(vaddr: Vaddr, frame: &VmFrame) -> Result<(), IommuError> {
let Some(table) = PAGE_TABLE.get() else {
return Err(IommuError::NoIommu);
};
@ -41,7 +41,7 @@ pub(crate) unsafe fn map(vaddr: Vaddr, paddr: Paddr) -> Result<(), IommuError> {
function: 0,
},
vaddr,
paddr,
frame,
)
.map_err(|err| match err {
context_table::ContextTableError::InvalidDeviceId => unreachable!(),
@ -78,7 +78,7 @@ pub(crate) fn init() -> Result<(), IommuError> {
let mut root_table = RootTable::new();
// For all PCI Device, use the same page table.
let page_table: PageTable<PageTableEntry> = PageTable::new(PageTableConfig {
address_width: crate::vm::page_table::AddressWidth::Level3PageTable,
address_width: crate::vm::page_table::AddressWidth::Level3,
});
for table in PciDeviceLocation::all() {
root_table.specify_device_page_table(table, page_table.clone())

View File

@ -45,6 +45,18 @@ pub fn tlb_flush(vaddr: Vaddr) {
tlb::flush(VirtAddr::new(vaddr as u64));
}
pub const fn is_user_vaddr(vaddr: Vaddr) -> bool {
// FIXME: Support 3/5 level page table.
// 47 = 12(offset) + 4 * 9(index) - 1
(vaddr >> 47) == 0
}
pub const fn is_kernel_vaddr(vaddr: Vaddr) -> bool {
// FIXME: Support 3/5 level page table.
// 47 = 12(offset) + 4 * 9(index) - 1
((vaddr >> 47) & 0x1) == 1
}
#[derive(Clone, Copy, Pod)]
#[repr(C)]
pub struct PageTableEntry(usize);

View File

@ -50,7 +50,7 @@ impl VmFrameVec {
for frame in frames.iter_mut() {
// Safety: The address is controlled by frame allocator.
unsafe {
if let Err(err) = iommu::map(frame.start_paddr(), frame.start_paddr()) {
if let Err(err) = iommu::map(frame.start_paddr(), frame) {
match err {
// do nothing
iommu::IommuError::NoIommu => {}

View File

@ -163,9 +163,9 @@ impl MemorySet {
// TODO: check overlap
if let Entry::Vacant(e) = self.areas.entry(area.start_va) {
let area = e.insert(area);
for (va, pa) in area.mapper.iter() {
debug_assert!(pa.start_paddr() < PHYS_OFFSET);
self.pt.map(*va, pa.start_paddr(), area.flags).unwrap();
for (va, frame) in area.mapper.iter() {
debug_assert!(frame.start_paddr() < PHYS_OFFSET);
self.pt.map(*va, frame, area.flags).unwrap();
}
} else {
panic!(
@ -191,7 +191,7 @@ impl MemorySet {
pub fn new() -> Self {
let mut page_table = PageTable::new(PageTableConfig {
address_width: super::page_table::AddressWidth::Level4PageTable,
address_width: super::page_table::AddressWidth::Level4,
});
let mapped_pte = crate::arch::mm::ALL_MAPPED_PTE.lock();
for (index, pte) in mapped_pte.iter() {
@ -300,7 +300,7 @@ impl fmt::Debug for MemorySet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MemorySet")
.field("areas", &self.areas)
.field("page_table_root", &self.pt.root_pa)
.field("page_table_root", &self.pt.root_paddr())
.finish()
}
}

View File

@ -63,6 +63,7 @@ pub static FRAMEBUFFER_REGIONS: Once<Vec<MemoryRegion>> = Once::new();
pub(crate) fn init() {
let memory_regions = crate::boot::memory_regions().to_owned();
frame_allocator::init(&memory_regions);
page_table::init();
let mut framebuffer_regions = Vec::new();
for i in memory_regions.iter() {

View File

@ -3,14 +3,16 @@ use super::{
frame_allocator, paddr_to_vaddr, VmAllocOptions, VmFrameVec, {Paddr, Vaddr},
};
use crate::{
arch::mm::{tlb_flush, PageTableEntry},
arch::mm::{is_kernel_vaddr, is_user_vaddr, tlb_flush, PageTableEntry},
config::{ENTRY_COUNT, PAGE_SIZE},
sync::SpinLock,
vm::VmFrame,
};
use alloc::{vec, vec::Vec};
use core::{fmt::Debug, marker::PhantomData, mem::size_of};
use log::trace;
use pod::Pod;
use spin::Once;
pub trait PageTableFlagsTrait: Clone + Copy + Sized + Pod + Debug {
fn new() -> Self;
@ -89,11 +91,10 @@ pub struct PageTableConfig {
#[derive(Debug, Clone, Copy)]
#[repr(usize)]
#[allow(clippy::enum_variant_names)]
pub enum AddressWidth {
Level3PageTable = 3,
Level4PageTable = 4,
Level5PageTable = 5,
Level3 = 3,
Level4 = 4,
Level5 = 5,
}
#[derive(Debug)]
@ -104,43 +105,119 @@ pub enum PageTableError {
/// 2. The mapping is already invalid before unmap operation.
/// 3. The mapping is not exists before protect operation.
InvalidModification,
InvalidVaddr,
}
pub static KERNEL_PAGE_TABLE: Once<SpinLock<PageTable<PageTableEntry, KernelMode>>> = Once::new();
#[derive(Clone)]
pub struct UserMode {}
#[derive(Clone)]
pub struct KernelMode {}
#[derive(Clone, Debug)]
pub struct PageTable<T: PageTableEntryTrait> {
pub root_pa: Paddr,
pub struct PageTable<T: PageTableEntryTrait, M = UserMode> {
root_paddr: Paddr,
/// store all the physical frame that the page table need to map all the frame e.g. the frame of the root_pa
tables: Vec<VmFrame>,
config: PageTableConfig,
_phantom: PhantomData<T>,
_phantom: PhantomData<(T, M)>,
}
impl<T: PageTableEntryTrait> PageTable<T> {
impl<T: PageTableEntryTrait> PageTable<T, UserMode> {
pub fn new(config: PageTableConfig) -> Self {
let root_frame = frame_allocator::alloc_zero(VmFrameFlags::empty()).unwrap();
Self {
root_pa: root_frame.start_paddr(),
root_paddr: root_frame.start_paddr(),
tables: vec![root_frame],
config,
_phantom: PhantomData,
}
}
/// Create the page table structure according to the physical address, note that the created page table can only use the page_walk function without create.
///
/// # Safety
///
/// User should ensure the physical address is valid and only invoke the `page_walk` function without creating new PTE.
///
pub unsafe fn from_paddr(config: PageTableConfig, paddr: Paddr) -> Self {
Self {
root_pa: paddr,
tables: Vec::new(),
config,
_phantom: PhantomData,
pub fn map(
&mut self,
vaddr: Vaddr,
frame: &VmFrame,
flags: T::F,
) -> Result<(), PageTableError> {
if is_kernel_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
// Safety:
// 1. The vaddr belongs to user mode program and does not affect the kernel mapping.
// 2. The area where the physical address islocated at untyped memory and does not affect kernel security.
unsafe { self.do_map(vaddr, frame.start_paddr(), flags) }
}
pub fn unmap(&mut self, vaddr: Vaddr) -> Result<(), PageTableError> {
if is_kernel_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
// Safety: The vaddr belongs to user mode program and does not affect the kernel mapping.
unsafe { self.do_unmap(vaddr) }
}
pub fn protect(&mut self, vaddr: Vaddr, flags: T::F) -> Result<(), PageTableError> {
if is_kernel_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
// Safety: The vaddr belongs to user mode program and does not affect the kernel mapping.
unsafe { self.do_protect(vaddr, flags) }
}
}
impl<T: PageTableEntryTrait> PageTable<T, KernelMode> {
/// Mapping `vaddr` to `paddr` with flags. The `vaddr` should not be at the low address
/// (memory belonging to the user mode program).
///
/// # Safety
///
/// Modifying kernel mappings is considered unsafe, and incorrect operation may cause crashes.
/// User must take care of the consequences when using this API.
pub unsafe fn map(
&mut self,
vaddr: Vaddr,
paddr: Paddr,
flags: T::F,
) -> Result<(), PageTableError> {
if is_user_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
self.do_map(vaddr, paddr, flags)
}
/// Unmap `vaddr`. The `vaddr` should not be at the low address
/// (memory belonging to the user mode program).
///
/// # Safety
///
/// Modifying kernel mappings is considered unsafe, and incorrect operation may cause crashes.
/// User must take care of the consequences when using this API.
pub unsafe fn unmap(&mut self, vaddr: Vaddr) -> Result<(), PageTableError> {
if is_user_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
self.do_unmap(vaddr)
}
/// Modify the flags mapped at `vaddr`. The `vaddr` should not be at the low address
/// (memory belonging to the user mode program).
///
/// # Safety
///
/// Modifying kernel mappings is considered unsafe, and incorrect operation may cause crashes.
/// User must take care of the consequences when using this API.
pub unsafe fn protect(&mut self, vaddr: Vaddr, flags: T::F) -> Result<(), PageTableError> {
if is_user_vaddr(vaddr) {
return Err(PageTableError::InvalidVaddr);
}
self.do_protect(vaddr, flags)
}
}
impl<T: PageTableEntryTrait, M> PageTable<T, M> {
/// Add a new mapping directly in the root page table.
///
/// # Safety
@ -149,12 +226,23 @@ impl<T: PageTableEntryTrait> PageTable<T> {
///
pub unsafe fn add_root_mapping(&mut self, index: usize, pte: &T) {
debug_assert!((index + 1) * size_of::<T>() <= PAGE_SIZE);
// Safety: The root_pa is refer to the root of a valid page table.
let root_ptes: &mut [T] = table_of(self.root_pa).unwrap();
// Safety: The root_paddr is refer to the root of a valid page table.
let root_ptes: &mut [T] = table_of(self.root_paddr).unwrap();
root_ptes[index] = *pte;
}
pub fn map(&mut self, vaddr: Vaddr, paddr: Paddr, flags: T::F) -> Result<(), PageTableError> {
/// Mapping `vaddr` to `paddr` with flags.
///
/// # Safety
///
/// This function allows arbitrary modifications to the page table.
/// Incorrect modifications may cause the kernel to crash (e.g., changing the linear mapping.).
unsafe fn do_map(
&mut self,
vaddr: Vaddr,
paddr: Paddr,
flags: T::F,
) -> Result<(), PageTableError> {
let last_entry = self.page_walk(vaddr, true).unwrap();
trace!(
"Page Table: Map vaddr:{:x?}, paddr:{:x?}, flags:{:x?}",
@ -181,7 +269,7 @@ impl<T: PageTableEntryTrait> PageTable<T> {
// Safety: The offset does not exceed the value of PAGE_SIZE.
// It only change the memory controlled by page table.
let mut current: &mut T = unsafe {
&mut *(paddr_to_vaddr(self.root_pa + size_of::<T>() * T::page_index(vaddr, count))
&mut *(paddr_to_vaddr(self.root_paddr + size_of::<T>() * T::page_index(vaddr, count))
as *mut T)
};
@ -220,7 +308,13 @@ impl<T: PageTableEntryTrait> PageTable<T> {
Some(current)
}
pub fn unmap(&mut self, vaddr: Vaddr) -> Result<(), PageTableError> {
/// Unmap `vaddr`.
///
/// # Safety
///
/// This function allows arbitrary modifications to the page table.
/// Incorrect modifications may cause the kernel to crash (e.g., unmap the linear mapping.).
unsafe fn do_unmap(&mut self, vaddr: Vaddr) -> Result<(), PageTableError> {
let last_entry = self.page_walk(vaddr, false).unwrap();
trace!("Page Table: Unmap vaddr:{:x?}", vaddr);
if last_entry.is_unused() && !last_entry.flags().is_present() {
@ -231,7 +325,14 @@ impl<T: PageTableEntryTrait> PageTable<T> {
Ok(())
}
pub fn protect(&mut self, vaddr: Vaddr, flags: T::F) -> Result<(), PageTableError> {
/// Modify the flags mapped at `vaddr`
///
/// # Safety
///
/// This function allows arbitrary modifications to the page table.
/// Incorrect modifications may cause the kernel to crash
/// (e.g., make the linear mapping visible to the user mode applications.).
unsafe fn do_protect(&mut self, vaddr: Vaddr, flags: T::F) -> Result<(), PageTableError> {
let last_entry = self.page_walk(vaddr, false).unwrap();
trace!("Page Table: Protect vaddr:{:x?}, flags:{:x?}", vaddr, flags);
if last_entry.is_unused() || !last_entry.flags().is_present() {
@ -243,7 +344,7 @@ impl<T: PageTableEntryTrait> PageTable<T> {
}
pub fn root_paddr(&self) -> Paddr {
self.root_pa
self.root_paddr
}
}
@ -263,22 +364,25 @@ pub unsafe fn table_of<'a, T: PageTableEntryTrait>(pa: Paddr) -> Option<&'a mut
/// translate a virtual address to physical address which cannot use offset to get physical address
pub fn vaddr_to_paddr(vaddr: Vaddr) -> Option<Paddr> {
#[cfg(target_arch = "x86_64")]
let (page_directory_base, _) = x86_64::registers::control::Cr3::read();
// TODO: Read and use different level page table.
// Safety: The page_directory_base is valid since it is read from PDBR.
// We only use this instance to do the page walk without creating.
let mut page_table: PageTable<PageTableEntry> = unsafe {
PageTable::from_paddr(
PageTableConfig {
address_width: AddressWidth::Level4PageTable,
},
page_directory_base.start_address().as_u64() as usize,
)
};
let page_directory_base = page_directory_base.start_address().as_u64() as usize;
let mut page_table = KERNEL_PAGE_TABLE.get().unwrap().lock();
// Although we bypass the unsafe APIs provided by KernelMode, the purpose here is
// only to obtain the corresponding physical address according to the mapping.
let last_entry = page_table.page_walk(vaddr, false)?;
// FIXME: Support huge page
Some(last_entry.paddr() + (vaddr & (PAGE_SIZE - 1)))
}
pub fn init() {
KERNEL_PAGE_TABLE.call_once(|| {
#[cfg(target_arch = "x86_64")]
let (page_directory_base, _) = x86_64::registers::control::Cr3::read();
SpinLock::new(PageTable {
root_paddr: page_directory_base.start_address().as_u64() as usize,
tables: Vec::new(),
config: PageTableConfig {
address_width: AddressWidth::Level4,
},
_phantom: PhantomData,
})
});
}

View File

@ -40,7 +40,7 @@ impl VmSpace {
pub unsafe fn activate(&self) {
#[cfg(target_arch = "x86_64")]
crate::arch::x86::mm::activate_page_table(
self.memory_set.lock().pt.root_pa,
self.memory_set.lock().pt.root_paddr(),
x86_64::registers::control::Cr3Flags::PAGE_LEVEL_CACHE_DISABLE,
);
}