use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
};
use ax_alloc::{UsageKind, global_allocator};
use ax_errno::{AxError, AxResult};
use ax_hal::{
mem::{phys_to_virt, virt_to_phys},
paging::{MappingFlags, PageSize, PageTable, PageTableCursor},
};
use ax_memory_addr::{DynPageIter, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange};
use ax_memory_set::MappingBackend;
use ax_sync::Mutex;
use enum_dispatch::enum_dispatch;
mod cow;
mod file;
mod linear;
mod shared;
pub use self::shared::SharedPages;
use super::AddrSpace;
fn divide_page(size: usize, page_size: PageSize) -> usize {
assert!(page_size.is_aligned(size), "unaligned");
size >> (page_size as usize).trailing_zeros()
}
fn alloc_frame(zeroed: bool, size: PageSize) -> AxResult<PhysAddr> {
let page_size = size as usize;
let num_pages = page_size / PAGE_SIZE_4K;
let vaddr = VirtAddr::from(
global_allocator()
.alloc_pages(num_pages, page_size, UsageKind::VirtMem)
.map_err(|_| AxError::NoMemory)?,
);
if zeroed {
unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, page_size) };
}
let paddr = virt_to_phys(vaddr);
Ok(paddr)
}
fn dealloc_frame(frame: PhysAddr, align: PageSize) {
let vaddr = phys_to_virt(frame);
let page_size: usize = align.into();
let num_pages = page_size / PAGE_SIZE_4K;
global_allocator().dealloc_pages(vaddr.as_usize(), num_pages, UsageKind::VirtMem);
}
fn pages_in(range: VirtAddrRange, align: PageSize) -> AxResult<DynPageIter<VirtAddr>> {
DynPageIter::new(range.start, range.end, align as usize).ok_or(AxError::InvalidInput)
}
type PopulateCallback = Box<dyn FnOnce(&mut AddrSpace)>;
#[enum_dispatch]
pub trait BackendOps {
fn page_size(&self) -> PageSize;
fn map(&self, range: VirtAddrRange, flags: MappingFlags, pt: &mut PageTableCursor) -> AxResult;
fn unmap(&self, range: VirtAddrRange, pt: &mut PageTableCursor) -> AxResult;
fn on_protect(
&self,
_range: VirtAddrRange,
_new_flags: MappingFlags,
_pt: &mut PageTableCursor,
) -> AxResult {
Ok(())
}
fn populate(
&self,
_range: VirtAddrRange,
_flags: MappingFlags,
_access_flags: MappingFlags,
_pt: &mut PageTableCursor,
) -> AxResult<(usize, Option<PopulateCallback>)> {
Ok((0, None))
}
fn clone_map(
&self,
range: VirtAddrRange,
flags: MappingFlags,
old_pt: &mut PageTableCursor,
new_pt: &mut PageTableCursor,
new_aspace: &Arc<Mutex<AddrSpace>>,
) -> AxResult<Backend>;
fn split(&mut self, align_diff: usize) -> Option<Backend>;
fn shrink_left(&mut self, _shrink_size: usize);
fn shrink_right(&mut self, _shrink_size: usize);
}
#[derive(Clone)]
#[enum_dispatch(BackendOps)]
pub enum Backend {
Linear(linear::LinearBackend),
Cow(cow::CowBackend),
Shared(shared::SharedBackend),
File(file::FileBackend),
}
pub struct BackendFileInfo {
pub path: String,
pub offset: Option<u64>,
pub inode: Option<u64>,
pub dev: Option<u64>,
pub shared: bool,
}
impl Backend {
pub fn file_info(&self) -> AxResult<BackendFileInfo> {
match self {
Backend::Cow(b) => b.file_info(),
Backend::Linear(b) => Ok(BackendFileInfo {
path: "".to_string(),
offset: None,
inode: None,
dev: None,
shared: b.is_shared(),
}),
Backend::Shared(_) => Ok(BackendFileInfo {
path: "".to_string(),
offset: None,
inode: None,
dev: None,
shared: true,
}),
Backend::File(b) => b.file_info(),
}
}
pub fn relocated(
&self,
new_start: VirtAddr,
src_offset: usize,
aspace: &Arc<Mutex<AddrSpace>>,
) -> AxResult<Self> {
let adjusted = new_start
.as_usize()
.checked_sub(src_offset)
.map(VirtAddr::from)
.ok_or(AxError::InvalidInput)?;
Ok(match self {
Self::Cow(cb) => Self::Cow(cb.with_start(adjusted)),
Self::Shared(sb) => Self::Shared(sb.with_start(adjusted)),
Self::Linear(_) => return Err(AxError::OperationNotSupported),
Self::File(fb) => Self::File(fb.with_start(adjusted, aspace)),
})
}
}
impl MappingBackend for Backend {
type Addr = VirtAddr;
type Flags = MappingFlags;
type PageTable = PageTable;
fn map(&self, start: VirtAddr, size: usize, flags: MappingFlags, pt: &mut PageTable) -> bool {
let range = VirtAddrRange::from_start_size(start, size);
if let Err(err) = BackendOps::map(self, range, flags, &mut pt.cursor()) {
warn!("Failed to map area: {:?}", err);
false
} else {
true
}
}
fn unmap(&self, start: VirtAddr, size: usize, pt: &mut PageTable) -> bool {
let range = VirtAddrRange::from_start_size(start, size);
if let Err(err) = BackendOps::unmap(self, range, &mut pt.cursor()) {
warn!("Failed to unmap area: {:?}", err);
false
} else {
true
}
}
fn protect(
&self,
start: Self::Addr,
size: usize,
new_flags: Self::Flags,
pt: &mut Self::PageTable,
) -> bool {
let range = VirtAddrRange::from_start_size(start, size);
let mut cursor = pt.cursor();
if let Err(err) = BackendOps::on_protect(self, range, new_flags, &mut cursor) {
warn!("Failed to protect area: {:?}", err);
return false;
}
cursor.protect_region(start, size, new_flags).is_ok()
}
fn split(&mut self, align_diff: usize) -> Option<Self> {
BackendOps::split(self, align_diff)
}
fn shrink_left(&mut self, shrink_size: usize) {
BackendOps::shrink_left(self, shrink_size)
}
fn shrink_right(&mut self, shrink_size: usize) {
BackendOps::shrink_right(self, shrink_size)
}
}