feat(userspace): log server sole console holder; init boots from manifest; adapter loads only init+storage
This commit is contained in:
@@ -5,13 +5,9 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grant_all(
|
/// Init gets storage's process and endpoint (slots 1 and 2), storage its
|
||||||
uart: &Uart,
|
/// endpoint (slot 0); everything else init starts itself.
|
||||||
init_pid: ProcessId,
|
pub fn grant_all(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId) {
|
||||||
storage_pid: ProcessId,
|
|
||||||
display_pid: ProcessId,
|
|
||||||
input_pid: ProcessId,
|
|
||||||
) {
|
|
||||||
let all_perms = types::permission::PermissionSet::empty()
|
let all_perms = types::permission::PermissionSet::empty()
|
||||||
.with(types::permission::Permission::Read)
|
.with(types::permission::Permission::Read)
|
||||||
.with(types::permission::Permission::Write)
|
.with(types::permission::Permission::Write)
|
||||||
@@ -49,74 +45,5 @@ pub fn grant_all(
|
|||||||
)
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
let display_ep = kern()
|
|
||||||
.create_endpoint(display_pid)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
display_pid,
|
|
||||||
domain::resource::Resource::Endpoint(display_ep),
|
|
||||||
ipc_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Endpoint(display_ep),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Process(display_pid),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
let input_ep = kern()
|
|
||||||
.create_endpoint(input_pid)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
input_pid,
|
|
||||||
domain::resource::Resource::Endpoint(input_ep),
|
|
||||||
ipc_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Endpoint(input_ep),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
kern()
|
|
||||||
.grant_capability(
|
|
||||||
init_pid,
|
|
||||||
domain::resource::Resource::Process(input_pid),
|
|
||||||
all_perms,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
|
||||||
|
|
||||||
uart.write_str("k-os: capabilities granted\n");
|
uart.write_str("k-os: capabilities granted\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Until the log server owns the console, the servers loaded here print
|
|
||||||
/// through a console cap of their own, its slot passed in `a0`.
|
|
||||||
pub fn grant_console(uart: &Uart, servers: &[super::loader::LoadedProcess]) {
|
|
||||||
let write =
|
|
||||||
types::permission::PermissionSet::empty().with(types::permission::Permission::Write);
|
|
||||||
for server in servers {
|
|
||||||
let slot = kern()
|
|
||||||
.grant_capability(server.pid, domain::resource::Resource::Console, write)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "console grant failed"));
|
|
||||||
if let Some(ctx) = kern().thread_context_mut(server.tid) {
|
|
||||||
ctx.registers_mut()[10] = types::start_arg::encode(Some(slot), None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ const USER_VA_BASE: usize = 0x10000;
|
|||||||
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
||||||
const MAX_FILE_SIZE: usize = 64 * 1024;
|
const MAX_FILE_SIZE: usize = 64 * 1024;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct LoadedProcess {
|
pub struct LoadedProcess {
|
||||||
pub pid: ProcessId,
|
pub pid: ProcessId,
|
||||||
pub tid: ThreadId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
@@ -50,30 +48,17 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
|
|||||||
setup_thread(tid, entry);
|
setup_thread(tid, entry);
|
||||||
uart.write_str("k-os: init loaded\n");
|
uart.write_str("k-os: init loaded\n");
|
||||||
|
|
||||||
LoadedProcess { pid, tid }
|
LoadedProcess { pid }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The servers boot loads, their budgets carved from init's.
|
/// Storage's budget, carved from init's.
|
||||||
pub const STORAGE: Limits = Limits {
|
const STORAGE: Limits = Limits {
|
||||||
pages: 256,
|
pages: 256,
|
||||||
endpoints: 2,
|
endpoints: 2,
|
||||||
notifications: 2,
|
notifications: 2,
|
||||||
// A buffer per client (8) and the disk's DMA.
|
// A buffer per client (8) and the disk's DMA.
|
||||||
regions: 12,
|
regions: 12,
|
||||||
};
|
};
|
||||||
/// The framebuffer alone is ~1000 pages at 1280x800.
|
|
||||||
pub const DISPLAY: Limits = Limits {
|
|
||||||
pages: 1280,
|
|
||||||
endpoints: 2,
|
|
||||||
notifications: 2,
|
|
||||||
regions: 4,
|
|
||||||
};
|
|
||||||
pub const INPUT: Limits = Limits {
|
|
||||||
pages: 64,
|
|
||||||
endpoints: 2,
|
|
||||||
notifications: 2,
|
|
||||||
regions: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn load_server(
|
pub fn load_server(
|
||||||
uart: &Uart,
|
uart: &Uart,
|
||||||
@@ -81,10 +66,9 @@ pub fn load_server(
|
|||||||
init: ProcessId,
|
init: ProcessId,
|
||||||
filename: &[u8],
|
filename: &[u8],
|
||||||
label: &str,
|
label: &str,
|
||||||
limits: Limits,
|
|
||||||
) -> LoadedProcess {
|
) -> LoadedProcess {
|
||||||
let pid = kern()
|
let pid = kern()
|
||||||
.create_process_internal(init, limits)
|
.create_process_internal(init, STORAGE)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||||
let entry = load_elf(uart, blk, pid, filename);
|
let entry = load_elf(uart, blk, pid, filename);
|
||||||
let tid = kern()
|
let tid = kern()
|
||||||
@@ -95,7 +79,7 @@ pub fn load_server(
|
|||||||
uart.write_str(label);
|
uart.write_str(label);
|
||||||
uart.write_str(" loaded\n");
|
uart.write_str(" loaded\n");
|
||||||
|
|
||||||
LoadedProcess { pid, tid }
|
LoadedProcess { pid }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_thread(tid: ThreadId, entry: usize) {
|
fn setup_thread(tid: ThreadId, entry: usize) {
|
||||||
|
|||||||
@@ -40,32 +40,14 @@ extern "C" fn kmain(hart: usize) -> ! {
|
|||||||
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
||||||
|
|
||||||
let init = boot::loader::load_init(&uart, &mut blk);
|
let init = boot::loader::load_init(&uart, &mut blk);
|
||||||
use boot::loader::{DISPLAY, INPUT, STORAGE, load_server};
|
// Init needs storage to load anything else; the rest is init's to start.
|
||||||
let storage = load_server(
|
let storage = boot::loader::load_server(&uart, &mut blk, init.pid, b"storage.elf", "storage");
|
||||||
&uart,
|
|
||||||
&mut blk,
|
|
||||||
init.pid,
|
|
||||||
b"storage.elf",
|
|
||||||
"storage",
|
|
||||||
STORAGE,
|
|
||||||
);
|
|
||||||
let display = load_server(
|
|
||||||
&uart,
|
|
||||||
&mut blk,
|
|
||||||
init.pid,
|
|
||||||
b"display.elf",
|
|
||||||
"display",
|
|
||||||
DISPLAY,
|
|
||||||
);
|
|
||||||
let input = load_server(&uart, &mut blk, init.pid, b"input.elf", "input", INPUT);
|
|
||||||
|
|
||||||
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||||
kern_mut()
|
kern_mut()
|
||||||
.register_board(init.pid, &board)
|
.register_board(init.pid, &board)
|
||||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
boot::caps::grant_all(&uart, init.pid, storage.pid);
|
||||||
// Last, so the servers' fixed slots stay where they expect them.
|
|
||||||
boot::caps::grant_console(&uart, &[storage, display, input]);
|
|
||||||
// After grant_all: init already relies on those slots.
|
// After grant_all: init already relies on those slots.
|
||||||
let mut placed = [None; board::BOARD_DEVICES];
|
let mut placed = [None; board::BOARD_DEVICES];
|
||||||
let boot_caps = kern_mut()
|
let boot_caps = kern_mut()
|
||||||
|
|||||||
115
crates/init/src/launch.rs
Normal file
115
crates/init/src/launch.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
//! Starting a manifest program: load it, hand it what the manifest says
|
||||||
|
//! and nothing else, run it.
|
||||||
|
|
||||||
|
use libkos::device::Device as BoardDevice;
|
||||||
|
use libkos::fs::Storage;
|
||||||
|
use libkos::syscall;
|
||||||
|
use types::address::StackPointer;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
|
use crate::devices::Devices;
|
||||||
|
use crate::load::{self, LoadedProcess};
|
||||||
|
use crate::manifest::{Device, Program};
|
||||||
|
|
||||||
|
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(0x10000);
|
||||||
|
const MAX_SERVED: usize = 8;
|
||||||
|
|
||||||
|
fn perms(list: &[Permission]) -> PermissionSet {
|
||||||
|
list.iter().fold(PermissionSet::empty(), |s, p| s.with(*p))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Launcher<'a> {
|
||||||
|
storage: &'a mut Storage,
|
||||||
|
scratch: &'a mut [u8],
|
||||||
|
devices: Devices,
|
||||||
|
/// The endpoint of every server started so far, by name.
|
||||||
|
served: [Option<(&'static str, CapSlot)>; MAX_SERVED],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Launcher<'a> {
|
||||||
|
pub(crate) fn new(storage: &'a mut Storage, scratch: &'a mut [u8], devices: Devices) -> Self {
|
||||||
|
Self { storage, scratch, devices, served: [None; MAX_SERVED] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) {
|
||||||
|
(self.storage, self.scratch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint(&self, name: &str) -> Option<CapSlot> {
|
||||||
|
self.served.iter().flatten().find(|(n, _)| *n == name).map(|(_, ep)| *ep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What became of the program's device, if it has one.
|
||||||
|
pub(crate) fn launch(&mut self, p: &Program) -> Result<&'static str, &'static str> {
|
||||||
|
let loaded =
|
||||||
|
load::load_binary(self.storage, self.scratch, p.file, p.limits).ok_or("cannot load")?;
|
||||||
|
let child = loaded.process_cap;
|
||||||
|
let ep = match p.serves {
|
||||||
|
true => {
|
||||||
|
let ep = syscall::create_endpoint().ok_or("no endpoint")?;
|
||||||
|
// Read to receive on it; init keeps the original.
|
||||||
|
let server = perms(&[Permission::Read, Permission::Execute]);
|
||||||
|
syscall::delegate(ep, child, server).ok_or("cannot give its endpoint")?;
|
||||||
|
Some(ep)
|
||||||
|
}
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
// A client only calls: Execute, never Read, so it cannot pose as the
|
||||||
|
// server and take other clients' requests.
|
||||||
|
for name in p.uses {
|
||||||
|
let ep = self.endpoint(name).ok_or("needs a server that is not running")?;
|
||||||
|
syscall::delegate(ep, child, perms(&[Permission::Execute])).ok_or("cannot give an endpoint")?;
|
||||||
|
}
|
||||||
|
let console = libkos::start::console()
|
||||||
|
.filter(|_| p.console)
|
||||||
|
.and_then(|c| syscall::delegate(c, child, perms(&[Permission::Write])));
|
||||||
|
start(&loaded, console);
|
||||||
|
|
||||||
|
let mut note = "";
|
||||||
|
if let Some(ep) = ep {
|
||||||
|
if let Some(free) = self.served.iter_mut().find(|s| s.is_none()) {
|
||||||
|
*free = Some((p.name, ep));
|
||||||
|
}
|
||||||
|
if let Some(device) = p.device {
|
||||||
|
note = self.give(device, ep);
|
||||||
|
}
|
||||||
|
if console.is_some() {
|
||||||
|
hand_over_console(ep);
|
||||||
|
note = ", console handed over";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The driver waits for its device before serving, and runs without
|
||||||
|
/// one (headless) if the board has none.
|
||||||
|
fn give(&self, device: Device, ep: CapSlot) -> &'static str {
|
||||||
|
let (dev, given, none): (BoardDevice, _, _) = match device {
|
||||||
|
Device::Gpu => (self.devices.gpu, ", gpu handed over", ", no gpu"),
|
||||||
|
Device::Input => (self.devices.input, ", input device handed over", ", no input device"),
|
||||||
|
};
|
||||||
|
match libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some() {
|
||||||
|
true => given,
|
||||||
|
false => none,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From here on init prints through the log server, like everyone else,
|
||||||
|
/// and holds no console of its own.
|
||||||
|
fn hand_over_console(log: CapSlot) {
|
||||||
|
if let Some(console) = libkos::start::console() {
|
||||||
|
syscall::drop_cap(console);
|
||||||
|
}
|
||||||
|
libkos::start::set_console(None);
|
||||||
|
libkos::start::set_log(Some(log));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts `program` with `console` and, once there is one, the log.
|
||||||
|
pub(crate) fn start(program: &LoadedProcess, console: Option<CapSlot>) {
|
||||||
|
let log = libkos::start::log()
|
||||||
|
.and_then(|l| syscall::delegate(l, program.process_cap, perms(&[Permission::Execute])));
|
||||||
|
let arg = types::start_arg::encode(console, log);
|
||||||
|
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg);
|
||||||
|
}
|
||||||
@@ -3,224 +3,80 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
mod devices;
|
mod devices;
|
||||||
|
mod launch;
|
||||||
|
mod load;
|
||||||
|
mod manifest;
|
||||||
mod selftest;
|
mod selftest;
|
||||||
|
|
||||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
|
||||||
use types::bounded::{CapSlot, PageCount};
|
|
||||||
use types::budget::Limits;
|
|
||||||
use types::permission::{Permission, PermissionSet};
|
|
||||||
use libkos::fs::Storage;
|
use libkos::fs::Storage;
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
|
use types::badge::Badge;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
const PAGE_SIZE: usize = 4096;
|
|
||||||
const USER_CODE_BASE: usize = 0x10000;
|
|
||||||
pub(crate) const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
|
|
||||||
const USER_STACK_PAGES: usize = 4;
|
|
||||||
/// 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_PAGES: u16 = 16;
|
|
||||||
/// Where the buffer storage shares with init is mapped.
|
/// Where the buffer storage shares with init is mapped.
|
||||||
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||||
|
/// Boot loads storage before init and leaves its endpoint here.
|
||||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
|
||||||
const STORAGE_EP: CapSlot = CapSlot::const_new(2);
|
const STORAGE_EP: CapSlot = CapSlot::const_new(2);
|
||||||
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
|
|
||||||
const INPUT_EP: CapSlot = CapSlot::const_new(5);
|
|
||||||
|
|
||||||
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
|
|
||||||
/// missing, unreadable or larger than `scratch`.
|
|
||||||
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
|
|
||||||
let handle = storage.open(filename).ok()?;
|
|
||||||
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
|
|
||||||
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
|
|
||||||
let _ = storage.close(handle);
|
|
||||||
read.filter(|r| Some(*r) == size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct LoadedProcess {
|
|
||||||
pub(crate) process_cap: CapSlot,
|
|
||||||
pub(crate) entry_point: EntryPoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_binary(
|
|
||||||
storage: &mut Storage,
|
|
||||||
scratch: &mut [u8],
|
|
||||||
filename: &[u8],
|
|
||||||
limits: Limits,
|
|
||||||
) -> Option<LoadedProcess> {
|
|
||||||
let file_size = read_file(storage, scratch, filename)?;
|
|
||||||
let elf_data = &scratch[..file_size];
|
|
||||||
let elf_info = match elf::parse(elf_data) {
|
|
||||||
Ok(info) => info,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
|
||||||
|
|
||||||
for segment in elf_info.segments() {
|
|
||||||
let page_start = segment.vaddr & !0xFFF;
|
|
||||||
let offset_in_page = segment.vaddr & 0xFFF;
|
|
||||||
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 copy_len = segment.file_size.min(segment.mem_size);
|
|
||||||
let src = segment
|
|
||||||
.file_offset
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for p in 0..seg_pages {
|
|
||||||
let src_page = VirtualAddress::from_raw(local_va.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..USER_STACK_PAGES {
|
|
||||||
let page_count = PageCount::new(1)?;
|
|
||||||
let stack_va = syscall::request_pages(page_count)?;
|
|
||||||
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
|
|
||||||
syscall::map_page(process_cap, stack_va, dst, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(LoadedProcess {
|
|
||||||
process_cap,
|
|
||||||
entry_point: EntryPoint::from_raw(elf_info.entry_point),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ServerInfo {
|
|
||||||
filename: &'static [u8],
|
|
||||||
name: &'static str,
|
|
||||||
limits: Limits,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enough for a small program: its image, stack and a little heap.
|
|
||||||
pub(crate) const SMALL: Limits = Limits {
|
|
||||||
pages: 64,
|
|
||||||
endpoints: 2,
|
|
||||||
notifications: 2,
|
|
||||||
regions: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SERVERS: &[ServerInfo] = &[
|
|
||||||
ServerInfo { filename: b"log.elf", name: "log", limits: SMALL },
|
|
||||||
ServerInfo { filename: b"permis.elf", name: "permis", limits: SMALL },
|
|
||||||
ServerInfo { filename: b"session.elf", name: "session", limits: SMALL },
|
|
||||||
];
|
|
||||||
|
|
||||||
const SNAKE: Limits = Limits { pages: 128, ..SMALL };
|
|
||||||
|
|
||||||
pub fn run() -> ! {
|
pub fn run() -> ! {
|
||||||
libkos::start::set_console(devices::console());
|
libkos::start::set_console(devices::console());
|
||||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||||
|
|
||||||
// Drivers wait for their device before serving, so this comes first:
|
// Storage waits for its disk before serving, so this comes first.
|
||||||
// storage needs its disk before init can load anything.
|
|
||||||
let found = devices::discover();
|
let found = devices::discover();
|
||||||
for (name, ep, dev) in [
|
let disk = libkos::device::give_device(STORAGE_EP, found.disk).is_ok() && found.disk.mmio.is_some();
|
||||||
("disk", STORAGE_EP, found.disk),
|
libkos::println!("init: disk: {}", if disk { "handed to its driver" } else { "none" });
|
||||||
("gpu", DISPLAY_EP, found.gpu),
|
|
||||||
("input", INPUT_EP, found.input),
|
|
||||||
] {
|
|
||||||
let given = libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some();
|
|
||||||
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else {
|
let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else {
|
||||||
libkos::println!("init: cannot reach storage, nothing to load");
|
libkos::println!("init: cannot reach storage, nothing to load");
|
||||||
idle();
|
idle();
|
||||||
};
|
};
|
||||||
let Some(scratch) = scratch() else {
|
// Storage has nothing to print with yet; init speaks for it.
|
||||||
|
libkos::println!("init: storage: up");
|
||||||
|
let Some(scratch) = load::scratch() else {
|
||||||
libkos::println!("init: no memory to load programs into");
|
libkos::println!("init: no memory to load programs into");
|
||||||
idle();
|
idle();
|
||||||
};
|
};
|
||||||
|
|
||||||
let ipc_perms = PermissionSet::empty()
|
let mut launcher = launch::Launcher::new(&mut storage, scratch, found);
|
||||||
.with(Permission::Read)
|
launch_all(&mut launcher, manifest::SERVERS);
|
||||||
.with(Permission::Execute);
|
give_storage_the_log();
|
||||||
|
|
||||||
for server in SERVERS {
|
|
||||||
libkos::print!("init: loading {}... ", server.name);
|
|
||||||
|
|
||||||
let loaded = match load_binary(&mut storage, scratch, server.filename, server.limits) {
|
|
||||||
Some(l) => l,
|
|
||||||
None => {
|
|
||||||
libkos::println!("SKIP");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ep_cap) = syscall::create_endpoint() {
|
|
||||||
syscall::delegate(ep_cap, loaded.process_cap, ipc_perms);
|
|
||||||
start(&loaded, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
libkos::println!("ok (entry={:#x})", loaded.entry_point.raw());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The test image carries the SMP self-test; it runs instead of the
|
// The test image carries the SMP self-test; it runs instead of the
|
||||||
// desktop and halts the machine.
|
// desktop and halts the machine.
|
||||||
|
let (storage, scratch) = launcher.storage();
|
||||||
if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) {
|
if storage.open(b"smptest.elf").is_ok_and(|h| storage.close(h).is_ok()) {
|
||||||
libkos::println!("init: running the SMP self-test");
|
libkos::println!("init: running the SMP self-test");
|
||||||
selftest::run(&mut storage, scratch);
|
selftest::run(storage, scratch);
|
||||||
}
|
|
||||||
|
|
||||||
libkos::print!("init: loading snake... ");
|
|
||||||
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf", SNAKE) {
|
|
||||||
// A client only calls: Execute, never Read, so it cannot pose as
|
|
||||||
// the server and take other clients' requests.
|
|
||||||
let client = PermissionSet::empty().with(Permission::Execute);
|
|
||||||
syscall::delegate(INPUT_EP, snake.process_cap, client);
|
|
||||||
syscall::delegate(DISPLAY_EP, snake.process_cap, client);
|
|
||||||
start(&snake, false);
|
|
||||||
libkos::println!("ok");
|
|
||||||
} else {
|
|
||||||
libkos::println!("SKIP");
|
|
||||||
}
|
}
|
||||||
|
launch_all(&mut launcher, manifest::APPS);
|
||||||
|
|
||||||
libkos::println!("init: all loaded, supervising");
|
libkos::println!("init: all loaded, supervising");
|
||||||
|
|
||||||
idle();
|
idle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts `program`, lending it init's console if `console`.
|
fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
|
||||||
pub(crate) fn start(program: &LoadedProcess, console: bool) {
|
for p in programs {
|
||||||
let write = PermissionSet::empty().with(Permission::Write);
|
libkos::print!("init: loading {}... ", p.name);
|
||||||
let slot = libkos::start::console()
|
match launcher.launch(p) {
|
||||||
.filter(|_| console)
|
Ok(note) => libkos::println!("ok{}", note),
|
||||||
.and_then(|c| syscall::delegate(c, program.process_cap, write));
|
Err(why) => libkos::println!("SKIP ({})", why),
|
||||||
let arg = types::start_arg::encode(slot, None);
|
}
|
||||||
syscall::spawn_thread_in(program.process_cap, program.entry_point, USER_STACK_TOP, arg);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A region of init's own to read program files into.
|
/// Storage started before the log server; it gets a call-only copy of
|
||||||
fn scratch() -> Option<&'static mut [u8]> {
|
/// the log endpoint now (Delegate only to send it: transfer strips it).
|
||||||
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
|
fn give_storage_the_log() {
|
||||||
syscall::map_region(
|
let Some(log) = libkos::start::log() else {
|
||||||
region,
|
return;
|
||||||
VirtualAddress::from_raw(SCRATCH_VA),
|
};
|
||||||
types::mapping::PagePerms::ReadWrite,
|
let call_only = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
|
||||||
)
|
if let Ok(copy) = syscall::mint(log, Badge::NONE, call_only) {
|
||||||
.ok()?;
|
let _ = syscall::call(STORAGE_EP, [types::storage::op::SET_LOG, 0, 0, 0], Some(copy));
|
||||||
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
|
syscall::drop_cap(copy);
|
||||||
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Nothing to supervise yet: block on a notification nobody signals
|
/// Nothing to supervise yet: block on a notification nobody signals
|
||||||
|
|||||||
106
crates/init/src/load.rs
Normal file
106
crates/init/src/load.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
//! Reading program files from storage and loading them into new processes.
|
||||||
|
|
||||||
|
use libkos::fs::Storage;
|
||||||
|
use libkos::syscall;
|
||||||
|
use types::address::{EntryPoint, VirtualAddress};
|
||||||
|
use types::bounded::{CapSlot, PageCount};
|
||||||
|
use types::budget::Limits;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = 4096;
|
||||||
|
const USER_CODE_BASE: usize = 0x10000;
|
||||||
|
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_PAGES: u16 = 16;
|
||||||
|
|
||||||
|
pub(crate) struct LoadedProcess {
|
||||||
|
pub(crate) process_cap: CapSlot,
|
||||||
|
pub(crate) entry_point: EntryPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
|
||||||
|
/// missing, unreadable or larger than `scratch`.
|
||||||
|
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
|
||||||
|
let handle = storage.open(filename).ok()?;
|
||||||
|
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
|
||||||
|
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
|
||||||
|
let _ = storage.close(handle);
|
||||||
|
read.filter(|r| Some(*r) == size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_binary(
|
||||||
|
storage: &mut Storage,
|
||||||
|
scratch: &mut [u8],
|
||||||
|
filename: &[u8],
|
||||||
|
limits: Limits,
|
||||||
|
) -> Option<LoadedProcess> {
|
||||||
|
let file_size = read_file(storage, scratch, filename)?;
|
||||||
|
let elf_data = &scratch[..file_size];
|
||||||
|
let elf_info = match elf::parse(elf_data) {
|
||||||
|
Ok(info) => info,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
||||||
|
|
||||||
|
for segment in elf_info.segments() {
|
||||||
|
let page_start = segment.vaddr & !0xFFF;
|
||||||
|
let offset_in_page = segment.vaddr & 0xFFF;
|
||||||
|
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 copy_len = segment.file_size.min(segment.mem_size);
|
||||||
|
let src = segment
|
||||||
|
.file_offset
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in 0..seg_pages {
|
||||||
|
let src_page = VirtualAddress::from_raw(local_va.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..USER_STACK_PAGES {
|
||||||
|
let page_count = PageCount::new(1)?;
|
||||||
|
let stack_va = syscall::request_pages(page_count)?;
|
||||||
|
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (USER_STACK_PAGES - i) * PAGE_SIZE);
|
||||||
|
syscall::map_page(process_cap, stack_va, dst, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(LoadedProcess {
|
||||||
|
process_cap,
|
||||||
|
entry_point: EntryPoint::from_raw(elf_info.entry_point),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) })
|
||||||
|
}
|
||||||
55
crates/init/src/manifest.rs
Normal file
55
crates/init/src/manifest.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//! What init starts, in order: the system's boot policy, as data.
|
||||||
|
|
||||||
|
use types::budget::Limits;
|
||||||
|
|
||||||
|
/// A board device init hands to a program over its endpoint.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Device {
|
||||||
|
Gpu,
|
||||||
|
Input,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Program {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub file: &'static [u8],
|
||||||
|
pub limits: Limits,
|
||||||
|
/// Serves an endpoint, which it finds in slot 0.
|
||||||
|
pub serves: bool,
|
||||||
|
pub device: Option<Device>,
|
||||||
|
/// Earlier programs' endpoints it may call, in its next slots.
|
||||||
|
pub uses: &'static [&'static str],
|
||||||
|
/// Takes the console over from init; everyone else prints through it.
|
||||||
|
pub console: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enough for a small program: its image, stack and a little heap.
|
||||||
|
pub const SMALL: Limits = Limits {
|
||||||
|
pages: 64,
|
||||||
|
endpoints: 2,
|
||||||
|
notifications: 2,
|
||||||
|
regions: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Program {
|
||||||
|
Program { name, file, limits, serves: true, device: None, uses: &[], console: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Started on every boot. The log server comes first so the rest can print.
|
||||||
|
pub const SERVERS: &[Program] = &[
|
||||||
|
Program { console: true, ..server("log", b"log.elf", SMALL) },
|
||||||
|
Program {
|
||||||
|
device: Some(Device::Gpu),
|
||||||
|
// The framebuffer alone is ~1000 pages at 1280x800.
|
||||||
|
..server("display", b"display.elf", Limits { pages: 1280, regions: 4, ..SMALL })
|
||||||
|
},
|
||||||
|
Program { device: Some(Device::Input), ..server("input", b"input.elf", Limits { regions: 4, ..SMALL }) },
|
||||||
|
server("permis", b"permis.elf", SMALL),
|
||||||
|
server("session", b"session.elf", SMALL),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The desktop; the test image runs the SMP self-test instead.
|
||||||
|
pub const APPS: &[Program] = &[Program {
|
||||||
|
serves: false,
|
||||||
|
uses: &["input", "display"],
|
||||||
|
..server("snake", b"snake.elf", Limits { pages: 128, ..SMALL })
|
||||||
|
}];
|
||||||
@@ -40,7 +40,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
|
|||||||
for i in 0..=THREADS {
|
for i in 0..=THREADS {
|
||||||
word(i).store(0, Ordering::SeqCst);
|
word(i).store(0, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
let spinner = crate::load_binary(storage, scratch, b"smptest.elf", crate::SMALL).ok_or("cannot load smptest.elf")?;
|
let spinner = crate::load::load_binary(storage, scratch, b"smptest.elf", crate::manifest::SMALL).ok_or("cannot load smptest.elf")?;
|
||||||
let rw = PermissionSet::empty()
|
let rw = PermissionSet::empty()
|
||||||
.with(Permission::Read)
|
.with(Permission::Read)
|
||||||
.with(Permission::Write)
|
.with(Permission::Write)
|
||||||
@@ -49,7 +49,7 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
|
|||||||
if syscall::delegate(page, spinner.process_cap, rw).is_none() {
|
if syscall::delegate(page, spinner.process_cap, rw).is_none() {
|
||||||
return Err("cannot give the spinner its page");
|
return Err("cannot give the spinner its page");
|
||||||
}
|
}
|
||||||
crate::start(&spinner, true);
|
crate::launch::start(&spinner, None);
|
||||||
if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
|
if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
|
||||||
return Err("spinner threads did not all start");
|
return Err("spinner threads did not all start");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ struct Writer;
|
|||||||
|
|
||||||
impl fmt::Write for Writer {
|
impl fmt::Write for Writer {
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
// Without a console capability, output goes nowhere.
|
// The log server first; a process without either prints nothing.
|
||||||
if let Some(console) = crate::start::console() {
|
// A call, not a send: the text is out before the caller goes on.
|
||||||
|
if let Some(log) = crate::start::log() {
|
||||||
|
for chunk in s.as_bytes().chunks(types::log::TEXT_BYTES) {
|
||||||
|
let _ = crate::syscall::call(log, types::log::text_words(chunk), None);
|
||||||
|
}
|
||||||
|
} else if let Some(console) = crate::start::console() {
|
||||||
for &b in s.as_bytes() {
|
for &b in s.as_bytes() {
|
||||||
crate::syscall::debug_putchar(console, b);
|
crate::syscall::debug_putchar(console, b);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ use types::start_arg;
|
|||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0);
|
static LIBKOS_START_ARG: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
/// Overrides the start argument's console once set; `NONE` drops output.
|
/// Override the start argument's slots once set; `NONE` means none.
|
||||||
static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET);
|
static CONSOLE: AtomicUsize = AtomicUsize::new(UNSET);
|
||||||
|
static LOG: AtomicUsize = AtomicUsize::new(UNSET);
|
||||||
const UNSET: usize = usize::MAX;
|
const UNSET: usize = usize::MAX;
|
||||||
const NONE: usize = usize::MAX - 1;
|
const NONE: usize = usize::MAX - 1;
|
||||||
|
|
||||||
@@ -19,18 +20,31 @@ fn arg() -> usize {
|
|||||||
|
|
||||||
/// The console capability this process prints through, if any.
|
/// The console capability this process prints through, if any.
|
||||||
pub fn console() -> Option<CapSlot> {
|
pub fn console() -> Option<CapSlot> {
|
||||||
match CONSOLE.load(Ordering::Relaxed) {
|
read(&CONSOLE, start_arg::console)
|
||||||
UNSET => start_arg::console(arg()),
|
|
||||||
v => CapSlot::new(u8::try_from(v).ok()?),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For a process that gets its console some other way (init, from BootInfo).
|
/// For a process that gets its console some other way (init, from BootInfo).
|
||||||
pub fn set_console(slot: Option<CapSlot>) {
|
pub fn set_console(slot: Option<CapSlot>) {
|
||||||
CONSOLE.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed);
|
write(&CONSOLE, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The log server endpoint this process was given, if any.
|
/// The log server endpoint this process prints to, if any.
|
||||||
pub fn log() -> Option<CapSlot> {
|
pub fn log() -> Option<CapSlot> {
|
||||||
start_arg::log(arg())
|
read(&LOG, start_arg::log)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a process given its log endpoint after it started.
|
||||||
|
pub fn set_log(slot: Option<CapSlot>) {
|
||||||
|
write(&LOG, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(cell: &AtomicUsize, from_arg: fn(usize) -> Option<CapSlot>) -> Option<CapSlot> {
|
||||||
|
match cell.load(Ordering::Relaxed) {
|
||||||
|
UNSET => from_arg(arg()),
|
||||||
|
v => CapSlot::new(u8::try_from(v).ok()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(cell: &AtomicUsize, slot: Option<CapSlot>) {
|
||||||
|
cell.store(slot.map_or(NONE, |s| s.value() as usize), Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ pub mod server;
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub fn run() -> ! {
|
pub fn run() -> ! {
|
||||||
let mut srv = server::LogServer::new();
|
let console = libkos::start::console();
|
||||||
|
let mut srv = server::LogServer::with_output(|bytes: &[u8]| {
|
||||||
|
if let Some(console) = console {
|
||||||
|
for &b in bytes {
|
||||||
|
libkos::syscall::debug_putchar(console, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
let ep = types::bounded::CapSlot::new(0).unwrap();
|
let ep = types::bounded::CapSlot::new(0).unwrap();
|
||||||
server_framework::serve(&mut srv, ep)
|
server_framework::serve(&mut srv, ep)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ pub enum LogRequest {
|
|||||||
Write { level: LogLevel, tag: u16, payload: usize },
|
Write { level: LogLevel, tag: u16, payload: usize },
|
||||||
GetCount,
|
GetCount,
|
||||||
GetEntry { index: usize },
|
GetEntry { index: usize },
|
||||||
|
/// Console text (`types::log`): `len` bytes of `bytes`.
|
||||||
|
Text { bytes: [u8; types::log::TEXT_BYTES], len: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@@ -50,6 +52,7 @@ impl Encode for LogRequest {
|
|||||||
}
|
}
|
||||||
LogRequest::GetCount => [1, 0, 0, 0],
|
LogRequest::GetCount => [1, 0, 0, 0],
|
||||||
LogRequest::GetEntry { index } => [2, *index, 0, 0],
|
LogRequest::GetEntry { index } => [2, *index, 0, 0],
|
||||||
|
LogRequest::Text { bytes, len } => types::log::text_words(&bytes[..*len]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +67,7 @@ impl Decode for LogRequest {
|
|||||||
}
|
}
|
||||||
1 => Some(LogRequest::GetCount),
|
1 => Some(LogRequest::GetCount),
|
||||||
2 => Some(LogRequest::GetEntry { index: words[1] }),
|
2 => Some(LogRequest::GetEntry { index: words[1] }),
|
||||||
_ => None,
|
_ => types::log::text_of(words).map(|(bytes, len)| LogRequest::Text { bytes, len }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,28 @@ struct LogEntry {
|
|||||||
payload: usize,
|
payload: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LogServer {
|
/// Holds the console: every process's text goes out through `out`.
|
||||||
|
pub struct LogServer<O: FnMut(&[u8]) = fn(&[u8])> {
|
||||||
entries: [Option<LogEntry>; MAX_ENTRIES],
|
entries: [Option<LogEntry>; MAX_ENTRIES],
|
||||||
count: usize,
|
count: usize,
|
||||||
write_pos: usize,
|
write_pos: usize,
|
||||||
|
out: O,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogServer {
|
impl LogServer {
|
||||||
|
/// Text goes nowhere.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
Self::with_output(|_| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<O: FnMut(&[u8])> LogServer<O> {
|
||||||
|
pub fn with_output(out: O) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: [const { None }; MAX_ENTRIES],
|
entries: [const { None }; MAX_ENTRIES],
|
||||||
count: 0,
|
count: 0,
|
||||||
write_pos: 0,
|
write_pos: 0,
|
||||||
|
out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +40,7 @@ impl LogServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server for LogServer {
|
impl<O: FnMut(&[u8])> Server for LogServer<O> {
|
||||||
type Request = LogRequest;
|
type Request = LogRequest;
|
||||||
type Response = LogResponse;
|
type Response = LogResponse;
|
||||||
|
|
||||||
@@ -44,6 +54,10 @@ impl Server for LogServer {
|
|||||||
}
|
}
|
||||||
LogResponse::Ok
|
LogResponse::Ok
|
||||||
}
|
}
|
||||||
|
LogRequest::Text { bytes, len } => {
|
||||||
|
(self.out)(&bytes[..len.min(bytes.len())]);
|
||||||
|
LogResponse::Ok
|
||||||
|
}
|
||||||
LogRequest::GetCount => LogResponse::Count(self.count),
|
LogRequest::GetCount => LogResponse::Count(self.count),
|
||||||
LogRequest::GetEntry { index } => {
|
LogRequest::GetEntry { index } => {
|
||||||
if index >= self.count {
|
if index >= self.count {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::protocol::*;
|
use crate::protocol::*;
|
||||||
use crate::server::LogServer;
|
use crate::server::LogServer;
|
||||||
use server_framework::Server;
|
use server_framework::{Decode, Server};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_server_has_zero_count() {
|
fn empty_server_has_zero_count() {
|
||||||
@@ -41,3 +41,20 @@ fn ring_buffer_wraps() {
|
|||||||
}
|
}
|
||||||
assert_eq!(server.count(), 128);
|
assert_eq!(server.count(), 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_goes_out_through_the_console() {
|
||||||
|
let mut seen = [0u8; 64];
|
||||||
|
let mut n = 0;
|
||||||
|
{
|
||||||
|
let mut server = LogServer::with_output(|b: &[u8]| {
|
||||||
|
seen[n..n + b.len()].copy_from_slice(b);
|
||||||
|
n += b.len();
|
||||||
|
});
|
||||||
|
for chunk in [&b"init: "[..], b"started\n"] {
|
||||||
|
let words = types::log::text_words(chunk);
|
||||||
|
server.handle(LogRequest::decode(words).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(&seen[..n], b"init: started\n");
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub enum StorageRequest {
|
|||||||
Create { path_len: usize },
|
Create { path_len: usize },
|
||||||
Delete { path_len: usize },
|
Delete { path_len: usize },
|
||||||
Mkdir { path_len: usize },
|
Mkdir { path_len: usize },
|
||||||
|
/// Carries the endpoint to print to.
|
||||||
|
SetLog,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -61,6 +63,7 @@ impl Encode for StorageRequest {
|
|||||||
Create { path_len } => [op::CREATE, path_len, 0, 0],
|
Create { path_len } => [op::CREATE, path_len, 0, 0],
|
||||||
Delete { path_len } => [op::DELETE, path_len, 0, 0],
|
Delete { path_len } => [op::DELETE, path_len, 0, 0],
|
||||||
Mkdir { path_len } => [op::MKDIR, path_len, 0, 0],
|
Mkdir { path_len } => [op::MKDIR, path_len, 0, 0],
|
||||||
|
SetLog => [op::SET_LOG, 0, 0, 0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,6 +82,7 @@ impl Decode for StorageRequest {
|
|||||||
op::CREATE => Create { path_len: w[1] },
|
op::CREATE => Create { path_len: w[1] },
|
||||||
op::DELETE => Delete { path_len: w[1] },
|
op::DELETE => Delete { path_len: w[1] },
|
||||||
op::MKDIR => Mkdir { path_len: w[1] },
|
op::MKDIR => Mkdir { path_len: w[1] },
|
||||||
|
op::SET_LOG => SetLog,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub struct StorageServer<F: FileSystem, B: Buffers> {
|
|||||||
clients: [Option<Client>; MAX_CLIENTS],
|
clients: [Option<Client>; MAX_CLIENTS],
|
||||||
/// The badge of the client that opened each handle.
|
/// The badge of the client that opened each handle.
|
||||||
owners: [Option<usize>; MAX_HANDLES],
|
owners: [Option<usize>; MAX_HANDLES],
|
||||||
|
log: Option<CapSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
||||||
@@ -40,6 +41,26 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
|||||||
buffers,
|
buffers,
|
||||||
clients: [const { None }; MAX_CLIENTS],
|
clients: [const { None }; MAX_CLIENTS],
|
||||||
owners: [None; MAX_HANDLES],
|
owners: [None; MAX_HANDLES],
|
||||||
|
log: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where this server prints, once init has said.
|
||||||
|
pub fn log(&self) -> Option<CapSlot> {
|
||||||
|
self.log
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clients hold badged copies of the endpoint, so the unbadged sender is
|
||||||
|
/// whoever was given the endpoint itself: init.
|
||||||
|
fn set_log(&mut self, badge: usize, cap: Option<CapSlot>) -> StorageResponse {
|
||||||
|
match (badge, cap, self.log) {
|
||||||
|
(0, Some(cap), None) => {
|
||||||
|
self.log = Some(cap);
|
||||||
|
libkos::start::set_log(Some(cap));
|
||||||
|
libkos::println!("storage: printing through the log server");
|
||||||
|
StorageResponse::Ok
|
||||||
|
}
|
||||||
|
_ => StorageResponse::Error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +138,7 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
|||||||
None => R::NotFound,
|
None => R::NotFound,
|
||||||
};
|
};
|
||||||
let response = match request {
|
let response = match request {
|
||||||
Attach => R::Error,
|
Attach | SetLog => R::Error,
|
||||||
Open { path_len } => {
|
Open { path_len } => {
|
||||||
let h = fs.open(&buf[path(path_len)]);
|
let h = fs.open(&buf[path(path_len)]);
|
||||||
opened(owners, h)
|
opened(owners, h)
|
||||||
@@ -195,9 +216,10 @@ impl<F: FileSystem, B: Buffers> Server for StorageServer<F, B> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
badge: usize,
|
badge: usize,
|
||||||
words: [usize; 4],
|
words: [usize; 4],
|
||||||
_cap: Option<CapSlot>,
|
cap: Option<CapSlot>,
|
||||||
) -> ([usize; 4], Option<CapSlot>) {
|
) -> ([usize; 4], Option<CapSlot>) {
|
||||||
match StorageRequest::decode(words) {
|
match StorageRequest::decode(words) {
|
||||||
|
Some(StorageRequest::SetLog) => (self.set_log(badge, cap).encode(), None),
|
||||||
Some(request) => {
|
Some(request) => {
|
||||||
let (response, cap) = self.serve(badge, request);
|
let (response, cap) = self.serve(badge, request);
|
||||||
(response.encode(), cap)
|
(response.encode(), cap)
|
||||||
|
|||||||
@@ -180,3 +180,18 @@ fn garbage_gets_a_malformed_reply() {
|
|||||||
let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None);
|
let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None);
|
||||||
assert_eq!((words[0], cap), (MALFORMED, None));
|
assert_eq!((words[0], cap), (MALFORMED, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_the_unbadged_holder_sets_the_log_and_only_once() {
|
||||||
|
let mut s = server();
|
||||||
|
let log = CapSlot::new(7);
|
||||||
|
let set = |s: &mut Srv, badge| {
|
||||||
|
let (words, _) = s.handle_call(badge, StorageRequest::SetLog.encode(), log);
|
||||||
|
StorageResponse::decode(words).unwrap()
|
||||||
|
};
|
||||||
|
assert_eq!(set(&mut s, 5), StorageResponse::Error);
|
||||||
|
assert_eq!(s.log(), None);
|
||||||
|
assert_eq!(set(&mut s, 0), StorageResponse::Ok);
|
||||||
|
assert_eq!(s.log(), log);
|
||||||
|
assert_eq!(set(&mut s, 0), StorageResponse::Error);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub mod budget;
|
|||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
pub mod id;
|
pub mod id;
|
||||||
|
pub mod log;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod mapping;
|
pub mod mapping;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
|
|||||||
49
crates/types/src/log.rs
Normal file
49
crates/types/src/log.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//! The log protocol's text message, shared by the log server and
|
||||||
|
//! `libkos::io` so the two cannot drift apart: word 0 is `TEXT` with the
|
||||||
|
//! length in bits 8..16, words 1..4 carry up to `TEXT_BYTES` bytes.
|
||||||
|
|
||||||
|
pub const TEXT: usize = 3;
|
||||||
|
pub const TEXT_BYTES: usize = 24;
|
||||||
|
|
||||||
|
/// `chunk` past `TEXT_BYTES` is cut off.
|
||||||
|
pub fn text_words(chunk: &[u8]) -> [usize; 4] {
|
||||||
|
let len = chunk.len().min(TEXT_BYTES);
|
||||||
|
let mut bytes = [0u8; TEXT_BYTES];
|
||||||
|
bytes[..len].copy_from_slice(&chunk[..len]);
|
||||||
|
let mut words = [TEXT | (len << 8), 0, 0, 0];
|
||||||
|
for (w, b) in words[1..].iter_mut().zip(bytes.as_chunks::<8>().0) {
|
||||||
|
*w = u64::from_le_bytes(*b) as usize;
|
||||||
|
}
|
||||||
|
words
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The bytes of a text message and how many of them count.
|
||||||
|
pub fn text_of(words: [usize; 4]) -> Option<([u8; TEXT_BYTES], usize)> {
|
||||||
|
if words[0] & 0xFF != TEXT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let len = (words[0] >> 8) & 0xFF;
|
||||||
|
if len > TEXT_BYTES {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut bytes = [0u8; TEXT_BYTES];
|
||||||
|
for (b, w) in bytes.as_chunks_mut::<8>().0.iter_mut().zip(&words[1..]) {
|
||||||
|
*b = (*w as u64).to_le_bytes();
|
||||||
|
}
|
||||||
|
Some((bytes, len))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_round_trips_and_is_cut_at_the_limit() {
|
||||||
|
let (bytes, len) = text_of(text_words(b"init: hello")).unwrap();
|
||||||
|
assert_eq!(&bytes[..len], b"init: hello");
|
||||||
|
let long = [b'x'; 30];
|
||||||
|
assert_eq!(text_of(text_words(&long)).unwrap().1, TEXT_BYTES);
|
||||||
|
assert_eq!(text_of([TEXT | (25 << 8), 0, 0, 0]), None);
|
||||||
|
assert_eq!(text_of([0, 0, 0, 0]), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ pub mod op {
|
|||||||
pub const CREATE: usize = 7;
|
pub const CREATE: usize = 7;
|
||||||
pub const DELETE: usize = 8;
|
pub const DELETE: usize = 8;
|
||||||
pub const MKDIR: usize = 9;
|
pub const MKDIR: usize = 9;
|
||||||
|
/// Carries the log endpoint; only from the unbadged holder (init).
|
||||||
|
pub const SET_LOG: usize = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reply tags (word 0).
|
/// Reply tags (word 0).
|
||||||
|
|||||||
19
docs/ipc.md
19
docs/ipc.md
@@ -67,17 +67,24 @@ No process is privileged by its id. At boot the kernel gives init:
|
|||||||
- one `Console` capability (`Write`, `Delegate`).
|
- one `Console` capability (`Write`, `Delegate`).
|
||||||
|
|
||||||
`DebugPutchar(console, byte)` needs a `Console` capability with `Write`;
|
`DebugPutchar(console, byte)` needs a `Console` capability with `Write`;
|
||||||
there is no other way to reach the UART. Init lends its console to the
|
there is no other way to reach the UART. Init prints with it only until it
|
||||||
servers it starts (snake gets none). Until the log server owns the console,
|
has started the log server, then hands it over and drops its own; from then
|
||||||
boot code also grants one to the servers it loads itself.
|
on the log server is the only holder, and every other process prints by
|
||||||
|
calling it (`types::log`) if init gave it a log endpoint.
|
||||||
|
|
||||||
A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's
|
A thread made by `SpawnThread`/`SpawnThreadIn` starts with the caller's
|
||||||
`arg` in `a0`. A process's first thread reads it as `types::start_arg`:
|
`arg` in `a0`. A process's first thread reads it as `types::start_arg`:
|
||||||
the slots of the console and log capabilities it was handed.
|
the slots of the console and log capabilities it was handed.
|
||||||
|
|
||||||
The board capabilities come after the capabilities boot code hands out
|
Boot loads only init and storage (init needs it to read anything else):
|
||||||
for the built-in servers, so those slots are unchanged. Device regions
|
init gets storage's process and endpoint in slots 1 and 2, before the
|
||||||
never overlap each other or the frame pool, and each device has exactly
|
board capabilities. Everything else init starts from its manifest
|
||||||
|
(`init/src/manifest.rs`), handing each program only the endpoints, device
|
||||||
|
and budget listed there. Storage, started before the log server, receives
|
||||||
|
its log endpoint from init later (`SET_LOG`, accepted only from the
|
||||||
|
unbadged holder of its endpoint).
|
||||||
|
|
||||||
|
Device regions never overlap each other or the frame pool, and each device has exactly
|
||||||
one region, so delegating or revoking it means the same thing everywhere.
|
one region, so delegating or revoking it means the same thing everywhere.
|
||||||
|
|
||||||
`RegionAddress(cap)` returns a region's physical base so a driver can
|
`RegionAddress(cap)` returns a region's physical base so a driver can
|
||||||
|
|||||||
Reference in New Issue
Block a user