138 lines
5.2 KiB
Rust
138 lines
5.2 KiB
Rust
//! SMP self-test, run when the disk carries `bin/smptest`: after revoking a
|
|
//! spinner's page or killing it, no spinner counter may move on any hart.
|
|
|
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
|
use libkos::fs::Storage;
|
|
use libkos::syscall;
|
|
use types::address::VirtualAddress;
|
|
use types::bounded::{CapSlot, PageCount};
|
|
use types::mapping::PagePerms;
|
|
use types::permission::{Permission, PermissionSet};
|
|
|
|
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;
|
|
|
|
/// 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(words: Words) -> [usize; THREADS] {
|
|
core::array::from_fn(|i| words[1 + i].load(Ordering::SeqCst))
|
|
}
|
|
|
|
/// Spins (yielding) until `done` or `ms` milliseconds pass.
|
|
fn wait_until(ms: u64, mut done: impl FnMut() -> bool) -> bool {
|
|
let deadline = syscall::get_time().raw() + ms * TICKS_PER_MS;
|
|
while syscall::get_time().raw() < deadline {
|
|
if done() {
|
|
return true;
|
|
}
|
|
syscall::yield_now();
|
|
}
|
|
done()
|
|
}
|
|
|
|
/// Starts a spinner on `page`; its process capability.
|
|
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()
|
|
.with(Permission::Read)
|
|
.with(Permission::Write)
|
|
.with(Permission::Delegate);
|
|
// The page first: the spinner expects it in slot 0.
|
|
if syscall::delegate(page, spinner.process_cap, rw).is_none() {
|
|
return Err("cannot give the spinner its page");
|
|
}
|
|
crate::launch::start(&spinner, None);
|
|
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(words: Words, what: &'static str) -> Result<(), &'static str> {
|
|
let before = counters(words);
|
|
wait_until(200, || false);
|
|
if counters(words) != before {
|
|
return Err(what);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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")?;
|
|
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, words)?;
|
|
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
|
|
if !syscall::revoke(page) {
|
|
return Err("revoke failed");
|
|
}
|
|
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");
|
|
}
|
|
if syscall::poll(events) != Ok(1) {
|
|
return Err("nobody heard the spinner fault");
|
|
}
|
|
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, words)?;
|
|
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
|
|
if !syscall::kill(spinner) {
|
|
return Err("kill failed");
|
|
}
|
|
check_stopped(words, "a counter moved after its process was killed")?;
|
|
if syscall::poll(events) != Ok(1) {
|
|
return Err("nobody heard the spinner killed");
|
|
}
|
|
libkos::println!("smptest: kill stops every hart: ok");
|
|
Ok(())
|
|
}
|
|
|
|
/// A timer must wake its waiter at the deadline, not at the next scheduling
|
|
/// tick (a second away).
|
|
fn timer_is_on_time(events: CapSlot) -> Result<(), &'static str> {
|
|
let start = syscall::get_time().raw();
|
|
let deadline = start + 50 * TICKS_PER_MS;
|
|
syscall::set_timer(events, types::time::Ticks::from_raw(deadline)).map_err(|_| "set_timer failed")?;
|
|
syscall::wait(events).map_err(|_| "wait failed")?;
|
|
let woke = syscall::get_time().raw();
|
|
if woke < deadline {
|
|
return Err("the timer fired early");
|
|
}
|
|
if woke - deadline > 20 * TICKS_PER_MS {
|
|
return Err("the timer fired late");
|
|
}
|
|
libkos::println!("smptest: timer wakes on time: ok");
|
|
Ok(())
|
|
}
|
|
|
|
/// Runs the hart checks; on failure reports and halts the machine, else
|
|
/// returns so the spawn test can run (and report the whole result).
|
|
pub fn run(storage: &mut Storage, scratch: &mut [u8]) {
|
|
let Err(why) = run_phases(storage, scratch) else {
|
|
return;
|
|
};
|
|
libkos::println!("SMPTEST FAIL: {}", why);
|
|
match crate::devices::system_control() {
|
|
Some(control) => syscall::halt(control),
|
|
None => {
|
|
libkos::println!("smptest: no SystemControl capability to halt with");
|
|
crate::idle()
|
|
}
|
|
}
|
|
}
|