use alloc::sync::Arc;
use ax_errno::{AxError, AxResult};
use ax_fs::{FileBackend, FileFlags};
use ax_hal::paging::{MappingFlags, PageSize};
use ax_memory_addr::{MemoryAddr, VirtAddr, VirtAddrRange, align_up_4k};
use ax_task::current;
use linux_raw_sys::general::*;
use starry_vm::{vm_load, vm_write_slice};
use crate::{
file::get_file_like,
mm::{Backend, SharedPages},
pseudofs::{Device, DeviceMmap},
task::AsThread,
};
bitflags::bitflags! {
#[derive(Debug, Clone, Copy)]
struct MmapProt: u32 {
const READ = PROT_READ;
const WRITE = PROT_WRITE;
const EXEC = PROT_EXEC;
const GROWDOWN = PROT_GROWSDOWN;
const GROWSUP = PROT_GROWSUP;
}
}
impl From<MmapProt> for MappingFlags {
fn from(value: MmapProt) -> Self {
let mut flags = MappingFlags::USER;
if value.contains(MmapProt::READ) {
flags |= MappingFlags::READ;
}
if value.contains(MmapProt::WRITE) {
flags |= MappingFlags::WRITE;
}
if value.contains(MmapProt::EXEC) {
flags |= MappingFlags::EXECUTE;
}
flags
}
}
fn capped_device_map_len(request_len: usize, available_len: usize, page_size: PageSize) -> usize {
request_len.min(available_len.align_up(page_size))
}
bitflags::bitflags! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct MmapFlags: u32 {
const SHARED = MAP_SHARED;
const SHARED_VALIDATE = MAP_SHARED_VALIDATE;
const PRIVATE = MAP_PRIVATE;
const FIXED = MAP_FIXED;
const FIXED_NOREPLACE = MAP_FIXED_NOREPLACE;
const ANONYMOUS = MAP_ANONYMOUS;
const POPULATE = MAP_POPULATE;
const NORESERVE = MAP_NORESERVE;
const STACK = MAP_STACK;
const HUGE = MAP_HUGETLB;
const HUGE_1GB = MAP_HUGETLB | MAP_HUGE_1GB;
const DENYWRITE = MAP_DENYWRITE;
const TYPE = MAP_TYPE;
}
}
pub fn sys_mmap(
addr: usize,
length: usize,
prot: u32,
flags: u32,
fd: i32,
offset: isize,
) -> AxResult<isize> {
if length == 0 {
return Err(AxError::InvalidInput);
}
let curr = current();
let mut aspace = curr.as_thread().proc_data.aspace.lock();
let permission_flags = MmapProt::from_bits_truncate(prot);
let map_flags = match MmapFlags::from_bits(flags) {
Some(flags) => flags,
None => {
warn!("unknown mmap flags: {flags}");
if (flags & MmapFlags::TYPE.bits()) == MmapFlags::SHARED_VALIDATE.bits() {
return Err(AxError::OperationNotSupported);
}
MmapFlags::from_bits_truncate(flags)
}
};
let map_type = map_flags & MmapFlags::TYPE;
let type_bits = map_type.bits();
if type_bits != MAP_PRIVATE && type_bits != MAP_SHARED {
return Err(AxError::InvalidInput);
}
if map_flags.contains(MmapFlags::ANONYMOUS) != (fd <= 0) {
return Err(AxError::InvalidInput);
}
if fd <= 0 && offset != 0 {
return Err(AxError::InvalidInput);
}
let offset: usize = offset.try_into().map_err(|_| AxError::InvalidInput)?;
if !PageSize::Size4K.is_aligned(offset) {
return Err(AxError::InvalidInput);
}
debug!(
"sys_mmap <= addr: {addr:#x?}, length: {length:#x?}, prot: {permission_flags:?}, flags: \
{map_flags:?}, fd: {fd:?}, offset: {offset:?}"
);
let page_size = if map_flags.contains(MmapFlags::HUGE_1GB) {
PageSize::Size1G
} else if map_flags.contains(MmapFlags::HUGE) {
PageSize::Size2M
} else {
PageSize::Size4K
};
let start = addr.align_down(page_size);
let end = (addr + length).align_up(page_size);
let mut length = end - start;
let start = if map_flags.intersects(MmapFlags::FIXED | MmapFlags::FIXED_NOREPLACE) {
let dst_addr = VirtAddr::from(start);
if !map_flags.contains(MmapFlags::FIXED_NOREPLACE) {
aspace.unmap(dst_addr, length)?;
}
dst_addr
} else {
let align = page_size as usize;
aspace
.find_free_area(
VirtAddr::from(start),
length,
VirtAddrRange::new(aspace.base(), aspace.end()),
align,
)
.or(aspace.find_free_area(
aspace.base(),
length,
VirtAddrRange::new(aspace.base(), aspace.end()),
align,
))
.ok_or(AxError::NoMemory)?
};
let file = if fd > 0 {
Some(get_file_like(fd)?)
} else {
None
};
let backend = match map_type {
MmapFlags::SHARED | MmapFlags::SHARED_VALIDATE => {
if let Some(ref file) = file {
if let Ok(device_mmap) = file.device_mmap(offset as u64) {
match device_mmap {
DeviceMmap::Physical(mut range) => {
range.start += offset;
if range.is_empty() {
return Err(AxError::InvalidInput);
}
length = length.min(range.size().align_down(page_size));
Backend::new_linear(
start.as_usize() as isize - range.start.as_usize() as isize,
);
}
DeviceMmap::None => return Err(AxError::NoSuchDevice),
_ => return Err(AxError::InvalidInput),
}
}
let (backend, flags) = file.file_mmap()?;
if !flags.contains(FileFlags::READ) {
return Err(AxError::PermissionDenied);
}
if permission_flags.contains(MmapProt::WRITE) && !flags.contains(FileFlags::WRITE) {
return Err(AxError::PermissionDenied);
}
match backend.clone() {
FileBackend::Cached(cache) => {
Backend::new_file(
start,
cache,
flags,
offset,
&curr.as_thread().proc_data.aspace,
)
}
FileBackend::Direct(loc) => {
let device = loc
.entry()
.downcast::<Device>()
.map_err(|_| AxError::NoSuchDevice)?;
match device.mmap(offset as u64) {
DeviceMmap::None => {
return Err(AxError::NoSuchDevice);
}
DeviceMmap::ReadOnly => {
Backend::new_cow(start, page_size, backend, offset as u64, None)
}
DeviceMmap::Physical(range) => {
if range.is_empty() {
return Err(AxError::InvalidInput);
}
length = capped_device_map_len(length, range.size(), page_size);
Backend::new_linear(
start.as_usize() as isize - range.start.as_usize() as isize,
)
}
DeviceMmap::Cache(cache) => Backend::new_file(
start,
cache,
flags,
offset,
&curr.as_thread().proc_data.aspace,
),
}
}
}
} else {
Backend::new_shared(start, Arc::new(SharedPages::new(length, PageSize::Size4K)?))
}
}
MmapFlags::PRIVATE => {
if let Some(ref file) = file {
let (backend, file_flags) = file.file_mmap()?;
if !file_flags.contains(FileFlags::READ) {
return Err(AxError::PermissionDenied);
}
Backend::new_cow(start, page_size, backend, offset as u64, None)
} else {
Backend::new_alloc(start, page_size)
}
}
_ => return Err(AxError::InvalidInput),
};
let populate = map_flags.contains(MmapFlags::POPULATE);
aspace.map(start, length, permission_flags.into(), populate, backend)?;
Ok(start.as_usize() as _)
}
pub fn sys_munmap(addr: usize, length: usize) -> AxResult<isize> {
if length == 0 {
return Err(AxError::InvalidInput);
}
debug!("sys_munmap <= addr: {addr:#x}, length: {length:x}");
let curr = current();
let mut aspace = curr.as_thread().proc_data.aspace.lock();
let length = align_up_4k(length);
let start_addr = VirtAddr::from(addr);
aspace.unmap(start_addr, length)?;
Ok(0)
}
pub fn sys_mprotect(addr: usize, length: usize, prot: u32) -> AxResult<isize> {
let Some(permission_flags) = MmapProt::from_bits(prot) else {
return Err(AxError::InvalidInput);
};
debug!("sys_mprotect <= addr: {addr:#x}, length: {length:x}, prot: {permission_flags:?}");
if permission_flags.contains(MmapProt::GROWDOWN | MmapProt::GROWSUP) {
return Err(AxError::InvalidInput);
}
if !PageSize::Size4K.is_aligned(addr) {
return Err(AxError::InvalidInput);
}
if length == 0 {
return Ok(0);
}
let curr = current();
let mut aspace = curr.as_thread().proc_data.aspace.lock();
let length = align_up_4k(length);
let start_addr = VirtAddr::from(addr);
if aspace.find_area(start_addr).is_none() {
return Err(AxError::NoMemory);
}
aspace.protect(start_addr, length, permission_flags.into())?;
Ok(0)
}
pub fn sys_mremap(addr: usize, old_size: usize, new_size: usize, flags: u32) -> AxResult<isize> {
debug!(
"sys_mremap <= addr: {addr:#x}, old_size: {old_size:x}, new_size: {new_size:x}, flags: \
{flags:#x}"
);
if !addr.is_multiple_of(PageSize::Size4K as usize) {
return Err(AxError::InvalidInput);
}
let addr = VirtAddr::from(addr);
let curr = current();
let aspace = curr.as_thread().proc_data.aspace.lock();
let old_size = align_up_4k(old_size);
let new_size = align_up_4k(new_size);
let area = aspace.find_area(addr).ok_or(AxError::NoMemory)?;
let flags = area.flags();
let mmap_flags = match area.backend() {
Backend::Shared(_) | Backend::File(_) => MmapFlags::SHARED | MmapFlags::ANONYMOUS,
Backend::Cow(_) | Backend::Linear(_) => MmapFlags::PRIVATE | MmapFlags::ANONYMOUS,
};
drop(aspace);
let new_addr = sys_mmap(
addr.as_usize(),
new_size,
flags.bits() as _,
mmap_flags.bits(),
-1,
0,
)? as usize;
let copy_len = new_size.min(old_size);
let data = vm_load(addr.as_ptr(), copy_len)?;
vm_write_slice(new_addr as *mut u8, &data)?;
sys_munmap(addr.as_usize(), old_size)?;
Ok(new_addr as isize)
}
pub fn sys_madvise(addr: usize, length: usize, advice: i32) -> AxResult<isize> {
debug!("sys_madvise <= addr: {addr:#x}, length: {length:x}, advice: {advice:#x}");
match advice as u32 {
MADV_NORMAL | MADV_RANDOM | MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTNEED | MADV_FREE
| MADV_REMOVE | MADV_DONTFORK | MADV_DOFORK | MADV_MERGEABLE | MADV_UNMERGEABLE
| MADV_HUGEPAGE | MADV_NOHUGEPAGE | MADV_DONTDUMP | MADV_DODUMP | MADV_WIPEONFORK
| MADV_KEEPONFORK | MADV_COLD | MADV_PAGEOUT | MADV_POPULATE_READ | MADV_POPULATE_WRITE
| MADV_DONTNEED_LOCKED | MADV_COLLAPSE | MADV_HWPOISON | MADV_SOFT_OFFLINE => {}
_ => return Err(AxError::InvalidInput),
}
if !addr.is_multiple_of(PageSize::Size4K as usize) {
return Err(AxError::InvalidInput);
}
if length > 0 {
let curr = current();
let aspace = curr.as_thread().proc_data.aspace.lock();
if aspace.find_area(VirtAddr::from(addr)).is_none() {
return Err(AxError::NoMemory);
}
}
Ok(0)
}
pub fn sys_msync(addr: usize, length: usize, flags: u32) -> AxResult<isize> {
debug!("sys_msync <= addr: {addr:#x}, length: {length:x}, flags: {flags:#x}");
Ok(0)
}
pub fn sys_mlock(addr: usize, length: usize) -> AxResult<isize> {
sys_mlock2(addr, length, 0)
}
pub fn sys_mlock2(_addr: usize, _length: usize, _flags: u32) -> AxResult<isize> {
Ok(0)
}