xous-core/kernel/src/mem.rs
Sean Cross ebe3efc097 kernel: properly zero out pages prior to handing them to userspace
Until now, we were relying on luck to ensure pages were zeroed.
However, now we properly zero pages in the kernel before handing them
off to userspace.

Signed-off-by: Sean Cross <sean@xobs.io>
2020-03-17 11:35:38 +08:00

559 lines
19 KiB
Rust

use crate::args::KernelArguments;
use crate::services::SystemServicesHandle;
use core::fmt;
use core::mem;
use core::slice;
use core::str;
pub use crate::arch::mem::{MemoryMapping, PAGE_SIZE};
use crate::arch::process::ProcessHandle;
use xous::{MemoryFlags, PID};
#[derive(Debug)]
enum ClaimOrRelease {
Claim,
Release,
}
#[repr(C)]
pub struct MemoryRangeExtra {
mem_start: u32,
mem_size: u32,
mem_tag: u32,
_padding: u32,
}
impl fmt::Display for MemoryRangeExtra {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let tag_name_bytes = self.mem_tag.to_le_bytes();
let s = unsafe {
// First, we build a &[u8]...
let slice = slice::from_raw_parts(tag_name_bytes.as_ptr(), 4);
// ... and then convert that slice into a string slice
str::from_utf8_unchecked(slice)
};
write!(
f,
"{} ({:08x}) {:08x} - {:08x} {} bytes",
s,
self.mem_tag,
self.mem_start,
self.mem_start + self.mem_size,
self.mem_size
)
}
}
pub struct MemoryManager {
allocations: &'static mut [PID],
extra: &'static [MemoryRangeExtra],
ram_start: usize,
ram_size: usize,
ram_name: u32,
last_ram_page: usize,
}
static mut MEMORY_MANAGER: MemoryManager = MemoryManager {
allocations: &mut [],
extra: &[],
ram_start: 0,
ram_size: 0,
ram_name: 0,
last_ram_page: 0,
};
/// How many people have checked out the handle object.
/// This should be replaced by an AtomicUsize when we get
/// multicore support.
/// For now, we can get away with this since the memory manager
/// should only be accessed in an IRQ context.
static mut MM_HANDLE_COUNT: usize = 0;
pub struct MemoryManagerHandle<'a> {
manager: &'a mut MemoryManager,
}
/// Wraps the MemoryManager in a safe mutex. Because of this, accesses
/// to the Memory Manager should only be made during interrupt contexts.
impl<'a> MemoryManagerHandle<'a> {
/// Get the singleton memory manager.
pub fn get() -> MemoryManagerHandle<'a> {
let count = unsafe {
MM_HANDLE_COUNT += 1;
MM_HANDLE_COUNT - 1
};
if count != 0 {
panic!("Multiple users of MemoryManagerHandle!");
}
MemoryManagerHandle {
manager: unsafe { &mut MEMORY_MANAGER },
}
}
}
impl Drop for MemoryManagerHandle<'_> {
fn drop(&mut self) {
unsafe { MM_HANDLE_COUNT -= 1 };
}
}
use core::ops::{Deref, DerefMut};
impl Deref for MemoryManagerHandle<'_> {
type Target = MemoryManager;
fn deref(&self) -> &MemoryManager {
&*self.manager
}
}
impl DerefMut for MemoryManagerHandle<'_> {
fn deref_mut(&mut self) -> &mut MemoryManager {
&mut *self.manager
}
}
/// Initialize the memory map.
/// This will go through memory and map anything that the kernel is
/// using to process 1, then allocate a pagetable for this process
/// and place it at the usual offset. The MMU will not be enabled yet,
/// as the process entry has not yet been created.
impl MemoryManager {
pub fn init(&mut self, base: *mut u32, args: &KernelArguments) -> Result<(), xous::Error> {
let mut args_iter = args.iter();
let xarg_def = args_iter.next().expect("mm: no kernel arguments found");
assert!(
self.extra.len() == 0,
"mm: self.extra.len() was {}, not 0",
self.extra.len()
);
assert!(
xarg_def.name == make_type!("XArg"),
"mm: first tag wasn't XArg"
);
assert!(xarg_def.data[1] == 1, "mm: XArg had unexpected version");
self.ram_start = xarg_def.data[2] as usize;
self.ram_size = xarg_def.data[3] as usize;
self.ram_name = xarg_def.data[4];
let mut mem_size = self.ram_size / PAGE_SIZE;
for tag in args_iter {
if tag.name == make_type!("MREx") {
assert!(
self.extra.len() == 0,
"mm: MREx tag appears twice! self.extra.len() is {}, not 0",
self.extra.len()
);
let ptr = tag.data.as_ptr() as *mut MemoryRangeExtra;
self.extra = unsafe {
slice::from_raw_parts_mut(
ptr,
tag.data.len() * 4 / mem::size_of::<MemoryRangeExtra>(),
)
};
}
}
for range in self.extra.iter() {
mem_size += range.mem_size as usize / PAGE_SIZE;
}
self.allocations = unsafe { slice::from_raw_parts_mut(base as *mut PID, mem_size) };
Ok(())
}
#[allow(dead_code)]
pub fn print_ownership(&self) {
println!("Ownership ({} bytes in all):", self.allocations.len());
let mut offset = 0;
unsafe {
// First, we build a &[u8]...
let name_bytes = self.ram_name.to_le_bytes();
// ... and then convert that slice into a string slice
let _ram_name = str::from_utf8_unchecked(&name_bytes);
println!(
" Region {} ({:08x}) {:08x} - {:08x} {} bytes:",
_ram_name,
self.ram_name,
self.ram_start,
self.ram_start + self.ram_size,
self.ram_size
);
};
for o in 0..self.ram_size / PAGE_SIZE {
if self.allocations[offset + o] != 0 {
println!(
" {:08x} => {}",
self.ram_size + o * PAGE_SIZE,
self.allocations[o]
);
}
}
offset += self.ram_size / PAGE_SIZE;
// Go through additional regions looking for this address, and claim it
// if it's not in use.
for region in self.extra {
println!(" Region {}:", region);
for o in 0..(region.mem_size as usize) / PAGE_SIZE {
if self.allocations[offset + o] != 0 {
println!(
" {:08x} => {}",
(region.mem_start as usize) + o * PAGE_SIZE,
self.allocations[offset + o]
)
}
}
offset += region.mem_size as usize / PAGE_SIZE;
}
}
/// Allocate a single page to the given process.
/// DOES NOT ZERO THE PAGE!!!
/// This function CANNOT zero the page, as it hasn't been mapped yet.
pub fn alloc_page(&mut self, pid: PID) -> Result<usize, xous::Error> {
// Go through all RAM pages looking for a free page.
// Optimization: start from the previous address.
// println!("Allocating page for PID {}", pid);
for index in self.last_ram_page..((self.ram_size as usize) / PAGE_SIZE) {
// println!(" Checking {:08x}...", index * PAGE_SIZE + self.ram_start as usize);
if self.allocations[index] == 0 {
self.allocations[index] = pid;
self.last_ram_page = index + 1;
let page = index * PAGE_SIZE + self.ram_start;
return Ok(page);
}
}
for index in 0..self.last_ram_page {
// println!(" Checking {:08x}...", index * PAGE_SIZE + self.ram_start as usize);
if self.allocations[index] == 0 {
self.allocations[index] = pid;
self.last_ram_page = index + 1;
let page = index * PAGE_SIZE + self.ram_start;
return Ok(page);
}
}
Err(xous::Error::OutOfMemory)
}
/// Find a virtual address in the current process that is big enough
/// to fit `size` bytes.
fn find_virtual_address(
&mut self,
virt_ptr: *mut usize,
size: usize,
kind: xous::MemoryType,
) -> Result<usize, xous::Error> {
// If we were supplied a perfectly good address, return that.
if virt_ptr as usize != 0 {
return Ok(virt_ptr as usize);
}
let mut process = ProcessHandle::get();
let (start, end, initial) = match kind {
xous::MemoryType::Stack => return Err(xous::Error::BadAddress),
xous::MemoryType::Heap => {
let new_virt =
process.inner.mem_heap_base + process.inner.mem_heap_size + PAGE_SIZE;
if new_virt + size > process.inner.mem_heap_base + process.inner.mem_heap_max {
return Err(xous::Error::OutOfMemory);
}
return Ok(new_virt);
}
xous::MemoryType::Default => (
process.inner.mem_default_base,
process.inner.mem_default_base + 0x10000000,
process.inner.mem_default_last,
),
xous::MemoryType::Messages => (
process.inner.mem_message_base,
process.inner.mem_message_base + 0x10000000,
process.inner.mem_message_last,
),
};
// Look for a sequence of `size` pages that are free.
for potential_start in (initial..end - size).step_by(PAGE_SIZE) {
println!(" Checking {:08x}...", potential_start);
let mut all_free = true;
for check_page in (potential_start..potential_start + size).step_by(PAGE_SIZE) {
if !crate::arch::mem::address_available(check_page) {
all_free = false;
break;
}
}
if all_free {
match kind {
xous::MemoryType::Default => process.inner.mem_default_last = potential_start,
xous::MemoryType::Messages => process.inner.mem_message_last = potential_start,
other => panic!("invalid kind: {:?}", other),
}
return Ok(potential_start);
}
}
for potential_start in (start..initial).step_by(PAGE_SIZE) {
println!(" Checking {:08x}...", potential_start);
let mut all_free = true;
for check_page in (potential_start..potential_start + size).step_by(PAGE_SIZE) {
if !crate::arch::mem::address_available(check_page) {
all_free = false;
break;
}
}
if all_free {
match kind {
xous::MemoryType::Default => process.inner.mem_default_last = potential_start,
xous::MemoryType::Messages => process.inner.mem_message_last = potential_start,
other => panic!("invalid kind: {:?}", other),
}
return Ok(potential_start);
}
}
Err(xous::Error::BadAddress)
}
/// Reserve the given range without actually allocating memory.
/// That way we can overpromise on stack size and heap size without
/// needing to actually have pages to back it.
pub fn reserve_range(
&mut self,
virt_ptr: *mut usize,
size: usize,
flags: MemoryFlags,
) -> Result<xous::Result, xous::Error> {
// If no address was specified, pick the next address that fits
// in the "default" range
let virt = self.find_virtual_address(virt_ptr, size, xous::MemoryType::Default)?;
if virt & 0xfff != 0 {
return Err(xous::Error::BadAlignment);
}
if size & 0xfff != 0 {
return Err(xous::Error::BadAlignment);
}
let mut mm = MemoryMapping::current();
for virt in (virt..(virt + size)).step_by(PAGE_SIZE) {
// FIXME: Un-reserve addresses if we encounter an error here
mm.reserve_address(self, virt, flags)?;
}
Ok(xous::Result::MemoryRange(xous::MemoryRange {
base: virt_ptr as *mut u8,
size,
}))
}
/// Attempt to allocate a single page from the default section.
/// Note that this will be backed by a real page.
pub fn map_zeroed_page(&mut self, pid: PID) -> Result<*mut usize, xous::Error> {
let virt =
self.find_virtual_address(0 as *mut usize, PAGE_SIZE, xous::MemoryType::Default)?;
// Grab the next available page. This claims it for this process.
let phys = self.alloc_page(pid)?;
// Actually perform the map. At this stage, every physical page should be owned by us.
if let Err(e) = crate::arch::mem::map_page_inner(
self,
pid,
phys as usize,
virt as usize,
xous::MemoryFlags::R | xous::MemoryFlags::W,
) {
self.release_page(phys as *mut usize, pid).ok();
return Err(e);
}
let virt = virt as *mut usize;
// Zero-out the page
unsafe { virt.write_bytes(0, PAGE_SIZE / mem::size_of::<usize>()) };
crate::arch::mem::hand_page_to_user(virt)?;
Ok(virt)
}
pub fn is_main_memory(&self, phys: *mut usize) -> bool {
(phys as usize) >= self.ram_start && (phys as usize) < self.ram_start + self.ram_size
}
/// Attempt to map the given physical address into the virtual address space
/// of this process.
///
/// # Errors
///
/// * MemoryInUse - The specified page is already mapped
pub fn map_range(
&mut self,
phys_ptr: *mut usize,
virt_ptr: *mut usize,
size: usize,
flags: MemoryFlags,
) -> Result<xous::Result, xous::Error> {
let phys = phys_ptr as usize;
let virt = self.find_virtual_address(virt_ptr, size, xous::MemoryType::Default)?;
let pid = {
let ss = SystemServicesHandle::get();
ss.current_pid()
};
// If no physical address is specified, give the user the next available pages
if phys == 0 {
return self.reserve_range(virt as *mut usize, size, flags);
}
// 1. Attempt to claim all physical pages in the range
for claim_phys in (phys..(phys + size)).step_by(PAGE_SIZE) {
if let Err(err) = self.claim_page(claim_phys as *mut usize, pid) {
// If we were unable to claim one or more pages, release everything and return
for rel_phys in (phys..claim_phys).step_by(PAGE_SIZE) {
self.release_page(rel_phys as *mut usize, pid).ok();
}
return Err(err);
}
}
// Actually perform the map. At this stage, every physical page should be owned by us.
for offset in (0..size).step_by(PAGE_SIZE) {
if let Err(e) = crate::arch::mem::map_page_inner(
self,
pid,
offset + phys as usize,
offset + virt as usize,
flags,
) {
for unmap_offset in (0..offset).step_by(PAGE_SIZE) {
crate::arch::mem::unmap_page_inner(self, unmap_offset + virt).ok();
self.release_page((unmap_offset + phys) as *mut usize, pid)
.ok();
}
return Err(e);
}
}
Ok(xous::Result::MemoryRange(xous::MemoryRange {
base: virt as *mut u8,
size,
}))
}
// /// Map a range of physical addresses into the current memory space.
// pub fn map_range(&mut self, phys: *mut usize, virt: *mut usize, size: usize, flags: MemoryFlags) -> xous::Result {
// // If a physical address range was requested, verify that it is valid
// if phys as usize != 0 {
// }
// let virt = if virt as usize == 0 {
// // No virt address was
// }
// for offset in (0..size).step_by(4096) {
// if let xous::Result::Error(e) = crate::arch::mem::
// map_page_inner(
// ((phys as usize) + offset) as usize,
// ((virt as usize) + offset) as usize,
// req_flags,
// )
// .map(|x| xous::Result::MemoryAddress(x.get() as *mut usize))
// .unwrap_or_else(|e| xous::Result::Error(e))
// {
// result = xous::Result::Error(e);
// break;
// }
// last_mapped = offset;
// }
// if result != xous::Result::Ok {
// for offset in (0..last_mapped).step_by(4096) {
// mm.unmap_page(
// ((phys as usize) + offset) as *mut usize,
// ((virt as usize) + offset) as *mut usize,
// req_flags,
// )
// .expect("couldn't unmap page");
// }
// }
// result
// }
/// Attempt to map the given physical address into the virtual address space
/// of this process.
///
/// # Errors
///
/// * MemoryInUse - The specified page is already mapped
pub fn unmap_page(&mut self, virt: *mut usize) -> Result<(), xous::Error> {
let pid = crate::arch::current_pid();
let phys = crate::arch::mem::virt_to_phys(virt as usize)?;
self.release_page(phys as *mut usize, pid)?;
crate::arch::mem::unmap_page_inner(self, virt as usize)
}
fn claim_or_release(
&mut self,
addr: *mut usize,
pid: PID,
action: ClaimOrRelease,
) -> Result<(), xous::Error> {
fn action_inner(
addr: &mut PID,
pid: PID,
action: ClaimOrRelease,
) -> Result<(), xous::Error> {
if *addr != 0 && *addr != pid {
return Err(xous::Error::MemoryInUse);
}
match action {
ClaimOrRelease::Claim => {
*addr = pid;
}
ClaimOrRelease::Release => {
*addr = 0;
}
}
Ok(())
}
let addr = addr as usize;
// Ensure the address lies on a page boundary
if addr & 0xfff != 0 {
return Err(xous::Error::BadAlignment);
}
let mut offset = 0;
// Happy path: The address is in main RAM
if addr >= self.ram_start && addr < self.ram_start + self.ram_size {
offset += (addr - self.ram_start) / PAGE_SIZE;
return action_inner(&mut self.allocations[offset], pid, action);
}
offset += self.ram_size / PAGE_SIZE;
// Go through additional regions looking for this address, and claim it
// if it's not in use.
for region in self.extra {
if addr >= (region.mem_start as usize)
&& addr < (region.mem_start + region.mem_size) as usize
{
offset += (addr - (region.mem_start as usize)) / PAGE_SIZE;
return action_inner(&mut self.allocations[offset], pid, action);
}
offset += region.mem_size as usize / PAGE_SIZE;
}
println!(
"mem: unable to claim or release physical address {:08x}",
addr
);
Err(xous::Error::BadAddress)
}
/// Mark a given address as being owned by the specified process ID
fn claim_page(&mut self, addr: *mut usize, pid: PID) -> Result<(), xous::Error> {
self.claim_or_release(addr, pid, ClaimOrRelease::Claim)
}
/// Mark a given address as no longer being owned by the specified process ID
fn release_page(&mut self, addr: *mut usize, pid: PID) -> Result<(), xous::Error> {
self.claim_or_release(addr, pid, ClaimOrRelease::Release)
}
}