refactor: unsafe only in adapter + libkos; forbid(unsafe_code) everywhere else; libkos Window (volatile, bounds-checked MMIO/DMA/shared mem), entry!, thread::spawn; MapRegion returns pages; kernel const-built (with_cores/install_platform); newtype addrs
This commit is contained in:
@@ -7,12 +7,13 @@ use hw_virtio::mmio::{
|
||||
use libkos::device::{self, Device};
|
||||
use libkos::syscall;
|
||||
use types::badge::Badge;
|
||||
use types::bootinfo::{BOOT_INFO_VA, BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
|
||||
use types::address::VirtualAddress;
|
||||
use types::bootinfo::{BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
|
||||
use types::bounded::CapSlot;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
/// Where init briefly maps a device to ask what it is (top of the window).
|
||||
const PROBE_VA: usize = 0x01F0_0000;
|
||||
const PROBE_VA: VirtualAddress = VirtualAddress::from_raw(0x01F0_0000);
|
||||
|
||||
pub struct Devices {
|
||||
pub disk: Device,
|
||||
@@ -36,10 +37,10 @@ fn virtio_id(region: CapSlot) -> u32 {
|
||||
return 0;
|
||||
};
|
||||
let id = match device::map_mmio(copy, PROBE_VA) {
|
||||
// SAFETY: PROBE_VA maps the device's registers until the drop below.
|
||||
Ok(()) => unsafe {
|
||||
let reg = |off: usize| core::ptr::read_volatile((PROBE_VA + off) as *const u32);
|
||||
if reg(REG_MAGIC) == MAGIC { reg(REG_DEVICE_ID) } else { 0 }
|
||||
// The registers stay mapped until the drop below.
|
||||
Ok(regs) => match regs.read::<u32>(REG_MAGIC) == MAGIC {
|
||||
true => regs.read(REG_DEVICE_ID),
|
||||
false => 0,
|
||||
},
|
||||
Err(_) => 0,
|
||||
};
|
||||
@@ -47,10 +48,9 @@ fn virtio_id(region: CapSlot) -> u32 {
|
||||
id
|
||||
}
|
||||
|
||||
fn boot_info() -> &'static BootInfo {
|
||||
// SAFETY: boot maps the BootInfo page read-only at BOOT_INFO_VA before
|
||||
// init runs; readers check its magic.
|
||||
unsafe { &*(BOOT_INFO_VA as *const BootInfo) }
|
||||
/// What boot left for init; readers check its magic.
|
||||
fn boot_info() -> BootInfo {
|
||||
libkos::boot_info()
|
||||
}
|
||||
|
||||
/// The capability that halts the machine, if boot said where it is.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
@@ -16,7 +17,7 @@ use types::bounded::CapSlot;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
/// Where the buffer storage shares with init is mapped.
|
||||
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
|
||||
/// Boot loads storage before init and leaves its endpoint here.
|
||||
pub(crate) const STORAGE_EP: CapSlot = CapSlot::const_new(2);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const USER_STACK_PAGES: usize = 4;
|
||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||
/// Where a program file is read to before it is loaded (16 pages: the
|
||||
/// largest program init will load).
|
||||
const SCRATCH_VA: usize = 0x30000;
|
||||
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x30000);
|
||||
const SCRATCH_PAGES: u16 = 16;
|
||||
|
||||
pub(crate) struct LoadedProcess {
|
||||
@@ -51,13 +51,9 @@ pub(crate) fn load_binary(
|
||||
let total_size = offset_in_page + segment.mem_size;
|
||||
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
|
||||
let page_count = PageCount::new(seg_pages as u16)?;
|
||||
let local_va = syscall::request_pages(page_count)?;
|
||||
|
||||
let local_ptr = local_va.raw() as *mut u8;
|
||||
unsafe {
|
||||
core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE);
|
||||
}
|
||||
let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?;
|
||||
let local = libkos::window::request(page_count)?;
|
||||
local.fill_u32(0, seg_pages * PAGE_SIZE / 4, 0);
|
||||
|
||||
let copy_len = segment.file_size.min(segment.mem_size);
|
||||
let src = segment
|
||||
@@ -65,15 +61,11 @@ pub(crate) fn load_binary(
|
||||
.checked_add(copy_len)
|
||||
.and_then(|end| elf_data.get(segment.file_offset..end));
|
||||
if let Some(src) = src.filter(|_| copy_len > 0) {
|
||||
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
|
||||
// bytes, which covers offset_in_page + copy_len.
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
|
||||
}
|
||||
local.copy_from(offset_in_page, src);
|
||||
}
|
||||
|
||||
for p in 0..seg_pages {
|
||||
let src_page = VirtualAddress::from_raw(local_va.raw() + p * PAGE_SIZE);
|
||||
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
|
||||
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
|
||||
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
|
||||
}
|
||||
@@ -95,12 +87,5 @@ pub(crate) fn load_binary(
|
||||
/// A region of init's own to read program files into.
|
||||
pub(crate) fn scratch() -> Option<&'static mut [u8]> {
|
||||
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
|
||||
syscall::map_region(
|
||||
region,
|
||||
VirtualAddress::from_raw(SCRATCH_VA),
|
||||
types::mapping::PagePerms::ReadWrite,
|
||||
)
|
||||
.ok()?;
|
||||
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
|
||||
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
|
||||
libkos::window::map_buffer(region, SCRATCH_VA).ok()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![no_main]
|
||||
|
||||
extern crate libkos;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn main() {
|
||||
libkos::entry!(main);
|
||||
|
||||
fn main() {
|
||||
init::run();
|
||||
}
|
||||
|
||||
@@ -9,18 +9,17 @@ use types::bounded::{CapSlot, PageCount};
|
||||
use types::mapping::PagePerms;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
const OBSERVE_VA: usize = 0x0140_0000;
|
||||
const OBSERVE_VA: VirtualAddress = VirtualAddress::from_raw(0x0140_0000);
|
||||
const THREADS: usize = 4;
|
||||
/// QEMU virt's timebase is 10 MHz.
|
||||
const TICKS_PER_MS: u64 = 10_000;
|
||||
|
||||
fn word(i: usize) -> &'static AtomicUsize {
|
||||
// SAFETY: OBSERVE_VA maps the counter page for the whole test.
|
||||
unsafe { &*((OBSERVE_VA + i * 8) as *const AtomicUsize) }
|
||||
}
|
||||
/// The spinner's page as init sees it: word 0 hands out counters, then
|
||||
/// one counter per spinner thread.
|
||||
type Words = &'static [AtomicUsize];
|
||||
|
||||
fn counters() -> [usize; THREADS] {
|
||||
core::array::from_fn(|i| word(1 + i).load(Ordering::SeqCst))
|
||||
fn counters(words: Words) -> [usize; THREADS] {
|
||||
core::array::from_fn(|i| words[1 + i].load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
/// Spins (yielding) until `done` or `ms` milliseconds pass.
|
||||
@@ -36,9 +35,9 @@ fn wait_until(ms: u64, mut done: impl FnMut() -> bool) -> bool {
|
||||
}
|
||||
|
||||
/// Starts a spinner on `page`; its process capability.
|
||||
fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Result<CapSlot, &'static str> {
|
||||
for i in 0..=THREADS {
|
||||
word(i).store(0, Ordering::SeqCst);
|
||||
fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot, words: Words) -> Result<CapSlot, &'static str> {
|
||||
for w in words {
|
||||
w.store(0, Ordering::SeqCst);
|
||||
}
|
||||
let spinner = crate::load::load_binary(storage, scratch, b"bin/smptest", crate::manifest::SMALL).ok_or("cannot load bin/smptest")?;
|
||||
let rw = PermissionSet::empty()
|
||||
@@ -50,17 +49,17 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
|
||||
return Err("cannot give the spinner its page");
|
||||
}
|
||||
crate::launch::start(&spinner, None);
|
||||
if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
|
||||
if !wait_until(3000, || counters(words).iter().all(|c| *c > 0)) {
|
||||
return Err("spinner threads did not all start");
|
||||
}
|
||||
Ok(spinner.process_cap)
|
||||
}
|
||||
|
||||
/// After `stop`, no counter may move.
|
||||
fn check_stopped(what: &'static str) -> Result<(), &'static str> {
|
||||
let before = counters();
|
||||
fn check_stopped(words: Words, what: &'static str) -> Result<(), &'static str> {
|
||||
let before = counters(words);
|
||||
wait_until(200, || false);
|
||||
if counters() != before {
|
||||
if counters(words) != before {
|
||||
return Err(what);
|
||||
}
|
||||
Ok(())
|
||||
@@ -68,19 +67,19 @@ fn check_stopped(what: &'static str) -> Result<(), &'static str> {
|
||||
|
||||
fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static str> {
|
||||
let page = syscall::create_region(PageCount::new(1).ok_or("page count")?).map_err(|_| "no counter page")?;
|
||||
syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite)
|
||||
.map_err(|_| "cannot map the counter page")?;
|
||||
let window = libkos::window::map(page, OBSERVE_VA, PagePerms::ReadWrite).map_err(|_| "cannot map the counter page")?;
|
||||
let words = window.atomics(0, 1 + THREADS);
|
||||
|
||||
let events = syscall::create_notification().map_err(|_| "no notification")?;
|
||||
timer_is_on_time(events)?;
|
||||
|
||||
// Revoke must unmap the page on every hart; the spinner then faults and dies.
|
||||
let spinner = start_spinner(storage, scratch, page)?;
|
||||
let spinner = start_spinner(storage, scratch, page, words)?;
|
||||
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
|
||||
if !syscall::revoke(page) {
|
||||
return Err("revoke failed");
|
||||
}
|
||||
check_stopped("a counter moved after its page was revoked (stale TLB)")?;
|
||||
check_stopped(words, "a counter moved after its page was revoked (stale TLB)")?;
|
||||
if syscall::kill(spinner) {
|
||||
return Err("the spinner survived losing its page");
|
||||
}
|
||||
@@ -90,12 +89,12 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static
|
||||
libkos::println!("smptest: revoke stops every hart: ok");
|
||||
|
||||
// Kill must stop threads running on other harts before it returns.
|
||||
let spinner = start_spinner(storage, scratch, page)?;
|
||||
let spinner = start_spinner(storage, scratch, page, words)?;
|
||||
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
|
||||
if !syscall::kill(spinner) {
|
||||
return Err("kill failed");
|
||||
}
|
||||
check_stopped("a counter moved after its process was killed")?;
|
||||
check_stopped(words, "a counter moved after its process was killed")?;
|
||||
if syscall::poll(events) != Ok(1) {
|
||||
return Err("nobody heard the spinner killed");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user