mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-08 04:56:43 +00:00
"detached-apps" are Xous applications loaded into a region of memory-mapped FLASH (and thus memory-mapped applications) that are separate from the main xous.img image. This allows developers to work on "just their app" instead of having to upload the whole Xous ball. Currently the only configuration that supports this is the dabao configuration.
375 lines
12 KiB
Rust
375 lines
12 KiB
Rust
use std::sync::Mutex;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use crate::BootConfig;
|
|
|
|
fn get_args_bin(idx: usize) -> &'static [u8] {
|
|
match idx {
|
|
0 => include_bytes!("../test/args-default.bin"),
|
|
1 => include_bytes!("../test/args-span-page.bin"),
|
|
_ => panic!("unrecognized args index"),
|
|
}
|
|
}
|
|
|
|
const REGION_COUNT: usize = 8;
|
|
static mut MEMORY_REGIONS: [[usize; 1024 * 1024]; REGION_COUNT] = [[0usize; 1024 * 1024]; REGION_COUNT];
|
|
lazy_static! {
|
|
static ref REGIONS_CHECKED_OUT: Mutex<[bool; REGION_COUNT]> = Mutex::new([false; REGION_COUNT]);
|
|
}
|
|
|
|
struct FakeMemory {
|
|
pub region: &'static mut [usize; 1024 * 1024],
|
|
index: usize,
|
|
}
|
|
|
|
impl FakeMemory {
|
|
pub fn get() -> Self {
|
|
unsafe {
|
|
let mut store = REGIONS_CHECKED_OUT.lock().unwrap();
|
|
let mut found_idx = None;
|
|
for (idx, flag) in store.iter().enumerate() {
|
|
if !flag {
|
|
found_idx = Some(idx);
|
|
break;
|
|
}
|
|
}
|
|
let found_idx = found_idx.expect("no available memory regions found");
|
|
println!("Checking out region {}", found_idx);
|
|
store[found_idx] = true;
|
|
FakeMemory { region: &mut MEMORY_REGIONS[found_idx], index: found_idx }
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for FakeMemory {
|
|
fn drop(&mut self) {
|
|
println!("Checking region {} back in", self.index);
|
|
match REGIONS_CHECKED_OUT.lock() {
|
|
Ok(mut o) => o[self.index] = false,
|
|
Err(e) => println!("not checking in because of a panic: {:?}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TestEnvironment {
|
|
pub cfg: BootConfig,
|
|
_mem: FakeMemory,
|
|
}
|
|
|
|
impl TestEnvironment {
|
|
pub fn new(idx: usize) -> TestEnvironment {
|
|
use crate::args::KernelArguments;
|
|
|
|
// Create a fake memory block into which the bootloader will write
|
|
let fake_memory = FakeMemory::get();
|
|
// use rand::prelude::*;
|
|
// for mem in fake_memory.region.iter_mut() {
|
|
// *mem = random();
|
|
// }
|
|
|
|
let args = get_args_bin(idx);
|
|
#[allow(clippy::cast_ptr_alignment)] // This test only works on 32-bit systems
|
|
let ka = KernelArguments::new(args.as_ptr() as *const usize);
|
|
#[allow(clippy::cast_ptr_alignment)] // This test only works on 32-bit systems
|
|
let mut cfg = BootConfig { args: ka, base_addr: ka.base as *const usize, ..Default::default() };
|
|
crate::read_initial_config(&mut cfg);
|
|
|
|
// Patch up the config memory address. Ensure the range is on a "page" boundary.
|
|
let raw_ptr = fake_memory.region.as_mut_ptr() as usize;
|
|
let raw_ptr_rounded = (raw_ptr + crate::PAGE_SIZE - 1) & !(crate::PAGE_SIZE - 1);
|
|
|
|
cfg.sram_start = raw_ptr_rounded as *mut _;
|
|
cfg.sram_size = fake_memory.region.len() * core::mem::size_of::<usize>() - crate::PAGE_SIZE;
|
|
|
|
println!(
|
|
"Patching RAM so it starts at {:016x} and is {} bytes long",
|
|
fake_memory.region.as_ptr() as usize,
|
|
fake_memory.region.len() * core::mem::size_of::<usize>()
|
|
);
|
|
|
|
TestEnvironment { cfg, _mem: fake_memory }
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn copy_processes() {
|
|
let mut env = TestEnvironment::new(0);
|
|
crate::copy_processes(&mut env.cfg);
|
|
}
|
|
|
|
#[test]
|
|
fn allocate_regions() {
|
|
let mut env = TestEnvironment::new(0);
|
|
crate::copy_processes(&mut env.cfg);
|
|
|
|
// The first region is defined as being "main RAM", which will be used
|
|
// to keep track of allocations.
|
|
println!("Allocating regions");
|
|
crate::allocate_regions(&mut env.cfg);
|
|
|
|
// The kernel, as well as initial processes, are all stored in RAM.
|
|
println!("Allocating processes");
|
|
crate::allocate_processes(&mut env.cfg);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_args_bin() {
|
|
use crate::args::KernelArguments;
|
|
let args = get_args_bin(0);
|
|
#[allow(clippy::cast_ptr_alignment)] // This test only works on 32-bit systems
|
|
let ka = KernelArguments::new(args.as_ptr() as *const usize);
|
|
|
|
let mut ka_iter = ka.iter();
|
|
let ka_first = ka_iter.next().expect("kernel args has no first tag");
|
|
assert_eq!(ka_first.name, u32::from_le_bytes(*b"XArg"), "first tag was not valid");
|
|
assert_eq!(ka_first.size, 20, "first tag had invalid size");
|
|
assert_eq!(ka_first.data[1], 1, "tag version number unexpected");
|
|
|
|
for arg in ka_iter {
|
|
let tag_name_bytes = arg.name.to_le_bytes();
|
|
let s = unsafe {
|
|
use core::slice;
|
|
use core::str;
|
|
// 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(slice).expect("tag had invalid utf8 characters")
|
|
};
|
|
|
|
println!("{} ({:08x}, {} bytes):", s, arg.name, arg.size);
|
|
for word in arg.data {
|
|
println!(" {:08x}", word);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn read_initial_config() {
|
|
use crate::BootConfig;
|
|
use crate::args::KernelArguments;
|
|
|
|
let args = get_args_bin(0);
|
|
#[allow(clippy::cast_ptr_alignment)] // This test only works on 32-bit systems
|
|
let ka = KernelArguments::new(args.as_ptr() as *const usize);
|
|
#[allow(clippy::cast_ptr_alignment)] // This test only works on 32-bit systems
|
|
let mut cfg = BootConfig { args: ka, base_addr: ka.base as *const usize, ..Default::default() };
|
|
crate::read_initial_config(&mut cfg);
|
|
}
|
|
|
|
fn read_word(satp: usize, virt: usize) -> Result<u32, &'static str> {
|
|
if satp & 0x8000_0000 != 0x8000_0000 {
|
|
return Err("satp valid bit isn't set");
|
|
}
|
|
// let ppn1 = (phys >> 22) & ((1 << 12) - 1);
|
|
// let ppn0 = (phys >> 12) & ((1 << 10) - 1);
|
|
// let ppo = (phys >> 0) & ((1 << 12) - 1);
|
|
|
|
let vpn1 = (virt >> 22) & ((1 << 10) - 1);
|
|
let vpn0 = (virt >> 12) & ((1 << 10) - 1);
|
|
let vpo = (virt) & ((1 << 12) - 1);
|
|
|
|
let l1_pt = unsafe { &mut (*((satp << 12) as *mut crate::PageTable)) };
|
|
let l1_entry = l1_pt.entries[vpn1];
|
|
// FIXME: This could also be a megapage
|
|
if l1_entry & 7 != 1 {
|
|
return Err("l1 page table not mapped");
|
|
}
|
|
let l0_pt = unsafe { &mut (*(((l1_entry >> 10) << 12) as *mut crate::PageTable)) };
|
|
let l0_entry = l0_pt.entries[vpn0];
|
|
if l0_entry & 1 != 1 {
|
|
return Err("l0 page table not mapped");
|
|
}
|
|
// println!("l0_entry: {:08x}", l0_entry);
|
|
let page_base = (((l0_entry as u32) >> 10) << 12) + vpo as u32;
|
|
// println!("virt {:08x} -> phys {:08x}", virt, page_base);
|
|
Ok(unsafe { (page_base as *mut u32).read() })
|
|
}
|
|
|
|
fn read_byte(satp: usize, virt: usize) -> Result<u8, &'static str> {
|
|
let word = read_word(satp, virt & 0xffff_fffc)?;
|
|
Ok(word.to_le_bytes()[virt & 3])
|
|
}
|
|
|
|
fn verify_kernel(cfg: &BootConfig, pid: usize, arg: &crate::args::KernelArgument) {
|
|
let prog = unsafe { &*(arg.data.as_ptr() as *const crate::ProgramDescription) };
|
|
let program_offset = prog.load_offset as usize;
|
|
let mut src_text = vec![];
|
|
let mut src_data = vec![];
|
|
{
|
|
for i in 0..(prog.text_size as usize) {
|
|
let word = unsafe { (cfg.base_addr as *mut u32).add((program_offset + i) / 4).read() };
|
|
src_text.push(word);
|
|
}
|
|
|
|
for i in 0..(prog.data_size as usize) {
|
|
let word = unsafe {
|
|
(cfg.base_addr as *mut u32).add((program_offset + prog.text_size as usize + i) / 4).read()
|
|
};
|
|
src_data.push(word);
|
|
}
|
|
}
|
|
println!(
|
|
"Inspecting {} bytes of PID ({} bytes of text, {} bytes of data) {}, starting from {:08x}",
|
|
src_text.len() * 4 + src_data.len() * 4,
|
|
src_text.len() * 4,
|
|
src_data.len() * 4,
|
|
pid,
|
|
prog.load_offset
|
|
);
|
|
for addr in (0..(prog.text_size as usize)).step_by(4) {
|
|
assert_eq!(
|
|
src_text[addr],
|
|
read_word(cfg.processes[pid].satp as usize, addr + prog.text_offset as usize).unwrap(),
|
|
"program text doesn't match @ offset {:08x}",
|
|
addr + prog.text_offset as usize
|
|
);
|
|
}
|
|
|
|
for addr in (0..(prog.data_size as usize)).step_by(4) {
|
|
assert_eq!(
|
|
src_data[addr],
|
|
read_word(cfg.processes[pid].satp as usize, addr + prog.data_offset as usize).unwrap(),
|
|
"program data doesn't match @ offset {:08x}",
|
|
addr + prog.data_offset as usize
|
|
);
|
|
}
|
|
|
|
for addr in ((prog.data_size as usize)..((prog.data_size + prog.bss_size) as usize)).step_by(4) {
|
|
println!("Verifying BSS @ {:08x} is 0", addr + prog.data_offset as usize);
|
|
assert_eq!(
|
|
0,
|
|
read_word(cfg.processes[pid].satp as usize, addr + prog.data_offset as usize).unwrap(),
|
|
"bss is not zero @ offset {:08x}",
|
|
addr + prog.data_offset as usize
|
|
);
|
|
}
|
|
}
|
|
|
|
fn verify_program(cfg: &BootConfig, pid: usize, arg: &crate::args::KernelArgument) {
|
|
let elf = crate::MiniElf::new(arg);
|
|
let mut program_offset = elf.load_offset as usize;
|
|
|
|
for section in elf.sections.iter() {
|
|
for addr in section.virt..(section.virt + section.len() as u32) {
|
|
let addr = addr as usize;
|
|
let word = read_byte(cfg.processes[pid].satp as usize, addr).unwrap();
|
|
if section.no_copy() {
|
|
assert!(word == 0, "bss is {:08x}, not 0 @ {:08x}", word, addr);
|
|
} else {
|
|
let check_word = unsafe { (cfg.base_addr as *mut u8).add(program_offset).read() };
|
|
program_offset += 1;
|
|
assert!(
|
|
word == check_word,
|
|
"program doesn't match @ {:08x} (expected: {:02x} found: {:02x})",
|
|
addr,
|
|
check_word,
|
|
word,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn full_boot() {
|
|
let mut env = TestEnvironment::new(0);
|
|
|
|
println!("Running phase_1");
|
|
crate::phase_1(&mut env.cfg, false);
|
|
println!("Running phase_2");
|
|
crate::phase_2(&mut env.cfg);
|
|
println!("Done with phases");
|
|
|
|
println!("Examining memory layout");
|
|
let mut xkrn_inspected = false;
|
|
let mut init_index = 0;
|
|
for arg in env.cfg.args.iter() {
|
|
if arg.name == u32::from_le_bytes(*b"XKrn") {
|
|
verify_kernel(&env.cfg, 0, &arg);
|
|
assert!(!xkrn_inspected, "multiple kernels found");
|
|
xkrn_inspected = true;
|
|
println!("Kernel PASS");
|
|
} else if arg.name == u32::from_le_bytes(*b"IniE") {
|
|
init_index += 1;
|
|
verify_program(&env.cfg, init_index, &arg);
|
|
println!("PID {} PASS", init_index + 1);
|
|
}
|
|
}
|
|
assert_eq!(xkrn_inspected, true, "didn't see kernel in output list");
|
|
}
|
|
|
|
#[test]
|
|
fn spanning_section() {
|
|
let mut env = TestEnvironment::new(1);
|
|
|
|
println!("Running phase_1");
|
|
crate::phase_1(&mut env.cfg, false);
|
|
println!("Running phase_2");
|
|
crate::phase_2(&mut env.cfg);
|
|
println!("Done with phases");
|
|
|
|
println!("Examining memory layout");
|
|
let mut xkrn_inspected = false;
|
|
let mut init_index = 0;
|
|
for arg in env.cfg.args.iter() {
|
|
if arg.name == u32::from_le_bytes(*b"XKrn") {
|
|
verify_kernel(&env.cfg, 0, &arg);
|
|
assert!(!xkrn_inspected, "multiple kernels found");
|
|
xkrn_inspected = true;
|
|
println!("Kernel PASS");
|
|
} else if arg.name == u32::from_le_bytes(*b"IniE") {
|
|
init_index += 1;
|
|
verify_program(&env.cfg, init_index, &arg);
|
|
println!("PID {} PASS", init_index + 1);
|
|
}
|
|
}
|
|
assert_eq!(xkrn_inspected, true, "didn't see kernel in output list");
|
|
}
|
|
|
|
#[test]
|
|
fn tracker_sane() {
|
|
let mut env = TestEnvironment::new(0);
|
|
|
|
crate::phase_1(&mut env.cfg, false);
|
|
crate::phase_2(&mut env.cfg);
|
|
|
|
let mut max_pid = 0;
|
|
for process in env.cfg.processes.iter() {
|
|
let satp = process.satp;
|
|
let pid = (satp >> 22 & ((1 << 9) - 1)) as u8;
|
|
if pid > max_pid {
|
|
max_pid = pid;
|
|
}
|
|
let mem_base = satp << 12;
|
|
println!(
|
|
"Process {} @ {:08x} ({:08x}), entrypoint {:08x}, sp {:08x}",
|
|
pid, mem_base, satp, process.entrypoint, process.sp
|
|
);
|
|
}
|
|
|
|
for (idx, addr) in env.cfg.runtime_page_tracker.iter().enumerate() {
|
|
assert!(
|
|
*addr <= max_pid,
|
|
"runtime page tracker contains invalid values @ {} ({:08x})! {} > {}",
|
|
idx,
|
|
addr as *const u8 as usize,
|
|
*addr,
|
|
max_pid
|
|
);
|
|
}
|
|
}
|
|
|
|
// Create a fake "start_kernel" function to allow
|
|
// this module to compile when not running natively.
|
|
#[export_name = "start_kernel"]
|
|
pub unsafe extern "C" fn start_kernel(
|
|
_args: usize,
|
|
_ss: usize,
|
|
_rpt: usize,
|
|
_satp: usize,
|
|
_entrypoint: usize,
|
|
_stack: usize,
|
|
) -> ! {
|
|
panic!("not running natively");
|
|
}
|