feat(kernel): SystemControl cap for halt, board device table + boot caps to init, RegionAddress syscall, idle loop services irqs
This commit is contained in:
@@ -8,3 +8,29 @@ pub const CLINT_BASE: usize = 0x02000000;
|
|||||||
pub const USER_POOL_BASE: usize = 0x84000000;
|
pub const USER_POOL_BASE: usize = 0x84000000;
|
||||||
pub const USER_POOL_PAGES: usize = 4096;
|
pub const USER_POOL_PAGES: usize = 4096;
|
||||||
pub const TICK_INTERVAL: u64 = 10_000_000;
|
pub const TICK_INTERVAL: u64 = 10_000_000;
|
||||||
|
|
||||||
|
/// QEMU virt's PLIC lines.
|
||||||
|
const UART_IRQ: u16 = 10;
|
||||||
|
const VIRTIO_IRQ_BASE: u16 = 1;
|
||||||
|
const VIRTIO_SLOT_SIZE: usize = 0x1000;
|
||||||
|
const VIRTIO_SLOTS: usize = VIRTIO_SIZE / VIRTIO_SLOT_SIZE;
|
||||||
|
|
||||||
|
/// Every device userspace may drive: the UART and each VirtIO MMIO slot.
|
||||||
|
/// The PLIC, CLINT and RAM stay the kernel's.
|
||||||
|
pub fn devices() -> Option<[kernel::BoardDevice; 1 + VIRTIO_SLOTS]> {
|
||||||
|
use types::{address::PhysicalAddress, bounded::PageCount, id::IrqId};
|
||||||
|
let one = PageCount::new(1)?;
|
||||||
|
let device = |base: usize, irq: u16| kernel::BoardDevice {
|
||||||
|
base: PhysicalAddress::from_raw(base),
|
||||||
|
pages: one,
|
||||||
|
irq: Some(IrqId::from_raw(irq)),
|
||||||
|
};
|
||||||
|
let mut all = [device(UART_BASE, UART_IRQ); 1 + VIRTIO_SLOTS];
|
||||||
|
for (i, d) in all.iter_mut().skip(1).enumerate() {
|
||||||
|
*d = device(
|
||||||
|
VIRTIO_BASE + i * VIRTIO_SLOT_SIZE,
|
||||||
|
VIRTIO_IRQ_BASE + i as u16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(all)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use types::mapping::PagePerms;
|
|||||||
|
|
||||||
// Boot-time device wiring. Everything here goes through the kernel's
|
// Boot-time device wiring. Everything here goes through the kernel's
|
||||||
// mapping API so the kernel knows about every page a driver can touch.
|
// mapping API so the kernel knows about every page a driver can touch.
|
||||||
// P5 replaces these hardcoded grants with capabilities handed to init.
|
// Device regions come from the board table and belong to init, which
|
||||||
|
// also holds capabilities to them; moving drivers onto those caps is
|
||||||
|
// userspace work.
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
@@ -23,11 +25,16 @@ fn pages(uart: &Uart, n: usize) -> PageCount {
|
|||||||
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps device MMIO at `pa` into `pid` at `va`.
|
/// Maps the board device region at `pa` into `pid` at `va`. The region
|
||||||
|
/// belongs to init (see `kernel::Kernel::register_board`); `n` must match
|
||||||
|
/// its size.
|
||||||
pub(crate) fn map_mmio(uart: &Uart, pid: ProcessId, va: usize, pa: usize, n: usize) {
|
pub(crate) fn map_mmio(uart: &Uart, pid: ProcessId, va: usize, pa: usize, n: usize) {
|
||||||
let id = kern()
|
let id = kern()
|
||||||
.create_device_region(pid, PhysicalAddress::from_raw(pa), pages(uart, n))
|
.device_region_at(PhysicalAddress::from_raw(pa))
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "device region failed"));
|
.unwrap_or_else(|| crate::halt(uart, "no such board device"));
|
||||||
|
if kern().region_pages(id).map(|p| p.value() as usize) != Some(n) {
|
||||||
|
crate::halt(uart, "device size mismatch");
|
||||||
|
}
|
||||||
kern()
|
kern()
|
||||||
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ use kernel_cell::KernelCell;
|
|||||||
|
|
||||||
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
|
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
|
||||||
|
|
||||||
|
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
|
||||||
|
// SAFETY: single core; boot code runs before any trap can touch the
|
||||||
|
// kernel.
|
||||||
|
unsafe { &mut *kernel_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn kernel_ptr() -> *mut kernel::Kernel<platform::RiscVPlatform> {
|
pub fn kernel_ptr() -> *mut kernel::Kernel<platform::RiscVPlatform> {
|
||||||
KERNEL.as_ptr()
|
KERNEL.as_ptr()
|
||||||
}
|
}
|
||||||
@@ -51,10 +57,18 @@ extern "C" fn kmain() -> ! {
|
|||||||
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
||||||
let virtio_base = blk.base();
|
let virtio_base = blk.base();
|
||||||
|
|
||||||
|
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
|
||||||
|
kern_mut()
|
||||||
|
.register_board(init.pid, &board)
|
||||||
|
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||||
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
|
devices::storage_setup::configure(&uart, init.pid, storage.pid, virtio_base);
|
||||||
devices::gpu_setup::configure(&uart, display.pid);
|
devices::gpu_setup::configure(&uart, display.pid);
|
||||||
devices::input_setup::configure(&uart, input.pid);
|
devices::input_setup::configure(&uart, input.pid);
|
||||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||||
|
// After grant_all, so the slots init already relies on stay put.
|
||||||
|
kern_mut()
|
||||||
|
.grant_boot_authority(init.pid, &board)
|
||||||
|
.unwrap_or_else(|_| halt(&uart, "boot authority failed"));
|
||||||
|
|
||||||
uart.write_str("k-os: entering userspace\n");
|
uart.write_str("k-os: entering userspace\n");
|
||||||
boot::loader::enter_userspace(init.pid, init.entry, SHARED_BUF_VA);
|
boot::loader::enter_userspace(init.pid, init.entry, SHARED_BUF_VA);
|
||||||
|
|||||||
@@ -218,7 +218,8 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
|
|||||||
save_frame_to_context(frame, ctx);
|
save_frame_to_context(frame, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_tid) = kern.pick_next_thread() {
|
let next_tid = next_runnable();
|
||||||
|
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||||
let next_pid = kern.thread_process(next_tid);
|
let next_pid = kern.thread_process(next_tid);
|
||||||
let current_pid = kern.thread_process(current_tid);
|
let current_pid = kern.thread_process(current_tid);
|
||||||
|
|
||||||
@@ -240,10 +241,43 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
|
|||||||
crate::mmu::activate(pt);
|
crate::mmu::activate(pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
loop {
|
|
||||||
unsafe { core::arch::asm!("wfi") };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The next thread to run, idling until an interrupt makes one ready.
|
||||||
|
///
|
||||||
|
/// We are inside a trap, so SIE is clear and interrupts are not taken.
|
||||||
|
/// `wfi` still wakes once one is pending, and it is then serviced here by
|
||||||
|
/// polling `sip`, which is how a driver blocked on its IRQ notification
|
||||||
|
/// gets woken when nothing else can run.
|
||||||
|
fn next_runnable() -> types::id::ThreadId {
|
||||||
|
loop {
|
||||||
|
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||||
|
if let Some(tid) = kern.pick_next_thread() {
|
||||||
|
return tid;
|
||||||
|
}
|
||||||
|
unsafe { core::arch::asm!("wfi") };
|
||||||
|
let sip: usize;
|
||||||
|
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
|
||||||
|
if sip & (1 << CAUSE_S_TIMER) != 0 {
|
||||||
|
// No thread is running, so there is no time slice to charge.
|
||||||
|
crate::hw::timer::handle_interrupt();
|
||||||
|
}
|
||||||
|
if sip & (1 << CAUSE_S_EXTERNAL) != 0 {
|
||||||
|
dispatch_external();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claims every pending PLIC interrupt and hands each to the kernel.
|
||||||
|
fn dispatch_external() {
|
||||||
|
loop {
|
||||||
|
let irq_num = crate::hw::plic::Plic::claim();
|
||||||
|
if irq_num == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let kern = unsafe { &mut *crate::kernel_ptr() };
|
||||||
|
kern.handle_interrupt(types::id::IrqId::from_raw(irq_num as u16));
|
||||||
|
crate::hw::plic::Plic::complete(irq_num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,15 +306,7 @@ fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CAUSE_S_EXTERNAL => {
|
CAUSE_S_EXTERNAL => dispatch_external(),
|
||||||
let irq_num = crate::hw::plic::Plic::claim();
|
|
||||||
if irq_num != 0 {
|
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
|
||||||
let irq = types::id::IrqId::from_raw(irq_num as u16);
|
|
||||||
kern.handle_interrupt(irq);
|
|
||||||
crate::hw::plic::Plic::complete(irq_num);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ impl IrqRoutingTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `irq` is a line this table can route.
|
||||||
|
pub fn is_valid(&self, irq: IrqId) -> bool {
|
||||||
|
(irq.raw() as usize) < MAX_IRQS
|
||||||
|
}
|
||||||
|
|
||||||
/// Routes `irq`, replacing any previous route. Fails for lines beyond
|
/// Routes `irq`, replacing any previous route. Fails for lines beyond
|
||||||
/// the table.
|
/// the table.
|
||||||
pub fn set(&mut self, irq: IrqId, route: IrqRoute) -> bool {
|
pub fn set(&mut self, irq: IrqId, route: IrqRoute) -> bool {
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ pub enum Resource {
|
|||||||
Process(ProcessId),
|
Process(ProcessId),
|
||||||
Interrupt(IrqId),
|
Interrupt(IrqId),
|
||||||
ProcessCreate,
|
ProcessCreate,
|
||||||
|
/// Machine-wide controls such as halting; given only to init at boot.
|
||||||
|
SystemControl,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ pub enum Syscall {
|
|||||||
va: VirtualAddress,
|
va: VirtualAddress,
|
||||||
perms: PagePerms,
|
perms: PagePerms,
|
||||||
},
|
},
|
||||||
|
/// The physical base of the region named by `region`, so a driver can
|
||||||
|
/// point a device's DMA at memory it has mapped.
|
||||||
|
RegionAddress {
|
||||||
|
region: CapSlot,
|
||||||
|
},
|
||||||
/// Removes a capability from the caller's own table.
|
/// Removes a capability from the caller's own table.
|
||||||
DropCap {
|
DropCap {
|
||||||
slot: CapSlot,
|
slot: CapSlot,
|
||||||
@@ -104,7 +109,10 @@ pub enum Syscall {
|
|||||||
GetProcessId,
|
GetProcessId,
|
||||||
Yield,
|
Yield,
|
||||||
Exit,
|
Exit,
|
||||||
Halt,
|
/// Stops the machine; needs a SystemControl capability.
|
||||||
|
Halt {
|
||||||
|
control: CapSlot,
|
||||||
|
},
|
||||||
CreateEndpoint,
|
CreateEndpoint,
|
||||||
DebugPutchar {
|
DebugPutchar {
|
||||||
byte: u8,
|
byte: u8,
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ fn message(a: &[usize; 6]) -> Option<Message> {
|
|||||||
pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
||||||
Some(match nr {
|
Some(match nr {
|
||||||
SYS_EXIT => Syscall::Exit,
|
SYS_EXIT => Syscall::Exit,
|
||||||
SYS_HALT => Syscall::Halt,
|
SYS_HALT => Syscall::Halt {
|
||||||
|
control: slot(a[0])?,
|
||||||
|
},
|
||||||
SYS_EXIT_THREAD => Syscall::ExitThread,
|
SYS_EXIT_THREAD => Syscall::ExitThread,
|
||||||
SYS_YIELD => Syscall::Yield,
|
SYS_YIELD => Syscall::Yield,
|
||||||
SYS_GET_TIME => Syscall::GetTime,
|
SYS_GET_TIME => Syscall::GetTime,
|
||||||
@@ -125,6 +127,9 @@ pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
|||||||
va: VirtualAddress::from_raw(a[1]),
|
va: VirtualAddress::from_raw(a[1]),
|
||||||
perms: reg_perms(a[2])?,
|
perms: reg_perms(a[2])?,
|
||||||
},
|
},
|
||||||
|
SYS_REGION_ADDRESS => Syscall::RegionAddress {
|
||||||
|
region: slot(a[0])?,
|
||||||
|
},
|
||||||
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a[0] as u8 },
|
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a[0] as u8 },
|
||||||
SYS_MAP_PAGE => Syscall::MapPage {
|
SYS_MAP_PAGE => Syscall::MapPage {
|
||||||
process: slot(a[0])?,
|
process: slot(a[0])?,
|
||||||
|
|||||||
79
crates/kernel/src/board.rs
Normal file
79
crates/kernel/src/board.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
//! The authority init starts with. The board describes its devices; the
|
||||||
|
//! kernel turns each into capabilities (an MMIO region and an IRQ line)
|
||||||
|
//! and adds one SystemControl capability. Init then decides who gets what;
|
||||||
|
//! nothing else is privileged by its process id.
|
||||||
|
|
||||||
|
use domain::{error::KernelError, resource::Resource};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
address::PhysicalAddress,
|
||||||
|
bounded::PageCount,
|
||||||
|
id::{IrqId, ProcessId, RegionId},
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// A device the board exposes to userspace: its MMIO window and, if it
|
||||||
|
/// raises interrupts, its IRQ line.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BoardDevice {
|
||||||
|
pub base: PhysicalAddress,
|
||||||
|
pub pages: PageCount,
|
||||||
|
pub irq: Option<IrqId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Creates one device region per board device, owned by `init`. Fails
|
||||||
|
/// on the first device that is misaligned, overlaps the frame pool or
|
||||||
|
/// another device, or names an IRQ the kernel cannot route.
|
||||||
|
pub fn register_board(
|
||||||
|
&mut self,
|
||||||
|
init: ProcessId,
|
||||||
|
devices: &[BoardDevice],
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
for device in devices {
|
||||||
|
if let Some(irq) = device.irq
|
||||||
|
&& !self.irqs.is_valid(irq)
|
||||||
|
{
|
||||||
|
return Err(KernelError::InvalidIrq(irq));
|
||||||
|
}
|
||||||
|
self.create_device_region(init, device.base, device.pages)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives `init` a capability to every registered device's region and
|
||||||
|
/// IRQ line, then one SystemControl capability. All carry Delegate so
|
||||||
|
/// init can hand them on. Done separately from `register_board` so
|
||||||
|
/// boot code can wire drivers to the regions first.
|
||||||
|
pub fn grant_boot_authority(
|
||||||
|
&mut self,
|
||||||
|
init: ProcessId,
|
||||||
|
devices: &[BoardDevice],
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let delegate = PermissionSet::empty().with(Permission::Delegate);
|
||||||
|
let control = delegate.with(Permission::Execute);
|
||||||
|
let mmio = delegate.with(Permission::Read).with(Permission::Write);
|
||||||
|
for device in devices {
|
||||||
|
let region = self
|
||||||
|
.device_region_at(device.base)
|
||||||
|
.filter(|id| self.regions.get(*id).is_some_and(|r| r.owner() == init))
|
||||||
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
|
self.grant_cap(init, Resource::Memory(region), mmio)?;
|
||||||
|
if let Some(irq) = device.irq {
|
||||||
|
self.grant_cap(init, Resource::Interrupt(irq), control)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.grant_cap(init, Resource::SystemControl, control)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The device region that starts at `base`.
|
||||||
|
pub fn device_region_at(&self, base: PhysicalAddress) -> Option<RegionId> {
|
||||||
|
self.regions
|
||||||
|
.iter()
|
||||||
|
.find(|(_, r)| r.backing() == domain::memory::RegionBacking::Device && r.base() == base)
|
||||||
|
.map(|(id, _)| id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod abi;
|
pub mod abi;
|
||||||
|
mod board;
|
||||||
mod captree;
|
mod captree;
|
||||||
pub mod interrupt;
|
pub mod interrupt;
|
||||||
mod memory;
|
mod memory;
|
||||||
@@ -22,6 +23,7 @@ mod memory;
|
|||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
|
||||||
|
pub use board::BoardDevice;
|
||||||
pub use syscall::{SyscallOutcome, SyscallResult};
|
pub use syscall::{SyscallOutcome, SyscallResult};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -388,7 +390,9 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Resource::Memory(id) => self.regions.generation(*id),
|
Resource::Memory(id) => self.regions.generation(*id),
|
||||||
Resource::Notification(id) => self.notifications.generation(*id),
|
Resource::Notification(id) => self.notifications.generation(*id),
|
||||||
Resource::Process(pid) => self.processes.generation(*pid),
|
Resource::Process(pid) => self.processes.generation(*pid),
|
||||||
Resource::Interrupt(_) | Resource::ProcessCreate => Some(Generation::initial()),
|
Resource::Interrupt(_) | Resource::ProcessCreate | Resource::SystemControl => {
|
||||||
|
Some(Generation::initial())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -325,11 +325,23 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
// Device memory must never alias frames the kernel hands out.
|
// Device memory must never alias frames the kernel hands out.
|
||||||
let len = pages.value() as usize * PAGE_SIZE;
|
let len = pages.value() as usize * PAGE_SIZE;
|
||||||
|
let start = base.raw();
|
||||||
|
let end = start.checked_add(len).ok_or(KernelError::InvalidAddress)?;
|
||||||
let pool_start = self.frames.base().raw();
|
let pool_start = self.frames.base().raw();
|
||||||
let pool_end = pool_start + self.frames.capacity() * PAGE_SIZE;
|
let pool_end = pool_start + self.frames.capacity() * PAGE_SIZE;
|
||||||
if base.raw() < pool_end && base.raw() + len > pool_start {
|
if start < pool_end && end > pool_start {
|
||||||
return Err(KernelError::InvalidAddress);
|
return Err(KernelError::InvalidAddress);
|
||||||
}
|
}
|
||||||
|
// Nor another device's window: each device has one region, so
|
||||||
|
// revoking or delegating it means the same thing everywhere.
|
||||||
|
let overlaps = self.regions.iter().any(|(_, r)| {
|
||||||
|
let r_start = r.base().raw();
|
||||||
|
let r_end = r_start.saturating_add(r.pages().value() as usize * PAGE_SIZE);
|
||||||
|
r.backing() == RegionBacking::Device && start < r_end && end > r_start
|
||||||
|
});
|
||||||
|
if overlaps {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
self.regions
|
self.regions
|
||||||
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
||||||
.ok_or(KernelError::NoFreeSlot)
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
@@ -339,6 +351,10 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.regions.get(id).map(Region::base)
|
self.regions.get(id).map(Region::base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn region_pages(&self, id: RegionId) -> Option<PageCount> {
|
||||||
|
self.regions.get(id).map(Region::pages)
|
||||||
|
}
|
||||||
|
|
||||||
/// Maps all of region `id` at `va` in `pid`, all or nothing.
|
/// Maps all of region `id` at `va` in `pid`, all or nothing.
|
||||||
/// Maps region `id` for boot code; the mapping is not tied to any
|
/// Maps region `id` for boot code; the mapping is not tied to any
|
||||||
/// capability and lasts until the region or the process dies.
|
/// capability and lasts until the region or the process dies.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::{
|
use types::{
|
||||||
address::VirtualAddress, bounded::CapSlot, bounded::PageCount, id::ProcessId,
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
bounded::CapSlot,
|
||||||
|
bounded::PageCount,
|
||||||
|
id::ProcessId,
|
||||||
mapping::PagePerms,
|
mapping::PagePerms,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,4 +118,18 @@ impl<P: Platform> Kernel<P> {
|
|||||||
let via = self.cap_ref(pid, slot);
|
let via = self.cap_ref(pid, slot);
|
||||||
self.map_region_via(id, pid, va, perms, via)
|
self.map_region_via(id, pid, va, perms, via)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The physical base of the region behind `slot`. Needs Read on the
|
||||||
|
/// capability; pool regions are physically contiguous, so the base is
|
||||||
|
/// enough to program a device.
|
||||||
|
pub(crate) fn region_address_cap(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
) -> Result<PhysicalAddress, KernelError> {
|
||||||
|
let Resource::Memory(id) = *self.check_cap(pid, slot, Permission::Read)?.resource() else {
|
||||||
|
return Err(KernelError::WrongObject);
|
||||||
|
};
|
||||||
|
self.region_base(id).ok_or(KernelError::InvalidRegion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.map_region_cap(pid, region, va, perms)
|
self.map_region_cap(pid, region, va, perms)
|
||||||
.map(|()| SyscallResult::Ok),
|
.map(|()| SyscallResult::Ok),
|
||||||
),
|
),
|
||||||
|
Syscall::RegionAddress { region } => complete(
|
||||||
|
self.region_address_cap(pid, region)
|
||||||
|
.map(|pa| SyscallResult::Value(pa.raw())),
|
||||||
|
),
|
||||||
Syscall::Delegate {
|
Syscall::Delegate {
|
||||||
slot,
|
slot,
|
||||||
to_endpoint,
|
to_endpoint,
|
||||||
@@ -136,7 +140,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::AcknowledgeInterrupt { irq } => {
|
Syscall::AcknowledgeInterrupt { irq } => {
|
||||||
complete(self.ack_interrupt(pid, irq).map(|()| SyscallResult::Ok))
|
complete(self.ack_interrupt(pid, irq).map(|()| SyscallResult::Ok))
|
||||||
}
|
}
|
||||||
Syscall::Halt => self.handle_halt(pid),
|
Syscall::Halt { control } => self.handle_halt(pid, control),
|
||||||
Syscall::CreateEndpoint => self.handle_create_endpoint(pid),
|
Syscall::CreateEndpoint => self.handle_create_endpoint(pid),
|
||||||
Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte),
|
Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte),
|
||||||
Syscall::MapPage {
|
Syscall::MapPage {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
use domain::{error::KernelError, resource::Resource};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
|
use types::{bounded::CapSlot, id::ProcessId, permission::Permission};
|
||||||
|
|
||||||
use super::{SyscallOutcome, SyscallResult};
|
use super::{SyscallOutcome, SyscallResult};
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
@@ -87,12 +89,13 @@ impl<P: Platform> Kernel<P> {
|
|||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_halt(&mut self, pid: types::id::ProcessId) -> SyscallOutcome {
|
/// Halts the machine for a holder of a SystemControl capability with
|
||||||
if pid.raw() != 0 {
|
/// Execute; anyone else gets an error and nothing happens.
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
pub(crate) fn handle_halt(&mut self, pid: ProcessId, control: CapSlot) -> SyscallOutcome {
|
||||||
domain::error::KernelError::ProcessCreateDenied,
|
match self.check_cap(pid, control, Permission::Execute) {
|
||||||
));
|
Ok(cap) if *cap.resource() == Resource::SystemControl => self.platform.halt(),
|
||||||
}
|
Ok(_) => SyscallOutcome::Complete(SyscallResult::Error(KernelError::WrongObject)),
|
||||||
self.platform.halt();
|
Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ fn every_defined_syscall_decodes() {
|
|||||||
SYS_SET_IRQ_NOTIFICATION,
|
SYS_SET_IRQ_NOTIFICATION,
|
||||||
SYS_CREATE_REGION,
|
SYS_CREATE_REGION,
|
||||||
SYS_MAP_REGION,
|
SYS_MAP_REGION,
|
||||||
|
SYS_REGION_ADDRESS,
|
||||||
];
|
];
|
||||||
// slot 1, slot 2, small values, no cap
|
// slot 1, slot 2, small values, no cap
|
||||||
let args = [1, 2, 1, 0, 0, NONE];
|
let args = [1, 2, 1, 0, 0, NONE];
|
||||||
@@ -51,7 +52,7 @@ fn every_defined_syscall_decodes() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn retired_and_unknown_numbers_do_not_decode() {
|
fn retired_and_unknown_numbers_do_not_decode() {
|
||||||
for nr in [3, 5, 35, 1000, usize::MAX] {
|
for nr in [3, 5, 36, 1000, usize::MAX] {
|
||||||
assert!(decode(nr, &[0; 6]).is_none(), "{nr} decoded");
|
assert!(decode(nr, &[0; 6]).is_none(), "{nr} decoded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
323
crates/kernel/src/tests/board.rs
Normal file
323
crates/kernel/src/tests/board.rs
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::{BoardDevice, SyscallOutcome, SyscallResult};
|
||||||
|
use domain::{error::KernelError, resource::Resource, syscall::Syscall};
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
badge::Badge,
|
||||||
|
bounded::{CapSlot, PageCount, Priority},
|
||||||
|
id::IrqId,
|
||||||
|
mapping::PagePerms,
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
time::TimeSlice,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UART: usize = 0x1000_0000;
|
||||||
|
const VIRTIO: usize = 0x1000_1000;
|
||||||
|
|
||||||
|
fn device(base: usize, pages: u16, irq: Option<u16>) -> BoardDevice {
|
||||||
|
BoardDevice {
|
||||||
|
base: PhysicalAddress::from_raw(base),
|
||||||
|
pages: PageCount::new(pages).unwrap(),
|
||||||
|
irq: irq.map(IrqId::from_raw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn board() -> [BoardDevice; 2] {
|
||||||
|
[device(UART, 1, Some(10)), device(VIRTIO, 1, Some(1))]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Init after boot with the board registered and its authority granted.
|
||||||
|
fn booted() -> (Kernel<MockPlatform>, ProcessId) {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
k.register_board(init, &board()).unwrap();
|
||||||
|
k.grant_boot_authority(init, &board()).unwrap();
|
||||||
|
(k, init)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slot_holding(k: &Kernel<MockPlatform>, pid: ProcessId, resource: Resource) -> CapSlot {
|
||||||
|
k.processes
|
||||||
|
.get(pid)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.iter()
|
||||||
|
.find(|(_, c)| *c.resource() == resource)
|
||||||
|
.map(|(s, _)| s)
|
||||||
|
.expect("capability present")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perms_of(k: &Kernel<MockPlatform>, pid: ProcessId, slot: CapSlot) -> PermissionSet {
|
||||||
|
*k.processes
|
||||||
|
.get(pid)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.get(slot)
|
||||||
|
.unwrap()
|
||||||
|
.permissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_of(outcome: SyscallOutcome) -> KernelError {
|
||||||
|
match outcome {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(e)) => e,
|
||||||
|
_ => panic!("expected an error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- halt ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_cannot_halt_without_a_system_control_cap() {
|
||||||
|
let (mut k, _) = kernel_with_process();
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::Halt {
|
||||||
|
control: CapSlot::new(0).unwrap(),
|
||||||
|
}));
|
||||||
|
assert!(
|
||||||
|
matches!(e, KernelError::WrongObject),
|
||||||
|
"slot 0 is ProcessCreate"
|
||||||
|
);
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::Halt {
|
||||||
|
control: CapSlot::new(9).unwrap(),
|
||||||
|
}));
|
||||||
|
assert!(matches!(e, KernelError::InvalidSlot(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "halt")]
|
||||||
|
fn system_control_cap_halts() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let control = slot_holding(&k, init, Resource::SystemControl);
|
||||||
|
k.handle_syscall(Syscall::Halt { control });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn halt_needs_execute_on_the_cap() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let control = slot_holding(&k, init, Resource::SystemControl);
|
||||||
|
let weak = k
|
||||||
|
.mint(
|
||||||
|
init,
|
||||||
|
control,
|
||||||
|
Badge::new(0),
|
||||||
|
PermissionSet::empty().with(Permission::Delegate),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::Halt { control: weak }));
|
||||||
|
assert!(matches!(e, KernelError::PermissionDenied { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "halt")]
|
||||||
|
fn any_holder_of_the_cap_may_halt_not_just_init() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let other = k.create_process_internal().unwrap();
|
||||||
|
let tid = k.spawn_thread_for_test(other, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
|
k.grant_cap_for_test(
|
||||||
|
other,
|
||||||
|
Resource::SystemControl,
|
||||||
|
PermissionSet::empty().with(Permission::Execute),
|
||||||
|
);
|
||||||
|
let _ = init;
|
||||||
|
k.switch_to_thread_for_test(tid);
|
||||||
|
let control = slot_holding(&k, other, Resource::SystemControl);
|
||||||
|
k.handle_syscall(Syscall::Halt { control });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- boot authority ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_gets_mmio_irq_and_control_caps_it_can_delegate() {
|
||||||
|
let (k, init) = booted();
|
||||||
|
let delegate = Permission::Delegate;
|
||||||
|
for d in board() {
|
||||||
|
let region = k.device_region_at(d.base).unwrap();
|
||||||
|
let mem = slot_holding(&k, init, Resource::Memory(region));
|
||||||
|
let p = perms_of(&k, init, mem);
|
||||||
|
assert!(p.allows(Permission::Read) && p.allows(Permission::Write) && p.allows(delegate));
|
||||||
|
assert!(!p.allows(Permission::Execute), "MMIO is never executable");
|
||||||
|
let irq = slot_holding(&k, init, Resource::Interrupt(d.irq.unwrap()));
|
||||||
|
let p = perms_of(&k, init, irq);
|
||||||
|
assert!(p.allows(Permission::Execute) && p.allows(delegate));
|
||||||
|
}
|
||||||
|
let control = slot_holding(&k, init, Resource::SystemControl);
|
||||||
|
let p = perms_of(&k, init, control);
|
||||||
|
assert!(p.allows(Permission::Execute) && p.allows(delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boot_authority_keeps_existing_slots() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let before = k.cap_slots_for_test(init);
|
||||||
|
k.register_board(init, &board()).unwrap();
|
||||||
|
k.grant_boot_authority(init, &board()).unwrap();
|
||||||
|
let after = k.cap_slots_for_test(init);
|
||||||
|
for (b, a) in before.iter().zip(after.iter()) {
|
||||||
|
assert!(!b || *a, "an existing slot was disturbed");
|
||||||
|
}
|
||||||
|
assert_eq!(after.iter().filter(|s| **s).count(), 1 + 2 + 2 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_can_map_a_device_through_its_cap() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let region = slot_holding(
|
||||||
|
&k,
|
||||||
|
init,
|
||||||
|
Resource::Memory(k.device_region_at(PhysicalAddress::from_raw(UART)).unwrap()),
|
||||||
|
);
|
||||||
|
let outcome = k.handle_syscall(Syscall::MapRegion {
|
||||||
|
region,
|
||||||
|
va: VirtualAddress::from_raw(0x10_0000),
|
||||||
|
perms: PagePerms::ReadWrite,
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
|
));
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::MapRegion {
|
||||||
|
region,
|
||||||
|
va: VirtualAddress::from_raw(0x20_0000),
|
||||||
|
perms: PagePerms::ReadExecute,
|
||||||
|
}));
|
||||||
|
assert!(matches!(e, KernelError::PermissionDenied { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_can_route_a_board_irq() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let irq = slot_holding(&k, init, Resource::Interrupt(IrqId::from_raw(1)));
|
||||||
|
let notification = k.create_notification_cap(init).unwrap();
|
||||||
|
let outcome = k.handle_syscall(Syscall::SetIrqNotification { irq, notification });
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn granting_needs_a_registered_board() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
assert!(matches!(
|
||||||
|
k.grant_boot_authority(init, &board()),
|
||||||
|
Err(KernelError::NoSuchObject)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn granting_needs_the_regions_to_be_inits() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let other = k.create_process_internal().unwrap();
|
||||||
|
k.register_board(other, &board()).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
k.grant_boot_authority(init, &board()),
|
||||||
|
Err(KernelError::NoSuchObject)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn board_devices_may_not_overlap() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let overlapping = [device(UART, 2, None), device(UART + 0x1000, 1, None)];
|
||||||
|
assert!(matches!(
|
||||||
|
k.register_board(init, &overlapping),
|
||||||
|
Err(KernelError::AddressInUse)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn board_devices_may_not_alias_the_frame_pool() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
assert!(matches!(
|
||||||
|
k.register_board(init, &[device(0x8400_0000, 1, None)]),
|
||||||
|
Err(KernelError::InvalidAddress)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn board_devices_must_be_page_aligned_and_in_range() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
assert!(matches!(
|
||||||
|
k.register_board(init, &[device(UART + 8, 1, None)]),
|
||||||
|
Err(KernelError::InvalidAddress)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
k.register_board(init, &[device(usize::MAX & !0xfff, 2, None)]),
|
||||||
|
Err(KernelError::InvalidAddress)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn board_irqs_must_be_routable() {
|
||||||
|
let (mut k, init) = kernel_with_process();
|
||||||
|
let bad = IrqId::from_raw(u16::MAX);
|
||||||
|
assert!(matches!(
|
||||||
|
k.register_board(init, &[device(UART, 1, Some(u16::MAX))]),
|
||||||
|
Err(KernelError::InvalidIrq(i)) if i == bad
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- region address ----
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn region_address_reports_the_physical_base() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let pool = k
|
||||||
|
.create_region_cap(init, PageCount::new(2).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let outcome = k.handle_syscall(Syscall::RegionAddress { region: pool });
|
||||||
|
let SyscallOutcome::Complete(SyscallResult::Value(pa)) = outcome else {
|
||||||
|
panic!("expected an address");
|
||||||
|
};
|
||||||
|
let Resource::Memory(id) = *k
|
||||||
|
.processes
|
||||||
|
.get(init)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.get(pool)
|
||||||
|
.unwrap()
|
||||||
|
.resource()
|
||||||
|
else {
|
||||||
|
panic!("not a region cap");
|
||||||
|
};
|
||||||
|
assert_eq!(Some(pa), k.region_base(id).map(|b| b.raw()));
|
||||||
|
|
||||||
|
let uart = slot_holding(
|
||||||
|
&k,
|
||||||
|
init,
|
||||||
|
Resource::Memory(k.device_region_at(PhysicalAddress::from_raw(UART)).unwrap()),
|
||||||
|
);
|
||||||
|
let outcome = k.handle_syscall(Syscall::RegionAddress { region: uart });
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Value(UART))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn region_address_needs_read() {
|
||||||
|
let (mut k, init) = booted();
|
||||||
|
let pool = k
|
||||||
|
.create_region_cap(init, PageCount::new(1).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let blind = k
|
||||||
|
.mint(
|
||||||
|
init,
|
||||||
|
pool,
|
||||||
|
Badge::new(0),
|
||||||
|
PermissionSet::empty().with(Permission::Write),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: blind }));
|
||||||
|
assert!(matches!(e, KernelError::PermissionDenied { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn region_address_rejects_other_objects() {
|
||||||
|
let (mut k, _) = booted();
|
||||||
|
// An endpoint cap carries Read, so this fails on the object type.
|
||||||
|
let SyscallOutcome::Complete(SyscallResult::Slot(endpoint)) =
|
||||||
|
k.handle_syscall(Syscall::CreateEndpoint)
|
||||||
|
else {
|
||||||
|
panic!("expected an endpoint");
|
||||||
|
};
|
||||||
|
let e = error_of(k.handle_syscall(Syscall::RegionAddress { region: endpoint }));
|
||||||
|
assert!(matches!(e, KernelError::WrongObject));
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ mod helpers;
|
|||||||
mod mock_platform;
|
mod mock_platform;
|
||||||
|
|
||||||
mod abi;
|
mod abi;
|
||||||
|
mod board;
|
||||||
mod capability;
|
mod capability;
|
||||||
mod captree_model;
|
mod captree_model;
|
||||||
mod exit;
|
mod exit;
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_kernel_survives_a_syscall_storm() {
|
fn the_kernel_survives_a_syscall_storm() {
|
||||||
let mut total = [0usize; 36];
|
let mut total = [0usize; 36 + 1];
|
||||||
for seed in [17u64, 0xABCD_EF01, 4242, 0x1357_9BDF, 8] {
|
for seed in [17u64, 0xABCD_EF01, 4242, 0x1357_9BDF, 8] {
|
||||||
let (mut k, init) = kernel_with_process();
|
let (mut k, init) = kernel_with_process();
|
||||||
let init_thread = k.current_thread().unwrap();
|
let init_thread = k.current_thread().unwrap();
|
||||||
@@ -318,7 +318,7 @@ fn the_kernel_survives_a_syscall_storm() {
|
|||||||
let mut rng = Rng(seed);
|
let mut rng = Rng(seed);
|
||||||
let mut executed = 0;
|
let mut executed = 0;
|
||||||
let mut rounds = 0;
|
let mut rounds = 0;
|
||||||
let mut succeeded = [0usize; 36];
|
let mut succeeded = [0usize; 36 + 1];
|
||||||
new_round(&mut k, init, init_thread);
|
new_round(&mut k, init, init_thread);
|
||||||
|
|
||||||
let mut step = 0;
|
let mut step = 0;
|
||||||
@@ -350,9 +350,11 @@ fn the_kernel_survives_a_syscall_storm() {
|
|||||||
}
|
}
|
||||||
let tid = runnable[rng.below(runnable.len())];
|
let tid = runnable[rng.below(runnable.len())];
|
||||||
let is_init = k.thread_process(tid) == Some(init);
|
let is_init = k.thread_process(tid) == Some(init);
|
||||||
let nr = rng.below(36);
|
let nr = rng.below(36 + 1);
|
||||||
// Halting stops the machine and init exiting ends the test.
|
// init exiting ends the test. Halt stays in: nobody here holds
|
||||||
if nr == SYS_HALT || (is_init && (nr == SYS_EXIT || nr == SYS_EXIT_THREAD)) {
|
// a SystemControl cap, so a halt that got through would panic
|
||||||
|
// the mock platform and fail the test.
|
||||||
|
if is_init && (nr == SYS_EXIT || nr == SYS_EXIT_THREAD) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
let args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
||||||
@@ -415,6 +417,7 @@ fn the_kernel_survives_a_syscall_storm() {
|
|||||||
SYS_RELEASE_PAGES,
|
SYS_RELEASE_PAGES,
|
||||||
SYS_CREATE_REGION,
|
SYS_CREATE_REGION,
|
||||||
SYS_MAP_REGION,
|
SYS_MAP_REGION,
|
||||||
|
SYS_REGION_ADDRESS,
|
||||||
SYS_KILL,
|
SYS_KILL,
|
||||||
SYS_CREATE_PROCESS,
|
SYS_CREATE_PROCESS,
|
||||||
SYS_SPAWN_THREAD,
|
SYS_SPAWN_THREAD,
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ pub fn yield_now() {
|
|||||||
let _ = call0(SYS_YIELD);
|
let _ = call0(SYS_YIELD);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn halt() -> ! {
|
/// Stops the machine. Needs a SystemControl capability; without one the
|
||||||
let _ = call0(SYS_HALT);
|
/// call fails and this spins, since there is nothing sensible to return.
|
||||||
|
pub fn halt(control: CapSlot) -> ! {
|
||||||
|
let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +187,12 @@ pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Resu
|
|||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The physical base of the region behind `region`, for programming a
|
||||||
|
/// device's DMA. Pool regions are physically contiguous.
|
||||||
|
pub fn region_address(region: CapSlot) -> Result<usize> {
|
||||||
|
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
|
||||||
|
}
|
||||||
|
|
||||||
// ---- capabilities ----
|
// ---- capabilities ----
|
||||||
|
|
||||||
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
|
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub const SYS_BIND_NOTIFICATION: usize = 31;
|
|||||||
pub const SYS_SET_IRQ_NOTIFICATION: usize = 32;
|
pub const SYS_SET_IRQ_NOTIFICATION: usize = 32;
|
||||||
pub const SYS_CREATE_REGION: usize = 33;
|
pub const SYS_CREATE_REGION: usize = 33;
|
||||||
pub const SYS_MAP_REGION: usize = 34;
|
pub const SYS_MAP_REGION: usize = 34;
|
||||||
|
pub const SYS_REGION_ADDRESS: usize = 35;
|
||||||
|
|
||||||
/// `PagePerms` in a register.
|
/// `PagePerms` in a register.
|
||||||
pub const PERMS_READ_ONLY: usize = 0;
|
pub const PERMS_READ_ONLY: usize = 0;
|
||||||
|
|||||||
20
docs/ipc.md
20
docs/ipc.md
@@ -48,6 +48,26 @@ as `cap`: deleting or revoking `cap` unmaps it. `perms` needs `Read`, plus
|
|||||||
| Notification | Read | `Wait`, `Poll`, `BindNotification` |
|
| Notification | Read | `Wait`, `Poll`, `BindNotification` |
|
||||||
| Notification | Delegate | `Mint` |
|
| Notification | Delegate | `Mint` |
|
||||||
| Interrupt | Execute | `SetIrqNotification`, `AckInterrupt` |
|
| Interrupt | Execute | `SetIrqNotification`, `AckInterrupt` |
|
||||||
|
| Region | Read | `MapRegion` (read-only), `RegionAddress` |
|
||||||
|
| SystemControl| Execute | `Halt` |
|
||||||
|
|
||||||
|
## Boot authority
|
||||||
|
|
||||||
|
No process is privileged by its id. At boot the kernel gives init:
|
||||||
|
|
||||||
|
- a `ProcessCreate` capability in slot 0;
|
||||||
|
- for every device in the board table, a capability to its MMIO region
|
||||||
|
(`Read`, `Write`, `Delegate`; never `Execute`) and, if it interrupts, one
|
||||||
|
to its IRQ line (`Execute`, `Delegate`);
|
||||||
|
- one `SystemControl` capability (`Execute`, `Delegate`).
|
||||||
|
|
||||||
|
The board capabilities come after the capabilities boot code hands out
|
||||||
|
for the built-in servers, so those slots are unchanged. 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.
|
||||||
|
|
||||||
|
`RegionAddress(cap)` returns a region's physical base so a driver can
|
||||||
|
program a device's DMA; pool regions are physically contiguous.
|
||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user