- FrameTable: per-frame owner/use, replaces PagePool + ProcessMemory - Quota per process, page tables charged to owner - Regions for shared mem + device MMIO, torn down w/ owner - Platform: create/destroy AS, map/unmap, zero_frame; drop translate_va - RequestPages/ReleasePages/MapPage(move) checked vs kernel records - user window 0x1000..32MiB, private pts by construction - boot loader + device setup go through kernel api - kernel built in place, boot allocator bounded - isolation tests, randomized invariant test, FrameTable kani proofs - fixes: inflated release, unmapped release, dst va unchecked, heap overflow, unbounded pt alloc, unzeroed frames, double free
189 lines
5.1 KiB
Rust
189 lines
5.1 KiB
Rust
use super::*;
|
|
use types::address::{PhysicalAddress, VirtualAddress};
|
|
use types::id::{ProcessId, RegionId};
|
|
use types::mapping::PagePerms;
|
|
|
|
const BASE: usize = 0x8400_0000;
|
|
|
|
fn table() -> FrameTable<8> {
|
|
FrameTable::new(PhysicalAddress::from_raw(BASE), 8)
|
|
}
|
|
|
|
fn pid(n: u16) -> ProcessId {
|
|
ProcessId::from_raw(n)
|
|
}
|
|
|
|
fn va(addr: usize) -> VirtualAddress {
|
|
VirtualAddress::from_raw(addr)
|
|
}
|
|
|
|
fn user(owner: u16, addr: usize) -> FrameUse {
|
|
FrameUse::user(pid(owner), va(addr), PagePerms::ReadWrite).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn new_table_has_every_frame_free() {
|
|
let t = table();
|
|
assert_eq!(t.free_count(), 8);
|
|
assert_eq!(t.capacity(), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn total_is_clamped_to_capacity() {
|
|
let t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 100);
|
|
assert_eq!(t.free_count(), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn user_use_requires_page_aligned_va() {
|
|
assert!(FrameUse::user(pid(0), va(0x1234), PagePerms::ReadOnly).is_none());
|
|
assert!(FrameUse::user(pid(0), va(0x1000), PagePerms::ReadOnly).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_hands_out_distinct_frames_inside_the_pool() {
|
|
let mut t = table();
|
|
let a = t.alloc(user(0, 0x1000)).unwrap();
|
|
let b = t.alloc(user(0, 0x2000)).unwrap();
|
|
assert_ne!(a, b);
|
|
for pa in [a, b] {
|
|
assert!(pa.raw() >= BASE && pa.raw() < BASE + 8 * 4096);
|
|
assert_eq!(pa.raw() % 4096, 0);
|
|
}
|
|
assert_eq!(t.free_count(), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_fails_when_exhausted() {
|
|
let mut t = table();
|
|
for i in 0..8 {
|
|
t.alloc(user(0, (i + 1) * 0x1000)).unwrap();
|
|
}
|
|
assert!(t.alloc(user(0, 0x9000)).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn free_returns_the_frame_and_its_use() {
|
|
let mut t = table();
|
|
let pa = t.alloc(user(3, 0x5000)).unwrap();
|
|
assert_eq!(t.free(pa), Some(user(3, 0x5000)));
|
|
assert_eq!(t.free_count(), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn freeing_a_free_frame_is_rejected() {
|
|
let mut t = table();
|
|
let pa = t.alloc(user(0, 0x1000)).unwrap();
|
|
t.free(pa).unwrap();
|
|
assert_eq!(t.free(pa), None);
|
|
assert_eq!(t.free_count(), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn freeing_outside_the_pool_or_unaligned_is_rejected() {
|
|
let mut t = table();
|
|
t.alloc(user(0, 0x1000)).unwrap();
|
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE - 4096)), None);
|
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 8 * 4096)), None);
|
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 1)), None);
|
|
assert_eq!(t.free_count(), 7);
|
|
}
|
|
|
|
#[test]
|
|
fn find_user_matches_owner_and_va() {
|
|
let mut t = table();
|
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
|
assert_eq!(
|
|
t.find_user(pid(1), va(0x3000)),
|
|
Some((pa, PagePerms::ReadWrite))
|
|
);
|
|
assert_eq!(t.find_user(pid(2), va(0x3000)), None);
|
|
assert_eq!(t.find_user(pid(1), va(0x4000)), None);
|
|
}
|
|
|
|
#[test]
|
|
fn retag_moves_a_frame_to_a_new_use() {
|
|
let mut t = table();
|
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
|
assert!(t.retag(pa, user(2, 0x7000)));
|
|
assert_eq!(t.find_user(pid(1), va(0x3000)), None);
|
|
assert_eq!(t.find_user(pid(2), va(0x7000)).map(|(p, _)| p), Some(pa));
|
|
}
|
|
|
|
#[test]
|
|
fn retag_of_a_free_frame_is_rejected() {
|
|
let mut t = table();
|
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
|
t.free(pa);
|
|
assert!(!t.retag(pa, user(2, 0x7000)));
|
|
assert_eq!(t.free_count(), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_contiguous_returns_adjacent_frames() {
|
|
let mut t = table();
|
|
let base = t
|
|
.alloc_contiguous(3, |_| FrameUse::Region {
|
|
region: RegionId::from_raw(0),
|
|
})
|
|
.unwrap();
|
|
assert_eq!(t.free_count(), 5);
|
|
for i in 0..3 {
|
|
assert_eq!(
|
|
t.get(base.offset(i)),
|
|
Some(&FrameUse::Region {
|
|
region: RegionId::from_raw(0)
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_contiguous_fails_without_a_long_enough_run() {
|
|
let mut t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 4);
|
|
let a = t.alloc(user(0, 0x1000)).unwrap();
|
|
let _b = t.alloc(user(0, 0x2000)).unwrap();
|
|
let _c = t.alloc(user(0, 0x3000)).unwrap();
|
|
t.free(a);
|
|
// free frames: 0 and 3 — no run of 2
|
|
assert!(
|
|
t.alloc_contiguous(2, |_| FrameUse::PageTable { owner: pid(0) })
|
|
.is_none()
|
|
);
|
|
assert_eq!(t.free_count(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn free_owned_by_releases_only_that_processes_frames() {
|
|
let mut t = table();
|
|
t.alloc(user(1, 0x1000)).unwrap();
|
|
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
|
|
let other = t.alloc(user(2, 0x1000)).unwrap();
|
|
let region = t
|
|
.alloc(FrameUse::Region {
|
|
region: RegionId::from_raw(1),
|
|
})
|
|
.unwrap();
|
|
|
|
assert_eq!(t.free_owned_by(pid(1)), 2);
|
|
assert_eq!(t.free_count(), 6);
|
|
assert!(t.get(other).is_some());
|
|
assert!(t.get(region).is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn user_frames_of_lists_mappings_of_one_process() {
|
|
let mut t = table();
|
|
t.alloc(user(1, 0x1000)).unwrap();
|
|
t.alloc(user(2, 0x2000)).unwrap();
|
|
t.alloc(user(1, 0x3000)).unwrap();
|
|
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
|
|
let mut vas = [0usize; 4];
|
|
let mut n = 0;
|
|
for (_, v) in t.user_frames_of(pid(1)) {
|
|
vas[n] = v.raw();
|
|
n += 1;
|
|
}
|
|
assert_eq!(&vas[..n], &[0x1000, 0x3000]);
|
|
}
|