kernel: initial work to support lending pages

This lays groundwork to support lending pages from one process to
another, and to restore permissions when pages are handed back.

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2020-04-02 17:40:14 +08:00
parent bedb499edb
commit ed977fd564
4 changed files with 95 additions and 57 deletions

View file

@ -46,7 +46,6 @@ assigned to every process simply by mapping megapage 1023 (`0xffc00000`).
| Address | Description
| ---------- | -----------
0x10000
| 0x00100000 | Default entrypoint for riscv64-unknown-elf-ld (as shown by `riscv64-unknown-elf-ld --verbose`)
| 0x80000000 | Process stack top
| 0xff000000 | End of memory available to processes
@ -151,3 +150,29 @@ struct PageAllocations {
Each process requires its own page table. The kernel will be mapped to
a fixed offset in each process, in order to save some RAM and make
context switches easier.
## RISC-V `RSW` and `V` Page Table Entry Fields
The RISC-V Page Table Entry specification reserves two bits in a field
called `RSW`. Additionally, it states that if `V` is `0`, then the
entry is invalid and all other entries are `don't care`. In particular,
the `RSW` fields are for use by the kernel for its own purposes. Xous
uses these to keep track of borrowed memory and pre-allocating pages.
For the purposes of Xous, `PTE[8]` shall have the bit name `S`, and
`PTE[9]` shall have the bit name `W`.
| W[9] | S[8] | X[3] | W[2] | R[1] | V[0] | Meaning |
| ---- | ---- | ---- | ---- | ---- | ---- | --------------------- |
| 0 | 0 | 0 | 0 | 0 | 0 | Page is unallocated |
| 0 | 0 | 0 | 1 | 0 | 0 | _Invalid_ |
| 0 | 0 | 1 | 1 | 0 | 0 | _Invalid_ |
| 0 | 0 | 0 | 0 | 0 | 1 | _Invalid_ |
| 0 | 0 | 0 | 1 | 0 | 1 | _Invalid_ |
| 0 | 0 | 1 | 1 | 0 | 1 | _Invalid_ |
| 0 | 0 | _x_ | _x_ | _x_ | 0 | Page is on-demand allocated, with permissions according to _RWX_ |
| 0 | 0 | _x_ | _x_ | _x_ | 1 | Page is allocated and valid, with permissions according to _RWX_ |
| _x_ | 1 | _x_ | 0 | _x_ | 1 | Page is immutably shared |
| _x_ | 1 | 0 | 0 | _x_ | 0 | Page is mutably shared (and is therefore unavailable) |
The `W[9]` bit indicates whether the page was writable prior to the borrow.

View file

@ -82,11 +82,11 @@ fn main() {
// if let xous::Result::MemoryRange(range) = heap {
// println!(
// "Heap goes from {:08x} - {:08x}",
// range.base as usize,
// range.base as usize + range.size
// range.addr.get(),
// range.addr.get() + range.size.get()
// );
// use core::slice;
// let mem_range = unsafe { slice::from_raw_parts_mut(range.base, range.size) };
// let mem_range = unsafe { slice::from_raw_parts_mut(range.as_mut_ptr() as *mut u8, range.len()) };
// println!("Filling with bytes...");
// for word in mem_range.iter_mut() {
// *word = 42;

View file

@ -130,21 +130,20 @@ pub extern "C" fn trap_handler(
match ex {
RiscvException::StorePageFault(pc, addr) | RiscvException::LoadPageFault(pc, addr) => {
println!("Fault {} @ {:08x}, addr {:08x}", ex, pc, addr);
let mapping = MemoryMapping::current();
let entry = mapping.pagetable_entry(addr);
if entry as usize == 0 {
let entry = crate::arch::mem::pagetable_entry(addr).unwrap_or_else(|x| {
// MemoryManagerHandle::get().print_ownership();
MemoryMapping::current().print_map();
panic!(
"error at {:08x}: memory not mapped or reserved for addr {:08x}",
pc, addr
);
}
let flags = unsafe { entry.read_volatile() } & 0xf;
});
let flags = *entry & 0x1ff;
// If the flags are nonzero, but the "Valid" bit is not 1, then this is
// a reserved page. Allocate a real page to back it and resume execution.
if flags & 1 == 0 && flags != 0 {
// If the flags are nonzero, but the "Valid" bit is not 1 and
// the page isn't shared, then this is a reserved page. Allocate
// a real page to back it and resume execution.
if flags & 1 == 0 && flags != 0 && flags & (1 << 8) == 0 {
let new_page = {
let mut mm = MemoryManagerHandle::get();
mm.alloc_page(pid).expect("Couldn't allocate new page")
@ -153,8 +152,9 @@ pub extern "C" fn trap_handler(
let ppn0 = (new_page >> 12) & ((1 << 10) - 1);
unsafe {
// Map the page to our process
entry.write_volatile((ppn1 << 20) | (ppn0 << 10) |
(flags | (1 << 0) /* valid */ | (1 << 6) /* D */ | (1 << 7) /* A */));
*entry = (ppn1 << 20)
| (ppn0 << 10)
| (flags | (1 << 0) /* valid */ | (1 << 6) /* D */ | (1 << 7)/* A */);
flush_mmu();
// Zero-out the page
@ -163,8 +163,9 @@ pub extern "C" fn trap_handler(
.write_bytes(0, PAGE_SIZE / core::mem::size_of::<usize>());
// Move the page into userspace
entry.write_volatile((ppn1 << 20) | (ppn0 << 10) |
(flags | (1 << 0) /* valid */ | (1 << 4) /* USER */ | (1 << 6) /* D */ | (1 << 7) /* A */));
*entry = (ppn1 << 20)
| (ppn0 << 10)
| (flags | (1 << 0) /* valid */ | (1 << 4) /* USER */ | (1 << 6) /* D */ | (1 << 7)/* A */);
flush_mmu();
};
@ -177,7 +178,9 @@ pub extern "C" fn trap_handler(
// we're in an interrupt context, it is safe to access this
// global variable.
let (previous_pid, previous_context) = unsafe {
PREVIOUS_PAIR.take().expect("got an instruction page fault with no previous PID")
PREVIOUS_PAIR
.take()
.expect("got an instruction page fault with no previous PID")
};
println!(
"ISR: Resuming previous pair of ({}, {})",

View file

@ -139,21 +139,6 @@ impl MemoryMapping {
println!("End of map");
}
/// Get the pagetable entry for a given address, or `0` if none is set.
pub fn pagetable_entry(&self, addr: usize) -> *mut usize {
let vpn1 = (addr >> 22) & ((1 << 10) - 1);
let vpn0 = (addr >> 12) & ((1 << 10) - 1);
let l1_pt = unsafe { &(*(PAGE_TABLE_ROOT_OFFSET as *mut RootPageTable)) };
let l1_pte = l1_pt.entries[vpn1];
if l1_pte & 1 == 0 {
return 0 as *mut usize;
}
let l0_pt_virt = PAGE_TABLE_OFFSET + vpn1 * PAGE_SIZE;
let entry = (l0_pt_virt + vpn0 * 4) as *mut usize;
entry
}
pub fn reserve_address(
&mut self,
mm: &mut MemoryManager,
@ -370,6 +355,26 @@ pub fn map_page_inner(
Ok(())
}
/// Get the pagetable entry for a given address, or `Err()` if the address is invalid
pub fn pagetable_entry(addr: usize) -> Result<&'static mut usize, xous::Error> {
if addr & 3 != 0 {
return Err(xous::Error::BadAlignment);
}
let vpn1 = (addr >> 22) & ((1 << 10) - 1);
let vpn0 = (addr >> 12) & ((1 << 10) - 1);
assert!(vpn1 < 1024);
assert!(vpn0 < 1024);
let l1_pt = unsafe { &(*(PAGE_TABLE_ROOT_OFFSET as *mut RootPageTable)) };
let l1_pte = l1_pt.entries[vpn1];
if l1_pte & 1 == 0 {
return Err(xous::Error::BadAddress);
}
let l0_pt_virt = PAGE_TABLE_OFFSET + vpn1 * PAGE_SIZE;
let entry = unsafe { &mut (*((l0_pt_virt + vpn0 * 4) as *mut usize)) };
Ok(entry)
}
/// Ummap the given page from the specified process table. Never allocate a new
/// page.
///
@ -381,40 +386,45 @@ pub fn map_page_inner(
///
/// * BadAddress - Address was not already mapped.
pub fn unmap_page_inner(_mm: &mut MemoryManager, virt: usize) -> Result<usize, xous::Error> {
let vpn1 = (virt >> 22) & ((1 << 10) - 1);
let vpn0 = (virt >> 12) & ((1 << 10) - 1);
let vpo = (virt >> 0) & ((1 << 12) - 1);
assert!(vpn1 < 1024);
assert!(vpn0 < 1024);
assert!(vpo < 4096);
// The root (l1) pagetable is defined to be mapped into our virtual
// address space at this address.
let l1_pt = unsafe { &mut (*(PAGE_TABLE_ROOT_OFFSET as *mut RootPageTable)) };
let ref mut l1_pt = l1_pt.entries;
// Subsequent pagetables are defined as being mapped starting at
// offset 0x0020_0004, so 4 must be added to the ppn1 value.
let l0pt_virt = PAGE_TABLE_OFFSET + vpn1 * PAGE_SIZE;
let ref mut l0_pt = unsafe { &mut (*(l0pt_virt as *mut LeafPageTable)) };
// If the level 1 pagetable doesn't exist, then this address isn't valid.
if l1_pt[vpn1] & MMUFlags::VALID.bits() == 0 {
return Err(xous::Error::BadAddress);
}
let mut entry = pagetable_entry(virt)?;
// Ensure the entry hasn't already been mapped.
if l0_pt.entries[vpn0] & 1 == 0 {
if *entry & 1 == 0 {
return Err(xous::Error::BadAddress);
}
let phys = (l0_pt.entries[vpn0] >> 10) << 12;
l0_pt.entries[vpn0] = 0;
let phys = (*entry >> 10) << 12;
*entry = 0;
unsafe { flush_mmu() };
Ok(phys)
}
/// Mark the given virtual address as being lent. If `writable`, clear the
/// `valid` bit so that this process can't accidentally write to this page while
/// it is lent.
///
/// This uses the `RWS` fields to keep track of the following pieces of information:
///
/// * **PTE[8]**: This is set to `1` indicating the page is lent
/// * **PTE[9]**: This is `1` if the page was previously writable
///
/// # Returns
///
/// # Errors
///
/// * BadAddress - Address was not already mapped.
pub fn lend_page_inner(
_mm: &mut MemoryManager,
virt: usize,
writable: bool,
) -> Result<(), xous::Error> {
let mut entry = pagetable_entry(virt)?;
*entry = 0;
unsafe { flush_mmu() };
Ok(())
}
pub fn virt_to_phys(virt: usize) -> Result<usize, xous::Error> {
let vpn1 = (virt >> 22) & ((1 << 10) - 1);
let vpn0 = (virt >> 12) & ((1 << 10) - 1);