use core::{
ops::Range,
sync::atomic::{AtomicPtr, Ordering},
};
use crate::{
arch::mm::{current_page_table_paddr, PageTableEntry, PagingConsts},
cpu::{num_cpus, CpuExceptionInfo, CpuSet, PinCurrentCpu},
cpu_local,
mm::{
io::Fallible,
kspace::KERNEL_PAGE_TABLE,
page_table::{self, PageTable, PageTableItem, UserMode},
tlb::{TlbFlushOp, TlbFlusher, FLUSH_ALL_RANGE_THRESHOLD},
Frame, PageProperty, VmReader, VmWriter, MAX_USERSPACE_VADDR,
},
prelude::*,
sync::{RwLock, RwLockReadGuard},
task::{disable_preempt, DisabledPreemptGuard},
Error,
};
#[allow(clippy::type_complexity)]
#[derive(Debug)]
pub struct VmSpace {
pt: PageTable<UserMode>,
page_fault_handler: Option<fn(&VmSpace, &CpuExceptionInfo) -> core::result::Result<(), ()>>,
activation_lock: RwLock<()>,
}
impl VmSpace {
pub fn new() -> Self {
Self {
pt: KERNEL_PAGE_TABLE.get().unwrap().create_user_page_table(),
page_fault_handler: None,
activation_lock: RwLock::new(()),
}
}
pub fn cursor(&self, va: &Range<Vaddr>) -> Result<Cursor<'_>> {
Ok(self.pt.cursor(va).map(Cursor)?)
}
pub fn cursor_mut(&self, va: &Range<Vaddr>) -> Result<CursorMut<'_, '_>> {
Ok(self.pt.cursor_mut(va).map(|pt_cursor| {
let activation_lock = self.activation_lock.read();
let mut activated_cpus = CpuSet::new_empty();
for cpu in 0..num_cpus() {
let ptr =
ACTIVATED_VM_SPACE.get_on_cpu(cpu).load(Ordering::Relaxed) as *const VmSpace;
if ptr == self as *const VmSpace {
activated_cpus.add(cpu);
}
}
CursorMut {
pt_cursor,
activation_lock,
flusher: TlbFlusher::new(activated_cpus, disable_preempt()),
}
})?)
}
pub(crate) fn activate(self: &Arc<Self>) {
let preempt_guard = disable_preempt();
let _activation_lock = self.activation_lock.write();
let cpu = preempt_guard.current_cpu();
let activated_vm_space = ACTIVATED_VM_SPACE.get_on_cpu(cpu);
let last_ptr = activated_vm_space.load(Ordering::Relaxed) as *const VmSpace;
if last_ptr != Arc::as_ptr(self) {
self.pt.activate();
let ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace;
activated_vm_space.store(ptr, Ordering::Relaxed);
if !last_ptr.is_null() {
drop(unsafe { Arc::from_raw(last_ptr) });
}
}
}
pub(crate) fn handle_page_fault(
&self,
info: &CpuExceptionInfo,
) -> core::result::Result<(), ()> {
if let Some(func) = self.page_fault_handler {
return func(self, info);
}
Err(())
}
pub fn register_page_fault_handler(
&mut self,
func: fn(&VmSpace, &CpuExceptionInfo) -> core::result::Result<(), ()>,
) {
self.page_fault_handler = Some(func);
}
pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'_, Fallible>> {
if current_page_table_paddr() != unsafe { self.pt.root_paddr() } {
return Err(Error::AccessDenied);
}
if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
return Err(Error::AccessDenied);
}
Ok(unsafe { VmReader::<Fallible>::from_user_space(vaddr as *const u8, len) })
}
pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'_, Fallible>> {
if current_page_table_paddr() != unsafe { self.pt.root_paddr() } {
return Err(Error::AccessDenied);
}
if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
return Err(Error::AccessDenied);
}
Ok(unsafe { VmWriter::<Fallible>::from_user_space(vaddr as *mut u8, len) })
}
}
impl Default for VmSpace {
fn default() -> Self {
Self::new()
}
}
pub struct Cursor<'a>(page_table::Cursor<'a, UserMode, PageTableEntry, PagingConsts>);
impl Iterator for Cursor<'_> {
type Item = VmItem;
fn next(&mut self) -> Option<Self::Item> {
let result = self.query();
if result.is_ok() {
self.0.move_forward();
}
result.ok()
}
}
impl Cursor<'_> {
pub fn query(&mut self) -> Result<VmItem> {
Ok(self.0.query().map(|item| item.try_into().unwrap())?)
}
pub fn jump(&mut self, va: Vaddr) -> Result<()> {
self.0.jump(va)?;
Ok(())
}
pub fn virt_addr(&self) -> Vaddr {
self.0.virt_addr()
}
}
pub struct CursorMut<'a, 'b> {
pt_cursor: page_table::CursorMut<'a, UserMode, PageTableEntry, PagingConsts>,
#[allow(dead_code)]
activation_lock: RwLockReadGuard<'b, ()>,
flusher: TlbFlusher<DisabledPreemptGuard>,
}
impl CursorMut<'_, '_> {
pub fn query(&mut self) -> Result<VmItem> {
Ok(self
.pt_cursor
.query()
.map(|item| item.try_into().unwrap())?)
}
pub fn jump(&mut self, va: Vaddr) -> Result<()> {
self.pt_cursor.jump(va)?;
Ok(())
}
pub fn virt_addr(&self) -> Vaddr {
self.pt_cursor.virt_addr()
}
pub fn flusher(&self) -> &TlbFlusher<DisabledPreemptGuard> {
&self.flusher
}
pub fn map(&mut self, frame: Frame, prop: PageProperty) {
let start_va = self.virt_addr();
let old = unsafe { self.pt_cursor.map(frame.into(), prop) };
if let Some(old) = old {
self.flusher
.issue_tlb_flush_with(TlbFlushOp::Address(start_va), old);
self.flusher.dispatch_tlb_flush();
}
}
pub fn unmap(&mut self, len: usize) {
assert!(len % super::PAGE_SIZE == 0);
let end_va = self.virt_addr() + len;
let tlb_prefer_flush_all = len > FLUSH_ALL_RANGE_THRESHOLD;
loop {
let result = unsafe { self.pt_cursor.take_next(end_va - self.virt_addr()) };
match result {
PageTableItem::Mapped { va, page, .. } => {
if !self.flusher.need_remote_flush() && tlb_prefer_flush_all {
drop(page);
continue;
}
self.flusher
.issue_tlb_flush_with(TlbFlushOp::Address(va), page);
}
PageTableItem::PageTableNode { page } => {
if !self.flusher.need_remote_flush() && tlb_prefer_flush_all {
drop(page);
continue;
}
self.flusher.issue_tlb_flush_with(TlbFlushOp::All, page);
}
PageTableItem::NotMapped { .. } => {
break;
}
PageTableItem::MappedUntracked { .. } => {
panic!("found untracked memory mapped into `VmSpace`");
}
}
}
if !self.flusher.need_remote_flush() && tlb_prefer_flush_all {
self.flusher.issue_tlb_flush(TlbFlushOp::All);
}
self.flusher.dispatch_tlb_flush();
}
pub fn protect_next(
&mut self,
len: usize,
mut op: impl FnMut(&mut PageProperty),
) -> Option<Range<Vaddr>> {
unsafe { self.pt_cursor.protect_next(len, &mut op) }
}
pub fn copy_from(
&mut self,
src: &mut Self,
len: usize,
op: &mut impl FnMut(&mut PageProperty),
) {
unsafe { self.pt_cursor.copy_from(&mut src.pt_cursor, len, op) }
}
}
cpu_local! {
static ACTIVATED_VM_SPACE: AtomicPtr<VmSpace> = AtomicPtr::new(core::ptr::null_mut());
}
#[derive(Debug)]
pub enum VmItem {
NotMapped {
va: Vaddr,
len: usize,
},
Mapped {
va: Vaddr,
frame: Frame,
prop: PageProperty,
},
}
impl TryFrom<PageTableItem> for VmItem {
type Error = &'static str;
fn try_from(item: PageTableItem) -> core::result::Result<Self, Self::Error> {
match item {
PageTableItem::NotMapped { va, len } => Ok(VmItem::NotMapped { va, len }),
PageTableItem::Mapped { va, page, prop } => Ok(VmItem::Mapped {
va,
frame: page
.try_into()
.map_err(|_| "found typed memory mapped into `VmSpace`")?,
prop,
}),
PageTableItem::MappedUntracked { .. } => {
Err("found untracked memory mapped into `VmSpace`")
}
PageTableItem::PageTableNode { .. } => {
unreachable!()
}
}
}
}