Files
k-os/crates/adapter-riscv/src/boot/loader.rs

160 lines
5.4 KiB
Rust

use crate::hw::uart::Uart;
use crate::mmu;
use types::address::VirtualAddress;
use types::budget::Limits;
use types::id::{ProcessId, ThreadId};
use types::mapping::PagePerms;
const PAGE_SIZE: usize = mmu::PAGE_SIZE;
const USER_VA_BASE: usize = 0x10000;
/// The stack sits just below the image and stops short of the null page.
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
pub struct LoadedProcess {
pub pid: ProcessId,
}
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() }
}
/// The two programs boot starts, packed into the kernel image.
pub static INIT_ELF: &[u8] = include_bytes!(env!("KOS_INIT_ELF"));
pub static STORAGE_ELF: &[u8] = include_bytes!(env!("KOS_STORAGE_ELF"));
pub fn load_init(uart: &Uart) -> LoadedProcess {
let (pid, tid) = kern()
.boot(
types::bounded::Priority::DEFAULT,
types::time::TimeSlice::DEFAULT,
)
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
let entry = load_elf(uart, pid, INIT_ELF);
setup_thread(tid, entry);
uart.write_str("k-os: init loaded\n");
LoadedProcess { pid }
}
/// Storage's budget, carved from init's.
const STORAGE: Limits = {
use types::storage::{BUFFER_PAGES, MAX_CLIENTS};
Limits {
// A buffer per client, and its image, heap and cache.
pages: MAX_CLIENTS * BUFFER_PAGES + 256,
endpoints: 2,
notifications: 2,
// A buffer per client, and the disk's DMA.
regions: MAX_CLIENTS + 4,
}
};
/// Storage, which init needs to read anything else, its budget carved
/// from init's.
pub fn load_storage(uart: &Uart, init: ProcessId) -> LoadedProcess {
let pid = kern()
.create_process_internal(init, STORAGE)
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
let entry = load_elf(uart, pid, STORAGE_ELF);
let tid = kern()
.spawn_thread_internal(pid)
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
setup_thread(tid, entry);
uart.write_str("k-os: storage loaded\n");
LoadedProcess { pid }
}
fn setup_thread(tid: ThreadId, entry: usize) {
if let Some(ctx) = kern().thread_context_mut(tid) {
ctx.set_pc(entry);
ctx.registers_mut()[2] = USER_VA_BASE;
ctx.set_status(1 << 5);
}
}
/// Copies `data` into `pid`'s already mapped pages starting at `va`.
fn copy_to_user(pid: ProcessId, va: usize, data: &[u8]) -> bool {
let mut done = 0;
while done < data.len() {
let addr = va + done;
let page = addr & !(PAGE_SIZE - 1);
let offset = addr - page;
let Some(pa) = kern().user_page_frame(pid, VirtualAddress::from_raw(page)) else {
return false;
};
let len = (PAGE_SIZE - offset).min(data.len() - done);
// SAFETY: `pa` is a pool frame the kernel just mapped for `pid`;
// pool frames are RAM, mapped at `phys_to_virt`.
unsafe {
core::ptr::copy_nonoverlapping(
data.as_ptr().add(done),
(mmu::phys_to_virt(pa.raw()) as *mut u8).add(offset),
len,
);
}
done += len;
}
true
}
fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
match (flags.write, flags.execute) {
(true, true) => None,
(false, true) => Some(PagePerms::ReadExecute),
(true, false) => Some(PagePerms::ReadWrite),
(false, false) => Some(PagePerms::ReadOnly),
}
}
/// Loads `filename` into `pid` through the kernel's own mapping API, so
/// every page is tracked, charged and checked like any other. Halts on any
/// malformed or overlapping image: boot servers are trusted to be sane.
fn load_elf(uart: &Uart, pid: ProcessId, file: &[u8]) -> usize {
if file.is_empty() {
crate::halt(uart, "program missing from the kernel image (make build)");
}
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
for segment in elf_info.segments() {
let perms =
segment_perms(segment.flags).unwrap_or_else(|| crate::halt(uart, "W+X segment"));
let page_start = segment.vaddr & !(PAGE_SIZE - 1);
let offset_in_page = segment.vaddr - page_start;
let pages = offset_in_page
.checked_add(segment.mem_size)
.map(|total| total.div_ceil(PAGE_SIZE))
.unwrap_or_else(|| crate::halt(uart, "segment too large"));
if pages == 0 {
continue;
}
kern()
.map_fresh_range(pid, VirtualAddress::from_raw(page_start), pages, perms)
.unwrap_or_else(|_| crate::halt(uart, "cannot map segment"));
let copy_len = segment.file_size.min(segment.mem_size);
let data = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| file.get(segment.file_offset..end))
.unwrap_or_else(|| crate::halt(uart, "segment outside file"));
if !copy_to_user(pid, segment.vaddr, data) {
crate::halt(uart, "segment copy failed");
}
}
let stack_bottom = USER_VA_BASE - USER_STACK_PAGES * PAGE_SIZE;
kern()
.map_fresh_range(
pid,
VirtualAddress::from_raw(stack_bottom),
USER_STACK_PAGES,
PagePerms::ReadWrite,
)
.unwrap_or_else(|_| crate::halt(uart, "cannot map stack"));
elf_info.entry_point
}