Compare commits
11 Commits
master
...
2e665fd66e
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e665fd66e | |||
| b87a62475f | |||
| 55758ae5b2 | |||
| bafc18a5f8 | |||
| 130a82f50d | |||
| 154599e3af | |||
| 76f10ff1f1 | |||
| d43421e33e | |||
| 8d2c40cb1e | |||
| e931485ca7 | |||
| 1c04ddf909 |
@@ -1,3 +1,7 @@
|
|||||||
[target.riscv64gc-unknown-none-elf]
|
[target.riscv64gc-unknown-none-elf]
|
||||||
runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel"
|
runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel"
|
||||||
rustflags = ["-C", "panic=abort"]
|
rustflags = ["-C", "panic=abort"]
|
||||||
|
|
||||||
|
[env]
|
||||||
|
# Kernel tables are large fixed arrays; tests build `Kernel` by value.
|
||||||
|
RUST_MIN_STACK = "33554432"
|
||||||
|
|||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -245,7 +245,6 @@ dependencies = [
|
|||||||
name = "platform"
|
name = "platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"domain",
|
|
||||||
"types",
|
"types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -123,3 +123,10 @@ panic = "abort"
|
|||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
|
# The randomized kernel invariant tests are too slow unoptimized.
|
||||||
|
[profile.test.package.kernel]
|
||||||
|
opt-level = 2
|
||||||
|
|
||||||
|
[profile.test.package.domain]
|
||||||
|
opt-level = 2
|
||||||
|
|||||||
34
Makefile
34
Makefile
@@ -1,10 +1,27 @@
|
|||||||
.PHONY: check fmt clippy test kani build all image run
|
.PHONY: check fmt clippy test kani build all image run ci kernel-fmt kernel-clippy kernel-test
|
||||||
|
|
||||||
RISCV_TARGET = riscv64gc-unknown-none-elf
|
RISCV_TARGET = riscv64gc-unknown-none-elf
|
||||||
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
||||||
|
# Harts QEMU boots; `make run-gui SMP=1` for a single core.
|
||||||
|
SMP ?= 4
|
||||||
|
|
||||||
|
KERNEL_CRATES = -p types -p domain -p kernel -p platform
|
||||||
|
|
||||||
all: check fmt clippy test
|
all: check fmt clippy test
|
||||||
|
|
||||||
|
# Gate for kernel work: formatting, lints, unit tests, Kani proofs, RISC-V build.
|
||||||
|
ci: kernel-fmt kernel-clippy kernel-test kani build
|
||||||
|
|
||||||
|
kernel-fmt:
|
||||||
|
cargo fmt $(KERNEL_CRATES) -p adapter-riscv -- --check
|
||||||
|
|
||||||
|
kernel-clippy:
|
||||||
|
cargo clippy $(KERNEL_CRATES) --all-targets -- -D warnings
|
||||||
|
cargo clippy -p adapter-riscv --target $(RISCV_TARGET) --no-deps -- -D warnings
|
||||||
|
|
||||||
|
kernel-test:
|
||||||
|
cargo test $(KERNEL_CRATES)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
cargo check --workspace
|
cargo check --workspace
|
||||||
|
|
||||||
@@ -17,14 +34,17 @@ clippy:
|
|||||||
test:
|
test:
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
|
|
||||||
|
# CBMC can eat all RAM and take the desktop down with it; cap each run.
|
||||||
|
KANI = systemd-run --user --scope -q -p MemoryMax=12G -p MemorySwapMax=0 cargo kani
|
||||||
|
|
||||||
kani-types:
|
kani-types:
|
||||||
cargo kani -p types
|
$(KANI) -p types
|
||||||
|
|
||||||
kani-domain:
|
kani-domain:
|
||||||
cargo kani -p domain
|
$(KANI) -p domain
|
||||||
|
|
||||||
kani-kernel:
|
kani-kernel:
|
||||||
cargo kani -p kernel
|
$(KANI) -p kernel
|
||||||
|
|
||||||
kani: kani-types kani-domain kani-kernel
|
kani: kani-types kani-domain kani-kernel
|
||||||
|
|
||||||
@@ -38,17 +58,17 @@ image: build
|
|||||||
scripts/make-disk-image.sh
|
scripts/make-disk-image.sh
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \
|
||||||
-kernel $(KERNEL_BIN)
|
-kernel $(KERNEL_BIN)
|
||||||
|
|
||||||
run-disk: image
|
run-disk: image
|
||||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
qemu-system-riscv64 -machine virt -smp $(SMP) -nographic -bios default \
|
||||||
-kernel $(KERNEL_BIN) \
|
-kernel $(KERNEL_BIN) \
|
||||||
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
||||||
-device virtio-blk-device,drive=hd0
|
-device virtio-blk-device,drive=hd0
|
||||||
|
|
||||||
run-gui: image
|
run-gui: image
|
||||||
qemu-system-riscv64 -machine virt -bios default \
|
qemu-system-riscv64 -machine virt -smp $(SMP) -bios default \
|
||||||
-kernel $(KERNEL_BIN) \
|
-kernel $(KERNEL_BIN) \
|
||||||
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
-drive file=boot.img,if=none,format=raw,id=hd0 \
|
||||||
-device virtio-blk-device,drive=hd0 \
|
-device virtio-blk-device,drive=hd0 \
|
||||||
|
|||||||
@@ -1,36 +1,60 @@
|
|||||||
OUTPUT_ARCH(riscv)
|
OUTPUT_ARCH(riscv)
|
||||||
ENTRY(_start)
|
|
||||||
|
|
||||||
|
/* The kernel runs in the upper half: linked at its physical load address
|
||||||
|
plus PHYS_OFFSET (mmu::PHYS_OFFSET), loaded at the physical address.
|
||||||
|
Firmware jumps to the physical entry with the MMU off; _start switches
|
||||||
|
to the high mapping before any Rust code runs. */
|
||||||
|
PHYS_OFFSET = 0xFFFFFFC000000000;
|
||||||
|
LOAD_ADDRESS = 0x80200000;
|
||||||
|
ENTRY(_start_phys)
|
||||||
|
|
||||||
|
/* Every boundary below is page aligned: the kernel maps each range with
|
||||||
|
its own permissions (W^X), see mmu::init_kernel_page_table. */
|
||||||
SECTIONS {
|
SECTIONS {
|
||||||
. = 0x80200000;
|
. = PHYS_OFFSET + LOAD_ADDRESS;
|
||||||
|
_text_start = .;
|
||||||
|
|
||||||
.text : {
|
.text : AT(ADDR(.text) - PHYS_OFFSET) {
|
||||||
*(.text.entry)
|
*(.text.entry)
|
||||||
*(.text .text.*)
|
*(.text .text.*)
|
||||||
}
|
}
|
||||||
|
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
.rodata : {
|
_text_end = .;
|
||||||
|
|
||||||
|
.rodata : AT(ADDR(.rodata) - PHYS_OFFSET) {
|
||||||
*(.rodata .rodata.*)
|
*(.rodata .rodata.*)
|
||||||
|
*(.srodata .srodata.*)
|
||||||
|
}
|
||||||
|
.eh_frame : AT(ADDR(.eh_frame) - PHYS_OFFSET) {
|
||||||
|
*(.eh_frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
.data : {
|
_rodata_end = .;
|
||||||
|
|
||||||
|
.data : AT(ADDR(.data) - PHYS_OFFSET) {
|
||||||
*(.data .data.*)
|
*(.data .data.*)
|
||||||
|
*(.sdata .sdata.*)
|
||||||
}
|
}
|
||||||
|
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
.bss : {
|
.bss : AT(ADDR(.bss) - PHYS_OFFSET) {
|
||||||
_bss_start = .;
|
_bss_start = .;
|
||||||
*(.bss .bss.*)
|
*(.bss .bss.*)
|
||||||
*(.sbss .sbss.*)
|
*(.sbss .sbss.*)
|
||||||
_bss_end = .;
|
_bss_end = .;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* One kernel stack per hart (smp::MAX_HARTS, smp::STACK_SLOT): each
|
||||||
|
slot is an unmapped guard page, so overflowing a stack faults
|
||||||
|
instead of silently overwriting what lies below, then the stack. */
|
||||||
. = ALIGN(4096);
|
. = ALIGN(4096);
|
||||||
_stack_bottom = .;
|
_stacks_start = .;
|
||||||
. += 0x80000;
|
. += 8 * (4096 + 0x80000);
|
||||||
_stack_top = .;
|
_stacks_end = .;
|
||||||
|
|
||||||
_heap_start = .;
|
_heap_start = .;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_start_phys = _start - PHYS_OFFSET;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,37 +22,85 @@ pub fn grant_all(
|
|||||||
.with(types::permission::Permission::Read)
|
.with(types::permission::Permission::Read)
|
||||||
.with(types::permission::Permission::Execute);
|
.with(types::permission::Permission::Execute);
|
||||||
|
|
||||||
let storage_ep = kern().create_endpoint()
|
let storage_ep = kern()
|
||||||
|
.create_endpoint(storage_pid)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "storage endpoint failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "storage endpoint failed"));
|
||||||
|
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Process(storage_pid), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Process(storage_pid),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Endpoint(storage_ep), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Endpoint(storage_ep),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
kern().grant_capability(storage_pid, domain::resource::Resource::Endpoint(storage_ep), ipc_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
storage_pid,
|
||||||
|
domain::resource::Resource::Endpoint(storage_ep),
|
||||||
|
ipc_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
let display_ep = kern().create_endpoint()
|
let display_ep = kern()
|
||||||
|
.create_endpoint(display_pid)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "display endpoint failed"));
|
||||||
|
|
||||||
kern().grant_capability(display_pid, domain::resource::Resource::Endpoint(display_ep), ipc_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
display_pid,
|
||||||
|
domain::resource::Resource::Endpoint(display_ep),
|
||||||
|
ipc_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Endpoint(display_ep), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Endpoint(display_ep),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Process(display_pid), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Process(display_pid),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
let input_ep = kern().create_endpoint()
|
let input_ep = kern()
|
||||||
|
.create_endpoint(input_pid)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "input endpoint failed"));
|
||||||
|
|
||||||
kern().grant_capability(input_pid, domain::resource::Resource::Endpoint(input_ep), ipc_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
input_pid,
|
||||||
|
domain::resource::Resource::Endpoint(input_ep),
|
||||||
|
ipc_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Endpoint(input_ep), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Endpoint(input_ep),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
kern().grant_capability(init_pid, domain::resource::Resource::Process(input_pid), all_perms)
|
kern()
|
||||||
|
.grant_capability(
|
||||||
|
init_pid,
|
||||||
|
domain::resource::Resource::Process(input_pid),
|
||||||
|
all_perms,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "grant failed"));
|
||||||
|
|
||||||
uart.write_str("k-os: capabilities granted\n");
|
uart.write_str("k-os: capabilities granted\n");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::hw::virtio_blk::VirtioBlk;
|
use crate::hw::virtio_blk::VirtioBlk;
|
||||||
use crate::{board, kernel_cell::KernelCell, mmu, platform, trap, KERNEL};
|
use crate::{KERNEL, board, mmu, platform, trap};
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
static _bss_start: u8;
|
static _bss_start: u8;
|
||||||
@@ -8,26 +8,66 @@ unsafe extern "C" {
|
|||||||
static _heap_start: u8;
|
static _heap_start: u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(uart: &Uart) -> &'static mut mmu::PageTable {
|
pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
|
||||||
zero_bss();
|
zero_bss();
|
||||||
|
|
||||||
let heap_start = unsafe { &_heap_start as *const u8 as usize };
|
let heap_start = unsafe { &_heap_start as *const u8 as usize };
|
||||||
mmu::init_allocator(heap_start);
|
mmu::init_allocator(heap_start);
|
||||||
let kernel_pt = mmu::init_kernel_page_table();
|
let kernel_pt = mmu::init_kernel_page_table();
|
||||||
|
let harts = crate::smp::probe_harts();
|
||||||
|
if boot_hart >= harts {
|
||||||
|
crate::halt(uart, "boot hart outside the hart range");
|
||||||
|
}
|
||||||
|
crate::smp::init(
|
||||||
|
boot_hart,
|
||||||
|
harts,
|
||||||
|
mmu::virt_to_phys(kernel_pt as *const mmu::PageTable as usize),
|
||||||
|
);
|
||||||
mmu::activate(kernel_pt);
|
mmu::activate(kernel_pt);
|
||||||
uart.write_str("k-os: MMU enabled (Sv39)\n");
|
uart.write_str("k-os: MMU enabled (Sv39)\n");
|
||||||
|
match mmu::check_wx(kernel_pt) {
|
||||||
|
Ok(code_pages) => {
|
||||||
|
uart.write_str("k-os: W^X verified, ");
|
||||||
|
uart.write_dec(code_pages);
|
||||||
|
uart.write_str(" code pages\n");
|
||||||
|
}
|
||||||
|
Err(v) => {
|
||||||
|
let (what, va) = match v {
|
||||||
|
mmu::page_table::WxViolation::WritableCode(va) => ("writable code", va),
|
||||||
|
mmu::page_table::WxViolation::ExecutableData(va) => ("executable data", va),
|
||||||
|
mmu::page_table::WxViolation::MappedHole(va) => ("mapped hole", va),
|
||||||
|
mmu::page_table::WxViolation::LowerHalf(va) => ("lower-half mapping", va),
|
||||||
|
};
|
||||||
|
uart.write_str("k-os: W^X violation: ");
|
||||||
|
uart.write_str(what);
|
||||||
|
uart.write_str(" at ");
|
||||||
|
uart.write_hex(va);
|
||||||
|
uart.put_char(b'\n');
|
||||||
|
crate::halt(uart, "kernel page table violates W^X");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trap::init();
|
trap::init();
|
||||||
crate::hw::timer::init();
|
crate::hw::timer::init();
|
||||||
crate::hw::plic::Plic::init();
|
crate::hw::plic::Plic::init();
|
||||||
uart.write_str("k-os: traps and timer initialized\n");
|
uart.write_str("k-os: traps and timer initialized\n");
|
||||||
|
|
||||||
let riscv_platform = platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize);
|
if board::USER_POOL_PAGES > domain::memory::frame::MAX_FRAMES {
|
||||||
let pool = domain::memory::page_pool::PagePool::new(
|
crate::halt(uart, "user pool larger than the frame table");
|
||||||
|
}
|
||||||
|
let riscv_platform =
|
||||||
|
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
|
||||||
|
// SAFETY: `init_in_place` writes every field of the kernel.
|
||||||
|
unsafe {
|
||||||
|
KERNEL.init_with(|slot| {
|
||||||
|
kernel::Kernel::init_in_place(
|
||||||
|
slot,
|
||||||
|
riscv_platform,
|
||||||
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
|
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
|
||||||
board::USER_POOL_PAGES,
|
board::USER_POOL_PAGES,
|
||||||
);
|
)
|
||||||
KERNEL.init(kernel::Kernel::new(riscv_platform, pool));
|
});
|
||||||
|
}
|
||||||
|
|
||||||
kernel_pt
|
kernel_pt
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,74 @@
|
|||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::hw::virtio_blk::VirtioBlk;
|
use crate::hw::virtio_blk::VirtioBlk;
|
||||||
use crate::{boot_fs, mmu};
|
use crate::{boot_fs, mmu};
|
||||||
|
use types::address::VirtualAddress;
|
||||||
use types::id::{ProcessId, ThreadId};
|
use types::id::{ProcessId, ThreadId};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = mmu::PAGE_SIZE;
|
||||||
const USER_VA_BASE: usize = 0x10000;
|
const USER_VA_BASE: usize = 0x10000;
|
||||||
|
/// The stack sits just below the image and stops short of the null page.
|
||||||
|
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
|
||||||
const MAX_FILE_SIZE: usize = 64 * 1024;
|
const MAX_FILE_SIZE: usize = 64 * 1024;
|
||||||
|
|
||||||
pub struct LoadedProcess {
|
pub struct LoadedProcess {
|
||||||
pub pid: ProcessId,
|
pub pid: ProcessId,
|
||||||
pub entry: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_init(
|
/// Scratch space for reading ELF files, allocated once from the boot heap.
|
||||||
uart: &Uart,
|
fn elf_buffer() -> &'static mut [u8] {
|
||||||
blk: &mut VirtioBlk,
|
static mut BUF: usize = 0;
|
||||||
kernel_pt: &mmu::PageTable,
|
// SAFETY: boot is single threaded; the buffer is kernel-only memory
|
||||||
) -> (LoadedProcess, ThreadId) {
|
// from the boot allocator and is never handed to a process.
|
||||||
|
unsafe {
|
||||||
|
if BUF == 0 {
|
||||||
|
BUF = mmu::allocator::alloc_zeroed_page();
|
||||||
|
for _ in 1..MAX_FILE_SIZE / PAGE_SIZE {
|
||||||
|
mmu::allocator::alloc_zeroed_page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core::slice::from_raw_parts_mut(BUF as *mut u8, MAX_FILE_SIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> (LoadedProcess, ThreadId) {
|
||||||
let (pid, tid) = kern()
|
let (pid, tid) = kern()
|
||||||
.boot(types::bounded::Priority::DEFAULT, types::time::TimeSlice::DEFAULT)
|
.boot(
|
||||||
|
types::bounded::Priority::DEFAULT,
|
||||||
|
types::time::TimeSlice::DEFAULT,
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
|
||||||
|
|
||||||
let entry = load_elf(blk, kernel_pt, pid, b"init.elf");
|
let entry = load_elf(uart, blk, pid, b"init.elf");
|
||||||
if entry == 0 { crate::halt(uart, "init.elf not found"); }
|
|
||||||
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, entry }, tid)
|
(LoadedProcess { pid }, tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_server(
|
pub fn load_server(
|
||||||
uart: &Uart,
|
uart: &Uart,
|
||||||
blk: &mut VirtioBlk,
|
blk: &mut VirtioBlk,
|
||||||
kernel_pt: &mmu::PageTable,
|
|
||||||
filename: &[u8],
|
filename: &[u8],
|
||||||
label: &str,
|
label: &str,
|
||||||
) -> LoadedProcess {
|
) -> LoadedProcess {
|
||||||
let pid = kern().create_process_internal()
|
let pid = kern()
|
||||||
|
.create_process_internal()
|
||||||
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
|
||||||
let entry = load_elf(blk, kernel_pt, pid, filename);
|
let entry = load_elf(uart, blk, pid, filename);
|
||||||
if entry == 0 { crate::halt(uart, "elf not found"); }
|
let tid = kern()
|
||||||
let tid = kern().spawn_thread_internal(pid)
|
.spawn_thread_internal(pid)
|
||||||
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
|
||||||
setup_thread(tid, entry);
|
setup_thread(tid, entry);
|
||||||
uart.write_str("k-os: ");
|
uart.write_str("k-os: ");
|
||||||
uart.write_str(label);
|
uart.write_str(label);
|
||||||
uart.write_str(" loaded\n");
|
uart.write_str(" loaded\n");
|
||||||
|
|
||||||
LoadedProcess { pid, entry }
|
LoadedProcess { pid }
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter_userspace(pid: ProcessId, entry: usize, shared_buf_va: usize) -> ! {
|
|
||||||
let pt_addr = kern().platform_mut().page_table_for(pid);
|
|
||||||
if pt_addr != 0 {
|
|
||||||
mmu::activate(unsafe { &*(pt_addr as *const mmu::PageTable) });
|
|
||||||
}
|
|
||||||
|
|
||||||
let kernel_sp: usize;
|
|
||||||
unsafe { core::arch::asm!("mv {0}, sp", out(reg) kernel_sp) };
|
|
||||||
crate::trap::set_kernel_stack(kernel_sp);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
core::arch::asm!(
|
|
||||||
"csrc sstatus, {spp}",
|
|
||||||
"csrs sstatus, {spie}",
|
|
||||||
"csrw sepc, {entry}",
|
|
||||||
"mv sp, {stack}",
|
|
||||||
"mv a0, {arg}",
|
|
||||||
"sret",
|
|
||||||
spp = in(reg) 1usize << 8,
|
|
||||||
spie = in(reg) 1usize << 5,
|
|
||||||
entry = in(reg) entry,
|
|
||||||
stack = in(reg) USER_VA_BASE,
|
|
||||||
arg = in(reg) shared_buf_va,
|
|
||||||
options(noreturn),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_thread(tid: ThreadId, entry: usize) {
|
fn setup_thread(tid: ThreadId, entry: usize) {
|
||||||
@@ -89,88 +79,89 @@ fn setup_thread(tid: ThreadId, entry: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_elf(
|
/// Copies `data` into `pid`'s already mapped pages starting at `va`.
|
||||||
blk: &mut VirtioBlk,
|
fn copy_to_user(pid: ProcessId, va: usize, data: &[u8]) -> bool {
|
||||||
kernel_pt: &mmu::PageTable,
|
let mut done = 0;
|
||||||
pid: ProcessId,
|
while done < data.len() {
|
||||||
filename: &[u8],
|
let addr = va + done;
|
||||||
) -> usize {
|
let page = addr & !(PAGE_SIZE - 1);
|
||||||
let buf_base = mmu::allocator::alloc_zeroed_page();
|
let offset = addr - page;
|
||||||
let file_pages = MAX_FILE_SIZE / 4096;
|
let Some(pa) = kern().user_page_frame(pid, VirtualAddress::from_raw(page)) else {
|
||||||
for _ in 1..file_pages {
|
return false;
|
||||||
mmu::allocator::alloc_zeroed_page();
|
|
||||||
}
|
|
||||||
let buf = unsafe { core::slice::from_raw_parts_mut(buf_base as *mut u8, MAX_FILE_SIZE) };
|
|
||||||
|
|
||||||
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
|
|
||||||
if file_size == 0 { return 0; }
|
|
||||||
|
|
||||||
let elf_info = match elf::parse(&buf[..file_size]) {
|
|
||||||
Ok(info) => info,
|
|
||||||
Err(_) => return 0,
|
|
||||||
};
|
};
|
||||||
|
let len = (PAGE_SIZE - offset).min(data.len() - done);
|
||||||
let user_pt = mmu::create_user_page_table(
|
// SAFETY: `pa` is a pool frame the kernel just mapped for `pid`;
|
||||||
kernel_pt,
|
// pool frames are RAM, mapped at `phys_to_virt`.
|
||||||
types::address::PhysicalAddress::from_raw(0),
|
|
||||||
0,
|
|
||||||
types::address::VirtualAddress::from_raw(USER_VA_BASE),
|
|
||||||
);
|
|
||||||
|
|
||||||
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 + 4095) / 4096;
|
|
||||||
|
|
||||||
for p in 0..seg_pages {
|
|
||||||
let page = mmu::allocator::alloc_zeroed_page();
|
|
||||||
let page_va = page_start + p * 4096;
|
|
||||||
let seg_file_start = if p == 0 { 0 } else { p * 4096 - offset_in_page };
|
|
||||||
let dest_off = if p == 0 { offset_in_page } else { 0 };
|
|
||||||
let copy_len = if seg_file_start < segment.file_size {
|
|
||||||
(segment.file_size - seg_file_start).min(4096 - dest_off)
|
|
||||||
} else { 0 };
|
|
||||||
|
|
||||||
if copy_len > 0 {
|
|
||||||
let src = segment.file_offset + seg_file_start;
|
|
||||||
if src + copy_len <= file_size {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::copy_nonoverlapping(
|
core::ptr::copy_nonoverlapping(
|
||||||
buf.as_ptr().add(src),
|
data.as_ptr().add(done),
|
||||||
(page as *mut u8).add(dest_off),
|
(mmu::phys_to_virt(pa.raw()) as *mut u8).add(offset),
|
||||||
copy_len,
|
len,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
done += len;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
|
||||||
|
match (flags.write, flags.execute) {
|
||||||
|
(true, true) => None,
|
||||||
|
(false, true) => Some(PagePerms::ReadExecute),
|
||||||
|
(true, false) => Some(PagePerms::ReadWrite),
|
||||||
|
(false, false) => Some(PagePerms::ReadOnly),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads `filename` into `pid` through the kernel's own mapping API, so
|
||||||
|
/// every page is tracked, charged and checked like any other. Halts on any
|
||||||
|
/// malformed or overlapping image: boot servers are trusted to be sane.
|
||||||
|
fn load_elf(uart: &Uart, blk: &mut VirtioBlk, pid: ProcessId, filename: &[u8]) -> usize {
|
||||||
|
let buf = elf_buffer();
|
||||||
|
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
|
||||||
|
if file_size == 0 {
|
||||||
|
crate::halt(uart, "ELF not found");
|
||||||
|
}
|
||||||
|
let file = &buf[..file_size];
|
||||||
|
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
|
||||||
|
|
||||||
|
for segment in elf_info.segments() {
|
||||||
|
let perms =
|
||||||
|
segment_perms(segment.flags).unwrap_or_else(|| crate::halt(uart, "W+X segment"));
|
||||||
|
let page_start = segment.vaddr & !(PAGE_SIZE - 1);
|
||||||
|
let offset_in_page = segment.vaddr - page_start;
|
||||||
|
let pages = offset_in_page
|
||||||
|
.checked_add(segment.mem_size)
|
||||||
|
.map(|total| total.div_ceil(PAGE_SIZE))
|
||||||
|
.unwrap_or_else(|| crate::halt(uart, "segment too large"));
|
||||||
|
if pages == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kern()
|
||||||
|
.map_fresh_range(pid, VirtualAddress::from_raw(page_start), pages, perms)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "cannot map segment"));
|
||||||
|
|
||||||
|
let copy_len = segment.file_size.min(segment.mem_size);
|
||||||
|
let data = segment
|
||||||
|
.file_offset
|
||||||
|
.checked_add(copy_len)
|
||||||
|
.and_then(|end| file.get(segment.file_offset..end))
|
||||||
|
.unwrap_or_else(|| crate::halt(uart, "segment outside file"));
|
||||||
|
if !copy_to_user(pid, segment.vaddr, data) {
|
||||||
|
crate::halt(uart, "segment copy failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let flags = if segment.flags.execute {
|
let stack_bottom = USER_VA_BASE - USER_STACK_PAGES * PAGE_SIZE;
|
||||||
mmu::Sv39Flags::user_rx()
|
kern()
|
||||||
} else if segment.flags.write {
|
.map_fresh_range(
|
||||||
mmu::Sv39Flags::user_rw()
|
pid,
|
||||||
} else {
|
VirtualAddress::from_raw(stack_bottom),
|
||||||
mmu::Sv39Flags::user_rx()
|
USER_STACK_PAGES,
|
||||||
};
|
PagePerms::ReadWrite,
|
||||||
mmu::map(user_pt, mmu::Mapping {
|
)
|
||||||
va: types::address::VirtualAddress::from_raw(page_va),
|
.unwrap_or_else(|_| crate::halt(uart, "cannot map stack"));
|
||||||
pa: types::address::PhysicalAddress::from_raw(page),
|
|
||||||
pages: 1,
|
|
||||||
flags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..16usize {
|
|
||||||
let sp = mmu::allocator::alloc_zeroed_page();
|
|
||||||
mmu::map(user_pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (16 - i) * 4096),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(sp),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
kern().platform_mut().set_page_table(pid, user_pt as *const mmu::PageTable as usize);
|
|
||||||
elf_info.entry_point
|
elf_info.entry_point
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use crate::hw::virtio_blk::VirtioBlk;
|
use crate::hw::virtio_blk::VirtioBlk;
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
const SECTOR_SIZE: usize = 512;
|
||||||
const MAX_FILE_SIZE: usize = 64 * 1024; // 64K max for init binary
|
|
||||||
|
|
||||||
struct BootFat16 {
|
struct BootFat16 {
|
||||||
reserved_sectors: u16,
|
reserved_sectors: u16,
|
||||||
sectors_per_fat: u16,
|
|
||||||
root_dir_start: u32,
|
root_dir_start: u32,
|
||||||
root_dir_sectors: u32,
|
root_dir_sectors: u32,
|
||||||
data_start: u32,
|
data_start: u32,
|
||||||
@@ -19,7 +17,9 @@ impl BootFat16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bytes_per_sector = u16::from_le_bytes([boot_sector[11], boot_sector[12]]);
|
let bytes_per_sector = u16::from_le_bytes([boot_sector[11], boot_sector[12]]);
|
||||||
if bytes_per_sector != SECTOR_SIZE as u16 { return None; }
|
if bytes_per_sector != SECTOR_SIZE as u16 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let sectors_per_cluster = boot_sector[13];
|
let sectors_per_cluster = boot_sector[13];
|
||||||
let reserved_sectors = u16::from_le_bytes([boot_sector[14], boot_sector[15]]);
|
let reserved_sectors = u16::from_le_bytes([boot_sector[14], boot_sector[15]]);
|
||||||
@@ -29,12 +29,11 @@ impl BootFat16 {
|
|||||||
|
|
||||||
let fat_start = reserved_sectors as u32;
|
let fat_start = reserved_sectors as u32;
|
||||||
let root_dir_start = fat_start + (num_fats as u32) * (sectors_per_fat as u32);
|
let root_dir_start = fat_start + (num_fats as u32) * (sectors_per_fat as u32);
|
||||||
let root_dir_sectors = ((root_entry_count as u32) * 32 + 511) / 512;
|
let root_dir_sectors = ((root_entry_count as u32) * 32).div_ceil(512);
|
||||||
let data_start = root_dir_start + root_dir_sectors;
|
let data_start = root_dir_start + root_dir_sectors;
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
reserved_sectors,
|
reserved_sectors,
|
||||||
sectors_per_fat,
|
|
||||||
root_dir_start,
|
root_dir_start,
|
||||||
root_dir_sectors,
|
root_dir_sectors,
|
||||||
data_start,
|
data_start,
|
||||||
@@ -50,7 +49,9 @@ impl BootFat16 {
|
|||||||
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
||||||
let mut fat = [b' '; 11];
|
let mut fat = [b' '; 11];
|
||||||
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
|
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
|
||||||
for i in 0..dot_pos.min(8) { fat[i] = name[i].to_ascii_uppercase(); }
|
for i in 0..dot_pos.min(8) {
|
||||||
|
fat[i] = name[i].to_ascii_uppercase();
|
||||||
|
}
|
||||||
if dot_pos < name.len() {
|
if dot_pos < name.len() {
|
||||||
for i in 0..(name.len() - dot_pos - 1).min(3) {
|
for i in 0..(name.len() - dot_pos - 1).min(3) {
|
||||||
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
|
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
|
||||||
@@ -67,7 +68,9 @@ pub fn read_file_from_disk(
|
|||||||
let mut sector_buf = [0u8; SECTOR_SIZE];
|
let mut sector_buf = [0u8; SECTOR_SIZE];
|
||||||
|
|
||||||
// Read boot sector
|
// Read boot sector
|
||||||
if !blk.read_sector(0, &mut sector_buf) { return None; }
|
if !blk.read_sector(0, &mut sector_buf) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let fs = BootFat16::parse(§or_buf)?;
|
let fs = BootFat16::parse(§or_buf)?;
|
||||||
|
|
||||||
let fat_name = to_fat_name(filename);
|
let fat_name = to_fat_name(filename);
|
||||||
@@ -78,36 +81,52 @@ pub fn read_file_from_disk(
|
|||||||
let mut found = false;
|
let mut found = false;
|
||||||
|
|
||||||
for s in 0..fs.root_dir_sectors {
|
for s in 0..fs.root_dir_sectors {
|
||||||
if !blk.read_sector((fs.root_dir_start + s) as u32, &mut sector_buf) { continue; }
|
if !blk.read_sector(fs.root_dir_start + s, &mut sector_buf) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for i in 0..(SECTOR_SIZE / 32) {
|
for i in 0..(SECTOR_SIZE / 32) {
|
||||||
let off = i * 32;
|
let off = i * 32;
|
||||||
if sector_buf[off] == 0x00 { break; }
|
if sector_buf[off] == 0x00 {
|
||||||
if sector_buf[off] == 0xE5 { continue; }
|
break;
|
||||||
|
}
|
||||||
|
if sector_buf[off] == 0xE5 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if sector_buf[off..off + 11] == fat_name {
|
if sector_buf[off..off + 11] == fat_name {
|
||||||
first_cluster = u16::from_le_bytes([sector_buf[off + 26], sector_buf[off + 27]]);
|
first_cluster = u16::from_le_bytes([sector_buf[off + 26], sector_buf[off + 27]]);
|
||||||
file_size = u32::from_le_bytes([
|
file_size = u32::from_le_bytes([
|
||||||
sector_buf[off + 28], sector_buf[off + 29],
|
sector_buf[off + 28],
|
||||||
sector_buf[off + 30], sector_buf[off + 31],
|
sector_buf[off + 29],
|
||||||
|
sector_buf[off + 30],
|
||||||
|
sector_buf[off + 31],
|
||||||
]);
|
]);
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found { break; }
|
if found {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found { return None; }
|
if !found {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let size = (file_size as usize).min(out_buf.len());
|
let size = (file_size as usize).min(out_buf.len());
|
||||||
let mut read = 0;
|
let mut read = 0;
|
||||||
let mut cluster = first_cluster;
|
let mut cluster = first_cluster;
|
||||||
let sectors_per_cluster = fs.sectors_per_cluster as u32;
|
let sectors_per_cluster = fs.sectors_per_cluster as u32;
|
||||||
|
|
||||||
while read < size && cluster >= 2 && cluster < 0xFFF8 {
|
while read < size && (2..0xFFF8).contains(&cluster) {
|
||||||
for s in 0..sectors_per_cluster {
|
for s in 0..sectors_per_cluster {
|
||||||
if read >= size { break; }
|
if read >= size {
|
||||||
|
break;
|
||||||
|
}
|
||||||
let sector = fs.cluster_to_sector(cluster) + s;
|
let sector = fs.cluster_to_sector(cluster) + s;
|
||||||
if !blk.read_sector(sector, &mut sector_buf) { return Some(read); }
|
if !blk.read_sector(sector, &mut sector_buf) {
|
||||||
|
return Some(read);
|
||||||
|
}
|
||||||
let chunk = (size - read).min(SECTOR_SIZE);
|
let chunk = (size - read).min(SECTOR_SIZE);
|
||||||
out_buf[read..read + chunk].copy_from_slice(§or_buf[..chunk]);
|
out_buf[read..read + chunk].copy_from_slice(§or_buf[..chunk]);
|
||||||
read += chunk;
|
read += chunk;
|
||||||
@@ -117,7 +136,9 @@ pub fn read_file_from_disk(
|
|||||||
let fat_offset = (cluster as u32) * 2;
|
let fat_offset = (cluster as u32) * 2;
|
||||||
let fat_sector = fs.reserved_sectors as u32 + fat_offset / SECTOR_SIZE as u32;
|
let fat_sector = fs.reserved_sectors as u32 + fat_offset / SECTOR_SIZE as u32;
|
||||||
let fat_byte = (fat_offset % SECTOR_SIZE as u32) as usize;
|
let fat_byte = (fat_offset % SECTOR_SIZE as u32) as usize;
|
||||||
if !blk.read_sector(fat_sector, &mut sector_buf) { break; }
|
if !blk.read_sector(fat_sector, &mut sector_buf) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
cluster = u16::from_le_bytes([sector_buf[fat_byte], sector_buf[fat_byte + 1]]);
|
cluster = u16::from_le_bytes([sector_buf[fat_byte], sector_buf[fat_byte + 1]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
|
use super::{dma_buffer, info_page, kern, map_mmio};
|
||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::hw::virtio_gpu::VirtioGpu;
|
use crate::hw::virtio_gpu::VirtioGpu;
|
||||||
use crate::mmu;
|
|
||||||
use types::display::Dimension;
|
use types::display::Dimension;
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
|
||||||
}
|
|
||||||
|
|
||||||
const GPU_VA: usize = 0x20000;
|
const GPU_VA: usize = 0x20000;
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
const VQUEUE_VA: usize = 0x21000;
|
||||||
const CMDBUF_VA: usize = 0x23000;
|
const CMDBUF_VA: usize = 0x23000;
|
||||||
@@ -15,6 +11,8 @@ const PHYSINFO_VA: usize = 0x24000;
|
|||||||
const FB_VA: usize = 0x100000;
|
const FB_VA: usize = 0x100000;
|
||||||
const DEFAULT_WIDTH: Dimension = Dimension::new(640);
|
const DEFAULT_WIDTH: Dimension = Dimension::new(640);
|
||||||
const DEFAULT_HEIGHT: Dimension = Dimension::new(480);
|
const DEFAULT_HEIGHT: Dimension = Dimension::new(480);
|
||||||
|
/// The framebuffer alone is ~1000 pages at 1280x800.
|
||||||
|
const DISPLAY_PAGE_LIMIT: usize = 2048;
|
||||||
|
|
||||||
pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
||||||
let gpu = match VirtioGpu::probe() {
|
let gpu = match VirtioGpu::probe() {
|
||||||
@@ -25,28 +23,24 @@ pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let pt_addr = kern().platform_mut().page_table_for(display_pid);
|
kern()
|
||||||
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
|
.set_page_limit(display_pid, DISPLAY_PAGE_LIMIT)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "display quota"));
|
||||||
|
|
||||||
map_page(pt, GPU_VA, gpu.base());
|
map_mmio(uart, display_pid, GPU_VA, gpu.base(), 1);
|
||||||
|
let vq_phys = dma_buffer(uart, display_pid, VQUEUE_VA, 2);
|
||||||
let vq_phys = alloc_contiguous(2);
|
let cmd_phys = dma_buffer(uart, display_pid, CMDBUF_VA, 1);
|
||||||
map_range(pt, VQUEUE_VA, vq_phys, 2);
|
|
||||||
|
|
||||||
let cmd_phys = mmu::allocator::alloc_zeroed_page();
|
|
||||||
map_page(pt, CMDBUF_VA, cmd_phys);
|
|
||||||
|
|
||||||
gpu.init_device(vq_phys);
|
gpu.init_device(vq_phys);
|
||||||
|
|
||||||
let queue = hw_virtio::queue::VirtioQueue::new(
|
let queue = hw_virtio::queue::VirtioQueue::new(hw_virtio::queue::QueueAddrs::from_base(
|
||||||
hw_virtio::queue::QueueAddrs::from_base(vq_phys),
|
crate::mmu::phys_to_virt(vq_phys),
|
||||||
);
|
));
|
||||||
let dma = hw_virtio::gpu::GpuDmaAddrs {
|
let dma = hw_virtio::gpu::GpuDmaAddrs {
|
||||||
cmd_pa: types::address::PhysicalAddress::from_raw(cmd_phys),
|
cmd_pa: types::address::PhysicalAddress::from_raw(cmd_phys),
|
||||||
cmd_va: types::address::VirtualAddress::from_raw(cmd_phys),
|
cmd_va: types::address::VirtualAddress::from_raw(crate::mmu::phys_to_virt(cmd_phys)),
|
||||||
resp_pa: types::address::PhysicalAddress::from_raw(cmd_phys + 512),
|
resp_pa: types::address::PhysicalAddress::from_raw(cmd_phys + 512),
|
||||||
};
|
};
|
||||||
use hw_virtio::mmio::MmioAccess;
|
|
||||||
|
|
||||||
let (width, height) = match hw_virtio::gpu::get_display_info(gpu.mmio(), &queue, &dma) {
|
let (width, height) = match hw_virtio::gpu::get_display_info(gpu.mmio(), &queue, &dma) {
|
||||||
Some((w, h)) => {
|
Some((w, h)) => {
|
||||||
@@ -63,20 +57,21 @@ pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let fb_pages = (width.raw() as usize * height.raw() as usize * 4 + 4095) / 4096;
|
let fb_pages = (width.raw() as usize * height.raw() as usize * 4).div_ceil(4096);
|
||||||
let fb_phys = alloc_contiguous(fb_pages);
|
let fb_phys = dma_buffer(uart, display_pid, FB_VA, fb_pages);
|
||||||
map_range(pt, FB_VA, fb_phys, fb_pages);
|
|
||||||
|
|
||||||
let info_phys = mmu::allocator::alloc_zeroed_page();
|
info_page(
|
||||||
map_page(pt, PHYSINFO_VA, info_phys);
|
uart,
|
||||||
unsafe {
|
display_pid,
|
||||||
let info = info_phys as *mut u64;
|
PHYSINFO_VA,
|
||||||
core::ptr::write_volatile(info, cmd_phys as u64);
|
&[
|
||||||
core::ptr::write_volatile(info.add(1), (cmd_phys + 512) as u64);
|
cmd_phys as u64,
|
||||||
core::ptr::write_volatile(info.add(2), fb_phys as u64);
|
(cmd_phys + 512) as u64,
|
||||||
core::ptr::write_volatile(info.add(3), width.raw() as u64);
|
fb_phys as u64,
|
||||||
core::ptr::write_volatile(info.add(4), height.raw() as u64);
|
width.raw() as u64,
|
||||||
}
|
height.raw() as u64,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
if gpu.setup_framebuffer(cmd_phys, vq_phys, fb_phys, width, height) {
|
if gpu.setup_framebuffer(cmd_phys, vq_phys, fb_phys, width, height) {
|
||||||
uart.write_str("k-os: VirtIO GPU configured\n");
|
uart.write_str("k-os: VirtIO GPU configured\n");
|
||||||
@@ -84,26 +79,3 @@ pub fn configure(uart: &Uart, display_pid: ProcessId) {
|
|||||||
uart.write_str("k-os: VirtIO GPU setup failed (display will be headless)\n");
|
uart.write_str("k-os: VirtIO GPU setup failed (display will be headless)\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(va),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(pa),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_range(pt: &mut mmu::PageTable, va_base: usize, pa_base: usize, count: usize) {
|
|
||||||
for i in 0..count {
|
|
||||||
map_page(pt, va_base + i * 4096, pa_base + i * 4096);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_contiguous(pages: usize) -> usize {
|
|
||||||
let first = mmu::allocator::alloc_zeroed_page();
|
|
||||||
for _ in 1..pages {
|
|
||||||
mmu::allocator::alloc_zeroed_page();
|
|
||||||
}
|
|
||||||
first
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
use super::{dma_buffer, info_page, map_mmio};
|
||||||
|
use crate::board;
|
||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::{board, mmu};
|
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
|
|
||||||
const UART_VA: usize = 0x20000;
|
const UART_VA: usize = 0x20000;
|
||||||
@@ -8,34 +9,23 @@ const INPUT_VQUEUE_VA: usize = 0x26000;
|
|||||||
const INPUT_EVBUF_VA: usize = 0x28000;
|
const INPUT_EVBUF_VA: usize = 0x28000;
|
||||||
const INPUT_PHYSINFO_VA: usize = 0x29000;
|
const INPUT_PHYSINFO_VA: usize = 0x29000;
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure(uart: &Uart, input_pid: ProcessId) {
|
pub fn configure(uart: &Uart, input_pid: ProcessId) {
|
||||||
let pt_addr = kern().platform_mut().page_table_for(input_pid);
|
|
||||||
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
|
|
||||||
|
|
||||||
// Always map UART for fallback
|
// Always map UART for fallback
|
||||||
map_page(pt, UART_VA, board::UART_BASE);
|
map_mmio(uart, input_pid, UART_VA, board::UART_BASE, 1);
|
||||||
|
|
||||||
// Try to find VirtIO input device
|
// Try to find VirtIO input device
|
||||||
if let Some(mmio) = hw_virtio::mmio::scan_for_device(hw_virtio::mmio::DEVICE_ID_INPUT) {
|
let first = crate::mmu::phys_to_virt(hw_virtio::mmio::MMIO_FIRST);
|
||||||
let base = mmio.base();
|
if let Some(mmio) = hw_virtio::mmio::scan_for_device(first, hw_virtio::mmio::DEVICE_ID_INPUT) {
|
||||||
map_page(pt, INPUT_MMIO_VA, base);
|
let base = crate::mmu::virt_to_phys(mmio.base());
|
||||||
|
map_mmio(uart, input_pid, INPUT_MMIO_VA, base, 1);
|
||||||
let vq_phys = alloc_contiguous(2);
|
let vq_phys = dma_buffer(uart, input_pid, INPUT_VQUEUE_VA, 2);
|
||||||
map_range(pt, INPUT_VQUEUE_VA, vq_phys, 2);
|
let evbuf_phys = dma_buffer(uart, input_pid, INPUT_EVBUF_VA, 1);
|
||||||
|
info_page(
|
||||||
let evbuf_phys = mmu::allocator::alloc_zeroed_page();
|
uart,
|
||||||
map_page(pt, INPUT_EVBUF_VA, evbuf_phys);
|
input_pid,
|
||||||
|
INPUT_PHYSINFO_VA,
|
||||||
let info_phys = mmu::allocator::alloc_zeroed_page();
|
&[evbuf_phys as u64, vq_phys as u64],
|
||||||
map_page(pt, INPUT_PHYSINFO_VA, info_phys);
|
);
|
||||||
unsafe {
|
|
||||||
core::ptr::write_volatile(info_phys as *mut u64, evbuf_phys as u64);
|
|
||||||
core::ptr::write_volatile((info_phys as *mut u64).add(1), vq_phys as u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
hw_virtio::mmio::init_legacy_device(&mmio, vq_phys, 32);
|
hw_virtio::mmio::init_legacy_device(&mmio, vq_phys, 32);
|
||||||
uart.write_str("k-os: VirtIO keyboard configured\n");
|
uart.write_str("k-os: VirtIO keyboard configured\n");
|
||||||
@@ -43,26 +33,3 @@ pub fn configure(uart: &Uart, input_pid: ProcessId) {
|
|||||||
uart.write_str("k-os: UART mapped for input\n");
|
uart.write_str("k-os: UART mapped for input\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(va),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(pa),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_range(pt: &mut mmu::PageTable, va_base: usize, pa_base: usize, count: usize) {
|
|
||||||
for i in 0..count {
|
|
||||||
map_page(pt, va_base + i * 4096, pa_base + i * 4096);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_contiguous(pages: usize) -> usize {
|
|
||||||
let first = mmu::allocator::alloc_zeroed_page();
|
|
||||||
for _ in 1..pages {
|
|
||||||
mmu::allocator::alloc_zeroed_page();
|
|
||||||
}
|
|
||||||
first
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,91 @@
|
|||||||
pub mod gpu_setup;
|
pub mod gpu_setup;
|
||||||
pub mod input_setup;
|
pub mod input_setup;
|
||||||
pub mod storage_setup;
|
pub mod storage_setup;
|
||||||
|
|
||||||
|
use crate::hw::uart::Uart;
|
||||||
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
|
use types::bounded::PageCount;
|
||||||
|
use types::id::ProcessId;
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
|
// Boot-time device wiring. Everything here goes through the kernel's
|
||||||
|
// mapping API so the kernel knows about every page a driver can touch.
|
||||||
|
// 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> {
|
||||||
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pages(uart: &Uart, n: usize) -> PageCount {
|
||||||
|
u16::try_from(n)
|
||||||
|
.ok()
|
||||||
|
.and_then(PageCount::new)
|
||||||
|
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
let id = kern()
|
||||||
|
.device_region_at(PhysicalAddress::from_raw(pa))
|
||||||
|
.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()
|
||||||
|
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "device map failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Physically contiguous, zeroed DMA memory for `owner`, mapped read-write
|
||||||
|
/// at `va`. Returns its physical base for programming the device.
|
||||||
|
pub(crate) fn dma_buffer(uart: &Uart, owner: ProcessId, va: usize, n: usize) -> usize {
|
||||||
|
let id = kern()
|
||||||
|
.create_pool_region(owner, pages(uart, n))
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "DMA region failed"));
|
||||||
|
kern()
|
||||||
|
.map_region(
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
VirtualAddress::from_raw(va),
|
||||||
|
PagePerms::ReadWrite,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "DMA map failed"));
|
||||||
|
kern()
|
||||||
|
.region_base(id)
|
||||||
|
.unwrap_or_else(|| crate::halt(uart, "DMA region vanished"))
|
||||||
|
.raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shares a fresh region between `owner` and `other` at the same `va`.
|
||||||
|
pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
|
||||||
|
let id = kern()
|
||||||
|
.create_pool_region(owner, pages(uart, n))
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "shared region failed"));
|
||||||
|
for pid in [owner, other] {
|
||||||
|
kern()
|
||||||
|
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "shared map failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `words` into a fresh page mapped at `va` in `pid`, so a driver
|
||||||
|
/// can learn the physical addresses of its DMA buffers.
|
||||||
|
pub(crate) fn info_page(uart: &Uart, pid: ProcessId, va: usize, words: &[u64]) {
|
||||||
|
let va = VirtualAddress::from_raw(va);
|
||||||
|
kern()
|
||||||
|
.map_fresh_range(pid, va, 1, PagePerms::ReadOnly)
|
||||||
|
.unwrap_or_else(|_| crate::halt(uart, "info page failed"));
|
||||||
|
let pa = kern()
|
||||||
|
.user_page_frame(pid, va)
|
||||||
|
.unwrap_or_else(|| crate::halt(uart, "info page vanished"));
|
||||||
|
let info = crate::mmu::phys_to_virt(pa.raw()) as *mut u64;
|
||||||
|
for (i, &w) in words.iter().enumerate() {
|
||||||
|
// SAFETY: `pa` is a pool frame just mapped for `pid`, reachable at
|
||||||
|
// `phys_to_virt`, and `words` fits in one page.
|
||||||
|
unsafe { core::ptr::write_volatile(info.add(i), w) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
|
use super::{dma_buffer, info_page, map_mmio, shared_buffer};
|
||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use crate::mmu;
|
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
|
|
||||||
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|
||||||
unsafe { &mut *crate::kernel_ptr() }
|
|
||||||
}
|
|
||||||
|
|
||||||
const VIRTIO_VA: usize = 0x20000;
|
const VIRTIO_VA: usize = 0x20000;
|
||||||
const VQUEUE_VA: usize = 0x21000;
|
const VQUEUE_VA: usize = 0x21000;
|
||||||
const REQHDR_VA: usize = 0x23000;
|
const REQHDR_VA: usize = 0x23000;
|
||||||
@@ -13,102 +9,28 @@ const PHYSINFO_VA: usize = 0x24000;
|
|||||||
const SHARED_BUF_VA: usize = 0x30000;
|
const SHARED_BUF_VA: usize = 0x30000;
|
||||||
const SHARED_BUF_PAGES: usize = 16;
|
const SHARED_BUF_PAGES: usize = 16;
|
||||||
|
|
||||||
pub fn configure(
|
pub fn configure(uart: &Uart, init_pid: ProcessId, storage_pid: ProcessId, virtio_base: usize) {
|
||||||
uart: &Uart,
|
map_mmio(uart, storage_pid, VIRTIO_VA, virtio_base, 1);
|
||||||
init_pid: ProcessId,
|
let vqueue_phys = dma_buffer(uart, storage_pid, VQUEUE_VA, 2);
|
||||||
storage_pid: ProcessId,
|
let reqhdr_phys = dma_buffer(uart, storage_pid, REQHDR_VA, 1);
|
||||||
virtio_base: usize,
|
info_page(
|
||||||
) {
|
uart,
|
||||||
let storage_pt = get_pt(storage_pid);
|
storage_pid,
|
||||||
let init_pt = get_pt(init_pid);
|
PHYSINFO_VA,
|
||||||
|
&[
|
||||||
map_virtio_mmio(storage_pt, virtio_base);
|
reqhdr_phys as u64,
|
||||||
let vqueue_phys = alloc_and_map_virtqueue(storage_pt);
|
(reqhdr_phys + 16) as u64,
|
||||||
let reqhdr_phys = alloc_and_map_reqhdr(storage_pt);
|
(reqhdr_phys + 32) as u64,
|
||||||
write_phys_info(storage_pt, reqhdr_phys);
|
],
|
||||||
|
);
|
||||||
reinit_device(virtio_base, vqueue_phys);
|
reinit_device(virtio_base, vqueue_phys);
|
||||||
uart.write_str("k-os: VirtIO configured for storage\n");
|
uart.write_str("k-os: VirtIO configured for storage\n");
|
||||||
|
|
||||||
map_shared_buffer(init_pt, storage_pt);
|
shared_buffer(uart, init_pid, storage_pid, SHARED_BUF_VA, SHARED_BUF_PAGES);
|
||||||
uart.write_str("k-os: shared buffer mapped\n");
|
uart.write_str("k-os: shared buffer mapped\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pt(pid: ProcessId) -> &'static mut mmu::PageTable {
|
|
||||||
let addr = kern().platform_mut().page_table_for(pid);
|
|
||||||
unsafe { &mut *(addr as *mut mmu::PageTable) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_virtio_mmio(pt: &mut mmu::PageTable, virtio_base: usize) {
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(VIRTIO_VA),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(virtio_base),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_and_map_virtqueue(pt: &mut mmu::PageTable) -> usize {
|
|
||||||
let phys = mmu::allocator::alloc_zeroed_page();
|
|
||||||
mmu::allocator::alloc_zeroed_page();
|
|
||||||
for i in 0..2usize {
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(VQUEUE_VA + i * 4096),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(phys + i * 4096),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
phys
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_and_map_reqhdr(pt: &mut mmu::PageTable) -> usize {
|
|
||||||
let phys = mmu::allocator::alloc_zeroed_page();
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(REQHDR_VA),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(phys),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
phys
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_phys_info(pt: &mut mmu::PageTable, reqhdr_phys: usize) {
|
|
||||||
let phys = mmu::allocator::alloc_zeroed_page();
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(PHYSINFO_VA),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(phys),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
unsafe {
|
|
||||||
let info = phys as *mut u64;
|
|
||||||
core::ptr::write_volatile(info, reqhdr_phys as u64);
|
|
||||||
core::ptr::write_volatile(info.add(1), (reqhdr_phys + 16) as u64);
|
|
||||||
core::ptr::write_volatile(info.add(2), (reqhdr_phys + 32) as u64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reinit_device(base: usize, vqueue_phys: usize) {
|
fn reinit_device(base: usize, vqueue_phys: usize) {
|
||||||
let mmio = hw_virtio::mmio::DirectMmio::new(base);
|
let mmio = hw_virtio::mmio::DirectMmio::new(crate::mmu::phys_to_virt(base));
|
||||||
hw_virtio::mmio::init_legacy_device(&mmio, vqueue_phys, hw_virtio::queue::QUEUE_SIZE as u32);
|
hw_virtio::mmio::init_legacy_device(&mmio, vqueue_phys, hw_virtio::queue::QUEUE_SIZE as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_shared_buffer(init_pt: &mut mmu::PageTable, storage_pt: &mut mmu::PageTable) {
|
|
||||||
for i in 0..SHARED_BUF_PAGES {
|
|
||||||
let page = mmu::allocator::alloc_zeroed_page();
|
|
||||||
let va = SHARED_BUF_VA + i * 4096;
|
|
||||||
let mapping = mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(va),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(page),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
};
|
|
||||||
mmu::map(init_pt, mapping);
|
|
||||||
mmu::map(storage_pt, mmu::Mapping {
|
|
||||||
va: types::address::VirtualAddress::from_raw(va),
|
|
||||||
pa: types::address::PhysicalAddress::from_raw(page),
|
|
||||||
pages: 1,
|
|
||||||
flags: mmu::Sv39Flags::user_rw(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,21 +1,37 @@
|
|||||||
use crate::board;
|
use crate::board;
|
||||||
|
|
||||||
const PLIC_PRIORITY: usize = board::PLIC_BASE;
|
const PLIC: usize = crate::mmu::phys_to_virt(board::PLIC_BASE);
|
||||||
const PLIC_S_ENABLE: usize = board::PLIC_BASE + 0x2080;
|
const PLIC_PRIORITY: usize = PLIC;
|
||||||
const PLIC_S_THRESHOLD: usize = board::PLIC_BASE + 0x201000;
|
|
||||||
const PLIC_S_CLAIM: usize = board::PLIC_BASE + 0x201004;
|
// External interrupts go to the boot hart only: its supervisor context is
|
||||||
|
// 2 * hart + 1 on QEMU virt (even contexts are machine mode).
|
||||||
|
fn context() -> usize {
|
||||||
|
2 * crate::smp::boot_hart() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_base() -> usize {
|
||||||
|
PLIC + 0x2000 + context() * 0x80
|
||||||
|
}
|
||||||
|
|
||||||
|
fn threshold() -> usize {
|
||||||
|
PLIC + 0x20_0000 + context() * 0x1000
|
||||||
|
}
|
||||||
|
|
||||||
|
fn claim_reg() -> usize {
|
||||||
|
threshold() + 4
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Plic;
|
pub struct Plic;
|
||||||
|
|
||||||
impl Plic {
|
impl Plic {
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
unsafe { core::ptr::write_volatile(PLIC_S_THRESHOLD as *mut u32, 0) };
|
unsafe { core::ptr::write_volatile(threshold() as *mut u32, 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable(irq: u32) {
|
pub fn enable(irq: u32) {
|
||||||
let word = (irq / 32) as usize;
|
let word = (irq / 32) as usize;
|
||||||
let bit = irq % 32;
|
let bit = irq % 32;
|
||||||
let addr = PLIC_S_ENABLE + word * 4;
|
let addr = enable_base() + word * 4;
|
||||||
unsafe {
|
unsafe {
|
||||||
let current = core::ptr::read_volatile(addr as *const u32);
|
let current = core::ptr::read_volatile(addr as *const u32);
|
||||||
core::ptr::write_volatile(addr as *mut u32, current | (1 << bit));
|
core::ptr::write_volatile(addr as *mut u32, current | (1 << bit));
|
||||||
@@ -27,7 +43,7 @@ impl Plic {
|
|||||||
pub fn disable(irq: u32) {
|
pub fn disable(irq: u32) {
|
||||||
let word = (irq / 32) as usize;
|
let word = (irq / 32) as usize;
|
||||||
let bit = irq % 32;
|
let bit = irq % 32;
|
||||||
let addr = PLIC_S_ENABLE + word * 4;
|
let addr = enable_base() + word * 4;
|
||||||
unsafe {
|
unsafe {
|
||||||
let current = core::ptr::read_volatile(addr as *const u32);
|
let current = core::ptr::read_volatile(addr as *const u32);
|
||||||
core::ptr::write_volatile(addr as *mut u32, current & !(1 << bit));
|
core::ptr::write_volatile(addr as *mut u32, current & !(1 << bit));
|
||||||
@@ -35,10 +51,10 @@ impl Plic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn claim() -> u32 {
|
pub fn claim() -> u32 {
|
||||||
unsafe { core::ptr::read_volatile(PLIC_S_CLAIM as *const u32) }
|
unsafe { core::ptr::read_volatile(claim_reg() as *const u32) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(irq: u32) {
|
pub fn complete(irq: u32) {
|
||||||
unsafe { core::ptr::write_volatile(PLIC_S_CLAIM as *mut u32, irq) };
|
unsafe { core::ptr::write_volatile(claim_reg() as *mut u32, irq) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ pub fn read_time() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn sbi_set_timer(stime: u64) {
|
fn sbi_set_timer(stime: u64) {
|
||||||
|
// SBI returns an error code in a0 and a value in a1; both are
|
||||||
|
// clobbered even though we ignore them.
|
||||||
unsafe {
|
unsafe {
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"ecall",
|
"ecall",
|
||||||
in("a0") stime,
|
inlateout("a0") stime => _,
|
||||||
|
lateout("a1") _,
|
||||||
in("a6") 0usize,
|
in("a6") 0usize,
|
||||||
in("a7") 0x54494D45usize,
|
in("a7") 0x54494D45usize,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ pub struct Uart {
|
|||||||
|
|
||||||
impl Uart {
|
impl Uart {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { base: board::UART_BASE }
|
Self {
|
||||||
|
base: crate::mmu::phys_to_virt(board::UART_BASE),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_char(&self, byte: u8) {
|
pub fn put_char(&self, byte: u8) {
|
||||||
@@ -30,7 +32,11 @@ impl Uart {
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while value > 0 {
|
while value > 0 {
|
||||||
let digit = (value & 0xF) as u8;
|
let digit = (value & 0xF) as u8;
|
||||||
buf[i] = if digit < 10 { b'0' + digit } else { b'a' + digit - 10 };
|
buf[i] = if digit < 10 {
|
||||||
|
b'0' + digit
|
||||||
|
} else {
|
||||||
|
b'a' + digit - 10
|
||||||
|
};
|
||||||
value >>= 4;
|
value >>= 4;
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use hw_virtio::mmio::{self, DirectMmio};
|
use crate::mmu::{phys_to_virt, virt_to_phys};
|
||||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue, QUEUE_SIZE};
|
|
||||||
use hw_virtio::block::{self, DmaAddrs};
|
use hw_virtio::block::{self, DmaAddrs};
|
||||||
|
use hw_virtio::mmio::{self, DirectMmio};
|
||||||
|
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
|
|
||||||
const SECTOR_SIZE: usize = 512;
|
const SECTOR_SIZE: usize = 512;
|
||||||
@@ -11,7 +12,11 @@ struct VirtqMem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mut VQUEUE: VirtqMem = VirtqMem { _data: [0; 8192] };
|
static mut VQUEUE: VirtqMem = VirtqMem { _data: [0; 8192] };
|
||||||
static mut REQ_HEADER: block::BlockReqHeader = block::BlockReqHeader { req_type: 0, reserved: 0, sector: 0 };
|
static mut REQ_HEADER: block::BlockReqHeader = block::BlockReqHeader {
|
||||||
|
req_type: 0,
|
||||||
|
reserved: 0,
|
||||||
|
sector: 0,
|
||||||
|
};
|
||||||
static mut REQ_STATUS: u8 = 0xFF;
|
static mut REQ_STATUS: u8 = 0xFF;
|
||||||
static mut SECTOR_BUF: [u8; SECTOR_SIZE] = [0; SECTOR_SIZE];
|
static mut SECTOR_BUF: [u8; SECTOR_SIZE] = [0; SECTOR_SIZE];
|
||||||
|
|
||||||
@@ -23,29 +28,38 @@ pub struct VirtioBlk {
|
|||||||
|
|
||||||
impl VirtioBlk {
|
impl VirtioBlk {
|
||||||
pub fn probe() -> Option<Self> {
|
pub fn probe() -> Option<Self> {
|
||||||
let mmio = mmio::scan_for_device(mmio::DEVICE_ID_BLOCK)?;
|
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_BLOCK)?;
|
||||||
|
|
||||||
let vq_phys = unsafe { core::ptr::addr_of!(VQUEUE) as usize };
|
// The device sees physical addresses; the CPU uses kernel ones.
|
||||||
mmio::init_legacy_device(&mmio, vq_phys, QUEUE_SIZE as u32);
|
let vq_va = core::ptr::addr_of!(VQUEUE) as usize;
|
||||||
|
mmio::init_legacy_device(&mmio, virt_to_phys(vq_va), QUEUE_SIZE as u32);
|
||||||
|
|
||||||
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_phys));
|
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_va));
|
||||||
|
|
||||||
let dma = unsafe {
|
let pair = |va: usize| {
|
||||||
DmaAddrs {
|
(
|
||||||
header_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(REQ_HEADER) as usize),
|
PhysicalAddress::from_raw(virt_to_phys(va)),
|
||||||
header_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(REQ_HEADER) as usize),
|
VirtualAddress::from_raw(va),
|
||||||
status_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(REQ_STATUS) as usize),
|
)
|
||||||
status_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(REQ_STATUS) as usize),
|
};
|
||||||
data_pa: PhysicalAddress::from_raw(core::ptr::addr_of!(SECTOR_BUF) as usize),
|
let (header_pa, header_va) = pair(core::ptr::addr_of_mut!(REQ_HEADER) as usize);
|
||||||
data_va: VirtualAddress::from_raw(core::ptr::addr_of_mut!(SECTOR_BUF) as usize),
|
let (status_pa, status_va) = pair(core::ptr::addr_of_mut!(REQ_STATUS) as usize);
|
||||||
}
|
let (data_pa, data_va) = pair(core::ptr::addr_of_mut!(SECTOR_BUF) as usize);
|
||||||
|
let dma = DmaAddrs {
|
||||||
|
header_pa,
|
||||||
|
header_va,
|
||||||
|
status_pa,
|
||||||
|
status_va,
|
||||||
|
data_pa,
|
||||||
|
data_va,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Self { mmio, queue, dma })
|
Some(Self { mmio, queue, dma })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Physical address of the device's MMIO window.
|
||||||
pub fn base(&self) -> usize {
|
pub fn base(&self) -> usize {
|
||||||
self.mmio.base()
|
virt_to_phys(self.mmio.base())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use hw_virtio::mmio::{self, DirectMmio};
|
use crate::mmu::{phys_to_virt, virt_to_phys};
|
||||||
use hw_virtio::queue::{QueueAddrs, VirtioQueue, QUEUE_SIZE};
|
|
||||||
use hw_virtio::gpu::{self, GpuDmaAddrs};
|
use hw_virtio::gpu::{self, GpuDmaAddrs};
|
||||||
|
use hw_virtio::mmio::{self, DirectMmio};
|
||||||
|
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
use types::display::Dimension;
|
use types::display::Dimension;
|
||||||
|
|
||||||
@@ -10,12 +11,13 @@ pub struct VirtioGpu {
|
|||||||
|
|
||||||
impl VirtioGpu {
|
impl VirtioGpu {
|
||||||
pub fn probe() -> Option<Self> {
|
pub fn probe() -> Option<Self> {
|
||||||
let mmio = mmio::scan_for_device(mmio::DEVICE_ID_GPU)?;
|
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_GPU)?;
|
||||||
Some(Self { mmio })
|
Some(Self { mmio })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Physical address of the device's MMIO window.
|
||||||
pub fn base(&self) -> usize {
|
pub fn base(&self) -> usize {
|
||||||
self.mmio.base()
|
virt_to_phys(self.mmio.base())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mmio(&self) -> &DirectMmio {
|
pub fn mmio(&self) -> &DirectMmio {
|
||||||
@@ -26,15 +28,29 @@ impl VirtioGpu {
|
|||||||
mmio::init_legacy_device(&self.mmio, vqueue_phys, QUEUE_SIZE as u32);
|
mmio::init_legacy_device(&self.mmio, vqueue_phys, QUEUE_SIZE as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_framebuffer(&self, cmd_phys: usize, vq_phys: usize, fb_phys: usize, width: Dimension, height: Dimension) -> bool {
|
pub fn setup_framebuffer(
|
||||||
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_phys));
|
&self,
|
||||||
|
cmd_phys: usize,
|
||||||
|
vq_phys: usize,
|
||||||
|
fb_phys: usize,
|
||||||
|
width: Dimension,
|
||||||
|
height: Dimension,
|
||||||
|
) -> bool {
|
||||||
|
let queue = VirtioQueue::new(QueueAddrs::from_base(phys_to_virt(vq_phys)));
|
||||||
let dma = GpuDmaAddrs {
|
let dma = GpuDmaAddrs {
|
||||||
cmd_pa: PhysicalAddress::from_raw(cmd_phys),
|
cmd_pa: PhysicalAddress::from_raw(cmd_phys),
|
||||||
cmd_va: VirtualAddress::from_raw(cmd_phys),
|
cmd_va: VirtualAddress::from_raw(phys_to_virt(cmd_phys)),
|
||||||
resp_pa: PhysicalAddress::from_raw(cmd_phys + 512),
|
resp_pa: PhysicalAddress::from_raw(cmd_phys + 512),
|
||||||
};
|
};
|
||||||
let ok1 = gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
|
let ok1 = gpu::create_2d(&self.mmio, &queue, &dma, 1, width, height);
|
||||||
let ok2 = gpu::attach_backing(&self.mmio, &queue, &dma, 1, PhysicalAddress::from_raw(fb_phys), width.raw() as u32 * height.raw() as u32 * 4);
|
let ok2 = gpu::attach_backing(
|
||||||
|
&self.mmio,
|
||||||
|
&queue,
|
||||||
|
&dma,
|
||||||
|
1,
|
||||||
|
PhysicalAddress::from_raw(fb_phys),
|
||||||
|
width.raw() as u32 * height.raw() as u32 * 4,
|
||||||
|
);
|
||||||
let ok3 = gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
|
let ok3 = gpu::set_scanout(&self.mmio, &queue, &dma, 0, 1, width, height);
|
||||||
ok1 && ok2 && ok3
|
ok1 && ok2 && ok3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,13 @@ impl<T> KernelCell<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&self, value: T) {
|
/// Initializes the cell by letting `build` write the value straight
|
||||||
unsafe { (*self.inner.get()).write(value) };
|
/// into its storage, so a large `T` never lives on the stack.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `build` must fully initialize the `T` behind the pointer.
|
||||||
|
pub unsafe fn init_with(&self, build: impl FnOnce(*mut T)) {
|
||||||
|
build(unsafe { (*self.inner.get()).as_mut_ptr() });
|
||||||
self.initialized.store(true, Ordering::Release);
|
self.initialized.store(true, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ mod hw;
|
|||||||
mod kernel_cell;
|
mod kernel_cell;
|
||||||
mod mmu;
|
mod mmu;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
mod smp;
|
||||||
mod trap;
|
mod trap;
|
||||||
|
|
||||||
use core::arch::global_asm;
|
use core::arch::global_asm;
|
||||||
@@ -17,55 +18,168 @@ 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: boot runs with the kernel lock held and before any other
|
||||||
|
// hart has started.
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Firmware enters `_start` (and SBI starts other harts at
|
||||||
|
// `_secondary_start`) at the physical load address with the MMU off and
|
||||||
|
// the hart id in a0, but the kernel is linked in the upper half. Using
|
||||||
|
// only PC-relative code, the stub turns on `boot_page_table`, which maps
|
||||||
|
// RAM twice (at its physical address, just so the next instruction still
|
||||||
|
// exists, and in the upper half) plus the MMIO window high, then jumps to
|
||||||
|
// its own high address and takes the hart's kernel stack. The kernel then
|
||||||
|
// switches every hart to the real table, which maps nothing low.
|
||||||
global_asm!(
|
global_asm!(
|
||||||
r#"
|
r#"
|
||||||
.section .text.entry
|
.section .text.entry
|
||||||
.globl _start
|
.globl _start
|
||||||
_start:
|
_start:
|
||||||
la sp, _stack_top
|
lla t2, start_high_address
|
||||||
|
j 1f
|
||||||
|
.globl _secondary_start
|
||||||
|
_secondary_start:
|
||||||
|
lla t2, secondary_high_address
|
||||||
|
1:
|
||||||
|
li t0, 8 # smp::MAX_HARTS
|
||||||
|
bgeu a0, t0, spin
|
||||||
|
mv s0, a0
|
||||||
|
lla t0, boot_page_table
|
||||||
|
srli t0, t0, 12
|
||||||
|
li t1, 8 << 60
|
||||||
|
or t0, t0, t1
|
||||||
|
csrw satp, t0
|
||||||
|
sfence.vma
|
||||||
|
ld t0, 0(t2)
|
||||||
|
jr t0
|
||||||
|
|
||||||
|
# sp = _stacks_start + (hart + 1) * smp::STACK_SLOT
|
||||||
|
start_high:
|
||||||
|
call hart_stack
|
||||||
|
mv a0, s0
|
||||||
call kmain
|
call kmain
|
||||||
|
j spin
|
||||||
|
secondary_high:
|
||||||
|
call hart_stack
|
||||||
|
mv a0, s0
|
||||||
|
call kmain_secondary
|
||||||
|
j spin
|
||||||
|
hart_stack:
|
||||||
|
lla sp, _stacks_start
|
||||||
|
addi t0, s0, 1
|
||||||
|
li t1, 0x81000
|
||||||
|
mul t0, t0, t1
|
||||||
|
add sp, sp, t0
|
||||||
|
ret
|
||||||
spin:
|
spin:
|
||||||
wfi
|
wfi
|
||||||
j spin
|
j spin
|
||||||
|
|
||||||
|
.pushsection .rodata
|
||||||
|
.balign 8
|
||||||
|
start_high_address:
|
||||||
|
.quad start_high
|
||||||
|
secondary_high_address:
|
||||||
|
.quad secondary_high
|
||||||
|
.popsection
|
||||||
|
|
||||||
|
# Sv39 root of 1 GiB leaves; an entry is (PA >> 12) << 10 | flags, with
|
||||||
|
# flags V|R|W|A|D = 0xc7, plus X = 0xcf.
|
||||||
|
.pushsection .data
|
||||||
|
.balign 4096
|
||||||
|
boot_page_table:
|
||||||
|
.quad 0
|
||||||
|
.quad 0
|
||||||
|
.quad 0x200000cf # [2] 0x8000_0000 -> RAM, for the jump only
|
||||||
|
.fill 253, 8, 0
|
||||||
|
.quad 0x000000c7 # [256] PHYS_OFFSET + 0 -> MMIO
|
||||||
|
.quad 0
|
||||||
|
.quad 0x200000cf # [258] PHYS_OFFSET + 0x8000_0000 -> RAM
|
||||||
|
.fill 253, 8, 0
|
||||||
|
.popsection
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
const SHARED_BUF_VA: usize = 0x30000;
|
const SHARED_BUF_VA: usize = 0x30000;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn kmain() -> ! {
|
extern "C" fn kmain(hart: usize) -> ! {
|
||||||
let uart = Uart::new();
|
let uart = Uart::new();
|
||||||
uart.write_str("k-os: booted on RISC-V\n");
|
uart.write_str("k-os: booted on RISC-V\n");
|
||||||
|
|
||||||
let kernel_pt = boot::hardware::init(&uart);
|
boot::hardware::init(&uart, hart);
|
||||||
let mut blk = boot::hardware::probe_block_device(&uart)
|
// Held until the boot hart first enters userspace; harts started
|
||||||
.unwrap_or_else(|| halt(&uart, "no block device"));
|
// below wait for it. Not before init: zeroing .bss resets the lock.
|
||||||
|
smp::lock();
|
||||||
|
let mut blk =
|
||||||
|
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
||||||
|
|
||||||
let (init, _) = boot::loader::load_init(&uart, &mut blk, kernel_pt);
|
let (init, init_thread) = boot::loader::load_init(&uart, &mut blk);
|
||||||
let storage = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"storage.elf", "storage");
|
if let Some(ctx) = kern_mut().thread_context_mut(init_thread) {
|
||||||
let display = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"display.elf", "display");
|
ctx.registers_mut()[10] = SHARED_BUF_VA;
|
||||||
let input = boot::loader::load_server(&uart, &mut blk, kernel_pt, b"input.elf", "input");
|
}
|
||||||
|
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
||||||
|
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
||||||
|
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
||||||
let virtio_base = blk.base();
|
let virtio_base = blk.base();
|
||||||
drop(blk);
|
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
|
start_other_harts(&uart, hart);
|
||||||
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);
|
trap::enter_first();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_other_harts(uart: &Uart, boot: usize) {
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn _secondary_start();
|
||||||
|
}
|
||||||
|
// Harts start with the MMU off, so at the entry's physical address.
|
||||||
|
let entry = mmu::virt_to_phys(_secondary_start as *const () as usize);
|
||||||
|
for hart in (0..smp::num_harts()).filter(|h| *h != boot) {
|
||||||
|
if !smp::sbi_hart_start(hart, entry) {
|
||||||
|
halt(uart, "could not start a hart");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uart.write_str("k-os: ");
|
||||||
|
uart.write_dec(smp::num_harts());
|
||||||
|
uart.write_str(" harts\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where every hart but the boot hart starts, on its own stack.
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn kmain_secondary(_hart: usize) -> ! {
|
||||||
|
trap::init();
|
||||||
|
hw::timer::init();
|
||||||
|
mmu::activate(mmu::page_table::table_at(smp::kernel_root()));
|
||||||
|
smp::lock();
|
||||||
|
trap::enter_first();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn halt(uart: &Uart, msg: &str) -> ! {
|
pub fn halt(uart: &Uart, msg: &str) -> ! {
|
||||||
uart.write_str("k-os: FATAL — ");
|
uart.write_str("k-os: FATAL — ");
|
||||||
uart.write_str(msg);
|
uart.write_str(msg);
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
loop {
|
||||||
|
unsafe { core::arch::asm!("wfi") };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
@@ -78,5 +192,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
|
|||||||
uart.write_dec(location.line() as usize);
|
uart.write_dec(location.line() as usize);
|
||||||
}
|
}
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
loop {
|
||||||
|
unsafe { core::arch::asm!("wfi") };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
use super::page_table::{PageTable, PAGE_SIZE};
|
use super::page_table::{PAGE_SIZE, PageTable};
|
||||||
|
use crate::board;
|
||||||
|
|
||||||
// Boot-time bump allocator for page tables.
|
// Boot-time bump allocator for kernel-only memory: the kernel page table
|
||||||
// Only used during init before the scheduler starts.
|
// and boot scratch buffers. User memory and user page tables come from the
|
||||||
|
// kernel's frame table instead. It must never reach the user pool.
|
||||||
static mut NEXT_FREE: usize = 0;
|
static mut NEXT_FREE: usize = 0;
|
||||||
|
|
||||||
pub fn init_allocator(start: usize) {
|
pub fn init_allocator(start: usize) {
|
||||||
unsafe { NEXT_FREE = start; }
|
unsafe {
|
||||||
|
NEXT_FREE = start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_zeroed_page() -> usize {
|
pub fn alloc_zeroed_page() -> usize {
|
||||||
unsafe {
|
unsafe {
|
||||||
let addr = NEXT_FREE;
|
let addr = NEXT_FREE;
|
||||||
|
if addr + PAGE_SIZE > super::phys_to_virt(board::USER_POOL_BASE) {
|
||||||
|
crate::halt(&crate::hw::uart::Uart::new(), "boot allocator exhausted");
|
||||||
|
}
|
||||||
NEXT_FREE += PAGE_SIZE;
|
NEXT_FREE += PAGE_SIZE;
|
||||||
core::ptr::write_bytes(addr as *mut u8, 0, PAGE_SIZE);
|
core::ptr::write_bytes(addr as *mut u8, 0, PAGE_SIZE);
|
||||||
addr
|
addr
|
||||||
@@ -18,9 +25,5 @@ pub fn alloc_zeroed_page() -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_page_table() -> &'static mut PageTable {
|
pub fn alloc_page_table() -> &'static mut PageTable {
|
||||||
unsafe {
|
unsafe { &mut *(alloc_zeroed_page() as *mut PageTable) }
|
||||||
let addr = alloc_zeroed_page();
|
|
||||||
let table = &mut *(addr as *mut PageTable);
|
|
||||||
table
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,24 @@ pub mod allocator;
|
|||||||
pub mod page_table;
|
pub mod page_table;
|
||||||
|
|
||||||
pub use allocator::init_allocator;
|
pub use allocator::init_allocator;
|
||||||
pub use page_table::{activate, init_kernel_page_table, create_user_page_table, map, translate, Sv39Flags, Mapping, PageTable, PAGE_SIZE};
|
pub use page_table::{
|
||||||
|
PAGE_SIZE, PageTable, Sv39Flags, USER_VA_END, USER_VA_START, activate, active_root, check_wx,
|
||||||
|
create_user_root, init_kernel_page_table, map_user_page, unmap_user_page,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The kernel lives in the upper half of the address space: all of RAM and
|
||||||
|
/// the MMIO windows appear at their physical address plus this offset (the
|
||||||
|
/// start of Sv39's upper half), and the kernel image is linked there too,
|
||||||
|
/// so one offset converts any kernel address. Must match linker.ld.
|
||||||
|
pub const PHYS_OFFSET: usize = 0xFFFF_FFC0_0000_0000;
|
||||||
|
|
||||||
|
/// Where the kernel sees physical address `pa`.
|
||||||
|
pub const fn phys_to_virt(pa: usize) -> usize {
|
||||||
|
pa + PHYS_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The physical address behind kernel address `va` (a static, the boot
|
||||||
|
/// heap, a page table). Only valid for addresses in the direct map.
|
||||||
|
pub const fn virt_to_phys(va: usize) -> usize {
|
||||||
|
va - PHYS_OFFSET
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use crate::board;
|
|
||||||
use super::allocator::alloc_page_table;
|
use super::allocator::alloc_page_table;
|
||||||
|
use super::{phys_to_virt, virt_to_phys};
|
||||||
|
use crate::board;
|
||||||
|
use crate::smp;
|
||||||
|
use platform::{FrameSource, MapError};
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
pub use types::mapping::Mapping;
|
pub use types::mapping::Mapping;
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
pub const PAGE_SIZE: usize = 4096;
|
||||||
@@ -20,27 +24,30 @@ pub struct Sv39Flags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Sv39Flags {
|
impl Sv39Flags {
|
||||||
pub fn kernel_rwx() -> Self {
|
pub fn kernel_rx() -> Self {
|
||||||
Self { bits: PTE_R | PTE_W | PTE_X | PTE_G }
|
Self {
|
||||||
|
bits: PTE_R | PTE_X | PTE_G,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kernel_ro() -> Self {
|
||||||
|
Self {
|
||||||
|
bits: PTE_R | PTE_G,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kernel_rw() -> Self {
|
pub fn kernel_rw() -> Self {
|
||||||
Self { bits: PTE_R | PTE_W | PTE_G }
|
Self {
|
||||||
|
bits: PTE_R | PTE_W | PTE_G,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_rx() -> Self {
|
pub fn user(perms: PagePerms) -> Self {
|
||||||
Self { bits: PTE_R | PTE_X | PTE_U }
|
let bits = match perms {
|
||||||
}
|
PagePerms::ReadOnly => PTE_R | PTE_U,
|
||||||
|
PagePerms::ReadWrite => PTE_R | PTE_W | PTE_U,
|
||||||
pub fn user_rw() -> Self {
|
PagePerms::ReadExecute => PTE_R | PTE_X | PTE_U,
|
||||||
Self { bits: PTE_R | PTE_W | PTE_U }
|
};
|
||||||
}
|
|
||||||
|
|
||||||
pub fn user(read: bool, write: bool, exec: bool) -> Self {
|
|
||||||
let mut bits = PTE_U;
|
|
||||||
if read { bits |= PTE_R; }
|
|
||||||
if write { bits |= PTE_W; }
|
|
||||||
if exec { bits |= PTE_X; }
|
|
||||||
Self { bits }
|
Self { bits }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,81 +99,223 @@ fn vpn(va: usize, level: usize) -> usize {
|
|||||||
(va >> (12 + 9 * level)) & 0x1FF
|
(va >> (12 + 9 * level)) & 0x1FF
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translate(root: &PageTable, va: usize) -> Option<usize> {
|
|
||||||
let vpn2 = vpn(va, 2);
|
|
||||||
if !root.entries[vpn2].is_valid() { return None; }
|
|
||||||
let l1 = unsafe { &*((root.entries[vpn2].ppn() << 12) as *const PageTable) };
|
|
||||||
|
|
||||||
let vpn1 = vpn(va, 1);
|
|
||||||
if !l1.entries[vpn1].is_valid() { return None; }
|
|
||||||
let l0 = unsafe { &*((l1.entries[vpn1].ppn() << 12) as *const PageTable) };
|
|
||||||
|
|
||||||
let vpn0 = vpn(va, 0);
|
|
||||||
if !l0.entries[vpn0].is_valid() { return None; }
|
|
||||||
let pa = (l0.entries[vpn0].ppn() << 12) as usize | (va & 0xFFF);
|
|
||||||
Some(pa)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_page(root: &mut PageTable, va: usize, pa: usize, flags: Sv39Flags) {
|
fn map_page(root: &mut PageTable, va: usize, pa: usize, flags: Sv39Flags) {
|
||||||
let vpn2 = vpn(va, 2);
|
let vpn2 = vpn(va, 2);
|
||||||
let vpn1 = vpn(va, 1);
|
let vpn1 = vpn(va, 1);
|
||||||
let vpn0 = vpn(va, 0);
|
let vpn0 = vpn(va, 0);
|
||||||
|
|
||||||
if !root.entries[vpn2].is_valid() {
|
if !root.entries[vpn2].is_valid() {
|
||||||
let table = alloc_page_table();
|
root.entries[vpn2] = PageTableEntry::new_branch(table_ppn(alloc_page_table()));
|
||||||
let ppn = (table as *const PageTable as u64) >> 12;
|
|
||||||
root.entries[vpn2] = PageTableEntry::new_branch(ppn);
|
|
||||||
}
|
}
|
||||||
|
let l1 = table_at((root.entries[vpn2].ppn() << 12) as usize);
|
||||||
let l1_addr = (root.entries[vpn2].ppn() << 12) as *mut PageTable;
|
|
||||||
let l1 = unsafe { &mut *l1_addr };
|
|
||||||
|
|
||||||
if !l1.entries[vpn1].is_valid() {
|
if !l1.entries[vpn1].is_valid() {
|
||||||
let table = alloc_page_table();
|
l1.entries[vpn1] = PageTableEntry::new_branch(table_ppn(alloc_page_table()));
|
||||||
let ppn = (table as *const PageTable as u64) >> 12;
|
|
||||||
l1.entries[vpn1] = PageTableEntry::new_branch(ppn);
|
|
||||||
}
|
}
|
||||||
|
let l0 = table_at((l1.entries[vpn1].ppn() << 12) as usize);
|
||||||
let l0_addr = (l1.entries[vpn1].ppn() << 12) as *mut PageTable;
|
|
||||||
let l0 = unsafe { &mut *l0_addr };
|
|
||||||
|
|
||||||
let ppn = (pa as u64) >> 12;
|
let ppn = (pa as u64) >> 12;
|
||||||
l0.entries[vpn0] = PageTableEntry::new_leaf(ppn, flags);
|
l0.entries[vpn0] = PageTableEntry::new_leaf(ppn, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The physical page number of a page table the kernel holds a reference to.
|
||||||
|
fn table_ppn(table: &PageTable) -> u64 {
|
||||||
|
(virt_to_phys(table as *const PageTable as usize) as u64) >> 12
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map(root: &mut PageTable, mapping: Mapping<Sv39Flags>) {
|
pub fn map(root: &mut PageTable, mapping: Mapping<Sv39Flags>) {
|
||||||
for i in 0..mapping.pages {
|
for i in 0..mapping.pages {
|
||||||
let offset = i * PAGE_SIZE;
|
let offset = i * PAGE_SIZE;
|
||||||
map_page(root, mapping.va.raw() + offset, mapping.pa.raw() + offset, mapping.flags);
|
map_page(
|
||||||
|
root,
|
||||||
|
mapping.va.raw() + offset,
|
||||||
|
mapping.pa.raw() + offset,
|
||||||
|
mapping.flags,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_kernel_page_table() -> &'static mut PageTable {
|
pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||||
let root = alloc_page_table();
|
let root = alloc_page_table();
|
||||||
|
|
||||||
let identity = |addr: usize| (VirtualAddress::from_raw(addr), PhysicalAddress::from_raw(addr));
|
// Physical `pa` at its place in the upper-half direct map.
|
||||||
|
let direct = |pa: usize| {
|
||||||
|
(
|
||||||
|
VirtualAddress::from_raw(phys_to_virt(pa)),
|
||||||
|
PhysicalAddress::from_raw(pa),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let (va, pa) = identity(board::RAM_BASE);
|
// The kernel image, then the rest of RAM, each with only the access it
|
||||||
map(root, Mapping { va, pa, pages: board::RAM_SIZE / PAGE_SIZE, flags: Sv39Flags::kernel_rwx() });
|
// needs: code is never writable, data never executable. RAM below the
|
||||||
|
// image belongs to the firmware and stays unmapped, as does the guard
|
||||||
|
// page under each hart's kernel stack. Nothing is mapped in the lower
|
||||||
|
// half, which is userspace's alone.
|
||||||
|
let layout = KernelLayout::from_linker();
|
||||||
|
let ram_end = phys_to_virt(board::RAM_BASE + board::RAM_SIZE);
|
||||||
|
let stacks = (0..smp::MAX_HARTS).map(|h| {
|
||||||
|
let slot = layout.stacks_start + h * smp::STACK_SLOT;
|
||||||
|
(
|
||||||
|
slot + PAGE_SIZE,
|
||||||
|
slot + smp::STACK_SLOT,
|
||||||
|
Sv39Flags::kernel_rw(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
for (start, end, flags) in [
|
||||||
|
(layout.text_start, layout.text_end, Sv39Flags::kernel_rx()),
|
||||||
|
(layout.text_end, layout.rodata_end, Sv39Flags::kernel_ro()),
|
||||||
|
(
|
||||||
|
layout.rodata_end,
|
||||||
|
layout.stacks_start,
|
||||||
|
Sv39Flags::kernel_rw(),
|
||||||
|
),
|
||||||
|
(layout.stacks_end, ram_end, Sv39Flags::kernel_rw()),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(stacks)
|
||||||
|
{
|
||||||
|
let (va, pa) = direct(virt_to_phys(start));
|
||||||
|
map(
|
||||||
|
root,
|
||||||
|
Mapping {
|
||||||
|
va,
|
||||||
|
pa,
|
||||||
|
pages: (end - start) / PAGE_SIZE,
|
||||||
|
flags,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let (va, pa) = identity(board::UART_BASE);
|
for (base, size) in [
|
||||||
map(root, Mapping { va, pa, pages: 1, flags: Sv39Flags::kernel_rw() });
|
(board::UART_BASE, PAGE_SIZE),
|
||||||
|
(board::PLIC_BASE, 0x40_0000),
|
||||||
let (va, pa) = identity(board::PLIC_BASE);
|
(board::CLINT_BASE, 0x1_0000),
|
||||||
map(root, Mapping { va, pa, pages: 0x400000 / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
(board::VIRTIO_BASE, board::VIRTIO_SIZE),
|
||||||
|
] {
|
||||||
let (va, pa) = identity(board::CLINT_BASE);
|
let (va, pa) = direct(base);
|
||||||
map(root, Mapping { va, pa, pages: 0x10000 / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
map(
|
||||||
|
root,
|
||||||
let (va, pa) = identity(board::VIRTIO_BASE);
|
Mapping {
|
||||||
map(root, Mapping { va, pa, pages: board::VIRTIO_SIZE / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
va,
|
||||||
|
pa,
|
||||||
|
pages: size / PAGE_SIZE,
|
||||||
|
flags: Sv39Flags::kernel_rw(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
root
|
root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Page-aligned boundaries of the kernel image, from linker.ld.
|
||||||
|
pub struct KernelLayout {
|
||||||
|
pub text_start: usize,
|
||||||
|
pub text_end: usize,
|
||||||
|
pub rodata_end: usize,
|
||||||
|
pub stacks_start: usize,
|
||||||
|
pub stacks_end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KernelLayout {
|
||||||
|
pub fn from_linker() -> Self {
|
||||||
|
unsafe extern "C" {
|
||||||
|
static _text_start: u8;
|
||||||
|
static _text_end: u8;
|
||||||
|
static _rodata_end: u8;
|
||||||
|
static _stacks_start: u8;
|
||||||
|
static _stacks_end: u8;
|
||||||
|
}
|
||||||
|
// SAFETY: only the symbols' addresses are taken, never their values.
|
||||||
|
unsafe {
|
||||||
|
Self {
|
||||||
|
text_start: &_text_start as *const u8 as usize,
|
||||||
|
text_end: &_text_end as *const u8 as usize,
|
||||||
|
rodata_end: &_rodata_end as *const u8 as usize,
|
||||||
|
stacks_start: &_stacks_start as *const u8 as usize,
|
||||||
|
stacks_end: &_stacks_end as *const u8 as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What `check_wx` found wrong with a kernel page table, if anything.
|
||||||
|
pub enum WxViolation {
|
||||||
|
/// A page that is both writable and executable.
|
||||||
|
WritableCode(usize),
|
||||||
|
/// An executable page outside the kernel's text.
|
||||||
|
ExecutableData(usize),
|
||||||
|
/// A mapped page under the firmware, or the stack guard.
|
||||||
|
MappedHole(usize),
|
||||||
|
/// A kernel mapping in the lower half, which belongs to userspace.
|
||||||
|
LowerHalf(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walks every leaf of `root` and checks the kernel layout: nothing is
|
||||||
|
/// both writable and executable, only kernel text is executable, the
|
||||||
|
/// firmware and every stack guard are unmapped, and nothing at all is mapped
|
||||||
|
/// in the lower half. Returns the number of executable pages.
|
||||||
|
pub fn check_wx(root: &PageTable) -> Result<usize, WxViolation> {
|
||||||
|
let layout = KernelLayout::from_linker();
|
||||||
|
let text = layout.text_start..layout.text_end;
|
||||||
|
let is_guard = |va: usize| {
|
||||||
|
(layout.stacks_start..layout.stacks_end).contains(&va)
|
||||||
|
&& (va - layout.stacks_start).is_multiple_of(smp::STACK_SLOT)
|
||||||
|
};
|
||||||
|
let firmware = phys_to_virt(board::RAM_BASE)..layout.text_start;
|
||||||
|
let mut executable = 0;
|
||||||
|
for (va, bits) in leaves(root) {
|
||||||
|
if va < phys_to_virt(0) {
|
||||||
|
return Err(WxViolation::LowerHalf(va));
|
||||||
|
}
|
||||||
|
if bits & PTE_W != 0 && bits & PTE_X != 0 {
|
||||||
|
return Err(WxViolation::WritableCode(va));
|
||||||
|
}
|
||||||
|
if bits & PTE_X != 0 {
|
||||||
|
if !text.contains(&va) {
|
||||||
|
return Err(WxViolation::ExecutableData(va));
|
||||||
|
}
|
||||||
|
executable += 1;
|
||||||
|
}
|
||||||
|
if is_guard(va) || firmware.contains(&va) {
|
||||||
|
return Err(WxViolation::MappedHole(va));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(executable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every 4 KiB leaf in `root` as (virtual address, PTE flag bits).
|
||||||
|
/// The kernel maps nothing larger.
|
||||||
|
fn leaves(root: &PageTable) -> impl Iterator<Item = (usize, u64)> + '_ {
|
||||||
|
let next = |e: &PageTableEntry| table_at((e.ppn() << 12) as usize) as &PageTable;
|
||||||
|
root.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, e)| e.is_valid())
|
||||||
|
.flat_map(move |(i2, e2)| {
|
||||||
|
next(e2)
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, e)| e.is_valid())
|
||||||
|
.flat_map(move |(i1, e1)| {
|
||||||
|
next(e1)
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, e)| e.is_valid())
|
||||||
|
.map(move |(i0, e0)| {
|
||||||
|
let va = (i2 << 30) | (i1 << 21) | (i0 << 12);
|
||||||
|
// Sv39 addresses are sign extended from bit 38.
|
||||||
|
let va = if i2 >= 256 { va | !((1 << 39) - 1) } else { va };
|
||||||
|
(va, e0.0 & 0x3ff)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn activate(root: &PageTable) {
|
pub fn activate(root: &PageTable) {
|
||||||
let ppn = (root as *const PageTable as u64) >> 12;
|
smp::note_active_root(virt_to_phys(root as *const PageTable as usize));
|
||||||
let satp = (8u64 << 60) | ppn;
|
let satp = (8u64 << 60) | table_ppn(root);
|
||||||
unsafe {
|
unsafe {
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"csrw satp, {0}",
|
"csrw satp, {0}",
|
||||||
@@ -176,34 +325,105 @@ pub fn activate(root: &PageTable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_user_page_table(
|
// ---- user address spaces ----
|
||||||
kernel_root: &PageTable,
|
//
|
||||||
_program_pa: PhysicalAddress,
|
// User mappings live in [USER_VA_START, USER_VA_END), which is root slot 0,
|
||||||
_program_pages: usize,
|
// the L1 slots below USER_VA_END. Every address space gets its own root and its
|
||||||
_user_va: VirtualAddress,
|
// own L1 for slot 0, and the kernel maps nothing in the lower half, so all
|
||||||
) -> &'static mut PageTable {
|
// page tables below the user window are private to one process. The
|
||||||
let root = alloc_page_table();
|
// kernel's upper-half entries are shared by copying them.
|
||||||
|
|
||||||
for i in 0..512 {
|
pub const USER_VA_START: usize = 0x1000;
|
||||||
if !kernel_root.entries[i].is_valid() {
|
pub const USER_VA_END: usize = 0x0200_0000;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
// User mappings land in VPN[2]=0 — each process needs its own L1
|
|
||||||
let kernel_l1_addr = (kernel_root.entries[i].ppn() << 12) as *const PageTable;
|
|
||||||
let kernel_l1 = unsafe { &*kernel_l1_addr };
|
|
||||||
let new_l1 = alloc_page_table();
|
|
||||||
for j in 0..512 {
|
|
||||||
if kernel_l1.entries[j].is_valid() {
|
|
||||||
new_l1.entries[j] = kernel_l1.entries[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let ppn = (new_l1 as *const PageTable as u64) >> 12;
|
|
||||||
root.entries[i] = PageTableEntry::new_branch(ppn);
|
|
||||||
} else {
|
|
||||||
root.entries[i] = kernel_root.entries[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root
|
/// The page table at physical address `pa`, through the direct map.
|
||||||
|
pub fn table_at(pa: usize) -> &'static mut PageTable {
|
||||||
|
// SAFETY: every page table lives in RAM, which the kernel maps at
|
||||||
|
// `phys_to_virt`.
|
||||||
|
unsafe { &mut *(phys_to_virt(pa) as *mut PageTable) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fresh_table(frames: &mut dyn FrameSource) -> Result<usize, MapError> {
|
||||||
|
let pa = frames
|
||||||
|
.alloc_table_frame()
|
||||||
|
.ok_or(MapError::OutOfFrames)?
|
||||||
|
.raw();
|
||||||
|
table_at(pa).zero();
|
||||||
|
Ok(pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_user_window(va: usize) -> bool {
|
||||||
|
va.is_multiple_of(PAGE_SIZE) && (USER_VA_START..USER_VA_END).contains(&va)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a root table sharing the kernel's mappings, with a private L1 for
|
||||||
|
/// the user window. Returns the root's physical address.
|
||||||
|
pub fn create_user_root(
|
||||||
|
kernel_root: &PageTable,
|
||||||
|
frames: &mut dyn FrameSource,
|
||||||
|
) -> Result<usize, MapError> {
|
||||||
|
let root_pa = fresh_table(frames)?;
|
||||||
|
let l1_pa = fresh_table(frames)?;
|
||||||
|
let root = table_at(root_pa);
|
||||||
|
|
||||||
|
for (mine, kernels) in root.entries.iter_mut().zip(&kernel_root.entries).skip(256) {
|
||||||
|
*mine = *kernels;
|
||||||
|
}
|
||||||
|
root.entries[0] = PageTableEntry::new_branch((l1_pa as u64) >> 12);
|
||||||
|
Ok(root_pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_leaf_table(
|
||||||
|
root_pa: usize,
|
||||||
|
va: usize,
|
||||||
|
frames: Option<&mut dyn FrameSource>,
|
||||||
|
) -> Result<&'static mut PageTable, MapError> {
|
||||||
|
let root = table_at(root_pa);
|
||||||
|
let l1 = table_at((root.entries[0].ppn() << 12) as usize);
|
||||||
|
let slot = &mut l1.entries[vpn(va, 1)];
|
||||||
|
if !slot.is_valid() {
|
||||||
|
let frames = frames.ok_or(MapError::NoAddressSpace)?;
|
||||||
|
let l0_pa = fresh_table(frames)?;
|
||||||
|
*slot = PageTableEntry::new_branch((l0_pa as u64) >> 12);
|
||||||
|
}
|
||||||
|
Ok(table_at((slot.ppn() << 12) as usize))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_user_page(
|
||||||
|
root_pa: usize,
|
||||||
|
va: usize,
|
||||||
|
pa: usize,
|
||||||
|
flags: Sv39Flags,
|
||||||
|
frames: &mut dyn FrameSource,
|
||||||
|
) -> Result<(), MapError> {
|
||||||
|
if !in_user_window(va) || !pa.is_multiple_of(PAGE_SIZE) {
|
||||||
|
return Err(MapError::InvalidAddress);
|
||||||
|
}
|
||||||
|
let l0 = user_leaf_table(root_pa, va, Some(frames))?;
|
||||||
|
l0.entries[vpn(va, 0)] = PageTableEntry::new_leaf((pa as u64) >> 12, flags);
|
||||||
|
flush_tlb(va);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmap_user_page(root_pa: usize, va: usize) {
|
||||||
|
if !in_user_window(va) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(l0) = user_leaf_table(root_pa, va, None) {
|
||||||
|
l0.entries[vpn(va, 0)] = PageTableEntry::empty();
|
||||||
|
flush_tlb(va);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_tlb(va: usize) {
|
||||||
|
// SAFETY: sfence.vma only invalidates translation caches.
|
||||||
|
unsafe { core::arch::asm!("sfence.vma {0}, zero", in(reg) va) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Physical address of the root table the hart is translating with.
|
||||||
|
pub fn active_root() -> usize {
|
||||||
|
let satp: usize;
|
||||||
|
// SAFETY: reading satp has no side effects.
|
||||||
|
unsafe { core::arch::asm!("csrr {0}, satp", out(reg) satp) };
|
||||||
|
(satp & ((1 << 44) - 1)) << 12
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
use domain::memory::MemoryRegion;
|
|
||||||
use domain::process::MAX_PROCESSES;
|
use domain::process::MAX_PROCESSES;
|
||||||
use platform::Platform;
|
use platform::{FrameSource, MapError, Platform};
|
||||||
use types::{
|
use types::{
|
||||||
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||||
bounded::PageCount,
|
|
||||||
id::{CoreId, IrqId, ProcessId},
|
id::{CoreId, IrqId, ProcessId},
|
||||||
permission::{Permission, PermissionSet},
|
mapping::PagePerms,
|
||||||
time::{Ticks, TimerDuration},
|
time::{Ticks, TimerDuration},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::board;
|
use crate::board;
|
||||||
use crate::hw::{plic::Plic, timer};
|
use crate::hw::{plic::Plic, timer};
|
||||||
use crate::mmu;
|
use crate::mmu;
|
||||||
|
use crate::smp;
|
||||||
use crate::trap;
|
use crate::trap;
|
||||||
|
|
||||||
pub struct RiscVPlatform {
|
pub struct RiscVPlatform {
|
||||||
num_cores: u8,
|
num_cores: u8,
|
||||||
kernel_pt: usize,
|
kernel_pt: usize,
|
||||||
|
/// Root table of each process's address space, 0 if it has none.
|
||||||
page_tables: [usize; MAX_PROCESSES],
|
page_tables: [usize; MAX_PROCESSES],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RiscVPlatform {
|
impl RiscVPlatform {
|
||||||
pub fn new(kernel_pt_addr: usize) -> Self {
|
pub fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
num_cores: 1,
|
num_cores,
|
||||||
kernel_pt: kernel_pt_addr,
|
kernel_pt: kernel_pt_addr,
|
||||||
page_tables: [0usize; MAX_PROCESSES],
|
page_tables: [0usize; MAX_PROCESSES],
|
||||||
}
|
}
|
||||||
@@ -33,37 +33,82 @@ impl RiscVPlatform {
|
|||||||
self.page_tables[pid.raw() as usize]
|
self.page_tables[pid.raw() as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_page_table(&mut self, pid: ProcessId, addr: usize) {
|
fn root(&self, pid: ProcessId) -> Option<usize> {
|
||||||
self.page_tables[pid.raw() as usize] = addr;
|
match self.page_tables.get(pid.raw() as usize) {
|
||||||
|
Some(&0) | None => None,
|
||||||
|
Some(&root) => Some(root),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_page_table(&mut self, pid: ProcessId) -> &mut mmu::PageTable {
|
|
||||||
let idx = pid.raw() as usize;
|
|
||||||
if self.page_tables[idx] == 0 {
|
|
||||||
let kernel_pt = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
|
|
||||||
let user_pt = mmu::create_user_page_table(
|
|
||||||
kernel_pt,
|
|
||||||
PhysicalAddress::from_raw(0),
|
|
||||||
0,
|
|
||||||
VirtualAddress::from_raw(0x10000),
|
|
||||||
);
|
|
||||||
self.page_tables[idx] = user_pt as *const mmu::PageTable as usize;
|
|
||||||
}
|
|
||||||
unsafe { &mut *(self.page_tables[idx] as *mut mmu::PageTable) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for RiscVPlatform {
|
impl Platform for RiscVPlatform {
|
||||||
fn configure_protection(&mut self, process: ProcessId, regions: &[MemoryRegion]) {
|
const USER_VA_START: usize = mmu::USER_VA_START;
|
||||||
let pt = self.ensure_page_table(process);
|
const USER_VA_END: usize = mmu::USER_VA_END;
|
||||||
for region in regions {
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
fn create_address_space(
|
||||||
va: VirtualAddress::from_raw(region.base().raw()),
|
&mut self,
|
||||||
pa: region.base(),
|
process: ProcessId,
|
||||||
pages: region.size().value() as usize,
|
frames: &mut dyn FrameSource,
|
||||||
flags: mmu::Sv39Flags::user(true, true, true),
|
) -> Result<(), MapError> {
|
||||||
});
|
let slot = self
|
||||||
|
.page_tables
|
||||||
|
.get_mut(process.raw() as usize)
|
||||||
|
.ok_or(MapError::NoAddressSpace)?;
|
||||||
|
// SAFETY: the kernel page table lives in the boot heap forever.
|
||||||
|
let kernel_root = unsafe { &*(self.kernel_pt as *const mmu::PageTable) };
|
||||||
|
*slot = mmu::create_user_root(kernel_root, frames)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn destroy_address_space(&mut self, process: ProcessId) {
|
||||||
|
let Some(root) = self.root(process) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// No hart may still translate through it once the kernel frees its
|
||||||
|
// frames: other harts leave it (and wait for nothing but that), and
|
||||||
|
// the caller may be tearing down the space we are running on.
|
||||||
|
smp::shoot_down(root);
|
||||||
|
if mmu::active_root() == root {
|
||||||
|
// SAFETY: the kernel page table lives in the boot heap forever.
|
||||||
|
mmu::activate(unsafe { &*(self.kernel_pt as *const mmu::PageTable) });
|
||||||
|
}
|
||||||
|
self.page_tables[process.raw() as usize] = 0;
|
||||||
|
// SAFETY: flushing translations has no other effect.
|
||||||
|
unsafe { core::arch::asm!("sfence.vma zero, zero") };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_page(
|
||||||
|
&mut self,
|
||||||
|
process: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pa: PhysicalAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
frames: &mut dyn FrameSource,
|
||||||
|
) -> Result<(), MapError> {
|
||||||
|
let root = self.root(process).ok_or(MapError::NoAddressSpace)?;
|
||||||
|
mmu::map_user_page(
|
||||||
|
root,
|
||||||
|
va.raw(),
|
||||||
|
pa.raw(),
|
||||||
|
mmu::Sv39Flags::user(perms),
|
||||||
|
frames,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmap_page(&mut self, process: ProcessId, va: VirtualAddress) {
|
||||||
|
if let Some(root) = self.root(process) {
|
||||||
|
mmu::unmap_user_page(root, va.raw());
|
||||||
|
// Other harts running this process may still cache it.
|
||||||
|
smp::shoot_down(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero_frame(&mut self, pa: PhysicalAddress) {
|
||||||
|
// SAFETY: pool frames are RAM, mapped at `phys_to_virt`, and the
|
||||||
|
// kernel only zeroes frames it has just allocated.
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_bytes(mmu::phys_to_virt(pa.raw()) as *mut u8, 0, mmu::PAGE_SIZE)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> ! {
|
fn enable_user_mode(&mut self, entry: EntryPoint, stack: StackPointer) -> ! {
|
||||||
@@ -104,68 +149,34 @@ impl Platform for RiscVPlatform {
|
|||||||
Plic::enable(irq.raw() as u32);
|
Plic::enable(irq.raw() as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_ipi(&mut self, _target: CoreId) {}
|
fn send_ipi(&mut self, target: CoreId) {
|
||||||
|
smp::kick(target.raw() as usize);
|
||||||
|
}
|
||||||
|
|
||||||
fn core_id(&self) -> CoreId {
|
fn core_id(&self) -> CoreId {
|
||||||
CoreId::from_raw(0)
|
CoreId::from_raw(smp::hart_id() as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn num_cores(&self) -> u8 {
|
fn num_cores(&self) -> u8 {
|
||||||
self.num_cores
|
self.num_cores
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_shared(
|
|
||||||
&mut self,
|
|
||||||
process: ProcessId,
|
|
||||||
base: PhysicalAddress,
|
|
||||||
size: PageCount,
|
|
||||||
permissions: PermissionSet,
|
|
||||||
) {
|
|
||||||
let pt = self.ensure_page_table(process);
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va: VirtualAddress::from_raw(base.raw()),
|
|
||||||
pa: base,
|
|
||||||
pages: size.value() as usize,
|
|
||||||
flags: mmu::Sv39Flags::user(
|
|
||||||
permissions.allows(Permission::Read),
|
|
||||||
permissions.allows(Permission::Write),
|
|
||||||
permissions.allows(Permission::Execute),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_putchar(&self, byte: u8) {
|
fn debug_putchar(&self, byte: u8) {
|
||||||
crate::hw::uart::Uart::new().put_char(byte);
|
crate::hw::uart::Uart::new().put_char(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
|
||||||
let pt_addr = self.page_tables[process.raw() as usize];
|
|
||||||
if pt_addr == 0 { return None; }
|
|
||||||
let pt = unsafe { &*(pt_addr as *const mmu::PageTable) };
|
|
||||||
mmu::translate(pt, va.raw()).map(PhysicalAddress::from_raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_user_page(&mut self, process: ProcessId, va: VirtualAddress, pa: PhysicalAddress, writable: bool, executable: bool) {
|
|
||||||
let pt = self.ensure_page_table(process);
|
|
||||||
let flags = mmu::Sv39Flags::user(true, writable, executable);
|
|
||||||
mmu::map(pt, mmu::Mapping {
|
|
||||||
va,
|
|
||||||
pa,
|
|
||||||
pages: 1,
|
|
||||||
flags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn halt(&self) -> ! {
|
fn halt(&self) -> ! {
|
||||||
unsafe {
|
unsafe {
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"ecall",
|
"ecall",
|
||||||
in("a0") 0usize,
|
inlateout("a0") 0usize => _,
|
||||||
in("a1") 0usize,
|
inlateout("a1") 0usize => _,
|
||||||
in("a7") 0x53525354usize,
|
in("a7") 0x53525354usize,
|
||||||
in("a6") 0usize,
|
in("a6") 0usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
loop {
|
||||||
|
unsafe { core::arch::asm!("wfi") };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
220
crates/adapter-riscv/src/smp.rs
Normal file
220
crates/adapter-riscv/src/smp.rs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
//! Harts. Each hart has its own kernel stack, and its hart id doubles as
|
||||||
|
//! its kernel `CoreId`. The kernel is entered under one lock for now (stage
|
||||||
|
//! 1 of multi-core; the kernel itself stays a sequential program), so
|
||||||
|
//! userspace runs in parallel and the kernel does not.
|
||||||
|
//!
|
||||||
|
//! Harts talk through IPIs carrying two kinds of request:
|
||||||
|
//! - reschedule: the kernel made work for this hart, or took its thread
|
||||||
|
//! away; handled under the lock on the way out of the kernel.
|
||||||
|
//! - leave root R: a page table this hart may be using is changing or about
|
||||||
|
//! to be freed; the hart switches to the kernel's root and flushes its
|
||||||
|
//! TLB. This needs no lock, so it is serviced even while a hart spins
|
||||||
|
//! waiting for the lock or idles, and the sender (which holds the lock)
|
||||||
|
//! can wait for the acknowledgement without deadlocking.
|
||||||
|
|
||||||
|
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use crate::mmu;
|
||||||
|
|
||||||
|
/// Must match linker.ld.
|
||||||
|
pub const MAX_HARTS: usize = 8;
|
||||||
|
/// A guard page and a 512 KiB stack (boot builds big tables on it). Must
|
||||||
|
/// match linker.ld.
|
||||||
|
pub const STACK_SLOT: usize = 4096 + 0x8_0000;
|
||||||
|
|
||||||
|
const SIP_SSIP: usize = 1 << 1;
|
||||||
|
|
||||||
|
/// The hart running this code, from which kernel stack it is on.
|
||||||
|
pub fn hart_id() -> usize {
|
||||||
|
let sp: usize;
|
||||||
|
// SAFETY: reading sp has no side effects.
|
||||||
|
unsafe { core::arch::asm!("mv {0}, sp", out(reg) sp) };
|
||||||
|
(sp - stacks_start()) / STACK_SLOT
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stacks_start() -> usize {
|
||||||
|
unsafe extern "C" {
|
||||||
|
static _stacks_start: u8;
|
||||||
|
}
|
||||||
|
// SAFETY: only the symbol's address is taken.
|
||||||
|
unsafe { &_stacks_start as *const u8 as usize }
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOT_HART: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
static NUM_HARTS: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
/// Physical address of the kernel's own root table.
|
||||||
|
static KERNEL_ROOT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
/// The root table each hart translates with.
|
||||||
|
static ACTIVE_ROOT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||||
|
static RESCHEDULE: [AtomicBool; MAX_HARTS] = [const { AtomicBool::new(false) }; MAX_HARTS];
|
||||||
|
static LEAVE_ROOT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||||
|
static LEFT: [AtomicUsize; MAX_HARTS] = [const { AtomicUsize::new(0) }; MAX_HARTS];
|
||||||
|
|
||||||
|
pub fn boot_hart() -> usize {
|
||||||
|
BOOT_HART.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_harts() -> usize {
|
||||||
|
NUM_HARTS.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kernel_root() -> usize {
|
||||||
|
KERNEL_ROOT.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records which hart booted and how many there are, and where the
|
||||||
|
/// kernel's root table is.
|
||||||
|
pub fn init(boot: usize, harts: usize, kernel_root: usize) {
|
||||||
|
BOOT_HART.store(boot, Ordering::Relaxed);
|
||||||
|
NUM_HARTS.store(harts, Ordering::Relaxed);
|
||||||
|
KERNEL_ROOT.store(kernel_root, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by `mmu::activate`.
|
||||||
|
pub fn note_active_root(root: usize) {
|
||||||
|
if let Some(r) = ACTIVE_ROOT.get(hart_id()) {
|
||||||
|
r.store(root, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- the kernel lock ----
|
||||||
|
|
||||||
|
static NEXT_TICKET: AtomicU32 = AtomicU32::new(0);
|
||||||
|
static SERVING: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
/// Takes the kernel lock, first come first served. Leave-root requests
|
||||||
|
/// are serviced while waiting.
|
||||||
|
pub fn lock() {
|
||||||
|
let ticket = NEXT_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||||
|
while SERVING.load(Ordering::Acquire) != ticket {
|
||||||
|
service_ipi();
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock() {
|
||||||
|
SERVING.fetch_add(1, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- IPIs ----
|
||||||
|
|
||||||
|
/// The lock-free half of an IPI: acknowledges it and, if asked, leaves the
|
||||||
|
/// root being changed. Safe to call at any time, any number of times.
|
||||||
|
pub fn service_ipi() {
|
||||||
|
let sip: usize;
|
||||||
|
// SAFETY: reading sip has no side effects.
|
||||||
|
unsafe { core::arch::asm!("csrr {0}, sip", out(reg) sip) };
|
||||||
|
if sip & SIP_SSIP == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Clear first: a request posted after this raises SSIP again.
|
||||||
|
// SAFETY: clearing our own pending software interrupt.
|
||||||
|
unsafe { core::arch::asm!("csrc sip, {0}", in(reg) SIP_SSIP) };
|
||||||
|
let me = hart_id();
|
||||||
|
let (Some(leave), Some(left)) = (LEAVE_ROOT.get(me), LEFT.get(me)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root = leave.swap(0, Ordering::SeqCst);
|
||||||
|
if root != 0 {
|
||||||
|
if ACTIVE_ROOT.get(me).map(|r| r.load(Ordering::SeqCst)) == Some(root) {
|
||||||
|
mmu::activate(mmu::page_table::table_at(kernel_root()));
|
||||||
|
}
|
||||||
|
// SAFETY: flushing translations has no other effect.
|
||||||
|
unsafe { core::arch::asm!("sfence.vma zero, zero") };
|
||||||
|
left.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks `hart` to look at its run queue again, on its way out of the
|
||||||
|
/// kernel.
|
||||||
|
pub fn kick(hart: usize) {
|
||||||
|
if let Some(r) = RESCHEDULE.get(hart) {
|
||||||
|
r.store(true, Ordering::SeqCst);
|
||||||
|
sbi_send_ipi(hart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes this hart's reschedule request.
|
||||||
|
pub fn take_reschedule() -> bool {
|
||||||
|
RESCHEDULE
|
||||||
|
.get(hart_id())
|
||||||
|
.is_some_and(|r| r.swap(false, Ordering::SeqCst))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes every other hart that translates through `root` leave it and
|
||||||
|
/// flush, and waits until each has. Called with the kernel lock held, so
|
||||||
|
/// no hart can start using `root` meanwhile.
|
||||||
|
pub fn shoot_down(root: usize) {
|
||||||
|
let me = hart_id();
|
||||||
|
for hart in 0..num_harts() {
|
||||||
|
if hart == me || ACTIVE_ROOT[hart].load(Ordering::SeqCst) != root {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let seen = LEFT[hart].load(Ordering::SeqCst);
|
||||||
|
LEAVE_ROOT[hart].store(root, Ordering::SeqCst);
|
||||||
|
sbi_send_ipi(hart);
|
||||||
|
while LEFT[hart].load(Ordering::SeqCst) == seen {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- SBI ----
|
||||||
|
|
||||||
|
const SBI_EXT_IPI: usize = 0x735049;
|
||||||
|
const SBI_EXT_HSM: usize = 0x48534D;
|
||||||
|
|
||||||
|
fn sbi_send_ipi(hart: usize) {
|
||||||
|
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!(
|
||||||
|
"ecall",
|
||||||
|
inlateout("a0") 1usize => _,
|
||||||
|
inlateout("a1") hart => _,
|
||||||
|
in("a6") 0usize,
|
||||||
|
in("a7") SBI_EXT_IPI,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `hart` exists, per SBI HSM `hart_get_status`.
|
||||||
|
fn sbi_hart_exists(hart: usize) -> bool {
|
||||||
|
let error: isize;
|
||||||
|
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!(
|
||||||
|
"ecall",
|
||||||
|
inlateout("a0") hart => error,
|
||||||
|
lateout("a1") _,
|
||||||
|
in("a6") 2usize,
|
||||||
|
in("a7") SBI_EXT_HSM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
error == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of harts, counting up from hart 0 until one is missing.
|
||||||
|
pub fn probe_harts() -> usize {
|
||||||
|
(0..MAX_HARTS)
|
||||||
|
.take_while(|h| sbi_hart_exists(*h))
|
||||||
|
.count()
|
||||||
|
.max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts `hart` at the physical address `entry` with the MMU off.
|
||||||
|
pub fn sbi_hart_start(hart: usize, entry: usize) -> bool {
|
||||||
|
let error: isize;
|
||||||
|
// SAFETY: an SBI call; a0 and a1 come back clobbered.
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!(
|
||||||
|
"ecall",
|
||||||
|
inlateout("a0") hart => error,
|
||||||
|
inlateout("a1") entry => _,
|
||||||
|
in("a2") 0usize,
|
||||||
|
in("a6") 0usize,
|
||||||
|
in("a7") SBI_EXT_HSM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
error == 0
|
||||||
|
}
|
||||||
@@ -7,17 +7,9 @@ pub struct TrapFrame {
|
|||||||
pub sstatus: usize,
|
pub sstatus: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrapFrame {
|
global_asm!(
|
||||||
pub fn new() -> Self {
|
r#"
|
||||||
Self {
|
.section .text
|
||||||
regs: [0; 32],
|
|
||||||
sepc: 0,
|
|
||||||
sstatus: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
global_asm!(r#"
|
|
||||||
.align 4
|
.align 4
|
||||||
.globl trap_vector
|
.globl trap_vector
|
||||||
trap_vector:
|
trap_vector:
|
||||||
@@ -68,7 +60,14 @@ _from_user:
|
|||||||
|
|
||||||
mv a0, sp
|
mv a0, sp
|
||||||
call trap_handler_rust
|
call trap_handler_rust
|
||||||
|
j _restore_frame
|
||||||
|
|
||||||
|
# trap_return(frame): resume whatever `frame` describes, as if returning
|
||||||
|
# from a trap that saved it. Used to enter userspace for the first time.
|
||||||
|
.globl trap_return
|
||||||
|
trap_return:
|
||||||
|
mv sp, a0
|
||||||
|
_restore_frame:
|
||||||
ld t0, 256(sp)
|
ld t0, 256(sp)
|
||||||
csrw sepc, t0
|
csrw sepc, t0
|
||||||
ld t0, 264(sp)
|
ld t0, 264(sp)
|
||||||
@@ -84,7 +83,6 @@ _restore_smode:
|
|||||||
ld x1, 8(sp)
|
ld x1, 8(sp)
|
||||||
ld x3, 24(sp)
|
ld x3, 24(sp)
|
||||||
ld x4, 32(sp)
|
ld x4, 32(sp)
|
||||||
ld x5, 40(sp)
|
|
||||||
ld x6, 48(sp)
|
ld x6, 48(sp)
|
||||||
ld x7, 56(sp)
|
ld x7, 56(sp)
|
||||||
ld x8, 64(sp)
|
ld x8, 64(sp)
|
||||||
@@ -112,27 +110,37 @@ _restore_smode:
|
|||||||
ld x30, 240(sp)
|
ld x30, 240(sp)
|
||||||
ld x31, 248(sp)
|
ld x31, 248(sp)
|
||||||
|
|
||||||
addi sp, sp, 272
|
# Every GPR except t0 (x5) is restored. Decide where we return to while
|
||||||
|
# t0 is still free, and restore it last on each path; using t0 after
|
||||||
|
# its restore would corrupt the interrupted program's t0.
|
||||||
csrr t0, sstatus
|
csrr t0, sstatus
|
||||||
andi t0, t0, 0x100
|
andi t0, t0, 0x100
|
||||||
bnez t0, _sret_smode
|
bnez t0, _sret_smode
|
||||||
|
ld x5, 40(sp)
|
||||||
|
addi sp, sp, 272
|
||||||
csrrw sp, sscratch, sp
|
csrrw sp, sscratch, sp
|
||||||
_sret_smode:
|
|
||||||
sret
|
sret
|
||||||
"#);
|
_sret_smode:
|
||||||
|
ld x5, 40(sp)
|
||||||
|
addi sp, sp, 272
|
||||||
|
sret
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
|
const CAUSE_INTERRUPT_BIT: usize = 1 << 63;
|
||||||
|
const CAUSE_S_SOFTWARE: usize = 1;
|
||||||
const CAUSE_S_TIMER: usize = 5;
|
const CAUSE_S_TIMER: usize = 5;
|
||||||
const CAUSE_S_EXTERNAL: usize = 9;
|
const CAUSE_S_EXTERNAL: usize = 9;
|
||||||
const CAUSE_ECALL_U: usize = 8;
|
const CAUSE_ECALL_U: usize = 8;
|
||||||
|
/// Set in `sstatus` when the trap came from supervisor mode.
|
||||||
|
const SSTATUS_SPP: usize = 1 << 8;
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn trap_vector();
|
fn trap_vector();
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let addr = trap_vector as usize;
|
let addr = trap_vector as *const () as usize;
|
||||||
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
|
core::arch::asm!("csrw stvec, {0}", in(reg) addr);
|
||||||
core::arch::asm!("csrw sscratch, zero");
|
core::arch::asm!("csrw sscratch, zero");
|
||||||
// SIE stays clear — interrupts are enabled only when entering userspace via sret
|
// SIE stays clear — interrupts are enabled only when entering userspace via sret
|
||||||
@@ -147,133 +155,18 @@ pub fn set_kernel_stack(kernel_sp: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
|
fn args(frame: &TrapFrame) -> [usize; 6] {
|
||||||
use domain::message::Message;
|
let mut a = [0usize; 6];
|
||||||
use domain::syscall::{Syscall, WaitSet};
|
a.copy_from_slice(&frame.regs[10..16]);
|
||||||
use types::syscall_abi::*;
|
a
|
||||||
|
|
||||||
let nr = frame.regs[17]; // a7
|
|
||||||
let a0 = frame.regs[10];
|
|
||||||
let a1 = frame.regs[11];
|
|
||||||
let a2 = frame.regs[12];
|
|
||||||
let a3 = frame.regs[13];
|
|
||||||
let a4 = frame.regs[14];
|
|
||||||
|
|
||||||
match nr {
|
|
||||||
SYS_EXIT => Some(Syscall::Exit),
|
|
||||||
SYS_HALT => Some(Syscall::Halt),
|
|
||||||
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
|
|
||||||
SYS_YIELD => Some(Syscall::Yield),
|
|
||||||
SYS_GET_TIME => Some(Syscall::GetTime),
|
|
||||||
SYS_GET_PROCESS_ID => Some(Syscall::GetProcessId),
|
|
||||||
SYS_SEND => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
let msg = Message::from_words([a1, a2, a3, a4]);
|
|
||||||
Some(Syscall::Send { endpoint: slot, message: msg })
|
|
||||||
}
|
|
||||||
SYS_RECEIVE => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::Receive { endpoint: slot })
|
|
||||||
}
|
|
||||||
SYS_DELEGATE => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
let to = types::bounded::CapSlot::new(a1 as u8)?;
|
|
||||||
let perms = types::permission::PermissionSet::from_bits(a2 as u8);
|
|
||||||
Some(Syscall::Delegate { slot, to_endpoint: to, permissions: perms })
|
|
||||||
}
|
|
||||||
SYS_WAIT_FOR_INTERRUPT => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::WaitForInterrupt { irq: slot })
|
|
||||||
}
|
|
||||||
SYS_ACKNOWLEDGE_INTERRUPT => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::AcknowledgeInterrupt { irq: slot })
|
|
||||||
}
|
|
||||||
SYS_WAIT_ANY => {
|
|
||||||
let ep0 = if a0 != usize::MAX { types::bounded::CapSlot::new(a0 as u8) } else { None };
|
|
||||||
let ep1 = if a1 != usize::MAX { types::bounded::CapSlot::new(a1 as u8) } else { None };
|
|
||||||
let ep2 = if a2 != usize::MAX { types::bounded::CapSlot::new(a2 as u8) } else { None };
|
|
||||||
let ep3 = if a3 != usize::MAX { types::bounded::CapSlot::new(a3 as u8) } else { None };
|
|
||||||
let sources = WaitSet::new(
|
|
||||||
[ep0, ep1, ep2, ep3],
|
|
||||||
[None, None, None, None],
|
|
||||||
);
|
|
||||||
Some(Syscall::WaitAny { sources })
|
|
||||||
}
|
|
||||||
SYS_CREATE_PROCESS => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::CreateProcess {
|
|
||||||
create_cap: slot,
|
|
||||||
image: types::address::DataRef::empty(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SYS_KILL => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::Kill { process: slot })
|
|
||||||
}
|
|
||||||
SYS_REVOKE => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::Revoke { slot })
|
|
||||||
}
|
|
||||||
SYS_SPAWN_THREAD => Some(Syscall::SpawnThread {
|
|
||||||
entry: types::address::EntryPoint::from_raw(a0),
|
|
||||||
stack: types::address::StackPointer::from_raw(a1),
|
|
||||||
}),
|
|
||||||
SYS_REQUEST_PAGES => {
|
|
||||||
let count = types::bounded::PageCount::new(a0 as u16)?;
|
|
||||||
Some(Syscall::RequestPages { count })
|
|
||||||
}
|
|
||||||
SYS_RELEASE_PAGES => {
|
|
||||||
let count = types::bounded::PageCount::new(a1 as u16)?;
|
|
||||||
Some(Syscall::ReleasePages {
|
|
||||||
addr: types::address::VirtualAddress::from_raw(a0),
|
|
||||||
count,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SYS_SPAWN_THREAD_IN => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::SpawnThreadIn {
|
|
||||||
process: slot,
|
|
||||||
entry: types::address::EntryPoint::from_raw(a1),
|
|
||||||
stack: types::address::StackPointer::from_raw(a2),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint),
|
|
||||||
SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }),
|
|
||||||
SYS_MAP_PAGE => {
|
|
||||||
let slot = types::bounded::CapSlot::new(a0 as u8)?;
|
|
||||||
Some(Syscall::MapPage {
|
|
||||||
process: slot,
|
|
||||||
src_va: types::address::VirtualAddress::from_raw(a1),
|
|
||||||
dst_va: types::address::VirtualAddress::from_raw(a2),
|
|
||||||
executable: a3 != 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_result(frame: &mut TrapFrame, outcome: &kernel::SyscallOutcome) {
|
fn set_results(frame: &mut TrapFrame, regs: [usize; 8]) {
|
||||||
use kernel::{SyscallOutcome, SyscallResult};
|
frame.regs[10..18].copy_from_slice(®s);
|
||||||
|
}
|
||||||
|
|
||||||
match outcome {
|
fn write_result(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
|
||||||
SyscallOutcome::Complete(result) => match result {
|
set_results(frame, kernel::abi::encode(result));
|
||||||
SyscallResult::Ok => frame.regs[10] = 0,
|
|
||||||
SyscallResult::Value(v) => frame.regs[10] = *v,
|
|
||||||
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
|
|
||||||
SyscallResult::Message(m) => {
|
|
||||||
frame.regs[10] = m.word(0);
|
|
||||||
frame.regs[11] = m.word(1);
|
|
||||||
frame.regs[12] = m.word(2);
|
|
||||||
frame.regs[13] = m.word(3);
|
|
||||||
}
|
|
||||||
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
|
|
||||||
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
|
|
||||||
},
|
|
||||||
SyscallOutcome::BlockCaller(_) => {}
|
|
||||||
SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0,
|
|
||||||
SyscallOutcome::TerminateCaller => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_frame_to_context(frame: &TrapFrame, ctx: &mut domain::thread::SavedContext) {
|
fn save_frame_to_context(frame: &TrapFrame, ctx: &mut domain::thread::SavedContext) {
|
||||||
@@ -292,88 +185,136 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
if let Some(result) = kern().take_pending_result(tid) {
|
||||||
if let Some(result) = kern.take_pending_result(tid) {
|
write_result(frame, &result);
|
||||||
write_result_direct(frame, &result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
|
unsafe extern "C" {
|
||||||
use kernel::SyscallResult;
|
fn trap_return(frame: *mut TrapFrame) -> !;
|
||||||
match result {
|
|
||||||
SyscallResult::Ok => frame.regs[10] = 0,
|
|
||||||
SyscallResult::Value(v) => frame.regs[10] = *v,
|
|
||||||
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
|
|
||||||
SyscallResult::Message(m) => {
|
|
||||||
frame.regs[10] = m.word(0);
|
|
||||||
frame.regs[11] = m.word(1);
|
|
||||||
frame.regs[12] = m.word(2);
|
|
||||||
frame.regs[13] = m.word(3);
|
|
||||||
}
|
|
||||||
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
|
|
||||||
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, next_tid: types::id::ThreadId) {
|
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
// SAFETY: only called with the kernel lock held.
|
||||||
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ctx) = kern.thread_context_mut(current_tid) {
|
fn this_core() -> types::id::CoreId {
|
||||||
|
types::id::CoreId::from_raw(crate::smp::hart_id() as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves `frame` into `from` (if it still exists) and loads `to`.
|
||||||
|
fn switch_to_thread(
|
||||||
|
frame: &mut TrapFrame,
|
||||||
|
from: Option<types::id::ThreadId>,
|
||||||
|
to: types::id::ThreadId,
|
||||||
|
) {
|
||||||
|
let kern = kern();
|
||||||
|
if let Some(ctx) = from.and_then(|t| kern.thread_context_mut(t)) {
|
||||||
save_frame_to_context(frame, ctx);
|
save_frame_to_context(frame, ctx);
|
||||||
}
|
}
|
||||||
|
if let Some(ctx) = kern.thread_context(to) {
|
||||||
let next_pid = kern.thread_process(next_tid);
|
|
||||||
let current_pid = kern.thread_process(current_tid);
|
|
||||||
|
|
||||||
if let Some(ctx) = kern.thread_context(next_tid) {
|
|
||||||
load_context_to_frame(ctx, frame);
|
load_context_to_frame(ctx, frame);
|
||||||
}
|
}
|
||||||
|
apply_pending_result(frame, to);
|
||||||
|
}
|
||||||
|
|
||||||
apply_pending_result(frame, next_tid);
|
/// Saves `from` and runs the next ready thread, idling until there is one.
|
||||||
|
fn switch_to_next(frame: &mut TrapFrame, from: Option<types::id::ThreadId>) {
|
||||||
if next_pid != current_pid {
|
if let Some(ctx) = from.and_then(|t| kern().thread_context_mut(t)) {
|
||||||
if let Some(pid) = next_pid {
|
save_frame_to_context(frame, ctx);
|
||||||
let pt_addr = kern.platform_mut().page_table_for(pid);
|
|
||||||
if pt_addr != 0 {
|
|
||||||
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
|
|
||||||
crate::mmu::activate(pt);
|
|
||||||
}
|
}
|
||||||
|
let next = next_runnable();
|
||||||
|
let _ = kern().set_thread_running(next, this_core());
|
||||||
|
switch_to_thread(frame, None, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The next thread to run on this hart, idling until one is ready.
|
||||||
|
///
|
||||||
|
/// Idling gives up the kernel lock: an idle hart holds nothing, so other
|
||||||
|
/// harts can make work for it (and kick it) meanwhile. It also leaves any
|
||||||
|
/// user address space first, so nobody needs to shoot it down. SIE stays
|
||||||
|
/// clear; `wfi` still wakes on a pending interrupt, serviced by polling.
|
||||||
|
fn next_runnable() -> types::id::ThreadId {
|
||||||
|
loop {
|
||||||
|
if let Some(tid) = kern().pick_next_thread() {
|
||||||
|
return tid;
|
||||||
|
}
|
||||||
|
crate::mmu::activate(crate::mmu::page_table::table_at(crate::smp::kernel_root()));
|
||||||
|
crate::smp::unlock();
|
||||||
|
unsafe { core::arch::asm!("wfi") };
|
||||||
|
crate::smp::service_ipi();
|
||||||
|
crate::smp::lock();
|
||||||
|
let _ = crate::smp::take_reschedule();
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) {
|
/// Claims every pending PLIC interrupt and hands each to the kernel. Only
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
/// the boot hart receives external interrupts.
|
||||||
|
fn dispatch_external() {
|
||||||
if let Some(ctx) = kern.thread_context_mut(current_tid) {
|
loop {
|
||||||
save_frame_to_context(frame, ctx);
|
let irq_num = crate::hw::plic::Plic::claim();
|
||||||
|
if irq_num == 0 {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
kern().handle_interrupt(types::id::IrqId::from_raw(irq_num as u16));
|
||||||
if let Some(next_tid) = kern.pick_next_thread() {
|
crate::hw::plic::Plic::complete(irq_num);
|
||||||
let next_pid = kern.thread_process(next_tid);
|
|
||||||
let current_pid = kern.thread_process(current_tid);
|
|
||||||
|
|
||||||
let core = types::id::CoreId::from_raw(0);
|
|
||||||
let _ = kern.set_thread_running(next_tid, core);
|
|
||||||
|
|
||||||
if let Some(ctx) = kern.thread_context(next_tid) {
|
|
||||||
load_context_to_frame(ctx, frame);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apply_pending_result(frame, next_tid);
|
/// Last step before leaving the kernel to userspace: act on a reschedule
|
||||||
|
/// request, make sure this hart has a thread (its own may have been taken
|
||||||
|
/// away by another hart), and translate through that thread's address
|
||||||
|
/// space.
|
||||||
|
fn finish(frame: &mut TrapFrame) {
|
||||||
|
let kern = kern();
|
||||||
|
let current = kern.current_thread();
|
||||||
|
if crate::smp::take_reschedule()
|
||||||
|
&& let Some(cur) = current
|
||||||
|
&& let domain::scheduler::SchedulerDecision::Preempt(next) = kern.reschedule(this_core())
|
||||||
|
{
|
||||||
|
switch_to_thread(frame, Some(cur), next);
|
||||||
|
}
|
||||||
|
if kern.current_thread().is_none() {
|
||||||
|
switch_to_next(frame, None);
|
||||||
|
}
|
||||||
|
let root = kern
|
||||||
|
.current_thread()
|
||||||
|
.and_then(|t| kern.thread_process(t))
|
||||||
|
.map(|pid| kern.platform_mut().page_table_for(pid))
|
||||||
|
.unwrap_or(0);
|
||||||
|
if root != 0 && crate::mmu::active_root() != root {
|
||||||
|
crate::mmu::activate(crate::mmu::page_table::table_at(root));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if next_pid != current_pid {
|
/// Leaves the kernel for userspace for the first time on this hart, with
|
||||||
if let Some(pid) = next_pid {
|
/// the kernel lock held: runs the thread the kernel already made current
|
||||||
let pt_addr = kern.platform_mut().page_table_for(pid);
|
/// here (init, on the boot hart) or the next one homed here.
|
||||||
if pt_addr != 0 {
|
pub fn enter_first() -> ! {
|
||||||
let pt = unsafe { &*(pt_addr as *const crate::mmu::PageTable) };
|
let mut frame = TrapFrame {
|
||||||
crate::mmu::activate(pt);
|
regs: [0; 32],
|
||||||
}
|
sepc: 0,
|
||||||
}
|
sstatus: 0,
|
||||||
}
|
};
|
||||||
} else {
|
match kern().current_thread() {
|
||||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
Some(tid) => switch_to_thread(&mut frame, None, tid),
|
||||||
|
None => switch_to_next(&mut frame, None),
|
||||||
}
|
}
|
||||||
|
finish(&mut frame);
|
||||||
|
crate::smp::unlock();
|
||||||
|
// SAFETY: `frame` holds a user context; sscratch is set from it on
|
||||||
|
// the way out, so later traps land at the top of this frame.
|
||||||
|
unsafe { trap_return(&mut frame) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
@@ -381,68 +322,83 @@ extern "C" fn trap_handler_rust(frame: &mut TrapFrame) {
|
|||||||
let scause: usize;
|
let scause: usize;
|
||||||
unsafe { core::arch::asm!("csrr {0}, scause", out(reg) scause) };
|
unsafe { core::arch::asm!("csrr {0}, scause", out(reg) scause) };
|
||||||
|
|
||||||
|
if frame.sstatus & SSTATUS_SPP != 0 {
|
||||||
|
// Checked before taking the lock: this hart may already hold it.
|
||||||
|
kernel_fault(frame, scause);
|
||||||
|
}
|
||||||
|
crate::smp::service_ipi();
|
||||||
|
crate::smp::lock();
|
||||||
if scause & CAUSE_INTERRUPT_BIT != 0 {
|
if scause & CAUSE_INTERRUPT_BIT != 0 {
|
||||||
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
|
handle_interrupt(frame, scause & !CAUSE_INTERRUPT_BIT);
|
||||||
} else {
|
} else {
|
||||||
handle_exception(frame, scause);
|
handle_exception(frame, scause);
|
||||||
}
|
}
|
||||||
|
finish(frame);
|
||||||
|
crate::smp::unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
|
fn handle_interrupt(frame: &mut TrapFrame, cause: usize) {
|
||||||
match cause {
|
match cause {
|
||||||
CAUSE_S_TIMER => {
|
CAUSE_S_TIMER => {
|
||||||
crate::hw::timer::handle_interrupt();
|
crate::hw::timer::handle_interrupt();
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
let kern = kern();
|
||||||
let core = types::id::CoreId::from_raw(0);
|
|
||||||
if let Some(current_tid) = kern.current_thread() {
|
if let Some(current_tid) = kern.current_thread() {
|
||||||
let decision = kern.handle_tick(core);
|
let decision = kern.handle_tick(this_core());
|
||||||
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
|
if let domain::scheduler::SchedulerDecision::Preempt(next) = decision {
|
||||||
switch_to_thread(frame, current_tid, next);
|
switch_to_thread(frame, Some(current_tid), next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CAUSE_S_EXTERNAL => {
|
CAUSE_S_EXTERNAL => dispatch_external(),
|
||||||
let irq_num = crate::hw::plic::Plic::claim();
|
// Serviced on entry; any reschedule happens in `finish`.
|
||||||
if irq_num != 0 {
|
CAUSE_S_SOFTWARE => {}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The kernel itself faulted (a W^X violation, a stack guard, a bad
|
||||||
|
/// pointer). Its state can no longer be trusted, and no user thread is to
|
||||||
|
/// blame.
|
||||||
|
fn kernel_fault(frame: &TrapFrame, scause: usize) -> ! {
|
||||||
|
let uart = crate::hw::uart::Uart::new();
|
||||||
|
uart.write_str("KERNEL FAULT: scause=");
|
||||||
|
uart.write_hex(scause);
|
||||||
|
let stval: usize;
|
||||||
|
unsafe { core::arch::asm!("csrr {0}, stval", out(reg) stval) };
|
||||||
|
uart.write_str(" stval=");
|
||||||
|
uart.write_hex(stval);
|
||||||
|
uart.write_str(" sepc=");
|
||||||
|
uart.write_hex(frame.sepc);
|
||||||
|
uart.write_str(" hart=");
|
||||||
|
uart.write_dec(crate::smp::hart_id());
|
||||||
|
uart.put_char(b'\n');
|
||||||
|
crate::halt(&uart, "fault in kernel mode");
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
||||||
match scause {
|
match scause {
|
||||||
CAUSE_ECALL_U => {
|
CAUSE_ECALL_U => {
|
||||||
frame.sepc += 4;
|
frame.sepc += 4;
|
||||||
|
|
||||||
if let Some(syscall) = decode_syscall(frame) {
|
let Some(syscall) = kernel::abi::decode(frame.regs[17], &args(frame)) else {
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
set_results(frame, kernel::abi::unknown_syscall());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let kern = kern();
|
||||||
let current_tid = kern.current_thread();
|
let current_tid = kern.current_thread();
|
||||||
let outcome = kern.handle_syscall(syscall);
|
let outcome = kern.handle_syscall(syscall);
|
||||||
write_result(frame, &outcome);
|
|
||||||
|
|
||||||
match outcome {
|
match outcome {
|
||||||
kernel::SyscallOutcome::Complete(_) => {}
|
kernel::SyscallOutcome::Complete(result) => write_result(frame, &result),
|
||||||
kernel::SyscallOutcome::BlockCaller(_) => {
|
kernel::SyscallOutcome::BlockCaller(_) => switch_to_next(frame, current_tid),
|
||||||
if let Some(tid) = current_tid {
|
|
||||||
switch_to_next(frame, tid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kernel::SyscallOutcome::SwitchTo(next) => {
|
kernel::SyscallOutcome::SwitchTo(next) => {
|
||||||
if let Some(tid) = current_tid {
|
switch_to_thread(frame, current_tid, next)
|
||||||
switch_to_thread(frame, tid, next);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
kernel::SyscallOutcome::TerminateCaller => {
|
kernel::SyscallOutcome::TerminateCaller => {
|
||||||
let uart = crate::hw::uart::Uart::new();
|
let uart = crate::hw::uart::Uart::new();
|
||||||
uart.write_str("k-os: process exited\n");
|
uart.write_str("k-os: process exited\n");
|
||||||
if let Some(tid) = current_tid {
|
switch_to_next(frame, current_tid);
|
||||||
switch_to_next(frame, tid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,7 +415,7 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
|||||||
uart.write_hex(frame.sepc);
|
uart.write_hex(frame.sepc);
|
||||||
uart.put_char(b'\n');
|
uart.put_char(b'\n');
|
||||||
|
|
||||||
let kern = unsafe { &mut *crate::kernel_ptr() };
|
let kern = kern();
|
||||||
if let Some(current_tid) = kern.current_thread() {
|
if let Some(current_tid) = kern.current_thread() {
|
||||||
let fault = match scause {
|
let fault = match scause {
|
||||||
2 => domain::fault::Fault::IllegalInstruction,
|
2 => domain::fault::Fault::IllegalInstruction,
|
||||||
@@ -469,10 +425,10 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
|
|||||||
_ => domain::fault::Fault::InvalidSyscall,
|
_ => domain::fault::Fault::InvalidSyscall,
|
||||||
};
|
};
|
||||||
kern.handle_fault(current_tid, fault, frame.sepc);
|
kern.handle_fault(current_tid, fault, frame.sepc);
|
||||||
switch_to_next(frame, current_tid);
|
switch_to_next(frame, Some(current_tid));
|
||||||
} else {
|
|
||||||
loop { unsafe { core::arch::asm!("wfi") }; }
|
|
||||||
}
|
}
|
||||||
|
// With no current thread (another hart just destroyed it),
|
||||||
|
// `finish` moves on to the next one.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const CELL_SIZE: u16 = 24;
|
|||||||
const BG_COLOR: u32 = 0xFF202040;
|
const BG_COLOR: u32 = 0xFF202040;
|
||||||
const SNAKE_COLOR: u32 = 0xFF00FF00;
|
const SNAKE_COLOR: u32 = 0xFF00FF00;
|
||||||
const HEAD_COLOR: u32 = 0xFF00CC00;
|
const HEAD_COLOR: u32 = 0xFF00CC00;
|
||||||
const FOOD_COLOR: u32 = 0xFFFF0000;
|
pub(crate) const FOOD_COLOR: u32 = 0xFFFF0000;
|
||||||
const BORDER_COLOR: u32 = 0xFF505080;
|
const BORDER_COLOR: u32 = 0xFF505080;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ fn food_is_rendered() {
|
|||||||
let count = render(&snake, &mut commands);
|
let count = render(&snake, &mut commands);
|
||||||
// Food is command index 2 (after bg and border)
|
// Food is command index 2 (after bg and border)
|
||||||
assert!(count > 2);
|
assert!(count > 2);
|
||||||
assert_eq!(commands[2].color, 0xFF0040);
|
assert_eq!(commands[2].color, crate::renderer::FOOD_COLOR);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||||
|
|||||||
88
crates/domain/src/capability/derivation.rs
Normal file
88
crates/domain/src/capability/derivation.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//! The capability derivation relation, reduced to its essence: every
|
||||||
|
//! capability is a node with at most one parent. Kept free of kernel
|
||||||
|
//! types so the revocation rule can be checked on its own.
|
||||||
|
|
||||||
|
/// `result[i]` is true iff `root` is `i` or an ancestor of `i`.
|
||||||
|
///
|
||||||
|
/// `parents` must be acyclic, which holds because a capability's parent
|
||||||
|
/// always exists before it does.
|
||||||
|
pub fn descendants<const N: usize>(parents: &[Option<u16>; N], root: usize) -> [bool; N] {
|
||||||
|
let mut marked = [false; N];
|
||||||
|
let Some(r) = marked.get_mut(root) else {
|
||||||
|
return marked;
|
||||||
|
};
|
||||||
|
*r = true;
|
||||||
|
// Each pass extends the marked set by at least one generation of
|
||||||
|
// children; a chain in an N-node forest is at most N - 1 long.
|
||||||
|
for _ in 0..N {
|
||||||
|
let mut changed = false;
|
||||||
|
for i in 0..N {
|
||||||
|
let parent_marked = parents
|
||||||
|
.get(i)
|
||||||
|
.copied()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|p| marked.get(usize::from(p)).copied())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if let Some(m) = marked.get_mut(i)
|
||||||
|
&& !*m
|
||||||
|
&& parent_marked
|
||||||
|
{
|
||||||
|
*m = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
marked
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
mod proofs {
|
||||||
|
use super::descendants;
|
||||||
|
|
||||||
|
const N: usize = 4;
|
||||||
|
|
||||||
|
fn is_ancestor(parents: &[Option<u16>; N], root: usize, mut i: usize) -> bool {
|
||||||
|
for _ in 0..=N {
|
||||||
|
if i == root {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match parents[i].map(usize::from) {
|
||||||
|
Some(p) if p < N => i = p,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On every acyclic forest of `N` capabilities, `descendants` marks
|
||||||
|
/// exactly the nodes `root` is an ancestor of: revocation removes the
|
||||||
|
/// whole subtree and nothing else.
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn marks_exactly_the_subtree() {
|
||||||
|
let mut parents = [None; N];
|
||||||
|
for slot in parents.iter_mut() {
|
||||||
|
if kani::any() {
|
||||||
|
*slot = Some(kani::any::<u16>() % N as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only forests: following parents never revisits a node.
|
||||||
|
for start in 0..N {
|
||||||
|
let mut i = start;
|
||||||
|
let mut steps = 0;
|
||||||
|
while let Some(p) = parents[i] {
|
||||||
|
steps += 1;
|
||||||
|
kani::assume(steps <= N - 1);
|
||||||
|
i = p as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let root = kani::any::<usize>() % N;
|
||||||
|
let marked = descendants(&parents, root);
|
||||||
|
for i in 0..N {
|
||||||
|
assert!(marked[i] == is_ancestor(&parents, root, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
crates/domain/src/capability/derivation_tests.rs
Normal file
40
crates/domain/src/capability/derivation_tests.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use super::derivation::descendants;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_root_with_no_children_marks_only_itself() {
|
||||||
|
let parents = [None, None, None];
|
||||||
|
assert_eq!(descendants(&parents, 1), [false, true, false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn marks_children_and_grandchildren() {
|
||||||
|
// 0 -> 1 -> 2, 0 -> 3, 4 unrelated
|
||||||
|
let parents = [None, Some(0), Some(1), Some(0), None];
|
||||||
|
assert_eq!(descendants(&parents, 0), [true, true, true, true, false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revoking_a_middle_node_spares_its_ancestors_and_siblings() {
|
||||||
|
// 0 -> 1 -> 2, 0 -> 3
|
||||||
|
let parents = [None, Some(0), Some(1), Some(0)];
|
||||||
|
assert_eq!(descendants(&parents, 1), [false, true, true, false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deep_chains_are_followed_to_the_end() {
|
||||||
|
let parents = [None, Some(0), Some(1), Some(2), Some(3), Some(4)];
|
||||||
|
assert_eq!(descendants(&parents, 0), [true; 6]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chains_listed_out_of_order_are_still_found() {
|
||||||
|
// 3 -> 2 -> 1 -> 0 (children appear before their parents)
|
||||||
|
let parents = [Some(1), Some(2), Some(3), None];
|
||||||
|
assert_eq!(descendants(&parents, 3), [true, true, true, true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_out_of_range_root_marks_nothing() {
|
||||||
|
let parents = [None, Some(0)];
|
||||||
|
assert_eq!(descendants(&parents, 7), [false, false]);
|
||||||
|
}
|
||||||
@@ -1,34 +1,80 @@
|
|||||||
use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet};
|
use types::{
|
||||||
|
badge::Badge, bounded::CapSlot, generation::Generation, id::ProcessId,
|
||||||
|
permission::PermissionSet,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod authority;
|
pub mod authority;
|
||||||
|
pub mod derivation;
|
||||||
|
|
||||||
use crate::{error::KernelError, resource::Resource};
|
#[cfg(test)]
|
||||||
|
#[path = "derivation_tests.rs"]
|
||||||
|
mod derivation_tests;
|
||||||
|
|
||||||
|
use crate::{error::KernelError, generations::SlotGenerations, resource::Resource};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
const MAX_CAPS_PER_PROCESS: usize = 16;
|
// Sized so that every representable `CapSlot` indexes a real slot.
|
||||||
|
pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1;
|
||||||
|
|
||||||
|
/// Names one capability slot at one point in time. Both generations must
|
||||||
|
/// still match for the reference to resolve, so a reused pid or slot is
|
||||||
|
/// never mistaken for the capability that used to live there.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct CapRef {
|
||||||
|
pub process: ProcessId,
|
||||||
|
pub process_generation: Generation,
|
||||||
|
pub slot: CapSlot,
|
||||||
|
pub slot_generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Capability {
|
pub struct Capability {
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
permissions: PermissionSet,
|
permissions: PermissionSet,
|
||||||
generation: Generation,
|
generation: Generation,
|
||||||
|
badge: Badge,
|
||||||
|
/// The capability this one was derived from (Mint, Delegate, IPC
|
||||||
|
/// transfer); `None` for capabilities the kernel created directly.
|
||||||
|
parent: Option<CapRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Capability {
|
impl Capability {
|
||||||
pub fn new(
|
pub fn new(resource: Resource, permissions: PermissionSet, generation: Generation) -> Self {
|
||||||
resource: Resource,
|
|
||||||
permissions: PermissionSet,
|
|
||||||
generation: Generation,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
resource,
|
resource,
|
||||||
permissions,
|
permissions,
|
||||||
generation,
|
generation,
|
||||||
|
badge: Badge::NONE,
|
||||||
|
parent: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_parent(mut self, parent: Option<CapRef>) -> Self {
|
||||||
|
self.parent = parent;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self) -> Option<CapRef> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_parent(&mut self, parent: Option<CapRef>) {
|
||||||
|
self.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The same capability with `badge` stamped on it.
|
||||||
|
pub fn with_badge(mut self, badge: Badge) -> Self {
|
||||||
|
self.badge = badge;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn badge(&self) -> Badge {
|
||||||
|
self.badge
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resource(&self) -> &Resource {
|
pub fn resource(&self) -> &Resource {
|
||||||
&self.resource
|
&self.resource
|
||||||
}
|
}
|
||||||
@@ -44,39 +90,71 @@ impl Capability {
|
|||||||
|
|
||||||
pub struct CapabilityTable {
|
pub struct CapabilityTable {
|
||||||
slots: [Option<Capability>; MAX_CAPS_PER_PROCESS],
|
slots: [Option<Capability>; MAX_CAPS_PER_PROCESS],
|
||||||
|
generations: SlotGenerations<MAX_CAPS_PER_PROCESS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CapabilityTable {
|
impl CapabilityTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
slots: [const { None }; MAX_CAPS_PER_PROCESS],
|
slots: [const { None }; MAX_CAPS_PER_PROCESS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
fn slot(&self, slot: CapSlot) -> Option<&Capability> {
|
||||||
let idx = slot.value() as usize;
|
self.slots.get(slot.value() as usize)?.as_ref()
|
||||||
self.slots[idx]
|
}
|
||||||
.as_ref()
|
|
||||||
|
/// Generation of an occupied slot.
|
||||||
|
pub fn slot_generation(&self, slot: CapSlot) -> Option<Generation> {
|
||||||
|
self.slot(slot)?;
|
||||||
|
self.generations.current(slot.value() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, slot: CapSlot) -> Result<&mut Capability, KernelError> {
|
||||||
|
self.slots
|
||||||
|
.get_mut(slot.value() as usize)
|
||||||
|
.and_then(Option::as_mut)
|
||||||
.ok_or(KernelError::InvalidSlot(slot))
|
.ok_or(KernelError::InvalidSlot(slot))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
pub fn iter(&self) -> impl Iterator<Item = (CapSlot, &Capability)> {
|
||||||
let idx = self
|
self.slots.iter().enumerate().filter_map(|(i, c)| {
|
||||||
.slots
|
let cap = c.as_ref()?;
|
||||||
.iter()
|
Some((CapSlot::new(i as u8)?, cap))
|
||||||
.position(|s| s.is_none())
|
})
|
||||||
.ok_or(KernelError::NoFreeSlot)?;
|
}
|
||||||
|
|
||||||
self.slots[idx] = Some(cap);
|
pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> {
|
||||||
CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)
|
self.slot(slot).ok_or(KernelError::InvalidSlot(slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, cap: Capability) -> Result<CapSlot, KernelError> {
|
||||||
|
let generations = &self.generations;
|
||||||
|
let (idx, free) = self
|
||||||
|
.slots
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
|
||||||
|
.ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
let slot = CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
*free = Some(cap);
|
||||||
|
Ok(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
|
pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> {
|
||||||
let idx = slot.value() as usize;
|
let idx = slot.value() as usize;
|
||||||
if self.slots[idx].is_none() {
|
let taken = self.slots.get_mut(idx).and_then(Option::take);
|
||||||
|
if taken.is_none() {
|
||||||
return Err(KernelError::InvalidSlot(slot));
|
return Err(KernelError::InvalidSlot(slot));
|
||||||
}
|
}
|
||||||
self.slots[idx] = None;
|
self.generations.advance(idx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CapabilityTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ fn full_table_returns_no_free_slot() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn generation_mismatch_detected() {
|
fn generation_mismatch_detected() {
|
||||||
let gen_v1 = Generation::initial();
|
let gen_v1 = Generation::initial();
|
||||||
let gen_v2 = gen_v1.next();
|
let gen_v2 = gen_v1.next().unwrap();
|
||||||
|
|
||||||
assert!(gen_v1.is_valid_against(&gen_v1));
|
assert!(gen_v1.is_valid_against(&gen_v1));
|
||||||
assert!(!gen_v1.is_valid_against(&gen_v2));
|
assert!(!gen_v1.is_valid_against(&gen_v2));
|
||||||
@@ -109,7 +109,6 @@ fn restrict_to_cannot_add_permissions() {
|
|||||||
Permission::Write,
|
Permission::Write,
|
||||||
Permission::Execute,
|
Permission::Execute,
|
||||||
Permission::Delegate,
|
Permission::Delegate,
|
||||||
Permission::Revoke,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let result = read_only.restrict_to(&wants_everything);
|
let result = read_only.restrict_to(&wants_everything);
|
||||||
@@ -118,7 +117,6 @@ fn restrict_to_cannot_add_permissions() {
|
|||||||
assert!(!result.allows(Permission::Write));
|
assert!(!result.allows(Permission::Write));
|
||||||
assert!(!result.allows(Permission::Execute));
|
assert!(!result.allows(Permission::Execute));
|
||||||
assert!(!result.allows(Permission::Delegate));
|
assert!(!result.allows(Permission::Delegate));
|
||||||
assert!(!result.allows(Permission::Revoke));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -138,7 +136,6 @@ fn empty_permission_set_allows_nothing() {
|
|||||||
assert!(!empty.allows(Permission::Write));
|
assert!(!empty.allows(Permission::Write));
|
||||||
assert!(!empty.allows(Permission::Execute));
|
assert!(!empty.allows(Permission::Execute));
|
||||||
assert!(!empty.allows(Permission::Delegate));
|
assert!(!empty.allows(Permission::Delegate));
|
||||||
assert!(!empty.allows(Permission::Revoke));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -150,3 +147,45 @@ fn capability_preserves_resource_and_permissions() {
|
|||||||
assert_eq!(cap.permissions(), &expected_permissions);
|
assert_eq!(cap.permissions(), &expected_permissions);
|
||||||
assert_eq!(cap.generation(), &Generation::initial());
|
assert_eq!(cap.generation(), &Generation::initial());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removing_a_cap_changes_its_slot_generation() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap();
|
||||||
|
let before = table.slot_generation(slot).unwrap();
|
||||||
|
table.remove(slot).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
table.slot_generation(slot),
|
||||||
|
None,
|
||||||
|
"empty slots have no generation"
|
||||||
|
);
|
||||||
|
let again = table.insert(endpoint_cap(2, &[Permission::Read])).unwrap();
|
||||||
|
assert_eq!(again, slot);
|
||||||
|
assert!(!before.is_valid_against(&table.slot_generation(again).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iter_lists_occupied_slots() {
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let a = table.insert(endpoint_cap(1, &[])).unwrap();
|
||||||
|
let b = table.insert(endpoint_cap(2, &[])).unwrap();
|
||||||
|
table.remove(a).unwrap();
|
||||||
|
let slots: [Option<types::bounded::CapSlot>; 2] = {
|
||||||
|
let mut it = table.iter().map(|(s, _)| s);
|
||||||
|
[it.next(), it.next()]
|
||||||
|
};
|
||||||
|
assert_eq!(slots, [Some(b), None]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn caps_record_their_parent() {
|
||||||
|
let parent = CapRef {
|
||||||
|
process: types::id::ProcessId::from_raw(3),
|
||||||
|
process_generation: Generation::initial(),
|
||||||
|
slot: types::bounded::CapSlot::new(4).unwrap(),
|
||||||
|
slot_generation: Generation::initial(),
|
||||||
|
};
|
||||||
|
let cap = endpoint_cap(1, &[]).with_parent(Some(parent));
|
||||||
|
assert_eq!(cap.parent(), Some(parent));
|
||||||
|
assert_eq!(endpoint_cap(1, &[]).parent(), None);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ use types::{
|
|||||||
id::{EndpointId, ProcessId, ThreadId},
|
id::{EndpointId, ProcessId, ThreadId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message};
|
use crate::{
|
||||||
|
endpoint::wait_queue::WaitQueue, error::KernelError, generations::SlotGenerations,
|
||||||
|
message::Message,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod wait_queue;
|
pub mod wait_queue;
|
||||||
|
|
||||||
@@ -14,18 +17,19 @@ mod tests;
|
|||||||
|
|
||||||
pub struct Endpoint {
|
pub struct Endpoint {
|
||||||
id: EndpointId,
|
id: EndpointId,
|
||||||
|
/// The process that created it; the endpoint dies with its owner.
|
||||||
|
owner: ProcessId,
|
||||||
waiting_senders: WaitQueue,
|
waiting_senders: WaitQueue,
|
||||||
waiting_receivers: WaitQueue,
|
waiting_receivers: WaitQueue,
|
||||||
generation: Generation,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
pub fn new(id: EndpointId) -> Self {
|
pub fn new(id: EndpointId, owner: ProcessId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
owner,
|
||||||
waiting_senders: WaitQueue::new(),
|
waiting_senders: WaitQueue::new(),
|
||||||
waiting_receivers: WaitQueue::new(),
|
waiting_receivers: WaitQueue::new(),
|
||||||
generation: Generation::initial(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +37,8 @@ impl Endpoint {
|
|||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generation(&self) -> &Generation {
|
pub fn owner(&self) -> ProcessId {
|
||||||
&self.generation
|
self.owner
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> {
|
||||||
@@ -53,6 +57,22 @@ impl Endpoint {
|
|||||||
self.waiting_receivers.pop()
|
self.waiting_receivers.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn senders(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||||
|
self.waiting_senders.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receivers(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||||
|
self.waiting_receivers.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sender(&self, thread: ThreadId) -> bool {
|
||||||
|
self.waiting_senders.contains(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_receiver(&self, thread: ThreadId) -> bool {
|
||||||
|
self.waiting_receivers.contains(thread)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_waiting_sender(&self) -> bool {
|
pub fn has_waiting_sender(&self) -> bool {
|
||||||
!self.waiting_senders.is_empty()
|
!self.waiting_senders.is_empty()
|
||||||
}
|
}
|
||||||
@@ -68,51 +88,84 @@ impl Endpoint {
|
|||||||
pub fn remove_receiver(&mut self, thread: ThreadId) -> bool {
|
pub fn remove_receiver(&mut self, thread: ThreadId) -> bool {
|
||||||
self.waiting_receivers.remove_thread(thread)
|
self.waiting_receivers.remove_thread(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn revoke(&mut self) {
|
|
||||||
self.generation = self.generation.next();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_ENDPOINTS: usize = 256;
|
pub const MAX_ENDPOINTS: usize = if cfg!(kani) { 2 } else { 256 };
|
||||||
|
|
||||||
pub struct EndpointTable {
|
pub struct EndpointTable {
|
||||||
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
|
endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
|
||||||
|
generations: SlotGenerations<MAX_ENDPOINTS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EndpointTable {
|
impl EndpointTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
endpoints: [const { None }; MAX_ENDPOINTS],
|
endpoints: [const { None }; MAX_ENDPOINTS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(&mut self) -> Option<EndpointId> {
|
pub fn create(&mut self, owner: ProcessId) -> Option<EndpointId> {
|
||||||
let idx = self.endpoints.iter().position(|s| s.is_none())?;
|
let generations = &self.generations;
|
||||||
|
let (idx, slot) = self
|
||||||
|
.endpoints
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
|
||||||
let id = EndpointId::from_raw(idx as u16);
|
let id = EndpointId::from_raw(idx as u16);
|
||||||
self.endpoints[idx] = Some(Endpoint::new(id));
|
*slot = Some(Endpoint::new(id, owner));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generation capabilities to a live endpoint must carry.
|
||||||
|
pub fn generation(&self, id: EndpointId) -> Option<Generation> {
|
||||||
|
self.get(id)?;
|
||||||
|
self.generations.current(id.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Endpoint> {
|
||||||
|
self.endpoints.iter().flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = EndpointId> + '_ {
|
||||||
|
self.endpoints
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(move |ep| ep.owner == owner)
|
||||||
|
.map(|ep| ep.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: EndpointId) -> Option<&Endpoint> {
|
pub fn get(&self, id: EndpointId) -> Option<&Endpoint> {
|
||||||
self.endpoints.get(id.raw() as usize).and_then(|s| s.as_ref())
|
self.endpoints
|
||||||
|
.get(id.raw() as usize)
|
||||||
|
.and_then(|s| s.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: EndpointId) -> Option<&mut Endpoint> {
|
pub fn get_mut(&mut self, id: EndpointId) -> Option<&mut Endpoint> {
|
||||||
self.endpoints.get_mut(id.raw() as usize).and_then(|s| s.as_mut())
|
self.endpoints
|
||||||
|
.get_mut(id.raw() as usize)
|
||||||
|
.and_then(|s| s.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the endpoint; capabilities to it become invalid even if the
|
||||||
|
/// slot is later reused.
|
||||||
pub fn destroy(&mut self, id: EndpointId) -> Option<Endpoint> {
|
pub fn destroy(&mut self, id: EndpointId) -> Option<Endpoint> {
|
||||||
self.endpoints.get_mut(id.raw() as usize).and_then(|s| s.take())
|
let ep = self.endpoints.get_mut(id.raw() as usize)?.take()?;
|
||||||
|
self.generations.advance(id.raw() as usize);
|
||||||
|
Some(ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||||
for slot in &mut self.endpoints {
|
for ep in self.endpoints.iter_mut().flatten() {
|
||||||
if let Some(ep) = slot {
|
|
||||||
ep.remove_sender(thread);
|
ep.remove_sender(thread);
|
||||||
ep.remove_receiver(thread);
|
ep.remove_receiver(thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EndpointTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,39 @@
|
|||||||
use crate::endpoint::wait_queue::MAX_WAITERS;
|
use crate::endpoint::wait_queue::MAX_WAITERS;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use types::id::{EndpointId, ThreadId};
|
use types::id::{EndpointId, ProcessId, ThreadId};
|
||||||
|
|
||||||
fn thread(id: u16) -> ThreadId {
|
fn thread(id: u16) -> ThreadId {
|
||||||
ThreadId::from_raw(id)
|
ThreadId::from_raw(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn owner() -> ProcessId {
|
||||||
|
ProcessId::from_raw(0)
|
||||||
|
}
|
||||||
|
|
||||||
fn test_endpoint() -> Endpoint {
|
fn test_endpoint() -> Endpoint {
|
||||||
Endpoint::new(EndpointId::from_raw(1))
|
Endpoint::new(EndpointId::from_raw(1), owner())
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- WaitQueue tests ---
|
// --- WaitQueue tests ---
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_queue_pop_returns_none() {
|
fn empty_queue_pop_returns_none() {
|
||||||
let mut queue = WaitQueue::new();
|
let mut queue: WaitQueue = WaitQueue::new();
|
||||||
assert!(queue.pop().is_none());
|
assert!(queue.pop().is_none());
|
||||||
assert!(queue.is_empty());
|
assert!(queue.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn push_then_pop_returns_same_thread() {
|
fn push_then_pop_returns_same_thread() {
|
||||||
let mut queue = WaitQueue::new();
|
let mut queue: WaitQueue = WaitQueue::new();
|
||||||
queue.push(thread(1)).unwrap();
|
queue.push(thread(1)).unwrap();
|
||||||
assert_eq!(queue.pop(), Some(thread(1)));
|
assert_eq!(queue.pop(), Some(thread(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pop_order_is_fifo() {
|
fn pop_order_is_fifo() {
|
||||||
let mut queue = WaitQueue::new();
|
let mut queue: WaitQueue = WaitQueue::new();
|
||||||
queue.push(thread(1)).unwrap();
|
queue.push(thread(1)).unwrap();
|
||||||
queue.push(thread(2)).unwrap();
|
queue.push(thread(2)).unwrap();
|
||||||
queue.push(thread(3)).unwrap();
|
queue.push(thread(3)).unwrap();
|
||||||
@@ -42,7 +46,7 @@ fn pop_order_is_fifo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn full_queue_rejects_push() {
|
fn full_queue_rejects_push() {
|
||||||
let mut queue = WaitQueue::new();
|
let mut queue: WaitQueue = WaitQueue::new();
|
||||||
for i in 0..MAX_WAITERS {
|
for i in 0..MAX_WAITERS {
|
||||||
assert!(queue.push(thread(i as u16)).is_some());
|
assert!(queue.push(thread(i as u16)).is_some());
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,7 @@ fn full_queue_rejects_push() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_empty_after_drain() {
|
fn is_empty_after_drain() {
|
||||||
let mut queue = WaitQueue::new();
|
let mut queue: WaitQueue = WaitQueue::new();
|
||||||
queue.push(thread(1)).unwrap();
|
queue.push(thread(1)).unwrap();
|
||||||
queue.push(thread(2)).unwrap();
|
queue.push(thread(2)).unwrap();
|
||||||
queue.pop();
|
queue.pop();
|
||||||
@@ -99,13 +103,34 @@ fn senders_and_receivers_are_independent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn revoke_bumps_generation() {
|
fn reused_slot_gets_a_new_generation() {
|
||||||
let mut ep = test_endpoint();
|
let mut table = EndpointTable::new();
|
||||||
let gen_before = *ep.generation();
|
let id = table.create(owner()).unwrap();
|
||||||
ep.revoke();
|
let old = table.generation(id).unwrap();
|
||||||
let gen_after = *ep.generation();
|
table.destroy(id);
|
||||||
|
assert_eq!(
|
||||||
|
table.generation(id),
|
||||||
|
None,
|
||||||
|
"dead endpoints have no generation"
|
||||||
|
);
|
||||||
|
let again = table.create(owner()).unwrap();
|
||||||
|
assert_eq!(again, id);
|
||||||
|
assert!(!old.is_valid_against(&table.generation(again).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
assert!(!gen_before.is_valid_against(&gen_after));
|
#[test]
|
||||||
|
fn ids_owned_by_lists_only_that_owners_endpoints() {
|
||||||
|
let mut table = EndpointTable::new();
|
||||||
|
let a = table.create(owner()).unwrap();
|
||||||
|
table.create(ProcessId::from_raw(1)).unwrap();
|
||||||
|
let c = table.create(owner()).unwrap();
|
||||||
|
let mut ids = [EndpointId::from_raw(u16::MAX); 4];
|
||||||
|
let mut n = 0;
|
||||||
|
for id in table.ids_owned_by(owner()) {
|
||||||
|
ids[n] = id;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(&ids[..n], &[a, c]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -141,22 +166,22 @@ fn rendezvous_receiver_first() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_create_and_lookup() {
|
fn table_create_and_lookup() {
|
||||||
let mut table = EndpointTable::new();
|
let mut table = EndpointTable::new();
|
||||||
let id = table.create().unwrap();
|
let id = table.create(owner()).unwrap();
|
||||||
assert!(table.get(id).is_some());
|
assert!(table.get(id).is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_create_returns_unique_ids() {
|
fn table_create_returns_unique_ids() {
|
||||||
let mut table = EndpointTable::new();
|
let mut table = EndpointTable::new();
|
||||||
let a = table.create().unwrap();
|
let a = table.create(owner()).unwrap();
|
||||||
let b = table.create().unwrap();
|
let b = table.create(owner()).unwrap();
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_destroy_then_get_fails() {
|
fn table_destroy_then_get_fails() {
|
||||||
let mut table = EndpointTable::new();
|
let mut table = EndpointTable::new();
|
||||||
let id = table.create().unwrap();
|
let id = table.create(owner()).unwrap();
|
||||||
let removed = table.destroy(id);
|
let removed = table.destroy(id);
|
||||||
assert!(removed.is_some());
|
assert!(removed.is_some());
|
||||||
assert!(table.get(id).is_none());
|
assert!(table.get(id).is_none());
|
||||||
@@ -165,16 +190,57 @@ fn table_destroy_then_get_fails() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_get_mut_allows_enqueue() {
|
fn table_get_mut_allows_enqueue() {
|
||||||
let mut table = EndpointTable::new();
|
let mut table = EndpointTable::new();
|
||||||
let id = table.create().unwrap();
|
let id = table.create(owner()).unwrap();
|
||||||
table.get_mut(id).unwrap().enqueue_sender(thread(1)).unwrap();
|
table
|
||||||
|
.get_mut(id)
|
||||||
|
.unwrap()
|
||||||
|
.enqueue_sender(thread(1))
|
||||||
|
.unwrap();
|
||||||
assert!(table.get(id).unwrap().has_waiting_sender());
|
assert!(table.get(id).unwrap().has_waiting_sender());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_destroy_reuses_slot() {
|
fn table_destroy_reuses_slot() {
|
||||||
let mut table = EndpointTable::new();
|
let mut table = EndpointTable::new();
|
||||||
let id = table.create().unwrap();
|
let id = table.create(owner()).unwrap();
|
||||||
table.destroy(id);
|
table.destroy(id);
|
||||||
let new_id = table.create().unwrap();
|
let new_id = table.create(owner()).unwrap();
|
||||||
assert_eq!(id, new_id);
|
assert_eq!(id, new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wait_queue_is_fifo_even_after_a_pop_leaves_a_hole() {
|
||||||
|
let mut q: WaitQueue = WaitQueue::new();
|
||||||
|
q.push(thread(1)).unwrap();
|
||||||
|
q.push(thread(2)).unwrap();
|
||||||
|
assert_eq!(q.pop(), Some(thread(1)));
|
||||||
|
q.push(thread(3)).unwrap();
|
||||||
|
assert_eq!(q.pop(), Some(thread(2)), "thread 3 jumped the queue");
|
||||||
|
assert_eq!(q.pop(), Some(thread(3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wait_queue_keeps_order_around_a_removed_thread() {
|
||||||
|
let mut q: WaitQueue = WaitQueue::new();
|
||||||
|
for n in 1..=3 {
|
||||||
|
q.push(thread(n)).unwrap();
|
||||||
|
}
|
||||||
|
assert!(q.remove_thread(thread(2)));
|
||||||
|
q.push(thread(4)).unwrap();
|
||||||
|
assert_eq!(q.pop(), Some(thread(1)));
|
||||||
|
assert_eq!(q.pop(), Some(thread(3)));
|
||||||
|
assert_eq!(q.pop(), Some(thread(4)));
|
||||||
|
assert_eq!(q.pop(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wait_queue_kani_trace_push_push_remove() {
|
||||||
|
let mut q: WaitQueue<3> = WaitQueue::new();
|
||||||
|
q.push(thread(3)).unwrap();
|
||||||
|
q.push(thread(2)).unwrap();
|
||||||
|
assert!(q.remove_thread(thread(3)));
|
||||||
|
assert!(!q.remove_thread(thread(0)));
|
||||||
|
assert!(!q.remove_thread(thread(3)));
|
||||||
|
assert_eq!(q.pop(), Some(thread(2)));
|
||||||
|
assert_eq!(q.pop(), None);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +1,141 @@
|
|||||||
use types::id::ThreadId;
|
use types::id::ThreadId;
|
||||||
|
|
||||||
pub const MAX_WAITERS: usize = 16;
|
pub const MAX_WAITERS: usize = if cfg!(kani) { 2 } else { 16 };
|
||||||
|
|
||||||
pub struct WaitQueue {
|
/// Bounded FIFO of blocked threads. Order is preserved across pops and
|
||||||
entries: [Option<ThreadId>; MAX_WAITERS],
|
/// removals, so no waiter can be overtaken.
|
||||||
count: u8,
|
pub struct WaitQueue<const N: usize = MAX_WAITERS> {
|
||||||
|
entries: [Option<ThreadId>; N],
|
||||||
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaitQueue {
|
impl<const N: usize> WaitQueue<N> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: [None; MAX_WAITERS],
|
entries: [None; N],
|
||||||
count: 0,
|
len: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
|
pub fn push(&mut self, thread: ThreadId) -> Option<()> {
|
||||||
let slot = self.entries.iter_mut().find(|s| s.is_none())?;
|
if self.len == N || self.contains(thread) {
|
||||||
*slot = Some(thread);
|
return None;
|
||||||
self.count += 1;
|
}
|
||||||
|
*self.entries.get_mut(self.len)? = Some(thread);
|
||||||
|
self.len += 1;
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Option<ThreadId> {
|
pub fn pop(&mut self) -> Option<ThreadId> {
|
||||||
let slot = self.entries.iter_mut().find(|s| s.is_some())?;
|
let head = self.entries.first().copied().flatten()?;
|
||||||
let thread = slot.take();
|
self.close_gap(0);
|
||||||
self.count -= 1;
|
Some(head)
|
||||||
thread
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_thread(&mut self, thread: ThreadId) -> bool {
|
pub fn remove_thread(&mut self, thread: ThreadId) -> bool {
|
||||||
for slot in &mut self.entries {
|
match self.entries[..self.len]
|
||||||
if *slot == Some(thread) {
|
.iter()
|
||||||
*slot = None;
|
.position(|e| *e == Some(thread))
|
||||||
self.count -= 1;
|
{
|
||||||
return true;
|
Some(i) => {
|
||||||
|
self.close_gap(i);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||||
|
self.entries.iter().take(self.len).flatten().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, thread: ThreadId) -> bool {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.take(self.len)
|
||||||
|
.any(|e| *e == Some(thread))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.count == 0
|
self.len == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes entry `i`, shifting later entries forward to keep order.
|
||||||
|
fn close_gap(&mut self, i: usize) {
|
||||||
|
for j in i..self.len.saturating_sub(1) {
|
||||||
|
let next = self.entries.get(j + 1).copied().flatten();
|
||||||
|
if let Some(e) = self.entries.get_mut(j) {
|
||||||
|
*e = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.len = self.len.saturating_sub(1);
|
||||||
|
if let Some(last) = self.entries.get_mut(self.len) {
|
||||||
|
*last = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Default for WaitQueue<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
mod proofs {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Against a simple reference model (a plain array kept in arrival
|
||||||
|
/// order), any sequence of pushes, pops and removals behaves the same:
|
||||||
|
/// FIFO order, no duplicates, nothing lost.
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(7)]
|
||||||
|
fn behaves_like_a_fifo_without_duplicates() {
|
||||||
|
const CAP: usize = 3;
|
||||||
|
let mut q: WaitQueue<CAP> = WaitQueue::new();
|
||||||
|
let mut model = [0u16; CAP];
|
||||||
|
let mut len = 0usize;
|
||||||
|
|
||||||
|
for _ in 0..5 {
|
||||||
|
let t: u16 = kani::any::<u16>() % 4;
|
||||||
|
match kani::any::<u8>() % 3 {
|
||||||
|
0 => {
|
||||||
|
let ok = q.push(ThreadId::from_raw(t)).is_some();
|
||||||
|
let dup = model[..len].contains(&t);
|
||||||
|
assert!(ok == (!dup && len < CAP));
|
||||||
|
if ok {
|
||||||
|
model[len] = t;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let got = q.pop();
|
||||||
|
if len == 0 {
|
||||||
|
assert!(got.is_none());
|
||||||
|
} else {
|
||||||
|
assert!(got == Some(ThreadId::from_raw(model[0])));
|
||||||
|
for j in 0..len - 1 {
|
||||||
|
model[j] = model[j + 1];
|
||||||
|
}
|
||||||
|
len -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let removed = q.remove_thread(ThreadId::from_raw(t));
|
||||||
|
match model[..len].iter().position(|&m| m == t) {
|
||||||
|
Some(i) => {
|
||||||
|
assert!(removed);
|
||||||
|
for j in i..len - 1 {
|
||||||
|
model[j] = model[j + 1];
|
||||||
|
}
|
||||||
|
len -= 1;
|
||||||
|
}
|
||||||
|
None => assert!(!removed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(q.len == len);
|
||||||
|
assert!(q.is_empty() == (len == 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ use types::{
|
|||||||
pub enum KernelError {
|
pub enum KernelError {
|
||||||
InvalidSlot(CapSlot),
|
InvalidSlot(CapSlot),
|
||||||
RevokedCapability(CapSlot),
|
RevokedCapability(CapSlot),
|
||||||
PermissionDenied { slot: CapSlot, needed: Permission },
|
PermissionDenied {
|
||||||
|
slot: CapSlot,
|
||||||
|
needed: Permission,
|
||||||
|
},
|
||||||
NoFreeSlot,
|
NoFreeSlot,
|
||||||
InvalidProcess(ProcessId),
|
InvalidProcess(ProcessId),
|
||||||
WouldBlock,
|
WouldBlock,
|
||||||
@@ -23,4 +26,19 @@ pub enum KernelError {
|
|||||||
EndpointFull,
|
EndpointFull,
|
||||||
ProcessCreateDenied,
|
ProcessCreateDenied,
|
||||||
IrqAlreadyOwned(IrqId),
|
IrqAlreadyOwned(IrqId),
|
||||||
|
/// Address outside the user window, unaligned, or not owned by the caller.
|
||||||
|
InvalidAddress,
|
||||||
|
/// The target virtual range already holds a mapping.
|
||||||
|
AddressInUse,
|
||||||
|
InvalidRegion,
|
||||||
|
/// The region is mapped executable somewhere and the request would map
|
||||||
|
/// it writable, or the other way round (W^X across processes).
|
||||||
|
WritableAndExecutable,
|
||||||
|
/// The object a capability would refer to no longer exists.
|
||||||
|
NoSuchObject,
|
||||||
|
/// The capability names the wrong kind of object for this operation.
|
||||||
|
WrongObject,
|
||||||
|
/// `Reply` with an empty reply slot, or a call whose reply was dropped.
|
||||||
|
NoReply,
|
||||||
|
InvalidArgument,
|
||||||
}
|
}
|
||||||
|
|||||||
86
crates/domain/src/generations.rs
Normal file
86
crates/domain/src/generations.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//! Per-slot generation counters shared by the kernel's object tables.
|
||||||
|
|
||||||
|
use types::generation::Generation;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "generations_tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// A slot's generation changes every time the object in it is destroyed
|
||||||
|
/// or revoked, so capabilities minted earlier stop matching. A slot whose
|
||||||
|
/// counter runs out is retired for good rather than reused.
|
||||||
|
pub struct SlotGenerations<const N: usize> {
|
||||||
|
gens: [Option<Generation>; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> SlotGenerations<N> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
gens: [Some(Generation::initial()); N],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current generation of `idx`, `None` if retired or out of range.
|
||||||
|
pub fn current(&self, idx: usize) -> Option<Generation> {
|
||||||
|
*self.gens.get(idx)?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_usable(&self, idx: usize) -> bool {
|
||||||
|
self.current(idx).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalidates every capability minted for the current generation.
|
||||||
|
pub fn advance(&mut self, idx: usize) {
|
||||||
|
if let Some(g) = self.gens.get_mut(idx) {
|
||||||
|
*g = g.and_then(|g| g.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Default for SlotGenerations<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
mod proofs {
|
||||||
|
use super::SlotGenerations;
|
||||||
|
use types::generation::Generation;
|
||||||
|
|
||||||
|
/// However many times a slot is advanced, a generation observed earlier
|
||||||
|
/// never comes back, so a capability minted for it stays dead.
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(5)]
|
||||||
|
fn an_observed_generation_never_returns() {
|
||||||
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
|
g.gens[0] = if kani::any() {
|
||||||
|
Some(Generation::from_raw(kani::any()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let observed = g.current(0);
|
||||||
|
let steps: u8 = kani::any();
|
||||||
|
kani::assume(steps >= 1 && steps <= 4);
|
||||||
|
for _ in 0..steps {
|
||||||
|
g.advance(0);
|
||||||
|
if let (Some(old), Some(now)) = (observed, g.current(0)) {
|
||||||
|
assert!(!old.is_valid_against(&now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A retired slot is never usable again.
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(5)]
|
||||||
|
fn retired_slots_stay_retired() {
|
||||||
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
|
g.gens[0] = None;
|
||||||
|
let steps: u8 = kani::any();
|
||||||
|
kani::assume(steps <= 4);
|
||||||
|
for _ in 0..steps {
|
||||||
|
g.advance(0);
|
||||||
|
}
|
||||||
|
assert!(!g.is_usable(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
36
crates/domain/src/generations_tests.rs
Normal file
36
crates/domain/src/generations_tests.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use super::SlotGenerations;
|
||||||
|
use types::generation::Generation;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slots_start_at_the_initial_generation() {
|
||||||
|
let g: SlotGenerations<2> = SlotGenerations::new();
|
||||||
|
assert_eq!(g.current(0), Some(Generation::initial()));
|
||||||
|
assert!(g.is_usable(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn advance_changes_the_generation() {
|
||||||
|
let mut g: SlotGenerations<2> = SlotGenerations::new();
|
||||||
|
let before = g.current(0);
|
||||||
|
g.advance(0);
|
||||||
|
assert_ne!(g.current(0), before);
|
||||||
|
assert_eq!(g.current(1), before, "other slots untouched");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn out_of_range_slots_are_never_usable() {
|
||||||
|
let mut g: SlotGenerations<2> = SlotGenerations::new();
|
||||||
|
assert_eq!(g.current(2), None);
|
||||||
|
g.advance(2); // no panic
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exhausted_counter_retires_the_slot() {
|
||||||
|
let mut g: SlotGenerations<1> = SlotGenerations::new();
|
||||||
|
g.gens[0] = Some(Generation::from_raw(u32::MAX));
|
||||||
|
g.advance(0);
|
||||||
|
assert_eq!(g.current(0), None);
|
||||||
|
assert!(!g.is_usable(0));
|
||||||
|
g.advance(0);
|
||||||
|
assert_eq!(g.current(0), None, "retired slots stay retired");
|
||||||
|
}
|
||||||
@@ -1,38 +1,66 @@
|
|||||||
use types::id::{IrqId, ThreadId};
|
use types::{
|
||||||
|
badge::Badge,
|
||||||
use crate::error::KernelError;
|
generation::Generation,
|
||||||
|
id::{IrqId, NotificationId},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
const MAX_IRQS: usize = 256;
|
const MAX_IRQS: usize = if cfg!(kani) { 2 } else { 256 };
|
||||||
|
|
||||||
|
/// Where an interrupt is delivered: a signal on a notification, through a
|
||||||
|
/// capability snapshot (generation and badge) taken when the route was set.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct IrqRoute {
|
||||||
|
pub notification: NotificationId,
|
||||||
|
pub generation: Generation,
|
||||||
|
pub badge: Badge,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IrqRoutingTable {
|
pub struct IrqRoutingTable {
|
||||||
owners: [Option<ThreadId>; MAX_IRQS],
|
routes: [Option<IrqRoute>; MAX_IRQS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqRoutingTable {
|
impl IrqRoutingTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
owners: [None; MAX_IRQS],
|
routes: [None; MAX_IRQS],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(&mut self, irq: IrqId, thread: ThreadId) -> Result<(), KernelError> {
|
/// Whether `irq` is a line this table can route.
|
||||||
let idx = irq.raw() as usize;
|
pub fn is_valid(&self, irq: IrqId) -> bool {
|
||||||
if self.owners[idx].is_some() {
|
(irq.raw() as usize) < MAX_IRQS
|
||||||
return Err(KernelError::IrqAlreadyOwned(irq));
|
|
||||||
}
|
|
||||||
self.owners[idx] = Some(thread);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister(&mut self, irq: IrqId) {
|
/// Routes `irq`, replacing any previous route. Fails for lines beyond
|
||||||
self.owners[irq.raw() as usize] = None;
|
/// the table.
|
||||||
|
pub fn set(&mut self, irq: IrqId, route: IrqRoute) -> bool {
|
||||||
|
match self.routes.get_mut(irq.raw() as usize) {
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = Some(route);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn owner(&self, irq: IrqId) -> Option<ThreadId> {
|
pub fn clear(&mut self, irq: IrqId) {
|
||||||
self.owners[irq.raw() as usize]
|
if let Some(slot) = self.routes.get_mut(irq.raw() as usize) {
|
||||||
|
*slot = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The route for `irq`; `None` for unknown or unrouted lines.
|
||||||
|
pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
|
||||||
|
*self.routes.get(irq.raw() as usize)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IrqRoutingTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,43 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use types::id::{IrqId, ThreadId};
|
use types::badge::Badge;
|
||||||
|
use types::generation::Generation;
|
||||||
|
use types::id::{IrqId, NotificationId};
|
||||||
|
|
||||||
fn irq(id: u16) -> IrqId {
|
fn route(n: u16) -> IrqRoute {
|
||||||
IrqId::from_raw(id)
|
IrqRoute {
|
||||||
}
|
notification: NotificationId::from_raw(n),
|
||||||
|
generation: Generation::initial(),
|
||||||
fn thread(id: u16) -> ThreadId {
|
badge: Badge::new(1 << n),
|
||||||
ThreadId::from_raw(id)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_table_has_no_owners() {
|
fn new_table_routes_nothing() {
|
||||||
let table = IrqRoutingTable::new();
|
let t = IrqRoutingTable::new();
|
||||||
assert!(table.owner(irq(0)).is_none());
|
assert_eq!(t.route(IrqId::from_raw(10)), None);
|
||||||
assert!(table.owner(irq(10)).is_none());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn register_and_lookup() {
|
fn set_then_route() {
|
||||||
let mut table = IrqRoutingTable::new();
|
let mut t = IrqRoutingTable::new();
|
||||||
table.register(irq(5), thread(1)).unwrap();
|
assert!(t.set(IrqId::from_raw(10), route(3)));
|
||||||
assert_eq!(table.owner(irq(5)), Some(thread(1)));
|
assert_eq!(t.route(IrqId::from_raw(10)), Some(route(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn register_already_owned_fails() {
|
fn set_replaces_and_clear_removes() {
|
||||||
let mut table = IrqRoutingTable::new();
|
let mut t = IrqRoutingTable::new();
|
||||||
table.register(irq(5), thread(1)).unwrap();
|
t.set(IrqId::from_raw(10), route(3));
|
||||||
assert!(table.register(irq(5), thread(2)).is_err());
|
t.set(IrqId::from_raw(10), route(4));
|
||||||
|
assert_eq!(t.route(IrqId::from_raw(10)), Some(route(4)));
|
||||||
|
t.clear(IrqId::from_raw(10));
|
||||||
|
assert_eq!(t.route(IrqId::from_raw(10)), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unregister_then_reregister() {
|
fn lines_beyond_the_table_are_rejected_without_panicking() {
|
||||||
let mut table = IrqRoutingTable::new();
|
let mut t = IrqRoutingTable::new();
|
||||||
table.register(irq(5), thread(1)).unwrap();
|
assert!(!t.set(IrqId::from_raw(1000), route(1)));
|
||||||
table.unregister(irq(5));
|
assert_eq!(t.route(IrqId::from_raw(1000)), None);
|
||||||
assert!(table.owner(irq(5)).is_none());
|
t.clear(IrqId::from_raw(1000));
|
||||||
table.register(irq(5), thread(2)).unwrap();
|
|
||||||
assert_eq!(table.owner(irq(5)), Some(thread(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn different_irqs_independent() {
|
|
||||||
let mut table = IrqRoutingTable::new();
|
|
||||||
table.register(irq(0), thread(10)).unwrap();
|
|
||||||
table.register(irq(1), thread(20)).unwrap();
|
|
||||||
assert_eq!(table.owner(irq(0)), Some(thread(10)));
|
|
||||||
assert_eq!(table.owner(irq(1)), Some(thread(20)));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,18 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
// A kernel panic takes the whole system down, so kernel code may not
|
||||||
|
// contain any way to panic. Tests and proofs are exempt.
|
||||||
|
#![cfg_attr(
|
||||||
|
not(any(test, kani)),
|
||||||
|
deny(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
clippy::expect_used,
|
||||||
|
clippy::panic,
|
||||||
|
clippy::unreachable,
|
||||||
|
clippy::todo,
|
||||||
|
clippy::unimplemented,
|
||||||
|
clippy::indexing_slicing
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
|
||||||
#[cfg(kani)]
|
#[cfg(kani)]
|
||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
@@ -7,9 +21,11 @@ pub mod capability;
|
|||||||
pub mod endpoint;
|
pub mod endpoint;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod fault;
|
pub mod fault;
|
||||||
|
pub mod generations;
|
||||||
pub mod irq;
|
pub mod irq;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod notification;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
|
|||||||
232
crates/domain/src/memory/frame/mod.rs
Normal file
232
crates/domain/src/memory/frame/mod.rs
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
//! Physical frame ownership. The kernel's single source of truth for what
|
||||||
|
//! every frame of the user pool is used for and who is charged for it.
|
||||||
|
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
id::{ProcessId, RegionId},
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
#[path = "proofs.rs"]
|
||||||
|
mod proofs;
|
||||||
|
|
||||||
|
pub const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
/// Frames the kernel can track. The board's user pool must fit.
|
||||||
|
pub const MAX_FRAMES: usize = if cfg!(kani) { 6 } else { 4096 };
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum FrameUse {
|
||||||
|
/// Mapped exactly once, at `page` in `owner`'s address space.
|
||||||
|
User {
|
||||||
|
owner: ProcessId,
|
||||||
|
page: u32,
|
||||||
|
perms: PagePerms,
|
||||||
|
},
|
||||||
|
/// Holds part of `owner`'s page tables.
|
||||||
|
PageTable { owner: ProcessId },
|
||||||
|
/// Backs a shared memory region.
|
||||||
|
Region { region: RegionId },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameUse {
|
||||||
|
/// A user mapping at `va`, which must be page aligned.
|
||||||
|
pub fn user(owner: ProcessId, va: VirtualAddress, perms: PagePerms) -> Option<Self> {
|
||||||
|
if !va.raw().is_multiple_of(PAGE_SIZE) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
|
||||||
|
Some(Self::User { owner, page, perms })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrameTable<const N: usize = MAX_FRAMES> {
|
||||||
|
base: PhysicalAddress,
|
||||||
|
total: usize,
|
||||||
|
frames: [Option<FrameUse>; N],
|
||||||
|
free: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> FrameTable<N> {
|
||||||
|
pub fn new(base: PhysicalAddress, total: usize) -> Self {
|
||||||
|
let total = total.min(N);
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
total,
|
||||||
|
frames: [None; N],
|
||||||
|
free: total,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base(&self) -> PhysicalAddress {
|
||||||
|
self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capacity(&self) -> usize {
|
||||||
|
self.total
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_count(&self) -> usize {
|
||||||
|
self.free
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(&self, pa: PhysicalAddress) -> Option<usize> {
|
||||||
|
let offset = pa.raw().checked_sub(self.base.raw())?;
|
||||||
|
if !offset.is_multiple_of(PAGE_SIZE) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let idx = offset / PAGE_SIZE;
|
||||||
|
(idx < self.total).then_some(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address_of(&self, idx: usize) -> PhysicalAddress {
|
||||||
|
self.base.offset(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The frames that exist (`total` is clamped to `N` at construction).
|
||||||
|
fn live(&self) -> &[Option<FrameUse>] {
|
||||||
|
self.frames.get(..self.total).unwrap_or(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_mut(&mut self) -> &mut [Option<FrameUse>] {
|
||||||
|
self.frames.get_mut(..self.total).unwrap_or(&mut [])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, pa: PhysicalAddress) -> Option<&FrameUse> {
|
||||||
|
self.frames.get(self.index_of(pa)?)?.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc(&mut self, usage: FrameUse) -> Option<PhysicalAddress> {
|
||||||
|
let (idx, slot) = self
|
||||||
|
.live_mut()
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, f)| f.is_none())?;
|
||||||
|
*slot = Some(usage);
|
||||||
|
self.free -= 1;
|
||||||
|
Some(self.address_of(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates `count` physically adjacent frames; frame `i` gets `usage(i)`.
|
||||||
|
pub fn alloc_contiguous(
|
||||||
|
&mut self,
|
||||||
|
count: usize,
|
||||||
|
usage: impl Fn(usize) -> FrameUse,
|
||||||
|
) -> Option<PhysicalAddress> {
|
||||||
|
if count == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut run = 0;
|
||||||
|
let mut start = None;
|
||||||
|
for (idx, f) in self.live().iter().enumerate() {
|
||||||
|
if f.is_some() {
|
||||||
|
run = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
run += 1;
|
||||||
|
if run == count {
|
||||||
|
start = Some(idx + 1 - count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let start = start?;
|
||||||
|
let run = self.live_mut().get_mut(start..start + count)?;
|
||||||
|
for (i, f) in run.iter_mut().enumerate() {
|
||||||
|
*f = Some(usage(i));
|
||||||
|
}
|
||||||
|
self.free -= count;
|
||||||
|
Some(self.address_of(start))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees an allocated frame and reports what it was used for.
|
||||||
|
pub fn free(&mut self, pa: PhysicalAddress) -> Option<FrameUse> {
|
||||||
|
let idx = self.index_of(pa)?;
|
||||||
|
let usage = self.frames.get_mut(idx)?.take()?;
|
||||||
|
self.free += 1;
|
||||||
|
Some(usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the use of an allocated frame (e.g. moving it to another
|
||||||
|
/// process). Fails on free frames.
|
||||||
|
pub fn retag(&mut self, pa: PhysicalAddress, usage: FrameUse) -> bool {
|
||||||
|
match self
|
||||||
|
.index_of(pa)
|
||||||
|
.and_then(|i| self.frames.get_mut(i))
|
||||||
|
.and_then(Option::as_mut)
|
||||||
|
{
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = usage;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The frame mapped at `va` in `owner`, if `owner` holds it as a user page.
|
||||||
|
pub fn find_user(
|
||||||
|
&self,
|
||||||
|
owner: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
) -> Option<(PhysicalAddress, PagePerms)> {
|
||||||
|
if !va.raw().is_multiple_of(PAGE_SIZE) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let page = u32::try_from(va.raw() / PAGE_SIZE).ok()?;
|
||||||
|
self.live().iter().enumerate().find_map(|(idx, f)| match f {
|
||||||
|
Some(FrameUse::User {
|
||||||
|
owner: o,
|
||||||
|
page: p,
|
||||||
|
perms,
|
||||||
|
}) if *o == owner && *p == page => Some((self.address_of(idx), *perms)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every allocated frame and its use.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (PhysicalAddress, &FrameUse)> + '_ {
|
||||||
|
self.live()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, f)| f.as_ref().map(|u| (self.address_of(idx), u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User pages of `owner` as (frame, virtual address) pairs.
|
||||||
|
pub fn user_frames_of(
|
||||||
|
&self,
|
||||||
|
owner: ProcessId,
|
||||||
|
) -> impl Iterator<Item = (PhysicalAddress, VirtualAddress)> + '_ {
|
||||||
|
self.live()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(move |(idx, f)| match f {
|
||||||
|
Some(FrameUse::User { owner: o, page, .. }) if *o == owner => Some((
|
||||||
|
self.address_of(idx),
|
||||||
|
VirtualAddress::from_raw(*page as usize * PAGE_SIZE),
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees every user and page-table frame charged to `owner`; returns
|
||||||
|
/// how many were freed.
|
||||||
|
pub fn free_owned_by(&mut self, owner: ProcessId) -> usize {
|
||||||
|
let mut freed = 0;
|
||||||
|
for slot in self.live_mut().iter_mut() {
|
||||||
|
let owned = matches!(
|
||||||
|
slot,
|
||||||
|
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if *o == owner
|
||||||
|
);
|
||||||
|
if owned {
|
||||||
|
*slot = None;
|
||||||
|
freed += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free += freed;
|
||||||
|
freed
|
||||||
|
}
|
||||||
|
}
|
||||||
195
crates/domain/src/memory/frame/proofs.rs
Normal file
195
crates/domain/src/memory/frame/proofs.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
//! Bounded proofs over every reachable state of a small frame table.
|
||||||
|
//! Each harness starts from an arbitrary *well-formed* table (free count
|
||||||
|
//! matches the number of empty slots) and checks one operation.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
|
use types::id::{ProcessId, RegionId};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
|
const N: usize = 4;
|
||||||
|
const BASE: usize = 0x8400_0000;
|
||||||
|
|
||||||
|
fn any_perms() -> PagePerms {
|
||||||
|
match kani::any::<u8>() % 3 {
|
||||||
|
0 => PagePerms::ReadOnly,
|
||||||
|
1 => PagePerms::ReadWrite,
|
||||||
|
_ => PagePerms::ReadExecute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_use() -> FrameUse {
|
||||||
|
// Small id ranges keep owner collisions likely, which is where the
|
||||||
|
// interesting cases are.
|
||||||
|
let owner = ProcessId::from_raw(kani::any::<u16>() % 3);
|
||||||
|
match kani::any::<u8>() % 3 {
|
||||||
|
0 => FrameUse::User {
|
||||||
|
owner,
|
||||||
|
page: kani::any::<u32>() % 4,
|
||||||
|
perms: any_perms(),
|
||||||
|
},
|
||||||
|
1 => FrameUse::PageTable { owner },
|
||||||
|
_ => FrameUse::Region {
|
||||||
|
region: RegionId::from_raw(kani::any::<u16>() % 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_slot() -> Option<FrameUse> {
|
||||||
|
if kani::any() { Some(any_use()) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_table() -> FrameTable<N> {
|
||||||
|
let frames = [any_slot(), any_slot(), any_slot(), any_slot()];
|
||||||
|
let free = frames.iter().filter(|f| f.is_none()).count();
|
||||||
|
FrameTable {
|
||||||
|
base: PhysicalAddress::from_raw(BASE),
|
||||||
|
total: N,
|
||||||
|
frames,
|
||||||
|
free,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn well_formed(t: &FrameTable<N>) -> bool {
|
||||||
|
t.free == t.frames[..t.total].iter().filter(|f| f.is_none()).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_pa() -> PhysicalAddress {
|
||||||
|
// Anything from below the pool to past its end, aligned or not.
|
||||||
|
PhysicalAddress::from_raw(BASE - 4096 + kani::any::<usize>() % ((N + 2) * 4096))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn alloc_only_hands_out_free_frames() {
|
||||||
|
let mut t = any_table();
|
||||||
|
let before = t.frames;
|
||||||
|
let usage = any_use();
|
||||||
|
match t.alloc(usage) {
|
||||||
|
Some(pa) => {
|
||||||
|
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
|
assert!(idx < N);
|
||||||
|
assert!(before[idx].is_none());
|
||||||
|
assert!(t.frames[idx] == Some(usage));
|
||||||
|
for i in 0..N {
|
||||||
|
if i != idx {
|
||||||
|
assert!(t.frames[i] == before[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert!(before.iter().all(Option::is_some));
|
||||||
|
assert!(t.frames == before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(well_formed(&t));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn free_releases_exactly_one_allocated_frame_or_nothing() {
|
||||||
|
let mut t = any_table();
|
||||||
|
let before = t.frames;
|
||||||
|
let pa = any_pa();
|
||||||
|
match t.free(pa) {
|
||||||
|
Some(usage) => {
|
||||||
|
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
|
assert!(pa.raw() % PAGE_SIZE == 0);
|
||||||
|
assert!(before[idx] == Some(usage));
|
||||||
|
assert!(t.frames[idx].is_none());
|
||||||
|
for i in 0..N {
|
||||||
|
if i != idx {
|
||||||
|
assert!(t.frames[i] == before[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => assert!(t.frames == before),
|
||||||
|
}
|
||||||
|
assert!(well_formed(&t));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn retag_never_allocates() {
|
||||||
|
let mut t = any_table();
|
||||||
|
let before = t.frames;
|
||||||
|
let pa = any_pa();
|
||||||
|
let usage = any_use();
|
||||||
|
let changed = t.retag(pa, usage);
|
||||||
|
for i in 0..N {
|
||||||
|
if before[i].is_none() {
|
||||||
|
assert!(t.frames[i].is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
assert!(t.frames == before);
|
||||||
|
}
|
||||||
|
assert!(well_formed(&t));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn free_owned_by_spares_everyone_else() {
|
||||||
|
let mut t = any_table();
|
||||||
|
let before = t.frames;
|
||||||
|
let owner = ProcessId::from_raw(kani::any::<u16>() % 3);
|
||||||
|
t.free_owned_by(owner);
|
||||||
|
for i in 0..N {
|
||||||
|
let was_owned = matches!(
|
||||||
|
before[i],
|
||||||
|
Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if o == owner
|
||||||
|
);
|
||||||
|
if was_owned {
|
||||||
|
assert!(t.frames[i].is_none());
|
||||||
|
} else {
|
||||||
|
assert!(t.frames[i] == before[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(well_formed(&t));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn find_user_only_reports_the_owners_mapping() {
|
||||||
|
let t = any_table();
|
||||||
|
let owner = ProcessId::from_raw(kani::any::<u16>() % 3);
|
||||||
|
let va = VirtualAddress::from_raw(kani::any::<usize>() % (5 * 4096));
|
||||||
|
if let Some((pa, perms)) = t.find_user(owner, va) {
|
||||||
|
let idx = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
|
assert!(idx < N);
|
||||||
|
assert!(va.raw() % PAGE_SIZE == 0);
|
||||||
|
assert!(
|
||||||
|
t.frames[idx]
|
||||||
|
== Some(FrameUse::User {
|
||||||
|
owner,
|
||||||
|
page: (va.raw() / PAGE_SIZE) as u32,
|
||||||
|
perms,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(6)]
|
||||||
|
fn alloc_contiguous_only_claims_a_free_run() {
|
||||||
|
let mut t = any_table();
|
||||||
|
let before = t.frames;
|
||||||
|
let count = kani::any::<usize>() % (N + 2);
|
||||||
|
let region = RegionId::from_raw(1);
|
||||||
|
match t.alloc_contiguous(count, |_| FrameUse::Region { region }) {
|
||||||
|
Some(pa) => {
|
||||||
|
let start = (pa.raw() - BASE) / PAGE_SIZE;
|
||||||
|
assert!(count > 0 && start + count <= N);
|
||||||
|
for i in 0..N {
|
||||||
|
if i >= start && i < start + count {
|
||||||
|
assert!(before[i].is_none());
|
||||||
|
assert!(t.frames[i] == Some(FrameUse::Region { region }));
|
||||||
|
} else {
|
||||||
|
assert!(t.frames[i] == before[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => assert!(t.frames == before),
|
||||||
|
}
|
||||||
|
assert!(well_formed(&t));
|
||||||
|
}
|
||||||
188
crates/domain/src/memory/frame/tests.rs
Normal file
188
crates/domain/src/memory/frame/tests.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use super::*;
|
||||||
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
|
use types::id::{ProcessId, RegionId};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
|
const BASE: usize = 0x8400_0000;
|
||||||
|
|
||||||
|
fn table() -> FrameTable<8> {
|
||||||
|
FrameTable::new(PhysicalAddress::from_raw(BASE), 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid(n: u16) -> ProcessId {
|
||||||
|
ProcessId::from_raw(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn va(addr: usize) -> VirtualAddress {
|
||||||
|
VirtualAddress::from_raw(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user(owner: u16, addr: usize) -> FrameUse {
|
||||||
|
FrameUse::user(pid(owner), va(addr), PagePerms::ReadWrite).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_table_has_every_frame_free() {
|
||||||
|
let t = table();
|
||||||
|
assert_eq!(t.free_count(), 8);
|
||||||
|
assert_eq!(t.capacity(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn total_is_clamped_to_capacity() {
|
||||||
|
let t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 100);
|
||||||
|
assert_eq!(t.free_count(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_use_requires_page_aligned_va() {
|
||||||
|
assert!(FrameUse::user(pid(0), va(0x1234), PagePerms::ReadOnly).is_none());
|
||||||
|
assert!(FrameUse::user(pid(0), va(0x1000), PagePerms::ReadOnly).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_hands_out_distinct_frames_inside_the_pool() {
|
||||||
|
let mut t = table();
|
||||||
|
let a = t.alloc(user(0, 0x1000)).unwrap();
|
||||||
|
let b = t.alloc(user(0, 0x2000)).unwrap();
|
||||||
|
assert_ne!(a, b);
|
||||||
|
for pa in [a, b] {
|
||||||
|
assert!(pa.raw() >= BASE && pa.raw() < BASE + 8 * 4096);
|
||||||
|
assert_eq!(pa.raw() % 4096, 0);
|
||||||
|
}
|
||||||
|
assert_eq!(t.free_count(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_fails_when_exhausted() {
|
||||||
|
let mut t = table();
|
||||||
|
for i in 0..8 {
|
||||||
|
t.alloc(user(0, (i + 1) * 0x1000)).unwrap();
|
||||||
|
}
|
||||||
|
assert!(t.alloc(user(0, 0x9000)).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_returns_the_frame_and_its_use() {
|
||||||
|
let mut t = table();
|
||||||
|
let pa = t.alloc(user(3, 0x5000)).unwrap();
|
||||||
|
assert_eq!(t.free(pa), Some(user(3, 0x5000)));
|
||||||
|
assert_eq!(t.free_count(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn freeing_a_free_frame_is_rejected() {
|
||||||
|
let mut t = table();
|
||||||
|
let pa = t.alloc(user(0, 0x1000)).unwrap();
|
||||||
|
t.free(pa).unwrap();
|
||||||
|
assert_eq!(t.free(pa), None);
|
||||||
|
assert_eq!(t.free_count(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn freeing_outside_the_pool_or_unaligned_is_rejected() {
|
||||||
|
let mut t = table();
|
||||||
|
t.alloc(user(0, 0x1000)).unwrap();
|
||||||
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE - 4096)), None);
|
||||||
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 8 * 4096)), None);
|
||||||
|
assert_eq!(t.free(PhysicalAddress::from_raw(BASE + 1)), None);
|
||||||
|
assert_eq!(t.free_count(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_user_matches_owner_and_va() {
|
||||||
|
let mut t = table();
|
||||||
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
t.find_user(pid(1), va(0x3000)),
|
||||||
|
Some((pa, PagePerms::ReadWrite))
|
||||||
|
);
|
||||||
|
assert_eq!(t.find_user(pid(2), va(0x3000)), None);
|
||||||
|
assert_eq!(t.find_user(pid(1), va(0x4000)), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retag_moves_a_frame_to_a_new_use() {
|
||||||
|
let mut t = table();
|
||||||
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
||||||
|
assert!(t.retag(pa, user(2, 0x7000)));
|
||||||
|
assert_eq!(t.find_user(pid(1), va(0x3000)), None);
|
||||||
|
assert_eq!(t.find_user(pid(2), va(0x7000)).map(|(p, _)| p), Some(pa));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retag_of_a_free_frame_is_rejected() {
|
||||||
|
let mut t = table();
|
||||||
|
let pa = t.alloc(user(1, 0x3000)).unwrap();
|
||||||
|
t.free(pa);
|
||||||
|
assert!(!t.retag(pa, user(2, 0x7000)));
|
||||||
|
assert_eq!(t.free_count(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_contiguous_returns_adjacent_frames() {
|
||||||
|
let mut t = table();
|
||||||
|
let base = t
|
||||||
|
.alloc_contiguous(3, |_| FrameUse::Region {
|
||||||
|
region: RegionId::from_raw(0),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(t.free_count(), 5);
|
||||||
|
for i in 0..3 {
|
||||||
|
assert_eq!(
|
||||||
|
t.get(base.offset(i)),
|
||||||
|
Some(&FrameUse::Region {
|
||||||
|
region: RegionId::from_raw(0)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alloc_contiguous_fails_without_a_long_enough_run() {
|
||||||
|
let mut t: FrameTable<4> = FrameTable::new(PhysicalAddress::from_raw(BASE), 4);
|
||||||
|
let a = t.alloc(user(0, 0x1000)).unwrap();
|
||||||
|
let _b = t.alloc(user(0, 0x2000)).unwrap();
|
||||||
|
let _c = t.alloc(user(0, 0x3000)).unwrap();
|
||||||
|
t.free(a);
|
||||||
|
// free frames: 0 and 3 — no run of 2
|
||||||
|
assert!(
|
||||||
|
t.alloc_contiguous(2, |_| FrameUse::PageTable { owner: pid(0) })
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
assert_eq!(t.free_count(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_owned_by_releases_only_that_processes_frames() {
|
||||||
|
let mut t = table();
|
||||||
|
t.alloc(user(1, 0x1000)).unwrap();
|
||||||
|
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
|
||||||
|
let other = t.alloc(user(2, 0x1000)).unwrap();
|
||||||
|
let region = t
|
||||||
|
.alloc(FrameUse::Region {
|
||||||
|
region: RegionId::from_raw(1),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(t.free_owned_by(pid(1)), 2);
|
||||||
|
assert_eq!(t.free_count(), 6);
|
||||||
|
assert!(t.get(other).is_some());
|
||||||
|
assert!(t.get(region).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_frames_of_lists_mappings_of_one_process() {
|
||||||
|
let mut t = table();
|
||||||
|
t.alloc(user(1, 0x1000)).unwrap();
|
||||||
|
t.alloc(user(2, 0x2000)).unwrap();
|
||||||
|
t.alloc(user(1, 0x3000)).unwrap();
|
||||||
|
t.alloc(FrameUse::PageTable { owner: pid(1) }).unwrap();
|
||||||
|
let mut vas = [0usize; 4];
|
||||||
|
let mut n = 0;
|
||||||
|
for (_, v) in t.user_frames_of(pid(1)) {
|
||||||
|
vas[n] = v.raw();
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(&vas[..n], &[0x1000, 0x3000]);
|
||||||
|
}
|
||||||
@@ -1,118 +1,210 @@
|
|||||||
|
use crate::{capability::CapRef, generations::SlotGenerations};
|
||||||
use types::{
|
use types::{
|
||||||
address::PhysicalAddress,
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
bounded::PageCount,
|
bounded::PageCount,
|
||||||
generation::Generation,
|
generation::Generation,
|
||||||
id::{ProcessId, RegionId},
|
id::{ProcessId, RegionId},
|
||||||
|
mapping::PagePerms,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod page_pool;
|
pub mod frame;
|
||||||
pub mod process_memory;
|
pub mod quota;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "quota_tests.rs"]
|
||||||
|
mod quota_tests;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
const MAX_REGIONS: usize = 256;
|
pub const MAX_REGIONS: usize = if cfg!(kani) { 1 } else { 64 };
|
||||||
|
pub const MAX_REGION_MAPPINGS: usize = if cfg!(kani) { 2 } else { 4 };
|
||||||
|
|
||||||
pub struct MemoryRegion {
|
/// Where a region's frames come from.
|
||||||
id: RegionId,
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
owner: ProcessId,
|
pub enum RegionBacking {
|
||||||
base: PhysicalAddress,
|
/// Frames from the user pool, freed with the region.
|
||||||
size: PageCount,
|
Pool,
|
||||||
generation: Generation,
|
/// Device MMIO; never freed.
|
||||||
|
Device,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryRegion {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub fn new(
|
pub struct RegionMapping {
|
||||||
id: RegionId,
|
pub process: ProcessId,
|
||||||
|
pub va: VirtualAddress,
|
||||||
|
pub perms: PagePerms,
|
||||||
|
/// The capability the mapping was made through. Deleting that
|
||||||
|
/// capability removes the mapping; `None` for kernel-made mappings.
|
||||||
|
pub via: Option<CapRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Physically contiguous memory that may be mapped into several address
|
||||||
|
/// spaces. `owner` is charged for pool-backed frames.
|
||||||
|
pub struct Region {
|
||||||
owner: ProcessId,
|
owner: ProcessId,
|
||||||
|
backing: RegionBacking,
|
||||||
base: PhysicalAddress,
|
base: PhysicalAddress,
|
||||||
size: PageCount,
|
pages: PageCount,
|
||||||
generation: Generation,
|
mappings: [Option<RegionMapping>; MAX_REGION_MAPPINGS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Region {
|
||||||
|
pub fn new(
|
||||||
|
owner: ProcessId,
|
||||||
|
backing: RegionBacking,
|
||||||
|
base: PhysicalAddress,
|
||||||
|
pages: PageCount,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
|
||||||
owner,
|
owner,
|
||||||
|
backing,
|
||||||
base,
|
base,
|
||||||
size,
|
pages,
|
||||||
generation,
|
mappings: [None; MAX_REGION_MAPPINGS],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> RegionId {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn owner(&self) -> ProcessId {
|
pub fn owner(&self) -> ProcessId {
|
||||||
self.owner
|
self.owner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn backing(&self) -> RegionBacking {
|
||||||
|
self.backing
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base(&self) -> PhysicalAddress {
|
pub fn base(&self) -> PhysicalAddress {
|
||||||
self.base
|
self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> PageCount {
|
pub fn pages(&self) -> PageCount {
|
||||||
self.size
|
self.pages
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generation(&self) -> &Generation {
|
pub fn mappings(&self) -> impl Iterator<Item = &RegionMapping> {
|
||||||
&self.generation
|
self.mappings.iter().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn revoke(&mut self) {
|
/// Records that `mapping.process` maps this region. Each process maps a
|
||||||
self.generation = self.generation.next();
|
/// region at most once.
|
||||||
|
pub fn add_mapping(&mut self, mapping: RegionMapping) -> bool {
|
||||||
|
if self.mappings().any(|m| m.process == mapping.process) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match self.mappings.iter_mut().find(|m| m.is_none()) {
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = Some(mapping);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_mapping(&mut self, process: ProcessId) -> Option<RegionMapping> {
|
||||||
|
self.mappings
|
||||||
|
.iter_mut()
|
||||||
|
.find(|m| m.is_some_and(|m| m.process == process))?
|
||||||
|
.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `va` falls inside this region's mapping in `process`.
|
||||||
|
pub fn covers(&self, process: ProcessId, va: VirtualAddress) -> bool {
|
||||||
|
let len = self.pages.value() as usize * frame::PAGE_SIZE;
|
||||||
|
self.mappings()
|
||||||
|
.any(|m| m.process == process && va.raw() >= m.va.raw() && va.raw() - m.va.raw() < len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegionTable {
|
pub struct RegionTable {
|
||||||
regions: [Option<MemoryRegion>; MAX_REGIONS],
|
regions: [Option<Region>; MAX_REGIONS],
|
||||||
|
generations: SlotGenerations<MAX_REGIONS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegionTable {
|
impl RegionTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
regions: [const { None }; MAX_REGIONS],
|
regions: [const { None }; MAX_REGIONS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, region: MemoryRegion) -> Option<RegionId> {
|
/// Generation capabilities to a live region must carry.
|
||||||
let idx = self.regions.iter().position(|s| s.is_none())?;
|
pub fn generation(&self, id: RegionId) -> Option<Generation> {
|
||||||
let id = RegionId::from_raw(idx as u16);
|
self.get(id)?;
|
||||||
self.regions[idx] = Some(region);
|
self.generations.current(id.raw() as usize)
|
||||||
Some(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: RegionId) -> Option<&MemoryRegion> {
|
fn free_slot(&self) -> Option<usize> {
|
||||||
self.regions.get(id.raw() as usize).and_then(|s| s.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut MemoryRegion> {
|
|
||||||
self.regions.get_mut(id.raw() as usize).and_then(|s| s.as_mut())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, id: RegionId) -> Option<MemoryRegion> {
|
|
||||||
self.regions.get_mut(id.raw() as usize).and_then(|s| s.take())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_by_base(
|
|
||||||
&self,
|
|
||||||
owner: ProcessId,
|
|
||||||
base: PhysicalAddress,
|
|
||||||
) -> Option<(RegionId, &MemoryRegion)> {
|
|
||||||
for (i, slot) in self.regions.iter().enumerate() {
|
|
||||||
if let Some(region) = slot {
|
|
||||||
if region.owner() == owner && region.base() == base {
|
|
||||||
return Some((RegionId::from_raw(i as u16), region));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn regions_for_process(&self, process: ProcessId) -> impl Iterator<Item = &MemoryRegion> {
|
|
||||||
self.regions
|
self.regions
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|s| s.as_ref())
|
.enumerate()
|
||||||
.filter(move |r| r.owner() == process)
|
.position(|(i, r)| r.is_none() && self.generations.is_usable(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The id the next `insert` will return.
|
||||||
|
pub fn next_free_id(&self) -> Option<RegionId> {
|
||||||
|
let idx = self.free_slot()?;
|
||||||
|
Some(RegionId::from_raw(idx as u16))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, region: Region) -> Option<RegionId> {
|
||||||
|
let idx = self.free_slot()?;
|
||||||
|
*self.regions.get_mut(idx)? = Some(region);
|
||||||
|
Some(RegionId::from_raw(idx as u16))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: RegionId) -> Option<&Region> {
|
||||||
|
self.regions.get(id.raw() as usize)?.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut Region> {
|
||||||
|
self.regions.get_mut(id.raw() as usize)?.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the region; capabilities to it become invalid even if the
|
||||||
|
/// slot is later reused.
|
||||||
|
pub fn remove(&mut self, id: RegionId) -> Option<Region> {
|
||||||
|
let region = self.regions.get_mut(id.raw() as usize)?.take()?;
|
||||||
|
self.generations.advance(id.raw() as usize);
|
||||||
|
Some(region)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = RegionId> + '_ {
|
||||||
|
self.regions.iter().enumerate().filter_map(move |(i, r)| {
|
||||||
|
r.as_ref()
|
||||||
|
.filter(|r| r.owner == owner)
|
||||||
|
.map(|_| RegionId::from_raw(i as u16))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (RegionId, &Region)> {
|
||||||
|
self.regions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, r)| r.as_ref().map(|r| (RegionId::from_raw(i as u16), r)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every region, by id, so callers can drop a process's mappings.
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut Region)> {
|
||||||
|
self.regions
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, r)| r.as_mut().map(|r| (RegionId::from_raw(i as u16), r)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The region whose mapping in `process` covers `va`.
|
||||||
|
pub fn covering(&self, process: ProcessId, va: VirtualAddress) -> Option<RegionId> {
|
||||||
|
self.regions.iter().enumerate().find_map(|(i, r)| {
|
||||||
|
r.as_ref()
|
||||||
|
.filter(|r| r.covers(process, va))
|
||||||
|
.map(|_| RegionId::from_raw(i as u16))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RegionTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
use types::address::PhysicalAddress;
|
|
||||||
|
|
||||||
const MAX_PAGES: usize = 16384;
|
|
||||||
|
|
||||||
pub struct PagePool {
|
|
||||||
pages: [bool; MAX_PAGES],
|
|
||||||
base: PhysicalAddress,
|
|
||||||
total: usize,
|
|
||||||
free_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PagePool {
|
|
||||||
pub fn new(base: PhysicalAddress, total_pages: usize) -> Self {
|
|
||||||
let mut pages = [false; MAX_PAGES];
|
|
||||||
for i in 0..total_pages {
|
|
||||||
pages[i] = true;
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
pages,
|
|
||||||
base,
|
|
||||||
total: total_pages,
|
|
||||||
free_count: total_pages,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allocate(&mut self, count: usize) -> Option<PhysicalAddress> {
|
|
||||||
let start = self.find_contiguous(count)?;
|
|
||||||
for i in start..start + count {
|
|
||||||
self.pages[i] = false;
|
|
||||||
}
|
|
||||||
self.free_count -= count;
|
|
||||||
Some(self.base.offset(start))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(&mut self, addr: PhysicalAddress, count: usize) -> bool {
|
|
||||||
if addr.raw() < self.base.raw() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let start = (addr.raw() - self.base.raw()) / 4096;
|
|
||||||
if start + count > self.total {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for i in start..start + count {
|
|
||||||
self.pages[i] = true;
|
|
||||||
}
|
|
||||||
self.free_count += count;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free_count(&self) -> usize {
|
|
||||||
self.free_count
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_contiguous(&self, count: usize) -> Option<usize> {
|
|
||||||
let mut run = 0;
|
|
||||||
for i in 0..self.total {
|
|
||||||
if self.pages[i] {
|
|
||||||
run += 1;
|
|
||||||
if run == count {
|
|
||||||
return Some(i + 1 - count);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
run = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
|
||||||
use types::id::ProcessId;
|
|
||||||
|
|
||||||
use crate::error::KernelError;
|
|
||||||
|
|
||||||
const USER_HEAP_START: usize = 0x40000;
|
|
||||||
const PAGE_SIZE: usize = 4096;
|
|
||||||
const MAX_ALLOCATIONS: usize = 16;
|
|
||||||
|
|
||||||
pub struct ProcessMemory {
|
|
||||||
process: ProcessId,
|
|
||||||
page_count: usize,
|
|
||||||
limit: usize,
|
|
||||||
next_heap_va: usize,
|
|
||||||
allocations: [(PhysicalAddress, usize); MAX_ALLOCATIONS],
|
|
||||||
alloc_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessMemory {
|
|
||||||
pub const DEFAULT_PAGE_LIMIT: usize = 1024;
|
|
||||||
|
|
||||||
pub fn new(process: ProcessId, limit: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
process,
|
|
||||||
page_count: 0,
|
|
||||||
limit,
|
|
||||||
next_heap_va: USER_HEAP_START,
|
|
||||||
allocations: [(PhysicalAddress::from_raw(0), 0); MAX_ALLOCATIONS],
|
|
||||||
alloc_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alloc_user_va(&mut self, pages: usize) -> VirtualAddress {
|
|
||||||
let va = self.next_heap_va;
|
|
||||||
self.next_heap_va += pages * PAGE_SIZE;
|
|
||||||
VirtualAddress::from_raw(va)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn record_allocation(&mut self, pa: PhysicalAddress, count: usize) {
|
|
||||||
if self.alloc_count < MAX_ALLOCATIONS {
|
|
||||||
self.allocations[self.alloc_count] = (pa, count);
|
|
||||||
self.alloc_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allocations(&self) -> &[(PhysicalAddress, usize)] {
|
|
||||||
&self.allocations[..self.alloc_count]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process(&self) -> ProcessId {
|
|
||||||
self.process
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page_count(&self) -> usize {
|
|
||||||
self.page_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn limit(&self) -> usize {
|
|
||||||
self.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grant(&mut self, count: usize) -> Result<(), KernelError> {
|
|
||||||
if self.page_count + count > self.limit {
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
}
|
|
||||||
self.page_count += count;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(&mut self, count: usize) -> Result<(), KernelError> {
|
|
||||||
if count > self.page_count {
|
|
||||||
return Err(KernelError::OutOfMemory);
|
|
||||||
}
|
|
||||||
self.page_count -= count;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
57
crates/domain/src/memory/quota.rs
Normal file
57
crates/domain/src/memory/quota.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/// Per-process page budget. Every frame charged to a process (user pages
|
||||||
|
/// and its page tables) counts against it.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Quota {
|
||||||
|
used: usize,
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Quota {
|
||||||
|
pub const DEFAULT_LIMIT: usize = 1024;
|
||||||
|
|
||||||
|
pub const fn new(limit: usize) -> Self {
|
||||||
|
Self { used: 0, limit }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn used(&self) -> usize {
|
||||||
|
self.used
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limit(&self) -> usize {
|
||||||
|
self.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn charge(&mut self, pages: usize) -> bool {
|
||||||
|
match self.used.checked_add(pages) {
|
||||||
|
Some(total) if total <= self.limit => {
|
||||||
|
self.used = total;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uncharge(&mut self, pages: usize) -> bool {
|
||||||
|
match self.used.checked_sub(pages) {
|
||||||
|
Some(rest) => {
|
||||||
|
self.used = rest;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_limit(&mut self, limit: usize) -> bool {
|
||||||
|
if limit < self.used {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.limit = limit;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Quota {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(Self::DEFAULT_LIMIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
crates/domain/src/memory/quota_tests.rs
Normal file
37
crates/domain/src/memory/quota_tests.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use super::quota::Quota;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn charge_within_limit_succeeds() {
|
||||||
|
let mut q = Quota::new(4);
|
||||||
|
assert!(q.charge(3));
|
||||||
|
assert_eq!(q.used(), 3);
|
||||||
|
assert!(q.charge(1));
|
||||||
|
assert_eq!(q.used(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn charge_past_limit_fails_and_changes_nothing() {
|
||||||
|
let mut q = Quota::new(4);
|
||||||
|
q.charge(3);
|
||||||
|
assert!(!q.charge(2));
|
||||||
|
assert_eq!(q.used(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uncharge_cannot_underflow() {
|
||||||
|
let mut q = Quota::new(4);
|
||||||
|
q.charge(1);
|
||||||
|
assert!(!q.uncharge(2));
|
||||||
|
assert_eq!(q.used(), 1);
|
||||||
|
assert!(q.uncharge(1));
|
||||||
|
assert_eq!(q.used(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limit_can_be_raised_but_not_below_usage() {
|
||||||
|
let mut q = Quota::new(4);
|
||||||
|
q.charge(3);
|
||||||
|
assert!(!q.set_limit(2));
|
||||||
|
assert!(q.set_limit(10));
|
||||||
|
assert!(q.charge(7));
|
||||||
|
}
|
||||||
@@ -1,207 +1,118 @@
|
|||||||
use super::page_pool::PagePool;
|
|
||||||
use super::process_memory::ProcessMemory;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use types::address::PhysicalAddress;
|
use types::address::{PhysicalAddress, VirtualAddress};
|
||||||
use types::bounded::PageCount;
|
use types::bounded::PageCount;
|
||||||
use types::generation::Generation;
|
|
||||||
use types::id::{ProcessId, RegionId};
|
use types::id::{ProcessId, RegionId};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
fn base() -> PhysicalAddress {
|
fn pid(n: u16) -> ProcessId {
|
||||||
PhysicalAddress::from_raw(0x8000_0000)
|
ProcessId::from_raw(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(id: u16) -> ProcessId {
|
fn va(addr: usize) -> VirtualAddress {
|
||||||
ProcessId::from_raw(id)
|
VirtualAddress::from_raw(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pages(n: u16) -> PageCount {
|
fn pool_region(owner: u16, pages: u16) -> Region {
|
||||||
PageCount::new(n).unwrap()
|
Region::new(
|
||||||
|
pid(owner),
|
||||||
|
RegionBacking::Pool,
|
||||||
|
PhysicalAddress::from_raw(0x8400_0000),
|
||||||
|
PageCount::new(pages).unwrap(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PagePool tests ---
|
fn mapping(process: u16, addr: usize) -> RegionMapping {
|
||||||
|
RegionMapping {
|
||||||
#[test]
|
process: pid(process),
|
||||||
fn new_pool_has_all_pages_free() {
|
va: va(addr),
|
||||||
let pool = PagePool::new(base(), 100);
|
perms: PagePerms::ReadWrite,
|
||||||
assert_eq!(pool.free_count(), 100);
|
via: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_reduces_free_count() {
|
fn new_region_has_no_mappings() {
|
||||||
let mut pool = PagePool::new(base(), 100);
|
let r = pool_region(0, 4);
|
||||||
pool.allocate(10).unwrap();
|
assert_eq!(r.mappings().count(), 0);
|
||||||
assert_eq!(pool.free_count(), 90);
|
assert_eq!(r.owner(), pid(0));
|
||||||
|
assert_eq!(r.backing(), RegionBacking::Pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_returns_base_aligned_address() {
|
fn a_process_maps_a_region_at_most_once() {
|
||||||
let mut pool = PagePool::new(base(), 100);
|
let mut r = pool_region(0, 4);
|
||||||
let addr = pool.allocate(1).unwrap();
|
assert!(r.add_mapping(mapping(1, 0x30000)));
|
||||||
assert_eq!(addr, base());
|
assert!(!r.add_mapping(mapping(1, 0x50000)));
|
||||||
|
assert!(r.add_mapping(mapping(2, 0x30000)));
|
||||||
|
assert_eq!(r.mappings().count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_sequential_returns_contiguous() {
|
fn mapping_slots_are_bounded() {
|
||||||
let mut pool = PagePool::new(base(), 100);
|
let mut r = pool_region(0, 1);
|
||||||
let a = pool.allocate(4).unwrap();
|
for p in 0..MAX_REGION_MAPPINGS as u16 {
|
||||||
let b = pool.allocate(4).unwrap();
|
assert!(r.add_mapping(mapping(p, 0x30000)));
|
||||||
assert_eq!(a, base());
|
}
|
||||||
assert_eq!(b, base().offset(4));
|
assert!(!r.add_mapping(mapping(99, 0x30000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_too_many_returns_none() {
|
fn remove_mapping_returns_it() {
|
||||||
let mut pool = PagePool::new(base(), 10);
|
let mut r = pool_region(0, 4);
|
||||||
assert!(pool.allocate(11).is_none());
|
r.add_mapping(mapping(1, 0x30000));
|
||||||
|
assert_eq!(r.remove_mapping(pid(1)), Some(mapping(1, 0x30000)));
|
||||||
|
assert_eq!(r.remove_mapping(pid(1)), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn free_then_reallocate() {
|
fn covers_checks_every_page_of_a_mapping() {
|
||||||
let mut pool = PagePool::new(base(), 10);
|
let mut r = pool_region(0, 4);
|
||||||
let addr = pool.allocate(5).unwrap();
|
r.add_mapping(mapping(1, 0x30000));
|
||||||
assert_eq!(pool.free_count(), 5);
|
assert!(r.covers(pid(1), va(0x30000)));
|
||||||
assert!(pool.free(addr, 5));
|
assert!(r.covers(pid(1), va(0x33000)));
|
||||||
assert_eq!(pool.free_count(), 10);
|
assert!(!r.covers(pid(1), va(0x34000)));
|
||||||
let addr2 = pool.allocate(5).unwrap();
|
assert!(!r.covers(pid(1), va(0x2F000)));
|
||||||
assert_eq!(addr2, base());
|
assert!(!r.covers(pid(2), va(0x30000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn free_out_of_range_returns_false() {
|
fn region_table_insert_get_remove() {
|
||||||
let mut pool = PagePool::new(base(), 10);
|
let mut t = RegionTable::new();
|
||||||
assert!(!pool.free(PhysicalAddress::from_raw(0), 1));
|
let id = t.insert(pool_region(0, 2)).unwrap();
|
||||||
|
assert_eq!(t.get(id).map(|r| r.owner()), Some(pid(0)));
|
||||||
|
assert!(t.remove(id).is_some());
|
||||||
|
assert!(t.get(id).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_fragmented_finds_gap() {
|
fn region_table_is_bounded() {
|
||||||
let mut pool = PagePool::new(base(), 10);
|
let mut t = RegionTable::new();
|
||||||
let a = pool.allocate(3).unwrap();
|
for _ in 0..MAX_REGIONS {
|
||||||
let _b = pool.allocate(3).unwrap();
|
assert!(t.insert(pool_region(0, 1)).is_some());
|
||||||
let _c = pool.allocate(3).unwrap();
|
}
|
||||||
pool.free(a, 3);
|
assert!(t.insert(pool_region(0, 1)).is_none());
|
||||||
// now pages 0-2 are free, 3-8 allocated, 9 free
|
|
||||||
// asking for 3 contiguous should find 0-2
|
|
||||||
let d = pool.allocate(3).unwrap();
|
|
||||||
assert_eq!(d, base());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn allocate_exact_remaining() {
|
fn region_table_finds_regions_owned_by_a_process() {
|
||||||
let mut pool = PagePool::new(base(), 5);
|
let mut t = RegionTable::new();
|
||||||
let addr = pool.allocate(5).unwrap();
|
let a = t.insert(pool_region(1, 1)).unwrap();
|
||||||
assert_eq!(addr, base());
|
t.insert(pool_region(2, 1)).unwrap();
|
||||||
assert_eq!(pool.free_count(), 0);
|
let c = t.insert(pool_region(1, 1)).unwrap();
|
||||||
assert!(pool.allocate(1).is_none());
|
let mut ids = [RegionId::from_raw(u16::MAX); 4];
|
||||||
}
|
let mut n = 0;
|
||||||
|
for id in t.ids_owned_by(pid(1)) {
|
||||||
// --- ProcessMemory tests ---
|
ids[n] = id;
|
||||||
|
n += 1;
|
||||||
#[test]
|
}
|
||||||
fn new_process_memory_has_zero_pages() {
|
assert_eq!(&ids[..n], &[a, c]);
|
||||||
let pm = ProcessMemory::new(process(0), 100);
|
|
||||||
assert_eq!(pm.page_count(), 0);
|
|
||||||
assert_eq!(pm.limit(), 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn grant_pages_within_limit() {
|
fn region_table_finds_mapping_covering_a_va() {
|
||||||
let mut pm = ProcessMemory::new(process(0), 100);
|
let mut t = RegionTable::new();
|
||||||
assert!(pm.grant(10).is_ok());
|
let id = t.insert(pool_region(1, 2)).unwrap();
|
||||||
assert_eq!(pm.page_count(), 10);
|
t.get_mut(id).unwrap().add_mapping(mapping(3, 0x30000));
|
||||||
}
|
assert_eq!(t.covering(pid(3), va(0x31000)), Some(id));
|
||||||
|
assert_eq!(t.covering(pid(3), va(0x32000)), None);
|
||||||
#[test]
|
|
||||||
fn grant_pages_exceeding_limit_fails() {
|
|
||||||
let mut pm = ProcessMemory::new(process(0), 10);
|
|
||||||
assert!(pm.grant(11).is_err());
|
|
||||||
assert_eq!(pm.page_count(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn release_pages_reduces_count() {
|
|
||||||
let mut pm = ProcessMemory::new(process(0), 100);
|
|
||||||
pm.grant(20).unwrap();
|
|
||||||
assert!(pm.release(5).is_ok());
|
|
||||||
assert_eq!(pm.page_count(), 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn release_more_than_owned_fails() {
|
|
||||||
let mut pm = ProcessMemory::new(process(0), 100);
|
|
||||||
pm.grant(5).unwrap();
|
|
||||||
assert!(pm.release(10).is_err());
|
|
||||||
assert_eq!(pm.page_count(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grant_up_to_exact_limit() {
|
|
||||||
let mut pm = ProcessMemory::new(process(0), 10);
|
|
||||||
pm.grant(10).unwrap();
|
|
||||||
assert!(pm.grant(1).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- MemoryRegion tests ---
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn memory_region_construction_and_accessors() {
|
|
||||||
let region = MemoryRegion::new(
|
|
||||||
RegionId::from_raw(1),
|
|
||||||
process(0),
|
|
||||||
base(),
|
|
||||||
pages(4),
|
|
||||||
Generation::initial(),
|
|
||||||
);
|
|
||||||
assert_eq!(region.id(), RegionId::from_raw(1));
|
|
||||||
assert_eq!(region.owner(), process(0));
|
|
||||||
assert_eq!(region.base(), base());
|
|
||||||
assert_eq!(region.size(), pages(4));
|
|
||||||
assert_eq!(region.generation(), &Generation::initial());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- RegionTable tests ---
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn region_table_insert_and_get() {
|
|
||||||
let mut table = RegionTable::new();
|
|
||||||
let id = table.insert(MemoryRegion::new(
|
|
||||||
RegionId::from_raw(0),
|
|
||||||
process(0),
|
|
||||||
base(),
|
|
||||||
pages(4),
|
|
||||||
Generation::initial(),
|
|
||||||
));
|
|
||||||
assert!(id.is_some());
|
|
||||||
let rid = id.unwrap();
|
|
||||||
assert!(table.get(rid).is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn region_table_remove() {
|
|
||||||
let mut table = RegionTable::new();
|
|
||||||
let id = table.insert(MemoryRegion::new(
|
|
||||||
RegionId::from_raw(0),
|
|
||||||
process(0),
|
|
||||||
base(),
|
|
||||||
pages(4),
|
|
||||||
Generation::initial(),
|
|
||||||
)).unwrap();
|
|
||||||
let removed = table.remove(id);
|
|
||||||
assert!(removed.is_some());
|
|
||||||
assert!(table.get(id).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn region_table_regions_for_process() {
|
|
||||||
let mut table = RegionTable::new();
|
|
||||||
table.insert(MemoryRegion::new(
|
|
||||||
RegionId::from_raw(0), process(0), base(), pages(4), Generation::initial(),
|
|
||||||
));
|
|
||||||
table.insert(MemoryRegion::new(
|
|
||||||
RegionId::from_raw(1), process(1), base(), pages(4), Generation::initial(),
|
|
||||||
));
|
|
||||||
table.insert(MemoryRegion::new(
|
|
||||||
RegionId::from_raw(2), process(0), base(), pages(2), Generation::initial(),
|
|
||||||
));
|
|
||||||
assert_eq!(table.regions_for_process(process(0)).count(), 2);
|
|
||||||
assert_eq!(table.regions_for_process(process(1)).count(), 1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,72 +5,38 @@ use types::bounded::CapSlot;
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub const MAX_MESSAGE_WORDS: usize = 4;
|
pub const MAX_MESSAGE_WORDS: usize = 4;
|
||||||
pub const MAX_CAP_TRANSFERS: usize = 4;
|
|
||||||
|
|
||||||
|
/// Four words and at most one capability (named by the sender's slot).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
words: [usize; MAX_MESSAGE_WORDS],
|
words: [usize; MAX_MESSAGE_WORDS],
|
||||||
caps: [Option<CapSlot>; MAX_CAP_TRANSFERS],
|
cap: Option<CapSlot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self::from_words([0; MAX_MESSAGE_WORDS])
|
||||||
words: [0; MAX_MESSAGE_WORDS],
|
|
||||||
caps: [None; MAX_CAP_TRANSFERS],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
pub fn from_words(words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
||||||
Self {
|
Self { words, cap: None }
|
||||||
words,
|
|
||||||
caps: [None; MAX_CAP_TRANSFERS],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_cap(mut self, slot: CapSlot) -> Self {
|
||||||
|
self.cap = Some(slot);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn words(&self) -> &[usize; MAX_MESSAGE_WORDS] {
|
pub fn words(&self) -> &[usize; MAX_MESSAGE_WORDS] {
|
||||||
&self.words
|
&self.words
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Word `index`, or 0 past the end.
|
||||||
pub fn word(&self, index: usize) -> usize {
|
pub fn word(&self, index: usize) -> usize {
|
||||||
self.words[index]
|
self.words.get(index).copied().unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn caps(&self) -> &[Option<CapSlot>; MAX_CAP_TRANSFERS] {
|
pub fn cap(&self) -> Option<CapSlot> {
|
||||||
&self.caps
|
self.cap
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cap(&self, index: usize) -> Option<CapSlot> {
|
|
||||||
self.caps[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn builder() -> MessageBuilder {
|
|
||||||
MessageBuilder {
|
|
||||||
message: Message::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MessageBuilder {
|
|
||||||
message: Message,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageBuilder {
|
|
||||||
pub fn word(mut self, index: usize, value: usize) -> Self {
|
|
||||||
self.message.words[index] = value;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn words(mut self, words: [usize; MAX_MESSAGE_WORDS]) -> Self {
|
|
||||||
self.message.words = words;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cap(mut self, index: usize, slot: CapSlot) -> Self {
|
|
||||||
self.message.caps[index] = Some(slot);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Message {
|
|
||||||
self.message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,23 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
fn slot(n: u8) -> CapSlot {
|
|
||||||
CapSlot::new(n).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Construction ---
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_message_has_zeroed_words_and_no_caps() {
|
fn empty_message_has_zeroed_words_and_no_cap() {
|
||||||
let msg = Message::empty();
|
let msg = Message::empty();
|
||||||
assert_eq!(msg.words(), &[0; MAX_MESSAGE_WORDS]);
|
assert_eq!(msg.words(), &[0; MAX_MESSAGE_WORDS]);
|
||||||
for i in 0..MAX_CAP_TRANSFERS {
|
assert!(msg.cap().is_none());
|
||||||
assert!(msg.cap(i).is_none());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_words_sets_words_and_no_caps() {
|
fn from_words_keeps_every_word() {
|
||||||
let msg = Message::from_words([10, 20, 30, 40]);
|
let msg = Message::from_words([10, 20, 30, 40]);
|
||||||
assert_eq!(msg.word(0), 10);
|
assert_eq!(msg.words(), &[10, 20, 30, 40]);
|
||||||
assert_eq!(msg.word(1), 20);
|
assert!(msg.cap().is_none());
|
||||||
assert_eq!(msg.word(2), 30);
|
|
||||||
assert_eq!(msg.word(3), 40);
|
|
||||||
assert!(msg.cap(0).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Accessors ---
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn words_returns_full_array() {
|
|
||||||
let msg = Message::from_words([1, 2, 3, 4]);
|
|
||||||
assert_eq!(msg.words(), &[1, 2, 3, 4]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn caps_returns_full_array() {
|
fn with_cap_attaches_one_capability() {
|
||||||
let msg = Message::empty();
|
let msg = Message::from_words([1, 2, 3, 4]).with_cap(CapSlot::new(5).unwrap());
|
||||||
assert_eq!(msg.caps(), &[None; MAX_CAP_TRANSFERS]);
|
assert_eq!(msg.cap(), CapSlot::new(5));
|
||||||
}
|
assert_eq!(msg.word(3), 4);
|
||||||
|
|
||||||
// --- Builder ---
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builder_sets_individual_words() {
|
|
||||||
let msg = Message::builder()
|
|
||||||
.word(0, 42)
|
|
||||||
.word(2, 99)
|
|
||||||
.build();
|
|
||||||
assert_eq!(msg.word(0), 42);
|
|
||||||
assert_eq!(msg.word(1), 0);
|
|
||||||
assert_eq!(msg.word(2), 99);
|
|
||||||
assert_eq!(msg.word(3), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builder_sets_caps() {
|
|
||||||
let msg = Message::builder()
|
|
||||||
.cap(0, slot(5))
|
|
||||||
.cap(1, slot(10))
|
|
||||||
.build();
|
|
||||||
assert_eq!(msg.cap(0), Some(slot(5)));
|
|
||||||
assert_eq!(msg.cap(1), Some(slot(10)));
|
|
||||||
assert!(msg.cap(2).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builder_sets_words_and_caps_together() {
|
|
||||||
let msg = Message::builder()
|
|
||||||
.word(0, 100)
|
|
||||||
.word(1, 200)
|
|
||||||
.cap(0, slot(3))
|
|
||||||
.build();
|
|
||||||
assert_eq!(msg.word(0), 100);
|
|
||||||
assert_eq!(msg.word(1), 200);
|
|
||||||
assert_eq!(msg.cap(0), Some(slot(3)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builder_with_all_words() {
|
|
||||||
let msg = Message::builder()
|
|
||||||
.words([11, 22, 33, 44])
|
|
||||||
.build();
|
|
||||||
assert_eq!(msg.words(), &[11, 22, 33, 44]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
192
crates/domain/src/notification/mod.rs
Normal file
192
crates/domain/src/notification/mod.rs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//! Asynchronous signals: a word of bits, a FIFO of waiters and at most one
|
||||||
|
//! bound thread. See docs/ipc.md.
|
||||||
|
|
||||||
|
use types::{
|
||||||
|
badge::Badge,
|
||||||
|
generation::Generation,
|
||||||
|
id::{NotificationId, ProcessId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{endpoint::wait_queue::WaitQueue, generations::SlotGenerations};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "tests.rs"]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
#[path = "proofs.rs"]
|
||||||
|
mod proofs;
|
||||||
|
|
||||||
|
pub const MAX_NOTIFICATIONS: usize = if cfg!(kani) { 1 } else { 128 };
|
||||||
|
|
||||||
|
/// What the kernel must do after a signal.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SignalEffect {
|
||||||
|
/// Wake this blocked `Wait`er with these bits.
|
||||||
|
WakeWaiter(ThreadId, usize),
|
||||||
|
/// Wake the bound thread out of its `Receive` with these bits.
|
||||||
|
WakeBound(ThreadId, usize),
|
||||||
|
/// Nobody to wake; the bits were stored.
|
||||||
|
Stored,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Notification {
|
||||||
|
id: NotificationId,
|
||||||
|
owner: ProcessId,
|
||||||
|
bits: usize,
|
||||||
|
waiters: WaitQueue,
|
||||||
|
bound: Option<ThreadId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Notification {
|
||||||
|
pub fn new(id: NotificationId, owner: ProcessId) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
bits: 0,
|
||||||
|
waiters: WaitQueue::new(),
|
||||||
|
bound: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> NotificationId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn owner(&self) -> ProcessId {
|
||||||
|
self.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bound(&self) -> Option<ThreadId> {
|
||||||
|
self.bound
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delivers a signal. `bound_is_receiving` says whether the bound
|
||||||
|
/// thread is currently blocked in `Receive` and can be woken.
|
||||||
|
pub fn signal(&mut self, badge: Badge, bound_is_receiving: bool) -> SignalEffect {
|
||||||
|
let bits = if badge.is_none() { 1 } else { badge.raw() };
|
||||||
|
if let Some(waiter) = self.waiters.pop() {
|
||||||
|
return SignalEffect::WakeWaiter(waiter, bits);
|
||||||
|
}
|
||||||
|
if let (Some(bound), true) = (self.bound, bound_is_receiving) {
|
||||||
|
return SignalEffect::WakeBound(bound, bits);
|
||||||
|
}
|
||||||
|
self.bits |= bits;
|
||||||
|
SignalEffect::Stored
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns and clears the pending bits.
|
||||||
|
pub fn take_bits(&mut self) -> usize {
|
||||||
|
core::mem::take(&mut self.bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_bits(&self) -> bool {
|
||||||
|
self.bits != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn waiters(&self) -> impl Iterator<Item = ThreadId> + '_ {
|
||||||
|
self.waiters.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_waiter(&self, thread: ThreadId) -> bool {
|
||||||
|
self.waiters.contains(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_waiter(&mut self, thread: ThreadId) -> bool {
|
||||||
|
self.waiters.push(thread).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(&mut self, thread: ThreadId) -> bool {
|
||||||
|
if self.bound.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.bound = Some(thread);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unbind(&mut self, thread: ThreadId) {
|
||||||
|
if self.bound == Some(thread) {
|
||||||
|
self.bound = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forgets `thread` as waiter and as bound thread.
|
||||||
|
pub fn remove_thread(&mut self, thread: ThreadId) {
|
||||||
|
self.waiters.remove_thread(thread);
|
||||||
|
self.unbind(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drains every waiter, e.g. when the notification is destroyed.
|
||||||
|
pub fn pop_waiter(&mut self) -> Option<ThreadId> {
|
||||||
|
self.waiters.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NotificationTable {
|
||||||
|
slots: [Option<Notification>; MAX_NOTIFICATIONS],
|
||||||
|
generations: SlotGenerations<MAX_NOTIFICATIONS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotificationTable {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
slots: [const { None }; MAX_NOTIFICATIONS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(&mut self, owner: ProcessId) -> Option<NotificationId> {
|
||||||
|
let generations = &self.generations;
|
||||||
|
let (idx, slot) = self
|
||||||
|
.slots
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(i, s)| s.is_none() && generations.is_usable(*i))?;
|
||||||
|
let id = NotificationId::from_raw(idx as u16);
|
||||||
|
*slot = Some(Notification::new(id, owner));
|
||||||
|
Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: NotificationId) -> Option<&Notification> {
|
||||||
|
self.slots.get(id.raw() as usize)?.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: NotificationId) -> Option<&mut Notification> {
|
||||||
|
self.slots.get_mut(id.raw() as usize)?.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generation(&self, id: NotificationId) -> Option<Generation> {
|
||||||
|
self.get(id)?;
|
||||||
|
self.generations.current(id.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(&mut self, id: NotificationId) -> Option<Notification> {
|
||||||
|
let n = self.slots.get_mut(id.raw() as usize)?.take()?;
|
||||||
|
self.generations.advance(id.raw() as usize);
|
||||||
|
Some(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Notification> {
|
||||||
|
self.slots.iter().flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ids_owned_by(&self, owner: ProcessId) -> impl Iterator<Item = NotificationId> + '_ {
|
||||||
|
self.slots
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(move |n| n.owner == owner)
|
||||||
|
.map(|n| n.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_thread_from_all(&mut self, thread: ThreadId) {
|
||||||
|
for n in self.slots.iter_mut().flatten() {
|
||||||
|
n.remove_thread(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NotificationTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
43
crates/domain/src/notification/proofs.rs
Normal file
43
crates/domain/src/notification/proofs.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//! Signals are never lost: every signal either wakes exactly one thread
|
||||||
|
//! with its bits, or leaves those bits set for the next `Wait`.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use types::badge::Badge;
|
||||||
|
use types::id::{NotificationId, ProcessId, ThreadId};
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(5)]
|
||||||
|
fn a_signal_is_delivered_or_stored() {
|
||||||
|
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
|
||||||
|
n.bits = kani::any();
|
||||||
|
if kani::any() {
|
||||||
|
n.add_waiter(ThreadId::from_raw(kani::any::<u16>() % 4));
|
||||||
|
}
|
||||||
|
if kani::any() {
|
||||||
|
n.bind(ThreadId::from_raw(kani::any::<u16>() % 4));
|
||||||
|
}
|
||||||
|
let before = n.bits;
|
||||||
|
let badge = Badge::new(kani::any());
|
||||||
|
let expected = if badge.is_none() { 1 } else { badge.raw() };
|
||||||
|
|
||||||
|
match n.signal(badge, kani::any()) {
|
||||||
|
SignalEffect::WakeWaiter(_, bits) | SignalEffect::WakeBound(_, bits) => {
|
||||||
|
assert!(bits == expected);
|
||||||
|
assert!(n.bits == before, "woken signals are not also stored");
|
||||||
|
}
|
||||||
|
SignalEffect::Stored => {
|
||||||
|
assert!(n.bits == before | expected);
|
||||||
|
assert!(n.bits & expected == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(5)]
|
||||||
|
fn take_bits_returns_everything_and_clears() {
|
||||||
|
let mut n = Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0));
|
||||||
|
let bits: usize = kani::any();
|
||||||
|
n.bits = bits;
|
||||||
|
assert!(n.take_bits() == bits);
|
||||||
|
assert!(n.bits == 0);
|
||||||
|
}
|
||||||
116
crates/domain/src/notification/tests.rs
Normal file
116
crates/domain/src/notification/tests.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use super::*;
|
||||||
|
use types::badge::Badge;
|
||||||
|
use types::id::{NotificationId, ProcessId, ThreadId};
|
||||||
|
|
||||||
|
fn ntfn() -> Notification {
|
||||||
|
Notification::new(NotificationId::from_raw(0), ProcessId::from_raw(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn t(n: u16) -> ThreadId {
|
||||||
|
ThreadId::from_raw(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signal_with_nobody_waiting_stores_the_badge() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
assert_eq!(n.signal(Badge::new(0b100), false), SignalEffect::Stored);
|
||||||
|
assert_eq!(n.signal(Badge::new(0b001), false), SignalEffect::Stored);
|
||||||
|
assert_eq!(n.take_bits(), 0b101);
|
||||||
|
assert_eq!(n.take_bits(), 0, "taking clears");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unbadged_signal_sets_bit_zero() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
n.signal(Badge::NONE, false);
|
||||||
|
assert_eq!(n.take_bits(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signal_wakes_the_first_waiter_with_its_badge() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
assert!(n.add_waiter(t(1)));
|
||||||
|
assert!(n.add_waiter(t(2)));
|
||||||
|
assert_eq!(
|
||||||
|
n.signal(Badge::new(8), false),
|
||||||
|
SignalEffect::WakeWaiter(t(1), 8)
|
||||||
|
);
|
||||||
|
assert_eq!(n.take_bits(), 0, "delivered bits are not also stored");
|
||||||
|
assert_eq!(
|
||||||
|
n.signal(Badge::new(4), false),
|
||||||
|
SignalEffect::WakeWaiter(t(2), 4)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signal_wakes_a_receiving_bound_thread_when_no_waiter() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
assert!(n.bind(t(5)));
|
||||||
|
assert_eq!(
|
||||||
|
n.signal(Badge::new(2), true),
|
||||||
|
SignalEffect::WakeBound(t(5), 2)
|
||||||
|
);
|
||||||
|
assert_eq!(n.signal(Badge::new(2), false), SignalEffect::Stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn waiters_take_priority_over_the_bound_thread() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
n.bind(t(5));
|
||||||
|
n.add_waiter(t(1));
|
||||||
|
assert_eq!(
|
||||||
|
n.signal(Badge::new(2), true),
|
||||||
|
SignalEffect::WakeWaiter(t(1), 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_one_thread_can_be_bound() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
assert!(n.bind(t(1)));
|
||||||
|
assert!(!n.bind(t(2)));
|
||||||
|
n.unbind(t(1));
|
||||||
|
assert!(n.bind(t(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removed_waiter_is_not_woken() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
n.add_waiter(t(1));
|
||||||
|
n.remove_thread(t(1));
|
||||||
|
assert_eq!(n.signal(Badge::new(1), false), SignalEffect::Stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_thread_also_unbinds() {
|
||||||
|
let mut n = ntfn();
|
||||||
|
n.bind(t(1));
|
||||||
|
n.remove_thread(t(1));
|
||||||
|
assert_eq!(n.bound(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_generations_follow_slot_reuse() {
|
||||||
|
let mut table = NotificationTable::new();
|
||||||
|
let owner = ProcessId::from_raw(3);
|
||||||
|
let id = table.create(owner).unwrap();
|
||||||
|
let g = table.generation(id).unwrap();
|
||||||
|
assert!(table.destroy(id).is_some());
|
||||||
|
assert_eq!(table.generation(id), None);
|
||||||
|
let again = table.create(owner).unwrap();
|
||||||
|
assert_eq!(again, id);
|
||||||
|
assert!(!g.is_valid_against(&table.generation(again).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_lists_notifications_by_owner() {
|
||||||
|
let mut table = NotificationTable::new();
|
||||||
|
let a = table.create(ProcessId::from_raw(1)).unwrap();
|
||||||
|
table.create(ProcessId::from_raw(2)).unwrap();
|
||||||
|
let mut n = 0;
|
||||||
|
for id in table.ids_owned_by(ProcessId::from_raw(1)) {
|
||||||
|
assert_eq!(id, a);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
assert_eq!(n, 1);
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
use types::{
|
use types::{
|
||||||
bounded::CapSlot,
|
bounded::CapSlot,
|
||||||
|
generation::Generation,
|
||||||
id::{ProcessId, ThreadId},
|
id::{ProcessId, ThreadId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{capability::CapabilityTable, error::KernelError};
|
use crate::{capability::CapabilityTable, error::KernelError, generations::SlotGenerations};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub const MAX_THREADS_PER_PROCESS: usize = 4;
|
pub const MAX_THREADS_PER_PROCESS: usize = if cfg!(kani) { 2 } else { 4 };
|
||||||
pub const MAX_PROCESSES: usize = 64;
|
pub const MAX_PROCESSES: usize = if cfg!(kani) { 3 } else { 64 };
|
||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
id: ProcessId,
|
id: ProcessId,
|
||||||
@@ -75,28 +76,38 @@ impl Process {
|
|||||||
|
|
||||||
pub struct ProcessTable {
|
pub struct ProcessTable {
|
||||||
processes: [Option<Process>; MAX_PROCESSES],
|
processes: [Option<Process>; MAX_PROCESSES],
|
||||||
|
generations: SlotGenerations<MAX_PROCESSES>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessTable {
|
impl ProcessTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
processes: [const { None }; MAX_PROCESSES],
|
processes: [const { None }; MAX_PROCESSES],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generation capabilities to a live process must carry.
|
||||||
|
pub fn generation(&self, id: ProcessId) -> Option<Generation> {
|
||||||
|
self.get(id).ok()?;
|
||||||
|
self.generations.current(id.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
&mut self,
|
&mut self,
|
||||||
capabilities: CapabilityTable,
|
capabilities: CapabilityTable,
|
||||||
fault_handler: Option<CapSlot>,
|
fault_handler: Option<CapSlot>,
|
||||||
) -> Result<ProcessId, KernelError> {
|
) -> Result<ProcessId, KernelError> {
|
||||||
let idx = self
|
let generations = &self.generations;
|
||||||
|
let (idx, slot) = self
|
||||||
.processes
|
.processes
|
||||||
.iter()
|
.iter_mut()
|
||||||
.position(|s| s.is_none())
|
.enumerate()
|
||||||
|
.find(|(i, s)| s.is_none() && generations.is_usable(*i))
|
||||||
.ok_or(KernelError::TooManyProcesses)?;
|
.ok_or(KernelError::TooManyProcesses)?;
|
||||||
|
|
||||||
let id = ProcessId::from_raw(idx as u16);
|
let id = ProcessId::from_raw(idx as u16);
|
||||||
self.processes[idx] = Some(Process::new(id, capabilities, fault_handler));
|
*slot = Some(Process::new(id, capabilities, fault_handler));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,11 +127,22 @@ impl ProcessTable {
|
|||||||
.ok_or(KernelError::InvalidProcess(id))
|
.ok_or(KernelError::InvalidProcess(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the process; capabilities to it become invalid even if the
|
||||||
|
/// pid is later reused.
|
||||||
pub fn destroy(&mut self, id: ProcessId) -> Result<Process, KernelError> {
|
pub fn destroy(&mut self, id: ProcessId) -> Result<Process, KernelError> {
|
||||||
let idx = id.raw() as usize;
|
let idx = id.raw() as usize;
|
||||||
self.processes
|
let process = self
|
||||||
|
.processes
|
||||||
.get_mut(idx)
|
.get_mut(idx)
|
||||||
.and_then(|s| s.take())
|
.and_then(|s| s.take())
|
||||||
.ok_or(KernelError::InvalidProcess(id))
|
.ok_or(KernelError::InvalidProcess(id))?;
|
||||||
|
self.generations.advance(idx);
|
||||||
|
Ok(process)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProcessTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
use crate::capability::CapabilityTable;
|
use crate::capability::CapabilityTable;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
|
||||||
use types::permission::PermissionSet;
|
use types::permission::PermissionSet;
|
||||||
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
use crate::capability::{Capability, CapabilityTable};
|
use crate::capability::{Capability, CapabilityTable};
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
|
||||||
use types::generation::Generation;
|
use types::generation::Generation;
|
||||||
use types::id::EndpointId;
|
use types::id::EndpointId;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
@@ -16,7 +16,10 @@ fn delegation_via_cap_table_never_escalates() {
|
|||||||
kani::assume(source_perms.allows(Permission::Delegate));
|
kani::assume(source_perms.allows(Permission::Delegate));
|
||||||
|
|
||||||
let mut source_table = CapabilityTable::new();
|
let mut source_table = CapabilityTable::new();
|
||||||
let slot = insert_cap(&mut source_table, arbitrary_endpoint_cap(source_bits, ep_id));
|
let slot = insert_cap(
|
||||||
|
&mut source_table,
|
||||||
|
arbitrary_endpoint_cap(source_bits, ep_id),
|
||||||
|
);
|
||||||
|
|
||||||
let cap = source_table.get(slot).unwrap();
|
let cap = source_table.get(slot).unwrap();
|
||||||
let original_perms = *cap.permissions();
|
let original_perms = *cap.permissions();
|
||||||
@@ -43,11 +46,18 @@ fn delegation_cannot_add_permissions_not_in_source() {
|
|||||||
let source = PermissionSet::from_bits(source_bits);
|
let source = PermissionSet::from_bits(source_bits);
|
||||||
let result = source.restrict_to(&PermissionSet::from_bits(restrict_bits));
|
let result = source.restrict_to(&PermissionSet::from_bits(restrict_bits));
|
||||||
|
|
||||||
if !source.allows(Permission::Read) { assert!(!result.allows(Permission::Read)); }
|
if !source.allows(Permission::Read) {
|
||||||
if !source.allows(Permission::Write) { assert!(!result.allows(Permission::Write)); }
|
assert!(!result.allows(Permission::Read));
|
||||||
if !source.allows(Permission::Execute) { assert!(!result.allows(Permission::Execute)); }
|
}
|
||||||
if !source.allows(Permission::Delegate) { assert!(!result.allows(Permission::Delegate)); }
|
if !source.allows(Permission::Write) {
|
||||||
if !source.allows(Permission::Revoke) { assert!(!result.allows(Permission::Revoke)); }
|
assert!(!result.allows(Permission::Write));
|
||||||
|
}
|
||||||
|
if !source.allows(Permission::Execute) {
|
||||||
|
assert!(!result.allows(Permission::Execute));
|
||||||
|
}
|
||||||
|
if !source.allows(Permission::Delegate) {
|
||||||
|
assert!(!result.allows(Permission::Delegate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fn dead_thread_rejects_all_transitions() {
|
|||||||
|
|
||||||
assert!(matches!(thread.state(), ThreadState::Dead));
|
assert!(matches!(thread.state(), ThreadState::Dead));
|
||||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
assert!(thread.block(BlockReason::AwaitingReply).is_err());
|
||||||
assert!(thread.unblock().is_err());
|
assert!(thread.unblock().is_err());
|
||||||
assert!(thread.preempt().is_err());
|
assert!(thread.preempt().is_err());
|
||||||
assert!(thread.exit().is_err());
|
assert!(thread.exit().is_err());
|
||||||
@@ -36,7 +36,7 @@ fn only_running_thread_can_block() {
|
|||||||
let mut thread = test_thread(Priority::new(prio_val).unwrap());
|
let mut thread = test_thread(Priority::new(prio_val).unwrap());
|
||||||
|
|
||||||
assert!(matches!(thread.state(), ThreadState::Ready));
|
assert!(matches!(thread.state(), ThreadState::Ready));
|
||||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
assert!(thread.block(BlockReason::AwaitingReply).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
@@ -44,7 +44,7 @@ fn blocked_thread_cannot_run_directly() {
|
|||||||
let mut thread = test_thread(Priority::new(16).unwrap());
|
let mut thread = test_thread(Priority::new(16).unwrap());
|
||||||
|
|
||||||
thread.set_running(CoreId::from_raw(0)).unwrap();
|
thread.set_running(CoreId::from_raw(0)).unwrap();
|
||||||
thread.block(BlockReason::WaitingAny).unwrap();
|
thread.block(BlockReason::AwaitingReply).unwrap();
|
||||||
|
|
||||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use types::id::{EndpointId, IrqId, ProcessId, RegionId};
|
use types::id::{EndpointId, IrqId, NotificationId, ProcessId, RegionId};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Resource {
|
pub enum Resource {
|
||||||
Memory(RegionId),
|
Memory(RegionId),
|
||||||
Endpoint(EndpointId),
|
Endpoint(EndpointId),
|
||||||
|
Notification(NotificationId),
|
||||||
Process(ProcessId),
|
Process(ProcessId),
|
||||||
Interrupt(IrqId),
|
Interrupt(IrqId),
|
||||||
ProcessCreate,
|
ProcessCreate,
|
||||||
|
/// Machine-wide controls such as halting; given only to init at boot.
|
||||||
|
SystemControl,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use types::{
|
|||||||
id::{CoreId, ThreadId},
|
id::{CoreId, ThreadId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{per_core::PerCoreScheduler, Scheduler, SchedulerDecision};
|
use super::{Scheduler, SchedulerDecision, per_core::PerCoreScheduler};
|
||||||
|
|
||||||
const MAX_CORES: usize = 8;
|
const MAX_CORES: usize = if cfg!(kani) { 1 } else { 8 };
|
||||||
const MAX_SCHEDULED_THREADS: usize = 256;
|
const MAX_SCHEDULED_THREADS: usize = crate::thread::MAX_THREADS;
|
||||||
const BALANCE_THRESHOLD: usize = 2;
|
const BALANCE_THRESHOLD: usize = 2;
|
||||||
|
|
||||||
struct ThreadPlacement {
|
struct ThreadPlacement {
|
||||||
@@ -25,17 +25,64 @@ impl MultiCoreScheduler {
|
|||||||
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8)));
|
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8)));
|
||||||
Self {
|
Self {
|
||||||
cores,
|
cores,
|
||||||
num_cores,
|
num_cores: num_cores.min(MAX_CORES as u8),
|
||||||
placements: [const { None }; MAX_SCHEDULED_THREADS],
|
placements: [const { None }; MAX_SCHEDULED_THREADS],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn core(&self, core: CoreId) -> Option<&PerCoreScheduler> {
|
||||||
|
self.active().get(core.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn core_mut(&mut self, core: CoreId) -> Option<&mut PerCoreScheduler> {
|
||||||
|
let n = self.num_cores as usize;
|
||||||
|
self.cores.get_mut(..n)?.get_mut(core.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active(&self) -> &[PerCoreScheduler] {
|
||||||
|
self.cores.get(..self.num_cores as usize).unwrap_or(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placement(&self, thread: ThreadId) -> Option<&ThreadPlacement> {
|
||||||
|
self.placements.get(thread.raw() as usize)?.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_cores(&self) -> u8 {
|
||||||
|
self.num_cores
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The highest priority waiting in `core`'s run queue.
|
||||||
|
pub fn highest_waiting(&self, core: CoreId) -> Option<Priority> {
|
||||||
|
self.core(core)?.highest_waiting_priority()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The core whose run queue holds `thread`, if any.
|
||||||
|
pub fn queued_on(&self, thread: ThreadId) -> Option<CoreId> {
|
||||||
|
self.active()
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.is_queued(thread))
|
||||||
|
.map(|c| c.core())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `thread` waits in some core's run queue.
|
||||||
|
pub fn is_queued(&self, thread: ThreadId) -> bool {
|
||||||
|
self.active().iter().any(|c| c.is_queued(thread))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current(&self, core: CoreId) -> Option<ThreadId> {
|
pub fn current(&self, core: CoreId) -> Option<ThreadId> {
|
||||||
self.cores[core.raw() as usize].current()
|
self.core(core)?.current()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current(&mut self, core: CoreId, thread: ThreadId) {
|
pub fn set_current(&mut self, core: CoreId, thread: ThreadId) {
|
||||||
self.cores[core.raw() as usize].set_current(thread);
|
if let Some(c) = self.core_mut(core) {
|
||||||
|
c.set_current(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `thread` next on `core` regardless of its place in the queue.
|
||||||
|
/// Fails if it is not queued there.
|
||||||
|
pub fn run_now(&mut self, core: CoreId, thread: ThreadId) -> bool {
|
||||||
|
self.core_mut(core).is_some_and(|c| c.run_now(thread))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thread_count_on(&self, core: CoreId) -> usize {
|
fn thread_count_on(&self, core: CoreId) -> usize {
|
||||||
@@ -47,53 +94,63 @@ impl MultiCoreScheduler {
|
|||||||
|
|
||||||
fn least_loaded_core(&self) -> Option<CoreId> {
|
fn least_loaded_core(&self) -> Option<CoreId> {
|
||||||
(0..self.num_cores)
|
(0..self.num_cores)
|
||||||
.map(|i| CoreId::from_raw(i))
|
.map(CoreId::from_raw)
|
||||||
.min_by_key(|&c| self.thread_count_on(c))
|
.min_by_key(|&c| self.thread_count_on(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn most_loaded_core(&self) -> Option<CoreId> {
|
fn most_loaded_core(&self) -> Option<CoreId> {
|
||||||
(0..self.num_cores)
|
(0..self.num_cores)
|
||||||
.map(|i| CoreId::from_raw(i))
|
.map(CoreId::from_raw)
|
||||||
.max_by_key(|&c| self.thread_count_on(c))
|
.max_by_key(|&c| self.thread_count_on(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scheduler for MultiCoreScheduler {
|
impl Scheduler for MultiCoreScheduler {
|
||||||
fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId) {
|
fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId) {
|
||||||
let idx = thread.raw() as usize;
|
if self.core(core).is_none() {
|
||||||
self.placements[idx] = Some(ThreadPlacement { core, priority });
|
return;
|
||||||
self.cores[core.raw() as usize].add(thread, priority);
|
}
|
||||||
|
let Some(placement) = self.placements.get_mut(thread.raw() as usize) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*placement = Some(ThreadPlacement { core, priority });
|
||||||
|
if let Some(c) = self.core_mut(core) {
|
||||||
|
c.add(thread, priority);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&mut self, thread: ThreadId) {
|
fn remove(&mut self, thread: ThreadId) {
|
||||||
let idx = thread.raw() as usize;
|
let Some(placement) = self
|
||||||
if let Some(placement) = self.placements[idx].take() {
|
.placements
|
||||||
let core_idx = placement.core.raw() as usize;
|
.get_mut(thread.raw() as usize)
|
||||||
self.cores[core_idx].remove(thread, placement.priority);
|
.and_then(Option::take)
|
||||||
if self.cores[core_idx].current() == Some(thread) {
|
else {
|
||||||
self.cores[core_idx].clear_current();
|
return;
|
||||||
|
};
|
||||||
|
if let Some(c) = self.core_mut(placement.core) {
|
||||||
|
c.remove(thread);
|
||||||
|
if c.current() == Some(thread) {
|
||||||
|
c.clear_current();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self, core: CoreId) -> Option<ThreadId> {
|
fn next(&mut self, core: CoreId) -> Option<ThreadId> {
|
||||||
self.cores[core.raw() as usize].pick_next()
|
self.core_mut(core)?.pick_next()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision {
|
fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision {
|
||||||
let idx = current.raw() as usize;
|
let Some(current_priority) = self.placement(current).map(|p| p.priority) else {
|
||||||
if let Some(placement) = &self.placements[idx] {
|
return SchedulerDecision::Continue;
|
||||||
let current_priority = placement.priority;
|
};
|
||||||
let core_idx = core.raw() as usize;
|
if let Some(c) = self.core_mut(core)
|
||||||
if let Some(highest) = self.cores[core_idx].highest_waiting_priority() {
|
&& let Some(highest) = c.highest_waiting_priority()
|
||||||
if highest > current_priority {
|
&& highest > current_priority
|
||||||
if let Some(next) = self.cores[core_idx].pick_next() {
|
&& let Some(next) = c.pick_next()
|
||||||
self.cores[core_idx].add(current, current_priority);
|
{
|
||||||
|
c.add(current, current_priority);
|
||||||
return SchedulerDecision::Preempt(next);
|
return SchedulerDecision::Preempt(next);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SchedulerDecision::Continue
|
SchedulerDecision::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +160,14 @@ impl Scheduler for MultiCoreScheduler {
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.thread_count_on(busiest) > self.thread_count_on(lightest) + BALANCE_THRESHOLD {
|
if self.thread_count_on(busiest) > self.thread_count_on(lightest) + BALANCE_THRESHOLD
|
||||||
let busiest_idx = busiest.raw() as usize;
|
&& let Some((thread, priority)) = self.core_mut(busiest).and_then(|c| c.steal_lowest())
|
||||||
if let Some((thread, priority)) = self.cores[busiest_idx].steal_lowest() {
|
{
|
||||||
let lightest_idx = lightest.raw() as usize;
|
if let Some(c) = self.core_mut(lightest) {
|
||||||
self.cores[lightest_idx].add(thread, priority);
|
c.add(thread, priority);
|
||||||
let tid = thread.raw() as usize;
|
}
|
||||||
self.placements[tid] = Some(ThreadPlacement {
|
if let Some(p) = self.placements.get_mut(thread.raw() as usize) {
|
||||||
|
*p = Some(ThreadPlacement {
|
||||||
core: lightest,
|
core: lightest,
|
||||||
priority,
|
priority,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,16 +29,29 @@ impl PerCoreScheduler {
|
|||||||
self.queue.add(thread, priority);
|
self.queue.add(thread, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn is_queued(&self, thread: ThreadId) -> bool {
|
||||||
self.queue.remove(thread, priority);
|
self.queue.contains(thread)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, thread: ThreadId) {
|
||||||
|
self.queue.remove(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
pub fn pick_next(&mut self) -> Option<ThreadId> {
|
||||||
let (thread, _priority) = self.queue.next()?;
|
let (thread, _priority) = self.queue.pop_highest()?;
|
||||||
self.current = Some(thread);
|
self.current = Some(thread);
|
||||||
Some(thread)
|
Some(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes `thread` out of the run queue and makes it current.
|
||||||
|
pub fn run_now(&mut self, thread: ThreadId) -> bool {
|
||||||
|
if !self.queue.remove(thread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.current = Some(thread);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_current(&mut self) {
|
pub fn clear_current(&mut self) {
|
||||||
self.current = None;
|
self.current = None;
|
||||||
}
|
}
|
||||||
@@ -56,6 +69,10 @@ impl PerCoreScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.queue.bitmap().count_ones() as usize
|
self.queue.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.queue.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,71 +2,42 @@ use types::bounded::Priority;
|
|||||||
use types::id::ThreadId;
|
use types::id::ThreadId;
|
||||||
|
|
||||||
const NUM_PRIORITIES: usize = 32;
|
const NUM_PRIORITIES: usize = 32;
|
||||||
const MAX_THREADS_PER_PRIORITY: usize = 16;
|
const MAX_THREADS: usize = crate::thread::MAX_THREADS;
|
||||||
|
|
||||||
struct RunList {
|
#[derive(Clone, Copy)]
|
||||||
threads: [Option<ThreadId>; MAX_THREADS_PER_PRIORITY],
|
struct Links {
|
||||||
head: usize,
|
prev: Option<ThreadId>,
|
||||||
tail: usize,
|
next: Option<ThreadId>,
|
||||||
count: u8,
|
queued_at: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunList {
|
#[derive(Clone, Copy)]
|
||||||
fn new() -> Self {
|
struct ListEnds {
|
||||||
Self {
|
head: Option<ThreadId>,
|
||||||
threads: [None; MAX_THREADS_PER_PRIORITY],
|
tail: Option<ThreadId>,
|
||||||
head: 0,
|
|
||||||
tail: 0,
|
|
||||||
count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, thread: ThreadId) -> bool {
|
|
||||||
match self.threads.iter_mut().find(|s| s.is_none()) {
|
|
||||||
Some(slot) => {
|
|
||||||
*slot = Some(thread);
|
|
||||||
self.count += 1;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(&mut self) -> Option<ThreadId> {
|
|
||||||
if self.count == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let slot = self.threads.iter_mut().find(|s| s.is_some())?;
|
|
||||||
let thread = slot.take();
|
|
||||||
self.count -= 1;
|
|
||||||
thread
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, thread: ThreadId) -> bool {
|
|
||||||
for slot in &mut self.threads {
|
|
||||||
if *slot == Some(thread) {
|
|
||||||
*slot = None;
|
|
||||||
self.count -= 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.count == 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ready queue: one FIFO per priority level, threaded through an
|
||||||
|
/// intrusive doubly linked list indexed by thread id. A thread is in at
|
||||||
|
/// most one list, so capacity never runs out and every op is O(1).
|
||||||
pub struct PriorityQueue {
|
pub struct PriorityQueue {
|
||||||
queues: [RunList; NUM_PRIORITIES],
|
lists: [ListEnds; NUM_PRIORITIES],
|
||||||
|
links: [Links; MAX_THREADS],
|
||||||
bitmap: u32,
|
bitmap: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriorityQueue {
|
impl PriorityQueue {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
queues: core::array::from_fn(|_| RunList::new()),
|
lists: [ListEnds {
|
||||||
|
head: None,
|
||||||
|
tail: None,
|
||||||
|
}; NUM_PRIORITIES],
|
||||||
|
links: [Links {
|
||||||
|
prev: None,
|
||||||
|
next: None,
|
||||||
|
queued_at: None,
|
||||||
|
}; MAX_THREADS],
|
||||||
bitmap: 0,
|
bitmap: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,34 +46,107 @@ impl PriorityQueue {
|
|||||||
self.bitmap
|
self.bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn len(&self) -> usize {
|
||||||
let idx = priority.value() as usize;
|
self.links.iter().filter(|l| l.queued_at.is_some()).count()
|
||||||
let added = self.queues[idx].push(thread);
|
|
||||||
debug_assert!(added, "scheduler run list full for priority {}", idx);
|
|
||||||
if added {
|
|
||||||
self.bitmap |= 1 << idx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, thread: ThreadId, priority: Priority) {
|
pub fn contains(&self, thread: ThreadId) -> bool {
|
||||||
|
self.link(thread).is_some_and(|l| l.queued_at.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link(&self, thread: ThreadId) -> Option<&Links> {
|
||||||
|
self.links.get(thread.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn links_mut(&mut self, thread: ThreadId) -> Option<&mut Links> {
|
||||||
|
self.links.get_mut(thread.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends `thread` at `priority`. A thread that is already queued is
|
||||||
|
/// left where it is; returns whether it was added.
|
||||||
|
pub fn add(&mut self, thread: ThreadId, priority: Priority) -> bool {
|
||||||
let idx = priority.value() as usize;
|
let idx = priority.value() as usize;
|
||||||
self.queues[idx].remove(thread);
|
let queued = self.link(thread).is_none_or(|l| l.queued_at.is_some());
|
||||||
if self.queues[idx].is_empty() {
|
let Some(old_tail) = self.lists.get(idx).map(|l| l.tail) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if queued {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(l) = self.links_mut(thread) {
|
||||||
|
*l = Links {
|
||||||
|
prev: old_tail,
|
||||||
|
next: None,
|
||||||
|
queued_at: Some(idx as u8),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match old_tail.and_then(|t| self.links_mut(t)) {
|
||||||
|
Some(tail) => tail.next = Some(thread),
|
||||||
|
None => {
|
||||||
|
if let Some(list) = self.lists.get_mut(idx) {
|
||||||
|
list.head = Some(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(list) = self.lists.get_mut(idx) {
|
||||||
|
list.tail = Some(thread);
|
||||||
|
}
|
||||||
|
self.bitmap |= 1 << idx;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlinks `thread` from whichever level it is queued at.
|
||||||
|
pub fn remove(&mut self, thread: ThreadId) -> bool {
|
||||||
|
let Some(Links {
|
||||||
|
prev,
|
||||||
|
next,
|
||||||
|
queued_at: Some(level),
|
||||||
|
}) = self.link(thread).copied()
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let idx = level as usize;
|
||||||
|
match prev.and_then(|p| self.links_mut(p)) {
|
||||||
|
Some(p) => p.next = next,
|
||||||
|
None => {
|
||||||
|
if let Some(list) = self.lists.get_mut(idx) {
|
||||||
|
list.head = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match next.and_then(|n| self.links_mut(n)) {
|
||||||
|
Some(n) => n.prev = prev,
|
||||||
|
None => {
|
||||||
|
if let Some(list) = self.lists.get_mut(idx) {
|
||||||
|
list.tail = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(l) = self.links_mut(thread) {
|
||||||
|
*l = Links {
|
||||||
|
prev: None,
|
||||||
|
next: None,
|
||||||
|
queued_at: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if self.lists.get(idx).is_none_or(|l| l.head.is_none()) {
|
||||||
self.bitmap &= !(1 << idx);
|
self.bitmap &= !(1 << idx);
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> Option<(ThreadId, Priority)> {
|
fn pop_level(&mut self, idx: usize) -> Option<(ThreadId, Priority)> {
|
||||||
|
let thread = self.lists.get(idx)?.head?;
|
||||||
|
self.remove(thread);
|
||||||
|
Some((thread, Priority::new(idx as u8)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_highest(&mut self) -> Option<(ThreadId, Priority)> {
|
||||||
if self.bitmap == 0 {
|
if self.bitmap == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// highest set bit = highest priority
|
// highest set bit = highest priority
|
||||||
let idx = 31 - self.bitmap.leading_zeros() as usize;
|
self.pop_level(31 - self.bitmap.leading_zeros() as usize)
|
||||||
let thread = self.queues[idx].pop()?;
|
|
||||||
if self.queues[idx].is_empty() {
|
|
||||||
self.bitmap &= !(1 << idx);
|
|
||||||
}
|
|
||||||
Some((thread, Priority::new(idx as u8)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highest_priority(&self) -> Option<Priority> {
|
pub fn highest_priority(&self) -> Option<Priority> {
|
||||||
@@ -117,15 +161,16 @@ impl PriorityQueue {
|
|||||||
if self.bitmap == 0 {
|
if self.bitmap == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let idx = self.bitmap.trailing_zeros() as usize;
|
self.pop_level(self.bitmap.trailing_zeros() as usize)
|
||||||
let thread = self.queues[idx].pop()?;
|
|
||||||
if self.queues[idx].is_empty() {
|
|
||||||
self.bitmap &= !(1 << idx);
|
|
||||||
}
|
|
||||||
Some((thread, Priority::new(idx as u8)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.bitmap == 0
|
self.bitmap == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PriorityQueue {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fn prio(value: u8) -> Priority {
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_queue_returns_none() {
|
fn empty_queue_returns_none() {
|
||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ fn single_thread() {
|
|||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
|
|
||||||
let (t, p) = q.next().unwrap();
|
let (t, p) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(1));
|
assert_eq!(t, thread(1));
|
||||||
assert_eq!(p, prio(10));
|
assert_eq!(p, prio(10));
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ fn highest_priority_runs_first() {
|
|||||||
q.add(thread(2), prio(20));
|
q.add(thread(2), prio(20));
|
||||||
q.add(thread(3), prio(10));
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
let (t, _) = q.next().unwrap();
|
let (t, _) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(2));
|
assert_eq!(t, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,10 +54,10 @@ fn same_priority_is_fifo() {
|
|||||||
q.add(thread(2), prio(10));
|
q.add(thread(2), prio(10));
|
||||||
q.add(thread(3), prio(10));
|
q.add(thread(3), prio(10));
|
||||||
|
|
||||||
assert_eq!(q.next().unwrap().0, thread(1));
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
assert_eq!(q.next().unwrap().0, thread(3));
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -66,18 +66,18 @@ fn remove_thread() {
|
|||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
q.add(thread(2), prio(10));
|
q.add(thread(2), prio(10));
|
||||||
|
|
||||||
q.remove(thread(1), prio(10));
|
q.remove(thread(1));
|
||||||
|
|
||||||
let (t, _) = q.next().unwrap();
|
let (t, _) = q.pop_highest().unwrap();
|
||||||
assert_eq!(t, thread(2));
|
assert_eq!(t, thread(2));
|
||||||
assert!(q.next().is_none());
|
assert!(q.pop_highest().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_clears_bitmap_when_empty() {
|
fn remove_clears_bitmap_when_empty() {
|
||||||
let mut q = PriorityQueue::new();
|
let mut q = PriorityQueue::new();
|
||||||
q.add(thread(1), prio(10));
|
q.add(thread(1), prio(10));
|
||||||
q.remove(thread(1), prio(10));
|
q.remove(thread(1));
|
||||||
|
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
@@ -89,9 +89,9 @@ fn drain_all_priorities() {
|
|||||||
q.add(thread(2), prio(15));
|
q.add(thread(2), prio(15));
|
||||||
q.add(thread(3), prio(31));
|
q.add(thread(3), prio(31));
|
||||||
|
|
||||||
assert_eq!(q.next().unwrap().0, thread(3)); // highest first
|
assert_eq!(q.pop_highest().unwrap().0, thread(3)); // highest first
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
assert_eq!(q.next().unwrap().0, thread(1)); // lowest last
|
assert_eq!(q.pop_highest().unwrap().0, thread(1)); // lowest last
|
||||||
assert!(q.is_empty());
|
assert!(q.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ fn highest_priority_peeks_without_popping() {
|
|||||||
q.add(thread(1), prio(5));
|
q.add(thread(1), prio(5));
|
||||||
q.add(thread(2), prio(20));
|
q.add(thread(2), prio(20));
|
||||||
assert_eq!(q.highest_priority(), Some(prio(20)));
|
assert_eq!(q.highest_priority(), Some(prio(20)));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -154,7 +154,7 @@ fn steal_lowest_takes_lowest_priority_thread() {
|
|||||||
let (t, p) = q.steal_lowest().unwrap();
|
let (t, p) = q.steal_lowest().unwrap();
|
||||||
assert_eq!(t, thread(1));
|
assert_eq!(t, thread(1));
|
||||||
assert_eq!(p, prio(5));
|
assert_eq!(p, prio(5));
|
||||||
assert_eq!(q.next().unwrap().0, thread(2));
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -240,3 +240,89 @@ fn multi_threads_on_different_cores() {
|
|||||||
assert_eq!(s.next(core(0)), Some(thread(1)));
|
assert_eq!(s.next(core(0)), Some(thread(1)));
|
||||||
assert_eq!(s.next(core(1)), Some(thread(2)));
|
assert_eq!(s.next(core(1)), Some(thread(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn requeued_thread_goes_behind_same_priority_peers() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
q.add(thread(1), p); // preempted, back in the run list
|
||||||
|
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_robin_survives_wraparound() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
q.add(thread(3), p);
|
||||||
|
for _ in 0..40 {
|
||||||
|
let (a, _) = q.pop_highest().unwrap();
|
||||||
|
q.add(a, p);
|
||||||
|
}
|
||||||
|
// 40 rotations of 3 threads: 40 % 3 == 1, so thread 2 is at the head
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn removed_thread_is_skipped_and_order_kept() {
|
||||||
|
let mut q = PriorityQueue::new();
|
||||||
|
let p = Priority::new(5).unwrap();
|
||||||
|
q.add(thread(1), p);
|
||||||
|
q.add(thread(2), p);
|
||||||
|
q.add(thread(3), p);
|
||||||
|
q.remove(thread(2));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(1));
|
||||||
|
assert_eq!(q.pop_highest().unwrap().0, thread(3));
|
||||||
|
assert!(q.pop_highest().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_now_picks_the_named_thread_not_the_queue_head() {
|
||||||
|
let mut s = MultiCoreScheduler::new(1);
|
||||||
|
s.add(thread(1), prio(10), core(0));
|
||||||
|
s.add(thread(2), prio(10), core(0));
|
||||||
|
s.add(thread(3), prio(10), core(0));
|
||||||
|
|
||||||
|
assert!(s.run_now(core(0), thread(2)));
|
||||||
|
assert_eq!(s.current(core(0)), Some(thread(2)));
|
||||||
|
// The others keep their order; thread 2 is no longer queued.
|
||||||
|
assert_eq!(s.next(core(0)), Some(thread(1)));
|
||||||
|
assert_eq!(s.next(core(0)), Some(thread(3)));
|
||||||
|
assert_eq!(s.next(core(0)), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_now_rejects_a_thread_that_is_not_queued() {
|
||||||
|
let mut s = MultiCoreScheduler::new(1);
|
||||||
|
s.add(thread(1), prio(10), core(0));
|
||||||
|
assert!(!s.run_now(core(0), thread(9)));
|
||||||
|
assert_eq!(s.current(core(0)), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_unknown_core_is_ignored_not_a_panic() {
|
||||||
|
let mut s = MultiCoreScheduler::new(1);
|
||||||
|
s.add(thread(1), prio(10), core(200));
|
||||||
|
assert_eq!(s.next(core(200)), None);
|
||||||
|
assert_eq!(s.current(core(200)), None);
|
||||||
|
s.set_current(core(200), thread(1));
|
||||||
|
assert!(!s.run_now(core(200), thread(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_out_of_range_thread_id_is_ignored_not_a_panic() {
|
||||||
|
let mut s = MultiCoreScheduler::new(1);
|
||||||
|
s.add(thread(60000), prio(10), core(0));
|
||||||
|
s.remove(thread(60000));
|
||||||
|
assert!(!s.is_queued(thread(60000)));
|
||||||
|
let _ = s.tick(core(0), thread(60000));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +1,13 @@
|
|||||||
use types::{
|
use types::{
|
||||||
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
|
badge::Badge,
|
||||||
bounded::{CapSlot, PageCount},
|
bounded::{CapSlot, PageCount},
|
||||||
|
mapping::PagePerms,
|
||||||
permission::PermissionSet,
|
permission::PermissionSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
|
|
||||||
pub struct WaitSet {
|
|
||||||
endpoints: [Option<CapSlot>; 4],
|
|
||||||
interrupts: [Option<CapSlot>; 4],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WaitSet {
|
|
||||||
pub fn new(
|
|
||||||
endpoints: [Option<CapSlot>; 4],
|
|
||||||
interrupts: [Option<CapSlot>; 4],
|
|
||||||
) -> Self {
|
|
||||||
Self { endpoints, interrupts }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn endpoints(&self) -> &[Option<CapSlot>; 4] {
|
|
||||||
&self.endpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interrupts(&self) -> &[Option<CapSlot>; 4] {
|
|
||||||
&self.interrupts
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.endpoints.iter().all(|s| s.is_none())
|
|
||||||
&& self.interrupts.iter().all(|s| s.is_none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum WakeReason {
|
|
||||||
MessageReady(CapSlot),
|
|
||||||
InterruptFired(CapSlot),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Syscall {
|
pub enum Syscall {
|
||||||
Send {
|
Send {
|
||||||
endpoint: CapSlot,
|
endpoint: CapSlot,
|
||||||
@@ -51,15 +21,47 @@ pub enum Syscall {
|
|||||||
to_endpoint: CapSlot,
|
to_endpoint: CapSlot,
|
||||||
permissions: PermissionSet,
|
permissions: PermissionSet,
|
||||||
},
|
},
|
||||||
WaitForInterrupt {
|
/// Send, then block for the reply (docs/ipc.md).
|
||||||
|
Call {
|
||||||
|
endpoint: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
|
/// Answer the thread in the caller's reply slot.
|
||||||
|
Reply {
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
|
/// Reply (if a reply is pending), then receive.
|
||||||
|
ReplyRecv {
|
||||||
|
endpoint: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
},
|
||||||
|
/// Copy an unbadged endpoint/notification cap with a badge and
|
||||||
|
/// narrower permissions into the caller's own table.
|
||||||
|
Mint {
|
||||||
|
slot: CapSlot,
|
||||||
|
badge: Badge,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
},
|
||||||
|
CreateNotification,
|
||||||
|
Signal {
|
||||||
|
notification: CapSlot,
|
||||||
|
},
|
||||||
|
Wait {
|
||||||
|
notification: CapSlot,
|
||||||
|
},
|
||||||
|
Poll {
|
||||||
|
notification: CapSlot,
|
||||||
|
},
|
||||||
|
BindNotification {
|
||||||
|
notification: CapSlot,
|
||||||
|
},
|
||||||
|
SetIrqNotification {
|
||||||
irq: CapSlot,
|
irq: CapSlot,
|
||||||
|
notification: CapSlot,
|
||||||
},
|
},
|
||||||
AcknowledgeInterrupt {
|
AcknowledgeInterrupt {
|
||||||
irq: CapSlot,
|
irq: CapSlot,
|
||||||
},
|
},
|
||||||
WaitAny {
|
|
||||||
sources: WaitSet,
|
|
||||||
},
|
|
||||||
CreateProcess {
|
CreateProcess {
|
||||||
create_cap: CapSlot,
|
create_cap: CapSlot,
|
||||||
image: DataRef,
|
image: DataRef,
|
||||||
@@ -67,9 +69,30 @@ pub enum Syscall {
|
|||||||
Kill {
|
Kill {
|
||||||
process: CapSlot,
|
process: CapSlot,
|
||||||
},
|
},
|
||||||
|
/// Deletes every capability derived from `slot`, keeping `slot`.
|
||||||
Revoke {
|
Revoke {
|
||||||
slot: CapSlot,
|
slot: CapSlot,
|
||||||
},
|
},
|
||||||
|
/// Allocates a shared-memory region charged to the caller and returns
|
||||||
|
/// a capability to it.
|
||||||
|
CreateRegion {
|
||||||
|
pages: PageCount,
|
||||||
|
},
|
||||||
|
/// Maps the whole region named by `region` at `va` in the caller.
|
||||||
|
MapRegion {
|
||||||
|
region: CapSlot,
|
||||||
|
va: VirtualAddress,
|
||||||
|
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.
|
||||||
|
DropCap {
|
||||||
|
slot: CapSlot,
|
||||||
|
},
|
||||||
SpawnThread {
|
SpawnThread {
|
||||||
entry: EntryPoint,
|
entry: EntryPoint,
|
||||||
stack: StackPointer,
|
stack: StackPointer,
|
||||||
@@ -86,9 +109,14 @@ pub enum Syscall {
|
|||||||
GetProcessId,
|
GetProcessId,
|
||||||
Yield,
|
Yield,
|
||||||
Exit,
|
Exit,
|
||||||
Halt,
|
/// Stops the machine; needs a SystemControl capability.
|
||||||
|
Halt {
|
||||||
|
control: CapSlot,
|
||||||
|
},
|
||||||
CreateEndpoint,
|
CreateEndpoint,
|
||||||
DebugPutchar { byte: u8 },
|
DebugPutchar {
|
||||||
|
byte: u8,
|
||||||
|
},
|
||||||
MapPage {
|
MapPage {
|
||||||
process: CapSlot,
|
process: CapSlot,
|
||||||
src_va: VirtualAddress,
|
src_va: VirtualAddress,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use types::{
|
use types::{
|
||||||
bounded::{CapSlot, Priority},
|
bounded::Priority,
|
||||||
id::{CoreId, ProcessId, ThreadId},
|
generation::Generation,
|
||||||
|
id::{CoreId, EndpointId, NotificationId, ProcessId, ThreadId},
|
||||||
time::TimeSlice,
|
time::TimeSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::KernelError;
|
use crate::{error::KernelError, generations::SlotGenerations};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "tests.rs"]
|
#[path = "tests.rs"]
|
||||||
@@ -20,6 +21,7 @@ pub struct Thread {
|
|||||||
core_affinity: Option<CoreId>,
|
core_affinity: Option<CoreId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ThreadState {
|
pub enum ThreadState {
|
||||||
Ready,
|
Ready,
|
||||||
Running(CoreId),
|
Running(CoreId),
|
||||||
@@ -77,11 +79,18 @@ impl SavedContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum BlockReason {
|
pub enum BlockReason {
|
||||||
Sending(CapSlot),
|
/// Queued as a sender on the endpoint.
|
||||||
Receiving(CapSlot),
|
Sending(EndpointId),
|
||||||
WaitingForInterrupt(CapSlot),
|
/// Queued as a `Call`er on the endpoint; will then await the reply.
|
||||||
WaitingAny,
|
Calling(EndpointId),
|
||||||
|
/// Queued as a receiver on the endpoint.
|
||||||
|
Receiving(EndpointId),
|
||||||
|
/// Delivered a `Call`; waiting for the server's reply.
|
||||||
|
AwaitingReply,
|
||||||
|
/// Blocked in `Wait` on the notification.
|
||||||
|
WaitingNotification(NotificationId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
@@ -178,6 +187,17 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keeps a blocked thread blocked, waiting for something else.
|
||||||
|
pub fn rewait(&mut self, reason: BlockReason) -> Result<(), KernelError> {
|
||||||
|
match self.state {
|
||||||
|
ThreadState::Blocked(_) => {
|
||||||
|
self.state = ThreadState::Blocked(reason);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(KernelError::InvalidStateTransition(self.id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unblock(&mut self) -> Result<(), KernelError> {
|
pub fn unblock(&mut self) -> Result<(), KernelError> {
|
||||||
match self.state {
|
match self.state {
|
||||||
ThreadState::Blocked(_) => {
|
ThreadState::Blocked(_) => {
|
||||||
@@ -209,19 +229,35 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MAX_THREADS: usize = 256;
|
pub const MAX_THREADS: usize = if cfg!(kani) { 3 } else { 256 };
|
||||||
|
|
||||||
pub struct ThreadTable {
|
pub struct ThreadTable {
|
||||||
threads: [Option<Thread>; MAX_THREADS],
|
threads: [Option<Thread>; MAX_THREADS],
|
||||||
|
generations: SlotGenerations<MAX_THREADS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadTable {
|
impl ThreadTable {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
threads: [const { None }; MAX_THREADS],
|
threads: [const { None }; MAX_THREADS],
|
||||||
|
generations: SlotGenerations::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current generation of a live thread, so references to it (reply
|
||||||
|
/// slots) can detect that the id was reused.
|
||||||
|
pub fn generation(&self, id: ThreadId) -> Option<Generation> {
|
||||||
|
self.get(id).ok()?;
|
||||||
|
self.generations.current(id.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_slot(&self) -> Option<usize> {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.position(|(i, t)| t.is_none() && self.generations.is_usable(i))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
pub fn spawn(
|
||||||
&mut self,
|
&mut self,
|
||||||
process: ProcessId,
|
process: ProcessId,
|
||||||
@@ -229,14 +265,19 @@ impl ThreadTable {
|
|||||||
time_slice: TimeSlice,
|
time_slice: TimeSlice,
|
||||||
core_affinity: Option<CoreId>,
|
core_affinity: Option<CoreId>,
|
||||||
) -> Result<ThreadId, KernelError> {
|
) -> Result<ThreadId, KernelError> {
|
||||||
let idx = self
|
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||||
.threads
|
|
||||||
.iter()
|
|
||||||
.position(|s| s.is_none())
|
|
||||||
.ok_or(KernelError::TooManyThreads)?;
|
|
||||||
|
|
||||||
let id = ThreadId::from_raw(idx as u16);
|
let id = ThreadId::from_raw(idx as u16);
|
||||||
self.threads[idx] = Some(Thread::new(id, process, priority, time_slice, core_affinity));
|
*self
|
||||||
|
.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.ok_or(KernelError::TooManyThreads)? = Some(Thread::new(
|
||||||
|
id,
|
||||||
|
process,
|
||||||
|
priority,
|
||||||
|
time_slice,
|
||||||
|
core_affinity,
|
||||||
|
));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,17 +289,31 @@ impl ThreadTable {
|
|||||||
core_affinity: Option<CoreId>,
|
core_affinity: Option<CoreId>,
|
||||||
context: SavedContext,
|
context: SavedContext,
|
||||||
) -> Result<ThreadId, KernelError> {
|
) -> Result<ThreadId, KernelError> {
|
||||||
let idx = self
|
let idx = self.free_slot().ok_or(KernelError::TooManyThreads)?;
|
||||||
.threads
|
|
||||||
.iter()
|
|
||||||
.position(|s| s.is_none())
|
|
||||||
.ok_or(KernelError::TooManyThreads)?;
|
|
||||||
|
|
||||||
let id = ThreadId::from_raw(idx as u16);
|
let id = ThreadId::from_raw(idx as u16);
|
||||||
self.threads[idx] = Some(Thread::with_context(id, process, priority, time_slice, core_affinity, context));
|
*self
|
||||||
|
.threads
|
||||||
|
.get_mut(idx)
|
||||||
|
.ok_or(KernelError::TooManyThreads)? = Some(Thread::with_context(
|
||||||
|
id,
|
||||||
|
process,
|
||||||
|
priority,
|
||||||
|
time_slice,
|
||||||
|
core_affinity,
|
||||||
|
context,
|
||||||
|
));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every live thread with its id.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (ThreadId, &Thread)> {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, t)| Some((ThreadId::from_raw(i as u16), t.as_ref()?)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: ThreadId) -> Result<&Thread, KernelError> {
|
pub fn get(&self, id: ThreadId) -> Result<&Thread, KernelError> {
|
||||||
let idx = id.raw() as usize;
|
let idx = id.raw() as usize;
|
||||||
self.threads
|
self.threads
|
||||||
@@ -277,10 +332,13 @@ impl ThreadTable {
|
|||||||
|
|
||||||
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
|
pub fn destroy(&mut self, id: ThreadId) -> Result<Thread, KernelError> {
|
||||||
let idx = id.raw() as usize;
|
let idx = id.raw() as usize;
|
||||||
self.threads
|
let thread = self
|
||||||
|
.threads
|
||||||
.get_mut(idx)
|
.get_mut(idx)
|
||||||
.and_then(|s| s.take())
|
.and_then(|s| s.take())
|
||||||
.ok_or(KernelError::InvalidThread(id))
|
.ok_or(KernelError::InvalidThread(id))?;
|
||||||
|
self.generations.advance(idx);
|
||||||
|
Ok(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
pub fn threads_for_process(&self, process: ProcessId) -> impl Iterator<Item = &Thread> {
|
||||||
@@ -290,3 +348,9 @@ impl ThreadTable {
|
|||||||
.filter(move |t| t.process() == process)
|
.filter(move |t| t.process() == process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ThreadTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use types::bounded::Priority;
|
use types::bounded::Priority;
|
||||||
use types::id::{CoreId, ProcessId, ThreadId};
|
use types::id::{CoreId, EndpointId, ProcessId, ThreadId};
|
||||||
use types::time::TimeSlice;
|
use types::time::TimeSlice;
|
||||||
|
|
||||||
fn thread(id: u16) -> ThreadId {
|
fn thread(id: u16) -> ThreadId {
|
||||||
@@ -65,15 +65,18 @@ fn ready_to_running() {
|
|||||||
fn running_to_blocked() {
|
fn running_to_blocked() {
|
||||||
let mut t = test_thread(0, 0);
|
let mut t = test_thread(0, 0);
|
||||||
t.set_running(core(0)).unwrap();
|
t.set_running(core(0)).unwrap();
|
||||||
t.block(BlockReason::WaitingAny).unwrap();
|
t.block(BlockReason::AwaitingReply).unwrap();
|
||||||
assert!(matches!(t.state(), ThreadState::Blocked(BlockReason::WaitingAny)));
|
assert!(matches!(
|
||||||
|
t.state(),
|
||||||
|
ThreadState::Blocked(BlockReason::AwaitingReply)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn blocked_to_ready() {
|
fn blocked_to_ready() {
|
||||||
let mut t = test_thread(0, 0);
|
let mut t = test_thread(0, 0);
|
||||||
t.set_running(core(0)).unwrap();
|
t.set_running(core(0)).unwrap();
|
||||||
t.block(BlockReason::WaitingAny).unwrap();
|
t.block(BlockReason::AwaitingReply).unwrap();
|
||||||
t.unblock().unwrap();
|
t.unblock().unwrap();
|
||||||
assert!(matches!(t.state(), ThreadState::Ready));
|
assert!(matches!(t.state(), ThreadState::Ready));
|
||||||
}
|
}
|
||||||
@@ -102,7 +105,7 @@ fn dead_cannot_transition() {
|
|||||||
t.set_running(core(0)).unwrap();
|
t.set_running(core(0)).unwrap();
|
||||||
t.exit().unwrap();
|
t.exit().unwrap();
|
||||||
assert!(t.set_running(core(0)).is_err());
|
assert!(t.set_running(core(0)).is_err());
|
||||||
assert!(t.block(BlockReason::WaitingAny).is_err());
|
assert!(t.block(BlockReason::AwaitingReply).is_err());
|
||||||
assert!(t.unblock().is_err());
|
assert!(t.unblock().is_err());
|
||||||
assert!(t.preempt().is_err());
|
assert!(t.preempt().is_err());
|
||||||
assert!(t.exit().is_err());
|
assert!(t.exit().is_err());
|
||||||
@@ -111,14 +114,14 @@ fn dead_cannot_transition() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn ready_cannot_block() {
|
fn ready_cannot_block() {
|
||||||
let mut t = test_thread(0, 0);
|
let mut t = test_thread(0, 0);
|
||||||
assert!(t.block(BlockReason::WaitingAny).is_err());
|
assert!(t.block(BlockReason::AwaitingReply).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn blocked_cannot_run_directly() {
|
fn blocked_cannot_run_directly() {
|
||||||
let mut t = test_thread(0, 0);
|
let mut t = test_thread(0, 0);
|
||||||
t.set_running(core(0)).unwrap();
|
t.set_running(core(0)).unwrap();
|
||||||
t.block(BlockReason::WaitingAny).unwrap();
|
t.block(BlockReason::AwaitingReply).unwrap();
|
||||||
assert!(t.set_running(core(0)).is_err());
|
assert!(t.set_running(core(0)).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +141,7 @@ fn ready_cannot_exit() {
|
|||||||
fn blocked_cannot_preempt() {
|
fn blocked_cannot_preempt() {
|
||||||
let mut t = test_thread(0, 0);
|
let mut t = test_thread(0, 0);
|
||||||
t.set_running(core(0)).unwrap();
|
t.set_running(core(0)).unwrap();
|
||||||
t.block(BlockReason::WaitingAny).unwrap();
|
t.block(BlockReason::AwaitingReply).unwrap();
|
||||||
assert!(t.preempt().is_err());
|
assert!(t.preempt().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +165,9 @@ fn time_slice_mut_ticks() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_spawn_and_lookup() {
|
fn table_spawn_and_lookup() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let id = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
let t = table.get(id).unwrap();
|
let t = table.get(id).unwrap();
|
||||||
assert_eq!(t.id(), id);
|
assert_eq!(t.id(), id);
|
||||||
assert_eq!(t.process(), process(0));
|
assert_eq!(t.process(), process(0));
|
||||||
@@ -171,15 +176,21 @@ fn table_spawn_and_lookup() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_spawn_returns_unique_ids() {
|
fn table_spawn_returns_unique_ids() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
let a = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let a = table
|
||||||
let b = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
|
let b = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
assert_ne!(a, b);
|
assert_ne!(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_destroy_then_get_fails() {
|
fn table_destroy_then_get_fails() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let id = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
table.destroy(id).unwrap();
|
table.destroy(id).unwrap();
|
||||||
assert!(table.get(id).is_err());
|
assert!(table.get(id).is_err());
|
||||||
}
|
}
|
||||||
@@ -187,9 +198,15 @@ fn table_destroy_then_get_fails() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_threads_for_process_filters() {
|
fn table_threads_for_process_filters() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
table
|
||||||
table.spawn(process(1), prio(10), time_slice(), None).unwrap();
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
table.spawn(process(0), prio(5), time_slice(), None).unwrap();
|
.unwrap();
|
||||||
|
table
|
||||||
|
.spawn(process(1), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
|
table
|
||||||
|
.spawn(process(0), prio(5), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let count = table.threads_for_process(process(0)).count();
|
let count = table.threads_for_process(process(0)).count();
|
||||||
assert_eq!(count, 2);
|
assert_eq!(count, 2);
|
||||||
@@ -198,16 +215,54 @@ fn table_threads_for_process_filters() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn table_get_mut_allows_state_transition() {
|
fn table_get_mut_allows_state_transition() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let id = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
table.get_mut(id).unwrap().set_running(core(0)).unwrap();
|
table.get_mut(id).unwrap().set_running(core(0)).unwrap();
|
||||||
assert!(matches!(table.get(id).unwrap().state(), ThreadState::Running(_)));
|
assert!(matches!(
|
||||||
|
table.get(id).unwrap().state(),
|
||||||
|
ThreadState::Running(_)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn table_destroy_reuses_slot() {
|
fn table_destroy_reuses_slot() {
|
||||||
let mut table = ThreadTable::new();
|
let mut table = ThreadTable::new();
|
||||||
let id = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let id = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
table.destroy(id).unwrap();
|
table.destroy(id).unwrap();
|
||||||
let new_id = table.spawn(process(0), prio(10), time_slice(), None).unwrap();
|
let new_id = table
|
||||||
|
.spawn(process(0), prio(10), time_slice(), None)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(id, new_id);
|
assert_eq!(id, new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_blocked_thread_can_change_what_it_waits_for() {
|
||||||
|
let mut t = Thread::new(
|
||||||
|
ThreadId::from_raw(1),
|
||||||
|
ProcessId::from_raw(0),
|
||||||
|
Priority::DEFAULT,
|
||||||
|
TimeSlice::DEFAULT,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
t.set_running(CoreId::from_raw(0)).unwrap();
|
||||||
|
t.block(BlockReason::Calling(EndpointId::from_raw(0)))
|
||||||
|
.unwrap();
|
||||||
|
t.rewait(BlockReason::AwaitingReply).unwrap();
|
||||||
|
assert_eq!(*t.state(), ThreadState::Blocked(BlockReason::AwaitingReply));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_blocked_threads_can_rewait() {
|
||||||
|
let mut t = Thread::new(
|
||||||
|
ThreadId::from_raw(1),
|
||||||
|
ProcessId::from_raw(0),
|
||||||
|
Priority::DEFAULT,
|
||||||
|
TimeSlice::DEFAULT,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert!(t.rewait(BlockReason::AwaitingReply).is_err());
|
||||||
|
assert_eq!(*t.state(), ThreadState::Ready);
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl ElfInfo {
|
|||||||
pub fn total_pages(&self) -> usize {
|
pub fn total_pages(&self) -> usize {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
for seg in self.segments() {
|
for seg in self.segments() {
|
||||||
total += (seg.mem_size + 4095) / 4096;
|
total += seg.mem_size.div_ceil(4096);
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,11 @@ pub const MMIO_FIRST: usize = 0x10001000;
|
|||||||
pub const MMIO_STRIDE: usize = 0x1000;
|
pub const MMIO_STRIDE: usize = 0x1000;
|
||||||
pub const MMIO_SLOTS: usize = 8;
|
pub const MMIO_SLOTS: usize = 8;
|
||||||
|
|
||||||
pub fn scan_for_device(device_id: u32) -> Option<DirectMmio> {
|
/// Probes the MMIO slots starting at `first`, the address the caller sees
|
||||||
|
/// slot 0 at (`MMIO_FIRST` itself, or wherever it is mapped).
|
||||||
|
pub fn scan_for_device(first: usize, device_id: u32) -> Option<DirectMmio> {
|
||||||
for slot in 0..MMIO_SLOTS {
|
for slot in 0..MMIO_SLOTS {
|
||||||
if let Some(mmio) = probe_slot(MMIO_FIRST + slot * MMIO_STRIDE, device_id) {
|
if let Some(mmio) = probe_slot(first + slot * MMIO_STRIDE, device_id) {
|
||||||
return Some(mmio);
|
return Some(mmio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ edition = "2024"
|
|||||||
types = { workspace = true }
|
types = { workspace = true }
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
platform = { workspace = true }
|
platform = { workspace = true }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] }
|
||||||
|
|||||||
162
crates/kernel/src/abi.rs
Normal file
162
crates/kernel/src/abi.rs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
//! The syscall register ABI (docs/ipc.md), as pure functions so it can be
|
||||||
|
//! tested and proved: registers in, `Syscall` out; result out, registers
|
||||||
|
//! in. Architecture adapters only move registers in and out of a frame.
|
||||||
|
|
||||||
|
use domain::{message::Message, syscall::Syscall};
|
||||||
|
use types::{
|
||||||
|
address::{DataRef, EntryPoint, StackPointer, VirtualAddress},
|
||||||
|
badge::Badge,
|
||||||
|
bounded::{CapSlot, PageCount},
|
||||||
|
permission::PermissionSet,
|
||||||
|
syscall_abi::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::syscall::{SyscallResult, error_code};
|
||||||
|
|
||||||
|
fn slot(r: usize) -> Option<CapSlot> {
|
||||||
|
reg_slot(r)?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pages(r: usize) -> Option<PageCount> {
|
||||||
|
PageCount::new(u16::try_from(r).ok()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(r: usize) -> Option<PermissionSet> {
|
||||||
|
Some(PermissionSet::from_bits(u8::try_from(r).ok()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The message in `a1..a5`.
|
||||||
|
fn message(a: &[usize; 6]) -> Option<Message> {
|
||||||
|
let msg = Message::from_words([a[1], a[2], a[3], a[4]]);
|
||||||
|
Some(match reg_slot(a[5])? {
|
||||||
|
None => msg,
|
||||||
|
Some(cap) => msg.with_cap(cap),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes syscall `nr` with arguments `a0..a5`. `None` for an unknown
|
||||||
|
/// number or an argument with no meaning (e.g. a slot out of range); the
|
||||||
|
/// caller then gets `UnknownSyscall` and nothing else happens.
|
||||||
|
pub fn decode(nr: usize, a: &[usize; 6]) -> Option<Syscall> {
|
||||||
|
Some(match nr {
|
||||||
|
SYS_EXIT => Syscall::Exit,
|
||||||
|
SYS_HALT => Syscall::Halt {
|
||||||
|
control: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_EXIT_THREAD => Syscall::ExitThread,
|
||||||
|
SYS_YIELD => Syscall::Yield,
|
||||||
|
SYS_GET_TIME => Syscall::GetTime,
|
||||||
|
SYS_GET_PROCESS_ID => Syscall::GetProcessId,
|
||||||
|
SYS_SEND => Syscall::Send {
|
||||||
|
endpoint: slot(a[0])?,
|
||||||
|
message: message(a)?,
|
||||||
|
},
|
||||||
|
SYS_CALL => Syscall::Call {
|
||||||
|
endpoint: slot(a[0])?,
|
||||||
|
message: message(a)?,
|
||||||
|
},
|
||||||
|
SYS_RECEIVE => Syscall::Receive {
|
||||||
|
endpoint: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_REPLY => Syscall::Reply {
|
||||||
|
message: message(a)?,
|
||||||
|
},
|
||||||
|
SYS_REPLY_RECV => Syscall::ReplyRecv {
|
||||||
|
endpoint: slot(a[0])?,
|
||||||
|
message: message(a)?,
|
||||||
|
},
|
||||||
|
SYS_DELEGATE => Syscall::Delegate {
|
||||||
|
slot: slot(a[0])?,
|
||||||
|
to_endpoint: slot(a[1])?,
|
||||||
|
permissions: permissions(a[2])?,
|
||||||
|
},
|
||||||
|
SYS_MINT => Syscall::Mint {
|
||||||
|
slot: slot(a[0])?,
|
||||||
|
badge: Badge::new(a[1]),
|
||||||
|
permissions: permissions(a[2])?,
|
||||||
|
},
|
||||||
|
SYS_CREATE_NOTIFICATION => Syscall::CreateNotification,
|
||||||
|
SYS_SIGNAL => Syscall::Signal {
|
||||||
|
notification: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_WAIT => Syscall::Wait {
|
||||||
|
notification: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_POLL => Syscall::Poll {
|
||||||
|
notification: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_BIND_NOTIFICATION => Syscall::BindNotification {
|
||||||
|
notification: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_SET_IRQ_NOTIFICATION => Syscall::SetIrqNotification {
|
||||||
|
irq: slot(a[0])?,
|
||||||
|
notification: slot(a[1])?,
|
||||||
|
},
|
||||||
|
SYS_ACKNOWLEDGE_INTERRUPT => Syscall::AcknowledgeInterrupt { irq: slot(a[0])? },
|
||||||
|
SYS_CREATE_PROCESS => Syscall::CreateProcess {
|
||||||
|
create_cap: slot(a[0])?,
|
||||||
|
image: DataRef::empty(),
|
||||||
|
},
|
||||||
|
SYS_KILL => Syscall::Kill {
|
||||||
|
process: slot(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_REVOKE => Syscall::Revoke { slot: slot(a[0])? },
|
||||||
|
SYS_DROP_CAP => Syscall::DropCap { slot: slot(a[0])? },
|
||||||
|
SYS_SPAWN_THREAD => Syscall::SpawnThread {
|
||||||
|
entry: EntryPoint::from_raw(a[0]),
|
||||||
|
stack: StackPointer::from_raw(a[1]),
|
||||||
|
},
|
||||||
|
SYS_REQUEST_PAGES => Syscall::RequestPages {
|
||||||
|
count: pages(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_RELEASE_PAGES => Syscall::ReleasePages {
|
||||||
|
addr: VirtualAddress::from_raw(a[0]),
|
||||||
|
count: pages(a[1])?,
|
||||||
|
},
|
||||||
|
SYS_SPAWN_THREAD_IN => Syscall::SpawnThreadIn {
|
||||||
|
process: slot(a[0])?,
|
||||||
|
entry: EntryPoint::from_raw(a[1]),
|
||||||
|
stack: StackPointer::from_raw(a[2]),
|
||||||
|
},
|
||||||
|
SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint,
|
||||||
|
SYS_CREATE_REGION => Syscall::CreateRegion {
|
||||||
|
pages: pages(a[0])?,
|
||||||
|
},
|
||||||
|
SYS_MAP_REGION => Syscall::MapRegion {
|
||||||
|
region: slot(a[0])?,
|
||||||
|
va: VirtualAddress::from_raw(a[1]),
|
||||||
|
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_MAP_PAGE => Syscall::MapPage {
|
||||||
|
process: slot(a[0])?,
|
||||||
|
src_va: VirtualAddress::from_raw(a[1]),
|
||||||
|
dst_va: VirtualAddress::from_raw(a[2]),
|
||||||
|
executable: a[3] != 0,
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result registers `a0..a7` for a completed syscall.
|
||||||
|
pub fn encode(result: &SyscallResult) -> [usize; 8] {
|
||||||
|
let mut r = [0usize; 8];
|
||||||
|
match result {
|
||||||
|
SyscallResult::Ok => {}
|
||||||
|
SyscallResult::Value(v) => r[1] = *v,
|
||||||
|
SyscallResult::Slot(s) => r[1] = s.value() as usize,
|
||||||
|
SyscallResult::Received(d) => r = delivery_regs(d),
|
||||||
|
SyscallResult::Error(e) => r[0] = error_code(e) as usize,
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers for a syscall number that does not decode.
|
||||||
|
pub fn unknown_syscall() -> [usize; 8] {
|
||||||
|
let mut r = [0usize; 8];
|
||||||
|
r[0] = ErrorCode::UnknownSyscall as usize;
|
||||||
|
r
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
172
crates/kernel/src/captree.rs
Normal file
172
crates/kernel/src/captree.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
//! The capability derivation tree. Every derived capability (Mint,
|
||||||
|
//! Delegate, IPC transfer) records its parent; `Revoke` deletes a subtree
|
||||||
|
//! and deleting a capability re-parents its children, so the tree can only
|
||||||
|
//! shrink by explicit deletion, never by a middleman stepping out.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
capability::{CapRef, Capability, MAX_CAPS_PER_PROCESS, derivation::descendants},
|
||||||
|
error::KernelError,
|
||||||
|
process::MAX_PROCESSES,
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
bounded::CapSlot,
|
||||||
|
id::{ProcessId, RegionId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// Every capability slot in the system, flattened.
|
||||||
|
const ALL_CAPS: usize = MAX_PROCESSES * MAX_CAPS_PER_PROCESS;
|
||||||
|
|
||||||
|
fn flat(pid: ProcessId, slot: CapSlot) -> usize {
|
||||||
|
pid.raw() as usize * MAX_CAPS_PER_PROCESS + slot.value() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unflat(i: usize) -> Option<(ProcessId, CapSlot)> {
|
||||||
|
let pid = ProcessId::from_raw((i / MAX_CAPS_PER_PROCESS) as u16);
|
||||||
|
let slot = CapSlot::new((i % MAX_CAPS_PER_PROCESS) as u8)?;
|
||||||
|
Some((pid, slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// A reference to the capability currently in `slot` of `pid`.
|
||||||
|
pub(crate) fn cap_ref(&self, pid: ProcessId, slot: CapSlot) -> Option<CapRef> {
|
||||||
|
let process = self.processes.get(pid).ok()?;
|
||||||
|
Some(CapRef {
|
||||||
|
process: pid,
|
||||||
|
process_generation: self.processes.generation(pid)?,
|
||||||
|
slot,
|
||||||
|
slot_generation: process.capabilities().slot_generation(slot)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `r` still names a live capability.
|
||||||
|
pub(crate) fn resolves(&self, r: CapRef) -> bool {
|
||||||
|
self.cap_ref(r.process, r.slot) == Some(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores `cap` in `to`'s table as a child of `parent`.
|
||||||
|
pub(crate) fn insert_derived(
|
||||||
|
&mut self,
|
||||||
|
to: ProcessId,
|
||||||
|
cap: Capability,
|
||||||
|
parent: Option<CapRef>,
|
||||||
|
) -> Result<CapSlot, KernelError> {
|
||||||
|
self.processes
|
||||||
|
.get_mut(to)?
|
||||||
|
.capabilities_mut()
|
||||||
|
.insert(cap.with_parent(parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls `f` with every live capability's flat index and reference.
|
||||||
|
fn for_each_cap(&self, mut f: impl FnMut(usize, CapRef, &Capability)) {
|
||||||
|
for p in 0..MAX_PROCESSES {
|
||||||
|
let pid = ProcessId::from_raw(p as u16);
|
||||||
|
let Ok(process) = self.processes.get(pid) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for (slot, cap) in process.capabilities().iter() {
|
||||||
|
if let Some(r) = self.cap_ref(pid, slot) {
|
||||||
|
f(flat(pid, slot), r, cap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes every region mapping made through `via`.
|
||||||
|
fn unmap_mappings_via(&mut self, via: CapRef) {
|
||||||
|
let mut doomed: [Option<RegionId>; domain::memory::MAX_REGIONS] =
|
||||||
|
[None; domain::memory::MAX_REGIONS];
|
||||||
|
for (slot, (id, _)) in doomed.iter_mut().zip(
|
||||||
|
self.regions
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, r)| r.mappings().any(|m| m.via == Some(via))),
|
||||||
|
) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in doomed.into_iter().flatten() {
|
||||||
|
self.unmap_region_from(id, via.process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes one capability: its mappings go, its children are adopted
|
||||||
|
/// by its parent.
|
||||||
|
pub(crate) fn delete_cap(&mut self, pid: ProcessId, slot: CapSlot) -> Result<(), KernelError> {
|
||||||
|
let me = self
|
||||||
|
.cap_ref(pid, slot)
|
||||||
|
.ok_or(KernelError::InvalidSlot(slot))?;
|
||||||
|
let grandparent = self.processes.get(pid)?.capabilities().get(slot)?.parent();
|
||||||
|
self.unmap_mappings_via(me);
|
||||||
|
|
||||||
|
let mut children = [false; ALL_CAPS];
|
||||||
|
self.for_each_cap(|i, _, cap| {
|
||||||
|
if cap.parent() == Some(me)
|
||||||
|
&& let Some(c) = children.get_mut(i)
|
||||||
|
{
|
||||||
|
*c = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (i, _) in children.iter().enumerate().filter(|(_, c)| **c) {
|
||||||
|
if let Some((cpid, cslot)) = unflat(i)
|
||||||
|
&& let Ok(p) = self.processes.get_mut(cpid)
|
||||||
|
&& let Ok(child) = p.capabilities_mut().get_mut(cslot)
|
||||||
|
{
|
||||||
|
child.set_parent(grandparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.processes.get_mut(pid)?.capabilities_mut().remove(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes every capability derived from `slot` of `pid`, transitively,
|
||||||
|
/// and keeps `slot` itself.
|
||||||
|
pub(crate) fn revoke_subtree(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if self.cap_ref(pid, slot).is_none() {
|
||||||
|
return Err(KernelError::InvalidSlot(slot));
|
||||||
|
}
|
||||||
|
// Snapshot the tree as parent indices. A parent that no longer
|
||||||
|
// resolves cannot exist (deletion re-parents), but is treated as
|
||||||
|
// a root rather than trusted.
|
||||||
|
let mut parents: [Option<u16>; ALL_CAPS] = [None; ALL_CAPS];
|
||||||
|
self.for_each_cap(|i, _, cap| {
|
||||||
|
if let Some(p) = parents.get_mut(i) {
|
||||||
|
*p = cap
|
||||||
|
.parent()
|
||||||
|
.filter(|r| self.resolves(*r))
|
||||||
|
.map(|r| flat(r.process, r.slot) as u16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let root = flat(pid, slot);
|
||||||
|
let marked = descendants(&parents, root);
|
||||||
|
for (i, _) in marked.iter().enumerate().filter(|(i, m)| **m && *i != root) {
|
||||||
|
if let Some((dpid, dslot)) = unflat(i)
|
||||||
|
&& let Some(r) = self.cap_ref(dpid, dslot)
|
||||||
|
{
|
||||||
|
self.unmap_mappings_via(r);
|
||||||
|
let _ = self
|
||||||
|
.processes
|
||||||
|
.get_mut(dpid)
|
||||||
|
.map(|p| p.capabilities_mut().remove(dslot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all of a dying process's capabilities, re-parenting their
|
||||||
|
/// children so revocation still reaches them.
|
||||||
|
pub(crate) fn delete_all_caps(&mut self, pid: ProcessId) {
|
||||||
|
let mut slots = [None; MAX_CAPS_PER_PROCESS];
|
||||||
|
if let Ok(p) = self.processes.get(pid) {
|
||||||
|
for (out, (slot, _)) in slots.iter_mut().zip(p.capabilities().iter()) {
|
||||||
|
*out = Some(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for slot in slots.into_iter().flatten() {
|
||||||
|
let _ = self.delete_cap(pid, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use domain::scheduler::{Scheduler, SchedulerDecision};
|
use domain::scheduler::{Scheduler, SchedulerDecision};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::id::{CoreId, IrqId, ThreadId};
|
use types::id::CoreId;
|
||||||
|
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
@@ -26,15 +26,26 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.scheduler.add(current_tid, priority, core);
|
self.scheduler.add(current_tid, priority, core);
|
||||||
match self.scheduler.next(core) {
|
match self.scheduler.next(core) {
|
||||||
Some(next) => {
|
Some(next) => {
|
||||||
if self.threads.get_mut(next).and_then(|t| t.set_running(core)).is_ok() {
|
if self
|
||||||
|
.threads
|
||||||
|
.get_mut(next)
|
||||||
|
.and_then(|t| t.set_running(core))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
SchedulerDecision::Preempt(next)
|
SchedulerDecision::Preempt(next)
|
||||||
} else {
|
} else {
|
||||||
let _ = self.threads.get_mut(current_tid).map(|t| t.set_running(core));
|
let _ = self
|
||||||
|
.threads
|
||||||
|
.get_mut(current_tid)
|
||||||
|
.map(|t| t.set_running(core));
|
||||||
SchedulerDecision::Continue
|
SchedulerDecision::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let _ = self.threads.get_mut(current_tid).map(|t| t.set_running(core));
|
let _ = self
|
||||||
|
.threads
|
||||||
|
.get_mut(current_tid)
|
||||||
|
.map(|t| t.set_running(core));
|
||||||
SchedulerDecision::Continue
|
SchedulerDecision::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,22 +53,4 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.scheduler.tick(core, current_tid)
|
self.scheduler.tick(core, current_tid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_interrupt(&mut self, irq: IrqId) -> Option<ThreadId> {
|
|
||||||
self.platform.mask_irq(irq);
|
|
||||||
|
|
||||||
let tid = self.irqs.owner(irq)?;
|
|
||||||
let thread = self.threads.get_mut(tid).ok()?;
|
|
||||||
|
|
||||||
if !matches!(thread.state(), domain::thread::ThreadState::Blocked(_)) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let priority = thread.priority();
|
|
||||||
let core = self.platform.core_id();
|
|
||||||
let _ = thread.unblock();
|
|
||||||
self.scheduler.add(tid, priority, core);
|
|
||||||
|
|
||||||
Some(tid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
// A kernel panic takes the whole system down, so kernel code may not
|
||||||
|
// contain any way to panic. Tests and proofs are exempt.
|
||||||
|
#![cfg_attr(
|
||||||
|
not(any(test, kani)),
|
||||||
|
deny(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
clippy::expect_used,
|
||||||
|
clippy::panic,
|
||||||
|
clippy::unreachable,
|
||||||
|
clippy::todo,
|
||||||
|
clippy::unimplemented,
|
||||||
|
clippy::indexing_slicing
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub mod abi;
|
||||||
|
mod board;
|
||||||
|
mod captree;
|
||||||
pub mod interrupt;
|
pub mod interrupt;
|
||||||
|
mod memory;
|
||||||
#[cfg(kani)]
|
#[cfg(kani)]
|
||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
|
mod smp;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
|
||||||
|
pub use board::BoardDevice;
|
||||||
pub use syscall::{SyscallOutcome, SyscallResult};
|
pub use syscall::{SyscallOutcome, SyscallResult};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -16,20 +36,16 @@ use domain::{
|
|||||||
endpoint::EndpointTable,
|
endpoint::EndpointTable,
|
||||||
error::KernelError,
|
error::KernelError,
|
||||||
irq::IrqRoutingTable,
|
irq::IrqRoutingTable,
|
||||||
memory::{
|
memory::{RegionTable, frame::FrameTable, quota::Quota},
|
||||||
page_pool::PagePool,
|
notification::{MAX_NOTIFICATIONS, NotificationTable},
|
||||||
process_memory::ProcessMemory,
|
process::{MAX_PROCESSES, ProcessTable},
|
||||||
RegionTable,
|
|
||||||
},
|
|
||||||
message::Message,
|
|
||||||
process::{ProcessTable, MAX_PROCESSES},
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
scheduler::{multi_core::MultiCoreScheduler, Scheduler},
|
scheduler::{Scheduler, multi_core::MultiCoreScheduler},
|
||||||
syscall::WaitSet,
|
thread::{MAX_THREADS, ThreadState, ThreadTable},
|
||||||
thread::{ThreadTable, ThreadState, MAX_THREADS},
|
|
||||||
};
|
};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::{
|
use types::{
|
||||||
|
address::PhysicalAddress,
|
||||||
bounded::{CapSlot, Priority},
|
bounded::{CapSlot, Priority},
|
||||||
generation::Generation,
|
generation::Generation,
|
||||||
id::{ProcessId, ThreadId},
|
id::{ProcessId, ThreadId},
|
||||||
@@ -43,17 +59,19 @@ pub struct Kernel<P: Platform> {
|
|||||||
threads: ThreadTable,
|
threads: ThreadTable,
|
||||||
endpoints: EndpointTable,
|
endpoints: EndpointTable,
|
||||||
scheduler: MultiCoreScheduler,
|
scheduler: MultiCoreScheduler,
|
||||||
page_pool: PagePool,
|
frames: FrameTable,
|
||||||
process_memory: [Option<ProcessMemory>; MAX_PROCESSES],
|
quotas: [Quota; MAX_PROCESSES],
|
||||||
regions: RegionTable,
|
regions: RegionTable,
|
||||||
irqs: IrqRoutingTable,
|
irqs: IrqRoutingTable,
|
||||||
pending_messages: [Option<Message>; MAX_THREADS],
|
notifications: NotificationTable,
|
||||||
|
ipc: [syscall::IpcState; MAX_THREADS],
|
||||||
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
pending_results: [Option<SyscallResult>; MAX_THREADS],
|
||||||
wait_sets: [Option<WaitSet>; MAX_THREADS],
|
/// The core each thread runs on (see smp.rs).
|
||||||
|
homes: [types::id::CoreId; MAX_THREADS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub fn new(platform: P, page_pool: PagePool) -> Self {
|
pub fn new(platform: P, pool_base: PhysicalAddress, pool_pages: usize) -> Self {
|
||||||
let num_cores = platform.num_cores();
|
let num_cores = platform.num_cores();
|
||||||
Self {
|
Self {
|
||||||
platform,
|
platform,
|
||||||
@@ -61,13 +79,47 @@ impl<P: Platform> Kernel<P> {
|
|||||||
threads: ThreadTable::new(),
|
threads: ThreadTable::new(),
|
||||||
endpoints: EndpointTable::new(),
|
endpoints: EndpointTable::new(),
|
||||||
scheduler: MultiCoreScheduler::new(num_cores),
|
scheduler: MultiCoreScheduler::new(num_cores),
|
||||||
page_pool,
|
frames: FrameTable::new(pool_base, pool_pages),
|
||||||
process_memory: [const { None }; MAX_PROCESSES],
|
quotas: [Quota::default(); MAX_PROCESSES],
|
||||||
regions: RegionTable::new(),
|
regions: RegionTable::new(),
|
||||||
irqs: IrqRoutingTable::new(),
|
irqs: IrqRoutingTable::new(),
|
||||||
pending_messages: [const { None }; MAX_THREADS],
|
notifications: NotificationTable::new(),
|
||||||
|
ipc: [syscall::IpcState::default(); MAX_THREADS],
|
||||||
pending_results: [const { None }; MAX_THREADS],
|
pending_results: [const { None }; MAX_THREADS],
|
||||||
wait_sets: [const { None }; MAX_THREADS],
|
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the kernel directly in `slot`, one table at a time, so the
|
||||||
|
/// whole structure never has to fit on the (small) boot stack.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `slot` must be valid for writes and properly aligned. Any previous
|
||||||
|
/// value is overwritten without being dropped.
|
||||||
|
pub unsafe fn init_in_place(
|
||||||
|
slot: *mut Self,
|
||||||
|
platform: P,
|
||||||
|
pool_base: PhysicalAddress,
|
||||||
|
pool_pages: usize,
|
||||||
|
) {
|
||||||
|
use core::ptr::addr_of_mut;
|
||||||
|
let num_cores = platform.num_cores();
|
||||||
|
// SAFETY: the caller guarantees `slot` is valid and aligned; each
|
||||||
|
// field is written exactly once.
|
||||||
|
unsafe {
|
||||||
|
addr_of_mut!((*slot).platform).write(platform);
|
||||||
|
addr_of_mut!((*slot).processes).write(ProcessTable::new());
|
||||||
|
addr_of_mut!((*slot).threads).write(ThreadTable::new());
|
||||||
|
addr_of_mut!((*slot).endpoints).write(EndpointTable::new());
|
||||||
|
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
|
||||||
|
addr_of_mut!((*slot).frames).write(FrameTable::new(pool_base, pool_pages));
|
||||||
|
addr_of_mut!((*slot).quotas).write([Quota::default(); MAX_PROCESSES]);
|
||||||
|
addr_of_mut!((*slot).regions).write(RegionTable::new());
|
||||||
|
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
|
||||||
|
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
|
||||||
|
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
|
||||||
|
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
|
||||||
|
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +129,25 @@ impl<P: Platform> Kernel<P> {
|
|||||||
time_slice: TimeSlice,
|
time_slice: TimeSlice,
|
||||||
) -> Result<(ProcessId, ThreadId), KernelError> {
|
) -> Result<(ProcessId, ThreadId), KernelError> {
|
||||||
let pid = self.processes.create(CapabilityTable::new(), None)?;
|
let pid = self.processes.create(CapabilityTable::new(), None)?;
|
||||||
|
if let Err(e) = self.create_address_space(pid) {
|
||||||
|
let _ = self.processes.destroy(pid);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
|
let tid = self.threads.spawn(pid, priority, time_slice, None)?;
|
||||||
self.processes.get_mut(pid)?.add_thread(tid)?;
|
self.processes.get_mut(pid)?.add_thread(tid)?;
|
||||||
|
|
||||||
self.process_memory[pid.raw() as usize] =
|
|
||||||
Some(ProcessMemory::new(pid, ProcessMemory::DEFAULT_PAGE_LIMIT));
|
|
||||||
|
|
||||||
let cap = Capability::new(
|
let cap = Capability::new(
|
||||||
Resource::ProcessCreate,
|
Resource::ProcessCreate,
|
||||||
PermissionSet::empty().with(Permission::Execute),
|
PermissionSet::empty().with(Permission::Execute),
|
||||||
Generation::initial(),
|
Generation::initial(),
|
||||||
);
|
);
|
||||||
self.processes.get_mut(pid)?.capabilities_mut().insert(cap)?;
|
self.processes
|
||||||
|
.get_mut(pid)?
|
||||||
|
.capabilities_mut()
|
||||||
|
.insert(cap)?;
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
|
self.set_home(tid, core);
|
||||||
self.scheduler.add(tid, priority, core);
|
self.scheduler.add(tid, priority, core);
|
||||||
self.scheduler.next(core);
|
self.scheduler.next(core);
|
||||||
self.threads.get_mut(tid)?.set_running(core)?;
|
self.threads.get_mut(tid)?.set_running(core)?;
|
||||||
@@ -102,21 +159,21 @@ impl<P: Platform> Kernel<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pending_result(&self, thread: ThreadId) -> Option<&SyscallResult> {
|
pub fn pending_result(&self, thread: ThreadId) -> Option<&SyscallResult> {
|
||||||
self.pending_results[thread.raw() as usize].as_ref()
|
self.pending_results.get(thread.raw() as usize)?.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_pending_result(&mut self, thread: ThreadId) -> Option<SyscallResult> {
|
pub fn take_pending_result(&mut self, thread: ThreadId) -> Option<SyscallResult> {
|
||||||
self.pending_results[thread.raw() as usize].take()
|
self.pending_results.get_mut(thread.raw() as usize)?.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_thread_running(&mut self, tid: ThreadId, core: types::id::CoreId) -> Result<(), domain::error::KernelError> {
|
pub fn set_thread_running(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
core: types::id::CoreId,
|
||||||
|
) -> Result<(), domain::error::KernelError> {
|
||||||
self.threads.get_mut(tid)?.set_running(core)
|
self.threads.get_mut(tid)?.set_running(core)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_page_count(&self) -> usize {
|
|
||||||
self.page_pool.free_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_exists(&self, pid: ProcessId) -> bool {
|
pub fn process_exists(&self, pid: ProcessId) -> bool {
|
||||||
self.processes.get(pid).is_ok()
|
self.processes.get(pid).is_ok()
|
||||||
}
|
}
|
||||||
@@ -125,8 +182,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.threads.get(tid).ok().map(|t| t.saved_context())
|
self.threads.get(tid).ok().map(|t| t.saved_context())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread_context_mut(&mut self, tid: ThreadId) -> Option<&mut domain::thread::SavedContext> {
|
pub fn thread_context_mut(
|
||||||
self.threads.get_mut(tid).ok().map(|t| t.saved_context_mut())
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
) -> Option<&mut domain::thread::SavedContext> {
|
||||||
|
self.threads
|
||||||
|
.get_mut(tid)
|
||||||
|
.ok()
|
||||||
|
.map(|t| t.saved_context_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread_process(&self, tid: ThreadId) -> Option<ProcessId> {
|
pub fn thread_process(&self, tid: ThreadId) -> Option<ProcessId> {
|
||||||
@@ -146,30 +209,31 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
|
pub fn create_process_internal(&mut self) -> Option<ProcessId> {
|
||||||
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
|
let pid = self.processes.create(CapabilityTable::new(), None).ok()?;
|
||||||
self.process_memory[pid.raw() as usize] =
|
if self.create_address_space(pid).is_err() {
|
||||||
Some(domain::memory::process_memory::ProcessMemory::new(pid, ProcessMemory::DEFAULT_PAGE_LIMIT));
|
let _ = self.processes.destroy(pid);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
Some(pid)
|
Some(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_thread_internal(
|
pub fn spawn_thread_internal(&mut self, pid: ProcessId) -> Option<ThreadId> {
|
||||||
&mut self,
|
let tid = self
|
||||||
pid: ProcessId,
|
.threads
|
||||||
) -> Option<ThreadId> {
|
.spawn(pid, Priority::DEFAULT, TimeSlice::DEFAULT, None)
|
||||||
use domain::scheduler::Scheduler;
|
.ok()?;
|
||||||
let tid = self.threads.spawn(
|
|
||||||
pid,
|
|
||||||
Priority::DEFAULT,
|
|
||||||
TimeSlice::DEFAULT,
|
|
||||||
None,
|
|
||||||
).ok()?;
|
|
||||||
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
|
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
|
||||||
let core = self.platform.core_id();
|
self.place(tid);
|
||||||
self.scheduler.add(tid, Priority::DEFAULT, core);
|
self.make_ready(tid, Priority::DEFAULT);
|
||||||
Some(tid)
|
Some(tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_endpoint(&mut self) -> Result<types::id::EndpointId, KernelError> {
|
/// Creates an endpoint owned by `owner`; it is destroyed with `owner`.
|
||||||
self.endpoints.create().ok_or(KernelError::NoFreeSlot)
|
pub fn create_endpoint(
|
||||||
|
&mut self,
|
||||||
|
owner: ProcessId,
|
||||||
|
) -> Result<types::id::EndpointId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
self.endpoints.create(owner).ok_or(KernelError::NoFreeSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grant_capability(
|
pub fn grant_capability(
|
||||||
@@ -181,34 +245,11 @@ impl<P: Platform> Kernel<P> {
|
|||||||
self.grant_cap(to, resource, permissions)
|
self.grant_cap(to, resource, permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch_to_thread(&mut self, tid: ThreadId) {
|
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
let core = self.platform.core_id();
|
|
||||||
|
|
||||||
if let Some(current) = self.scheduler.current(core) {
|
|
||||||
if current == tid {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Ok(t) = self.threads.get_mut(current) {
|
|
||||||
if matches!(t.state(), ThreadState::Running(_)) {
|
|
||||||
let priority = t.priority();
|
|
||||||
let _ = t.preempt();
|
|
||||||
self.scheduler.add(current, priority, core);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(t) = self.threads.get(tid) {
|
|
||||||
if matches!(t.state(), ThreadState::Ready) {
|
|
||||||
self.scheduler.next(core);
|
|
||||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> {
|
fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> {
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
let tid = self.scheduler.current(core)
|
let tid = self
|
||||||
|
.scheduler
|
||||||
|
.current(core)
|
||||||
.ok_or(KernelError::WouldBlock)?;
|
.ok_or(KernelError::WouldBlock)?;
|
||||||
let thread = self.threads.get(tid)?;
|
let thread = self.threads.get(tid)?;
|
||||||
match thread.state() {
|
match thread.state() {
|
||||||
@@ -220,11 +261,12 @@ impl<P: Platform> Kernel<P> {
|
|||||||
pub(crate) fn destroy_process(&mut self, pid: ProcessId) {
|
pub(crate) fn destroy_process(&mut self, pid: ProcessId) {
|
||||||
use domain::process::MAX_THREADS_PER_PROCESS;
|
use domain::process::MAX_THREADS_PER_PROCESS;
|
||||||
|
|
||||||
let thread_ids: [Option<ThreadId>; MAX_THREADS_PER_PROCESS] = match self.processes.get(pid) {
|
let thread_ids: [Option<ThreadId>; MAX_THREADS_PER_PROCESS] = match self.processes.get(pid)
|
||||||
|
{
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
let mut ids = [None; MAX_THREADS_PER_PROCESS];
|
let mut ids = [None; MAX_THREADS_PER_PROCESS];
|
||||||
for (i, tid) in p.thread_ids().enumerate() {
|
for (slot, tid) in ids.iter_mut().zip(p.thread_ids()) {
|
||||||
if i < MAX_THREADS_PER_PROCESS { ids[i] = Some(tid); }
|
*slot = Some(tid);
|
||||||
}
|
}
|
||||||
ids
|
ids
|
||||||
}
|
}
|
||||||
@@ -232,48 +274,76 @@ impl<P: Platform> Kernel<P> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.cleanup_threads(&thread_ids);
|
self.cleanup_threads(&thread_ids);
|
||||||
|
self.delete_all_caps(pid);
|
||||||
// Free allocated pages back to pool
|
self.destroy_endpoints_owned_by(pid);
|
||||||
if let Some(pm) = &self.process_memory[pid.raw() as usize] {
|
self.destroy_notifications_owned_by(pid);
|
||||||
for &(pa, count) in pm.allocations() {
|
self.teardown_memory(pid);
|
||||||
self.page_pool.free(pa, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unblock threads in other processes that were blocked waiting
|
|
||||||
// on endpoints where this process's threads were the other side.
|
|
||||||
// The cleanup_threads call above already removed this process's
|
|
||||||
// threads from all endpoint wait queues. Threads blocked waiting
|
|
||||||
// for this process will eventually time out or be rescheduled
|
|
||||||
// when the endpoint is revoked.
|
|
||||||
|
|
||||||
let _ = self.processes.destroy(pid);
|
let _ = self.processes.destroy(pid);
|
||||||
self.process_memory[pid.raw() as usize] = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option<ThreadId>]) {
|
||||||
use domain::scheduler::Scheduler;
|
use domain::scheduler::Scheduler;
|
||||||
for slot in thread_ids {
|
for tid in thread_ids.iter().flatten() {
|
||||||
if let Some(tid) = slot {
|
|
||||||
self.endpoints.remove_thread_from_all(*tid);
|
self.endpoints.remove_thread_from_all(*tid);
|
||||||
self.cleanup_wait_any(*tid);
|
self.notifications.remove_thread_from_all(*tid);
|
||||||
|
let ipc = self.ipc_mut(*tid).map(core::mem::take).unwrap_or_default();
|
||||||
|
if let Some(caller) = ipc.reply_to {
|
||||||
|
self.release_caller(caller);
|
||||||
|
}
|
||||||
|
self.evict(*tid);
|
||||||
self.scheduler.remove(*tid);
|
self.scheduler.remove(*tid);
|
||||||
self.pending_messages[tid.raw() as usize] = None;
|
self.set_pending_result(*tid, None);
|
||||||
self.pending_results[tid.raw() as usize] = None;
|
|
||||||
let _ = self.threads.destroy(*tid);
|
let _ = self.threads.destroy(*tid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Destroys every endpoint `owner` created, waking anyone blocked on
|
||||||
|
/// them with an error instead of leaving them blocked forever.
|
||||||
|
pub(crate) fn destroy_endpoints_owned_by(&mut self, owner: ProcessId) {
|
||||||
|
let mut owned = [None; domain::endpoint::MAX_ENDPOINTS];
|
||||||
|
for (slot, id) in owned.iter_mut().zip(self.endpoints.ids_owned_by(owner)) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(mut ep) = self.endpoints.destroy(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
while let Some(tid) = ep.dequeue_sender().or_else(|| ep.dequeue_receiver()) {
|
||||||
|
self.wake_with_error(tid, KernelError::InvalidEndpoint(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) {
|
/// Destroys every notification `owner` created, waking its waiters
|
||||||
if let Some(wait_set) = self.wait_sets[tid.raw() as usize].take() {
|
/// with an error and unbinding its bound thread.
|
||||||
for ep_slot in wait_set.endpoints() {
|
pub(crate) fn destroy_notifications_owned_by(&mut self, owner: ProcessId) {
|
||||||
if let Some(_slot) = ep_slot {
|
let mut owned = [None; MAX_NOTIFICATIONS];
|
||||||
self.endpoints.remove_thread_from_all(tid);
|
for (slot, id) in owned.iter_mut().zip(self.notifications.ids_owned_by(owner)) {
|
||||||
break;
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(mut n) = self.notifications.destroy(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(bound) = n.bound()
|
||||||
|
&& let Some(s) = self.ipc_mut(bound)
|
||||||
|
{
|
||||||
|
s.bound = None;
|
||||||
|
}
|
||||||
|
while let Some(tid) = n.pop_waiter() {
|
||||||
|
self.wake_with_error(tid, KernelError::NoSuchObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes a blocked thread runnable with `error` as its syscall result,
|
||||||
|
/// dropping whatever it was about to send.
|
||||||
|
fn wake_with_error(&mut self, tid: ThreadId, error: KernelError) {
|
||||||
|
if let Some(s) = self.ipc_mut(tid) {
|
||||||
|
s.outgoing = None;
|
||||||
|
}
|
||||||
|
self.wake(tid, SyscallResult::Error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_cap(
|
fn check_cap(
|
||||||
@@ -287,43 +357,24 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if !cap.permissions().allows(needed) {
|
if !cap.permissions().allows(needed) {
|
||||||
return Err(KernelError::PermissionDenied { slot, needed });
|
return Err(KernelError::PermissionDenied { slot, needed });
|
||||||
}
|
}
|
||||||
let current_gen = self.resource_generation(cap.resource());
|
match self.resource_generation(cap.resource()) {
|
||||||
if !cap.generation().is_valid_against(¤t_gen) {
|
Some(current) if cap.generation().is_valid_against(¤t) => Ok(cap),
|
||||||
return Err(KernelError::RevokedCapability(slot));
|
_ => Err(KernelError::RevokedCapability(slot)),
|
||||||
}
|
|
||||||
Ok(cap)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resource_generation(&self, resource: &Resource) -> Generation {
|
|
||||||
match resource {
|
|
||||||
Resource::Endpoint(id) => self
|
|
||||||
.endpoints
|
|
||||||
.get(*id)
|
|
||||||
.map(|ep| *ep.generation())
|
|
||||||
.unwrap_or_else(|| Generation::initial()),
|
|
||||||
Resource::Memory(id) => self
|
|
||||||
.regions
|
|
||||||
.get(*id)
|
|
||||||
.map(|r| *r.generation())
|
|
||||||
.unwrap_or_else(|| Generation::initial()),
|
|
||||||
_ => Generation::initial(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn revoke_resource(&mut self, resource: &Resource) {
|
/// The generation a capability to `resource` must carry, or `None` if
|
||||||
|
/// the object no longer exists.
|
||||||
|
fn resource_generation(&self, resource: &Resource) -> Option<Generation> {
|
||||||
match resource {
|
match resource {
|
||||||
Resource::Endpoint(id) => {
|
Resource::Endpoint(id) => self.endpoints.generation(*id),
|
||||||
if let Some(ep) = self.endpoints.get_mut(*id) {
|
Resource::Memory(id) => self.regions.generation(*id),
|
||||||
ep.revoke();
|
Resource::Notification(id) => self.notifications.generation(*id),
|
||||||
|
Resource::Process(pid) => self.processes.generation(*pid),
|
||||||
|
Resource::Interrupt(_) | Resource::ProcessCreate | Resource::SystemControl => {
|
||||||
|
Some(Generation::initial())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Resource::Memory(id) => {
|
|
||||||
if let Some(r) = self.regions.get_mut(*id) {
|
|
||||||
r.revoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grant_cap(
|
fn grant_cap(
|
||||||
@@ -332,7 +383,9 @@ impl<P: Platform> Kernel<P> {
|
|||||||
resource: Resource,
|
resource: Resource,
|
||||||
permissions: PermissionSet,
|
permissions: PermissionSet,
|
||||||
) -> Result<CapSlot, KernelError> {
|
) -> Result<CapSlot, KernelError> {
|
||||||
let generation = self.resource_generation(&resource);
|
let generation = self
|
||||||
|
.resource_generation(&resource)
|
||||||
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
let cap = Capability::new(resource, permissions, generation);
|
let cap = Capability::new(resource, permissions, generation);
|
||||||
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
||||||
}
|
}
|
||||||
|
|||||||
486
crates/kernel/src/memory.rs
Normal file
486
crates/kernel/src/memory.rs
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
//! Address-space bookkeeping. The kernel decides every user mapping from
|
||||||
|
//! its own records (`FrameTable`, `RegionTable`, quotas) and only then tells
|
||||||
|
//! the platform to apply it; it never asks the hardware what is mapped.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
error::KernelError,
|
||||||
|
memory::{
|
||||||
|
Region, RegionBacking, RegionMapping,
|
||||||
|
frame::{FrameTable, FrameUse, PAGE_SIZE},
|
||||||
|
quota::Quota,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use platform::{FrameSource, MapError, Platform};
|
||||||
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
bounded::PageCount,
|
||||||
|
id::{ProcessId, RegionId},
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// First VA the kernel considers when picking a spot for `RequestPages`.
|
||||||
|
/// Below it sit the program image and boot-time device windows.
|
||||||
|
pub(crate) const USER_HEAP_START: usize = 0x40000;
|
||||||
|
|
||||||
|
/// Page-table frames for one address space, charged to its owner.
|
||||||
|
struct ChargedFrames<'a> {
|
||||||
|
frames: &'a mut FrameTable,
|
||||||
|
quota: &'a mut Quota,
|
||||||
|
owner: ProcessId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameSource for ChargedFrames<'_> {
|
||||||
|
fn alloc_table_frame(&mut self) -> Option<PhysicalAddress> {
|
||||||
|
if !self.quota.charge(1) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match self.frames.alloc(FrameUse::PageTable { owner: self.owner }) {
|
||||||
|
Some(pa) => Some(pa),
|
||||||
|
None => {
|
||||||
|
self.quota.uncharge(1);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_error(e: MapError) -> KernelError {
|
||||||
|
match e {
|
||||||
|
MapError::OutOfFrames => KernelError::OutOfMemory,
|
||||||
|
MapError::InvalidAddress => KernelError::InvalidAddress,
|
||||||
|
MapError::NoAddressSpace => KernelError::OutOfMemory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// Whether `[va, va + pages)` is page aligned and inside the user window.
|
||||||
|
pub(crate) fn in_user_window(va: VirtualAddress, pages: usize) -> bool {
|
||||||
|
let start = va.raw();
|
||||||
|
let Some(len) = pages.checked_mul(PAGE_SIZE) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(end) = start.checked_add(len) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
pages > 0
|
||||||
|
&& start.is_multiple_of(PAGE_SIZE)
|
||||||
|
&& start >= P::USER_VA_START
|
||||||
|
&& end <= P::USER_VA_END
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_mapped(&self, pid: ProcessId, va: VirtualAddress) -> bool {
|
||||||
|
self.frames.find_user(pid, va).is_some() || self.regions.covering(pid, va).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_is_free(&self, pid: ProcessId, va: VirtualAddress, pages: usize) -> bool {
|
||||||
|
(0..pages).all(|i| !self.is_mapped(pid, page_at(va, i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quota_mut(&mut self, pid: ProcessId) -> Result<&mut Quota, KernelError> {
|
||||||
|
self.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_usage(&self, pid: ProcessId) -> usize {
|
||||||
|
self.quotas.get(pid.raw() as usize).map_or(0, Quota::used)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_page_limit(&mut self, pid: ProcessId, limit: usize) -> Result<(), KernelError> {
|
||||||
|
self.processes.get(pid)?;
|
||||||
|
if self.quota_mut(pid)?.set_limit(limit) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(KernelError::OutOfMemory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_page_count(&self) -> usize {
|
||||||
|
self.frames.free_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps `pa` at `va` in `pid`, building page tables on `pid`'s quota.
|
||||||
|
fn platform_map(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pa: PhysicalAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let quota = self
|
||||||
|
.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||||
|
let mut source = ChargedFrames {
|
||||||
|
frames: &mut self.frames,
|
||||||
|
quota,
|
||||||
|
owner: pid,
|
||||||
|
};
|
||||||
|
self.platform
|
||||||
|
.map_page(pid, va, pa, perms, &mut source)
|
||||||
|
.map_err(map_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_address_space(&mut self, pid: ProcessId) -> Result<(), KernelError> {
|
||||||
|
let quota = self
|
||||||
|
.quotas
|
||||||
|
.get_mut(pid.raw() as usize)
|
||||||
|
.ok_or(KernelError::InvalidProcess(pid))?;
|
||||||
|
*quota = Quota::default();
|
||||||
|
let mut source = ChargedFrames {
|
||||||
|
frames: &mut self.frames,
|
||||||
|
quota,
|
||||||
|
owner: pid,
|
||||||
|
};
|
||||||
|
let created = self
|
||||||
|
.platform
|
||||||
|
.create_address_space(pid, &mut source)
|
||||||
|
.map_err(map_error);
|
||||||
|
if created.is_err() {
|
||||||
|
self.frames.free_owned_by(pid);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
*q = Quota::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
created
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates, zeroes and maps one fresh page for `pid` at `va`.
|
||||||
|
fn map_fresh_page(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<PhysicalAddress, KernelError> {
|
||||||
|
let usage = FrameUse::user(pid, va, perms).ok_or(KernelError::InvalidAddress)?;
|
||||||
|
if !self.quota_mut(pid)?.charge(1) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
let Some(pa) = self.frames.alloc(usage) else {
|
||||||
|
self.quota_mut(pid)?.uncharge(1);
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
};
|
||||||
|
self.platform.zero_frame(pa);
|
||||||
|
if let Err(e) = self.platform_map(pid, va, pa, perms) {
|
||||||
|
self.frames.free(pa);
|
||||||
|
self.quota_mut(pid)?.uncharge(1);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes one user page of `pid` and returns its frame to the pool.
|
||||||
|
fn unmap_and_free(&mut self, pid: ProcessId, va: VirtualAddress) {
|
||||||
|
if let Some((pa, _)) = self.frames.find_user(pid, va) {
|
||||||
|
self.platform.unmap_page(pid, va);
|
||||||
|
self.frames.free(pa);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
q.uncharge(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frame backing the user page at `va` in `pid`. Lets boot code fill
|
||||||
|
/// pages it has just mapped for a process.
|
||||||
|
pub fn user_page_frame(&self, pid: ProcessId, va: VirtualAddress) -> Option<PhysicalAddress> {
|
||||||
|
self.frames.find_user(pid, va).map(|(pa, _)| pa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps `pages` fresh zeroed pages at `va`, all or nothing.
|
||||||
|
pub fn map_fresh_range(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pages: usize,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !self.range_is_free(pid, va, pages) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
if let Err(e) = self.map_fresh_page(pid, page_at(va, i), perms) {
|
||||||
|
for j in 0..i {
|
||||||
|
self.unmap_and_free(pid, page_at(va, j));
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First-fit search for `pages` free pages in the heap part of the window.
|
||||||
|
pub(crate) fn find_free_heap_range(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
pages: usize,
|
||||||
|
) -> Option<VirtualAddress> {
|
||||||
|
let mut candidate = USER_HEAP_START.max(P::USER_VA_START);
|
||||||
|
while Self::in_user_window(VirtualAddress::from_raw(candidate), pages) {
|
||||||
|
let va = VirtualAddress::from_raw(candidate);
|
||||||
|
match (0..pages).find(|&i| self.is_mapped(pid, page_at(va, i))) {
|
||||||
|
None => return Some(va),
|
||||||
|
Some(busy) => candidate += (busy + 1) * PAGE_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that every page of the range is a user page `pid` owns,
|
||||||
|
/// then releases them all.
|
||||||
|
pub(crate) fn release_range(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
pages: usize,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !(0..pages).all(|i| self.frames.find_user(pid, page_at(va, i)).is_some()) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
self.unmap_and_free(pid, page_at(va, i));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the page at `src` in `from` to `dst` in `to`. The frame and its
|
||||||
|
/// quota charge change hands; `from` loses access.
|
||||||
|
pub(crate) fn move_page(
|
||||||
|
&mut self,
|
||||||
|
from: ProcessId,
|
||||||
|
src: VirtualAddress,
|
||||||
|
to: ProcessId,
|
||||||
|
dst: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
if !Self::in_user_window(src, 1) || !Self::in_user_window(dst, 1) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
let (pa, _) = self
|
||||||
|
.frames
|
||||||
|
.find_user(from, src)
|
||||||
|
.ok_or(KernelError::InvalidAddress)?;
|
||||||
|
self.processes.get(to)?;
|
||||||
|
if !self.range_is_free(to, dst, 1) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
let usage = FrameUse::user(to, dst, perms).ok_or(KernelError::InvalidAddress)?;
|
||||||
|
if !self.quota_mut(to)?.charge(1) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
if let Err(e) = self.platform_map(to, dst, pa, perms) {
|
||||||
|
self.quota_mut(to)?.uncharge(1);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
self.platform.unmap_page(from, src);
|
||||||
|
self.frames.retag(pa, usage);
|
||||||
|
self.quota_mut(from)?.uncharge(1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates a physically contiguous, zeroed region charged to `owner`.
|
||||||
|
pub fn create_pool_region(
|
||||||
|
&mut self,
|
||||||
|
owner: ProcessId,
|
||||||
|
pages: PageCount,
|
||||||
|
) -> Result<RegionId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
let id = self.regions.next_free_id().ok_or(KernelError::NoFreeSlot)?;
|
||||||
|
let count = pages.value() as usize;
|
||||||
|
if !self.quota_mut(owner)?.charge(count) {
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
}
|
||||||
|
let Some(base) = self
|
||||||
|
.frames
|
||||||
|
.alloc_contiguous(count, |_| FrameUse::Region { region: id })
|
||||||
|
else {
|
||||||
|
self.quota_mut(owner)?.uncharge(count);
|
||||||
|
return Err(KernelError::OutOfMemory);
|
||||||
|
};
|
||||||
|
for i in 0..count {
|
||||||
|
self.platform.zero_frame(base.offset(i));
|
||||||
|
}
|
||||||
|
self.regions
|
||||||
|
.insert(Region::new(owner, RegionBacking::Pool, base, pages))
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes device memory at `base` so it can be mapped like a region.
|
||||||
|
pub fn create_device_region(
|
||||||
|
&mut self,
|
||||||
|
owner: ProcessId,
|
||||||
|
base: PhysicalAddress,
|
||||||
|
pages: PageCount,
|
||||||
|
) -> Result<RegionId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
if !base.raw().is_multiple_of(PAGE_SIZE) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
// Device memory must never alias frames the kernel hands out.
|
||||||
|
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_end = pool_start + self.frames.capacity() * PAGE_SIZE;
|
||||||
|
if start < pool_end && end > pool_start {
|
||||||
|
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
|
||||||
|
.insert(Region::new(owner, RegionBacking::Device, base, pages))
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn region_base(&self, id: RegionId) -> Option<PhysicalAddress> {
|
||||||
|
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 region `id` for boot code; the mapping is not tied to any
|
||||||
|
/// capability and lasts until the region or the process dies.
|
||||||
|
pub fn map_region(
|
||||||
|
&mut self,
|
||||||
|
id: RegionId,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
self.map_region_via(id, pid, va, perms, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps all of region `id` at `va` in `pid`, all or nothing. A mapping
|
||||||
|
/// made through a capability (`via`) is removed when that capability
|
||||||
|
/// is deleted.
|
||||||
|
pub(crate) fn map_region_via(
|
||||||
|
&mut self,
|
||||||
|
id: RegionId,
|
||||||
|
pid: ProcessId,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
via: Option<domain::capability::CapRef>,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
self.processes.get(pid)?;
|
||||||
|
let region = self.regions.get(id).ok_or(KernelError::InvalidRegion)?;
|
||||||
|
let (base, pages) = (region.base(), region.pages().value() as usize);
|
||||||
|
if region.mappings().any(|m| m.process == pid) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
// W^X holds across processes too: nobody may write what someone
|
||||||
|
// else can execute.
|
||||||
|
let clashes = |other: PagePerms| {
|
||||||
|
matches!(
|
||||||
|
(perms, other),
|
||||||
|
(PagePerms::ReadWrite, PagePerms::ReadExecute)
|
||||||
|
| (PagePerms::ReadExecute, PagePerms::ReadWrite)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if region.mappings().any(|m| clashes(m.perms)) {
|
||||||
|
return Err(KernelError::WritableAndExecutable);
|
||||||
|
}
|
||||||
|
if !Self::in_user_window(va, pages) {
|
||||||
|
return Err(KernelError::InvalidAddress);
|
||||||
|
}
|
||||||
|
if !self.range_is_free(pid, va, pages) {
|
||||||
|
return Err(KernelError::AddressInUse);
|
||||||
|
}
|
||||||
|
for i in 0..pages {
|
||||||
|
if let Err(e) = self.platform_map(pid, page_at(va, i), base.offset(i), perms) {
|
||||||
|
for j in 0..i {
|
||||||
|
self.platform.unmap_page(pid, page_at(va, j));
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let recorded = self.regions.get_mut(id).is_some_and(|r| {
|
||||||
|
r.add_mapping(RegionMapping {
|
||||||
|
process: pid,
|
||||||
|
va,
|
||||||
|
perms,
|
||||||
|
via,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if !recorded {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(pid, page_at(va, i));
|
||||||
|
}
|
||||||
|
return Err(KernelError::NoFreeSlot);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes `pid`'s mapping of region `id`, if it has one.
|
||||||
|
pub(crate) fn unmap_region_from(&mut self, id: RegionId, pid: ProcessId) {
|
||||||
|
let Some(region) = self.regions.get_mut(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let pages = region.pages().value() as usize;
|
||||||
|
if let Some(m) = region.remove_mapping(pid) {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(pid, page_at(m.va, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees regions owned by `pid` (unmapping them from every other
|
||||||
|
/// process) and forgets `pid`'s mappings of other regions.
|
||||||
|
fn teardown_regions(&mut self, pid: ProcessId) {
|
||||||
|
let mut owned = [None; domain::memory::MAX_REGIONS];
|
||||||
|
for (slot, id) in owned.iter_mut().zip(self.regions.ids_owned_by(pid)) {
|
||||||
|
*slot = Some(id);
|
||||||
|
}
|
||||||
|
for id in owned.into_iter().flatten() {
|
||||||
|
let Some(region) = self.regions.remove(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let pages = region.pages().value() as usize;
|
||||||
|
for m in region.mappings().filter(|m| m.process != pid) {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.platform.unmap_page(m.process, page_at(m.va, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if region.backing() == RegionBacking::Pool {
|
||||||
|
for i in 0..pages {
|
||||||
|
self.frames.free(region.base().offset(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (_, region) in self.regions.iter_mut() {
|
||||||
|
region.remove_mapping(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases everything `pid` holds in memory: its address space, page
|
||||||
|
/// tables, user pages and owned regions.
|
||||||
|
pub(crate) fn teardown_memory(&mut self, pid: ProcessId) {
|
||||||
|
self.teardown_regions(pid);
|
||||||
|
self.platform.destroy_address_space(pid);
|
||||||
|
self.frames.free_owned_by(pid);
|
||||||
|
if let Some(q) = self.quotas.get_mut(pid.raw() as usize) {
|
||||||
|
*q = Quota::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_at(base: VirtualAddress, i: usize) -> VirtualAddress {
|
||||||
|
VirtualAddress::from_raw(base.raw() + i * PAGE_SIZE)
|
||||||
|
}
|
||||||
75
crates/kernel/src/proofs/abi.rs
Normal file
75
crates/kernel/src/proofs/abi.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use crate::abi::{decode, encode};
|
||||||
|
use crate::syscall::SyscallResult;
|
||||||
|
use domain::error::KernelError;
|
||||||
|
use domain::syscall::Syscall;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
use types::syscall_abi::*;
|
||||||
|
|
||||||
|
/// No register contents can make decoding panic or overflow.
|
||||||
|
#[kani::proof]
|
||||||
|
fn decode_is_total() {
|
||||||
|
let nr: usize = kani::any();
|
||||||
|
let args: [usize; 6] = kani::any();
|
||||||
|
let _ = decode(nr, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A decoded slot argument is exactly the register it came from.
|
||||||
|
#[kani::proof]
|
||||||
|
fn decoded_slots_match_their_registers() {
|
||||||
|
let args: [usize; 6] = kani::any();
|
||||||
|
if let Some(Syscall::Receive { endpoint }) = decode(SYS_RECEIVE, &args) {
|
||||||
|
assert!(endpoint.value() as usize == args[0]);
|
||||||
|
}
|
||||||
|
if let Some(Syscall::Delegate {
|
||||||
|
slot, to_endpoint, ..
|
||||||
|
}) = decode(SYS_DELEGATE, &args)
|
||||||
|
{
|
||||||
|
assert!(slot.value() as usize == args[0]);
|
||||||
|
assert!(to_endpoint.value() as usize == args[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What libkos encodes for a Send is decoded back to the same message.
|
||||||
|
#[kani::proof]
|
||||||
|
fn send_roundtrips() {
|
||||||
|
let ep = CapSlot::new(kani::any::<u8>() % (CapSlot::MAX + 1)).unwrap();
|
||||||
|
let words: [usize; 4] = kani::any();
|
||||||
|
let cap = if kani::any() {
|
||||||
|
CapSlot::new(kani::any::<u8>() % (CapSlot::MAX + 1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let regs = message_regs(ep.value() as usize, words, cap);
|
||||||
|
match decode(SYS_SEND, ®s) {
|
||||||
|
Some(Syscall::Send { endpoint, message }) => {
|
||||||
|
assert!(endpoint == ep);
|
||||||
|
assert!(*message.words() == words);
|
||||||
|
assert!(message.cap() == cap);
|
||||||
|
}
|
||||||
|
_ => panic!("valid send did not decode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A failed syscall always reports a real, non-zero error code.
|
||||||
|
#[kani::proof]
|
||||||
|
fn errors_encode_to_valid_codes() {
|
||||||
|
let e = match kani::any::<u8>() % 6 {
|
||||||
|
0 => KernelError::NoReply,
|
||||||
|
1 => KernelError::OutOfMemory,
|
||||||
|
2 => KernelError::InvalidAddress,
|
||||||
|
3 => KernelError::WrongObject,
|
||||||
|
4 => KernelError::NoSuchObject,
|
||||||
|
_ => KernelError::InvalidSlot(CapSlot::new(0).unwrap()),
|
||||||
|
};
|
||||||
|
let regs = encode(&SyscallResult::Error(e));
|
||||||
|
assert!(regs[0] != 0);
|
||||||
|
assert!(ErrorCode::from_raw(regs[0]).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
fn successes_report_status_zero() {
|
||||||
|
let v: usize = kani::any();
|
||||||
|
assert!(encode(&SyscallResult::Ok)[0] == 0);
|
||||||
|
let regs = encode(&SyscallResult::Value(v));
|
||||||
|
assert!(regs[0] == 0 && regs[1] == v);
|
||||||
|
}
|
||||||
118
crates/kernel/src/proofs/authority.rs
Normal file
118
crates/kernel/src/proofs/authority.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
//! Mint creates no authority. The real kernel, with tables shrunk under
|
||||||
|
//! `cfg(kani)`, runs `mint` with arbitrary arguments in a small state
|
||||||
|
//! whose capabilities carry arbitrary rights. Afterwards every capability
|
||||||
|
//! to an object that already existed carries no more rights than some
|
||||||
|
//! capability to that object carried before.
|
||||||
|
//!
|
||||||
|
//! Only this harness is kept. The same check over delegate, delete,
|
||||||
|
//! revoke, create and kill either exceeded 12 GB in CBMC or tripped
|
||||||
|
//! modelling artifacts ("unreachable code" on safe `Option` matches that
|
||||||
|
//! an exhaustive host replay never reaches). Those operations are covered
|
||||||
|
//! by the syscall storm's global invariants (tests/storm.rs) and the
|
||||||
|
//! capability-tree model test (tests/captree_model.rs) instead.
|
||||||
|
|
||||||
|
use super::platform::KaniPlatform;
|
||||||
|
use crate::Kernel;
|
||||||
|
use domain::{capability::MAX_CAPS_PER_PROCESS, process::MAX_PROCESSES, resource::Resource};
|
||||||
|
use types::{
|
||||||
|
address::PhysicalAddress,
|
||||||
|
badge::Badge,
|
||||||
|
bounded::{CapSlot, Priority},
|
||||||
|
id::ProcessId,
|
||||||
|
permission::PermissionSet,
|
||||||
|
time::TimeSlice,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
struct Held {
|
||||||
|
resource: Resource,
|
||||||
|
perms: PermissionSet,
|
||||||
|
badge: Badge,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot = [[Option<Held>; MAX_CAPS_PER_PROCESS]; MAX_PROCESSES];
|
||||||
|
|
||||||
|
fn snapshot(k: &Kernel<KaniPlatform>) -> Snapshot {
|
||||||
|
let mut s = [[None; MAX_CAPS_PER_PROCESS]; MAX_PROCESSES];
|
||||||
|
for (p, row) in s.iter_mut().enumerate() {
|
||||||
|
if let Ok(process) = k.processes.get(ProcessId::from_raw(p as u16)) {
|
||||||
|
for (slot, cap) in process.capabilities().iter() {
|
||||||
|
row[slot.value() as usize] = Some(Held {
|
||||||
|
resource: *cap.resource(),
|
||||||
|
perms: *cap.permissions(),
|
||||||
|
badge: cap.badge(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `resource` named a live object before the operation.
|
||||||
|
fn existed(before: &Snapshot, resource: &Resource) -> bool {
|
||||||
|
before
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.any(|h| h.resource == *resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some capability before the operation already granted at least `h`.
|
||||||
|
fn justified(before: &Snapshot, h: &Held) -> bool {
|
||||||
|
before.iter().flatten().flatten().any(|b| {
|
||||||
|
b.resource == h.resource
|
||||||
|
&& h.perms.is_subset_of(&b.perms)
|
||||||
|
&& (b.badge == h.badge || b.badge.is_none())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_perms() -> PermissionSet {
|
||||||
|
PermissionSet::from_bits(kani::any::<u8>() & 0b1111)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn any_slot() -> CapSlot {
|
||||||
|
CapSlot::new(kani::any::<u8>() % (CapSlot::MAX + 1)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Init and one more process, an endpoint, a notification, and caps with
|
||||||
|
/// arbitrary rights spread over both, including a cap to the other process.
|
||||||
|
fn small_world() -> Kernel<KaniPlatform> {
|
||||||
|
let mut k = Kernel::new(KaniPlatform, PhysicalAddress::from_raw(0x8400_0000), 6);
|
||||||
|
let (init, _) = k.boot(Priority::DEFAULT, TimeSlice::DEFAULT).unwrap();
|
||||||
|
let b = k.create_process_internal().unwrap();
|
||||||
|
k.spawn_thread_internal(b).unwrap();
|
||||||
|
let ep = k.create_endpoint(init).unwrap();
|
||||||
|
let n = k.create_notification(b).unwrap();
|
||||||
|
k.grant_capability(init, Resource::Endpoint(ep), any_perms())
|
||||||
|
.unwrap();
|
||||||
|
k.grant_capability(init, Resource::Process(b), any_perms())
|
||||||
|
.unwrap();
|
||||||
|
k.grant_capability(b, Resource::Endpoint(ep), any_perms())
|
||||||
|
.unwrap();
|
||||||
|
k.grant_capability(b, Resource::Notification(n), any_perms())
|
||||||
|
.unwrap();
|
||||||
|
k
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller is concrete: a symbolic pid turns every table lookup into a
|
||||||
|
/// symbolic index and blows CBMC's formula up past 30 GB.
|
||||||
|
#[kani::proof]
|
||||||
|
#[kani::unwind(8)]
|
||||||
|
fn mint_creates_no_authority() {
|
||||||
|
let mut k = small_world();
|
||||||
|
let (slot, badge, perms) = (any_slot(), Badge::new(kani::any()), any_perms());
|
||||||
|
let before = snapshot(&k);
|
||||||
|
let _ = k.mint(ProcessId::from_raw(0), slot, badge, perms);
|
||||||
|
let after = snapshot(&k);
|
||||||
|
for (p, row) in after.iter().enumerate() {
|
||||||
|
for (s, held) in row.iter().enumerate() {
|
||||||
|
let Some(h) = held else { continue };
|
||||||
|
if before[p][s] == Some(*h) {
|
||||||
|
continue; // untouched
|
||||||
|
}
|
||||||
|
if existed(&before, &h.resource) {
|
||||||
|
assert!(justified(&before, h), "authority appeared from nowhere");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
use domain::capability::{Capability, CapabilityTable};
|
use domain::capability::{Capability, CapabilityTable};
|
||||||
use domain::resource::Resource;
|
use domain::resource::Resource;
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
|
||||||
use types::generation::Generation;
|
use types::generation::Generation;
|
||||||
use types::id::EndpointId;
|
use types::id::EndpointId;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
@@ -16,17 +16,16 @@ fn kernel_delegate_path_never_escalates() {
|
|||||||
kani::assume(source_perms.allows(Permission::Delegate));
|
kani::assume(source_perms.allows(Permission::Delegate));
|
||||||
|
|
||||||
let mut source_table = CapabilityTable::new();
|
let mut source_table = CapabilityTable::new();
|
||||||
let slot = insert_cap(&mut source_table, arbitrary_endpoint_cap(source_bits, ep_id));
|
let slot = insert_cap(
|
||||||
|
&mut source_table,
|
||||||
|
arbitrary_endpoint_cap(source_bits, ep_id),
|
||||||
|
);
|
||||||
|
|
||||||
let cap = source_table.get(slot).unwrap();
|
let cap = source_table.get(slot).unwrap();
|
||||||
let original_perms = *cap.permissions();
|
let original_perms = *cap.permissions();
|
||||||
let restricted = original_perms.restrict_to(&PermissionSet::from_bits(restrict_bits));
|
let restricted = original_perms.restrict_to(&PermissionSet::from_bits(restrict_bits));
|
||||||
|
|
||||||
let delegated = Capability::new(
|
let delegated = Capability::new(*cap.resource(), restricted, Generation::initial());
|
||||||
cap.resource().clone(),
|
|
||||||
restricted,
|
|
||||||
Generation::initial(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut target_table = CapabilityTable::new();
|
let mut target_table = CapabilityTable::new();
|
||||||
let target_slot = insert_cap(&mut target_table, delegated);
|
let target_slot = insert_cap(&mut target_table, delegated);
|
||||||
@@ -34,11 +33,18 @@ fn kernel_delegate_path_never_escalates() {
|
|||||||
let result = target_table.get(target_slot).unwrap();
|
let result = target_table.get(target_slot).unwrap();
|
||||||
assert!(result.permissions().is_subset_of(&original_perms));
|
assert!(result.permissions().is_subset_of(&original_perms));
|
||||||
|
|
||||||
if !original_perms.allows(Permission::Read) { assert!(!result.permissions().allows(Permission::Read)); }
|
if !original_perms.allows(Permission::Read) {
|
||||||
if !original_perms.allows(Permission::Write) { assert!(!result.permissions().allows(Permission::Write)); }
|
assert!(!result.permissions().allows(Permission::Read));
|
||||||
if !original_perms.allows(Permission::Execute) { assert!(!result.permissions().allows(Permission::Execute)); }
|
}
|
||||||
if !original_perms.allows(Permission::Delegate) { assert!(!result.permissions().allows(Permission::Delegate)); }
|
if !original_perms.allows(Permission::Write) {
|
||||||
if !original_perms.allows(Permission::Revoke) { assert!(!result.permissions().allows(Permission::Revoke)); }
|
assert!(!result.permissions().allows(Permission::Write));
|
||||||
|
}
|
||||||
|
if !original_perms.allows(Permission::Execute) {
|
||||||
|
assert!(!result.permissions().allows(Permission::Execute));
|
||||||
|
}
|
||||||
|
if !original_perms.allows(Permission::Delegate) {
|
||||||
|
assert!(!result.permissions().allows(Permission::Delegate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
@@ -58,15 +64,19 @@ fn kernel_chained_delegate_never_escalates() {
|
|||||||
let slot_a = insert_cap(&mut table_a, arbitrary_endpoint_cap(original_bits, ep_id));
|
let slot_a = insert_cap(&mut table_a, arbitrary_endpoint_cap(original_bits, ep_id));
|
||||||
|
|
||||||
let cap = table_a.get(slot_a).unwrap();
|
let cap = table_a.get(slot_a).unwrap();
|
||||||
let perms_for_b = cap.permissions().restrict_to(&PermissionSet::from_bits(restrict1_bits));
|
let perms_for_b = cap
|
||||||
|
.permissions()
|
||||||
|
.restrict_to(&PermissionSet::from_bits(restrict1_bits));
|
||||||
|
|
||||||
let mut table_b = CapabilityTable::new();
|
let mut table_b = CapabilityTable::new();
|
||||||
let cap_b = Capability::new(resource.clone(), perms_for_b, Generation::initial());
|
let cap_b = Capability::new(resource, perms_for_b, Generation::initial());
|
||||||
let slot_b = insert_cap(&mut table_b, cap_b);
|
let slot_b = insert_cap(&mut table_b, cap_b);
|
||||||
|
|
||||||
let cap_in_b = table_b.get(slot_b).unwrap();
|
let cap_in_b = table_b.get(slot_b).unwrap();
|
||||||
if cap_in_b.permissions().allows(Permission::Delegate) {
|
if cap_in_b.permissions().allows(Permission::Delegate) {
|
||||||
let perms_for_c = cap_in_b.permissions().restrict_to(&PermissionSet::from_bits(restrict2_bits));
|
let perms_for_c = cap_in_b
|
||||||
|
.permissions()
|
||||||
|
.restrict_to(&PermissionSet::from_bits(restrict2_bits));
|
||||||
|
|
||||||
let mut table_c = CapabilityTable::new();
|
let mut table_c = CapabilityTable::new();
|
||||||
let cap_c = Capability::new(resource, perms_for_c, Generation::initial());
|
let cap_c = Capability::new(resource, perms_for_c, Generation::initial());
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use domain::capability::CapabilityTable;
|
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
|
use domain::capability::CapabilityTable;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ fn handle_receive_rejects_without_read_permission() {
|
|||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
fn missing_cap_always_rejected() {
|
fn missing_cap_always_rejected() {
|
||||||
let slot_val: u8 = kani::any();
|
let slot_val: u8 = kani::any();
|
||||||
kani::assume(slot_val <= 63);
|
kani::assume(slot_val <= CapSlot::MAX);
|
||||||
let slot = CapSlot::new(slot_val).unwrap();
|
let slot = CapSlot::new(slot_val).unwrap();
|
||||||
|
|
||||||
let table = CapabilityTable::new();
|
let table = CapabilityTable::new();
|
||||||
@@ -60,7 +60,10 @@ fn check_cap_faithfully_reflects_stored_permissions() {
|
|||||||
|
|
||||||
assert!(retrieved.permissions().allows(Permission::Read) == perms.allows(Permission::Read));
|
assert!(retrieved.permissions().allows(Permission::Read) == perms.allows(Permission::Read));
|
||||||
assert!(retrieved.permissions().allows(Permission::Write) == perms.allows(Permission::Write));
|
assert!(retrieved.permissions().allows(Permission::Write) == perms.allows(Permission::Write));
|
||||||
assert!(retrieved.permissions().allows(Permission::Execute) == perms.allows(Permission::Execute));
|
assert!(
|
||||||
assert!(retrieved.permissions().allows(Permission::Delegate) == perms.allows(Permission::Delegate));
|
retrieved.permissions().allows(Permission::Execute) == perms.allows(Permission::Execute)
|
||||||
assert!(retrieved.permissions().allows(Permission::Revoke) == perms.allows(Permission::Revoke));
|
);
|
||||||
|
assert!(
|
||||||
|
retrieved.permissions().allows(Permission::Delegate) == perms.allows(Permission::Delegate)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
mod abi;
|
||||||
|
mod authority;
|
||||||
mod delegation;
|
mod delegation;
|
||||||
mod mediation;
|
mod mediation;
|
||||||
mod message;
|
mod message;
|
||||||
|
mod platform;
|
||||||
mod revocation;
|
mod revocation;
|
||||||
|
|
||||||
use domain::capability::{Capability, CapabilityTable};
|
use domain::capability::{Capability, CapabilityTable};
|
||||||
|
|||||||
62
crates/kernel/src/proofs/platform.rs
Normal file
62
crates/kernel/src/proofs/platform.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//! A platform with no hardware behind it, for running the real kernel
|
||||||
|
//! under Kani. Page tables cost one frame per address space.
|
||||||
|
|
||||||
|
use platform::{FrameSource, MapError, Platform};
|
||||||
|
use types::{
|
||||||
|
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},
|
||||||
|
id::{CoreId, IrqId, ProcessId},
|
||||||
|
mapping::PagePerms,
|
||||||
|
time::{Ticks, TimerDuration},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct KaniPlatform;
|
||||||
|
|
||||||
|
impl Platform for KaniPlatform {
|
||||||
|
const USER_VA_START: usize = 0x1000;
|
||||||
|
const USER_VA_END: usize = 0x0200_0000;
|
||||||
|
|
||||||
|
fn create_address_space(
|
||||||
|
&mut self,
|
||||||
|
_process: ProcessId,
|
||||||
|
frames: &mut dyn FrameSource,
|
||||||
|
) -> Result<(), MapError> {
|
||||||
|
frames.alloc_table_frame().ok_or(MapError::OutOfFrames)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn destroy_address_space(&mut self, _process: ProcessId) {}
|
||||||
|
fn map_page(
|
||||||
|
&mut self,
|
||||||
|
_process: ProcessId,
|
||||||
|
_va: VirtualAddress,
|
||||||
|
_pa: PhysicalAddress,
|
||||||
|
_perms: PagePerms,
|
||||||
|
_frames: &mut dyn FrameSource,
|
||||||
|
) -> Result<(), MapError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn unmap_page(&mut self, _process: ProcessId, _va: VirtualAddress) {}
|
||||||
|
fn zero_frame(&mut self, _pa: PhysicalAddress) {}
|
||||||
|
fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! {
|
||||||
|
kani::assume(false);
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
fn set_timer(&mut self, _duration: TimerDuration) {}
|
||||||
|
fn current_ticks(&self) -> Ticks {
|
||||||
|
Ticks::from_raw(0)
|
||||||
|
}
|
||||||
|
fn mask_irq(&mut self, _irq: IrqId) {}
|
||||||
|
fn unmask_irq(&mut self, _irq: IrqId) {}
|
||||||
|
fn send_ipi(&mut self, _target: CoreId) {}
|
||||||
|
fn core_id(&self) -> CoreId {
|
||||||
|
CoreId::from_raw(0)
|
||||||
|
}
|
||||||
|
fn num_cores(&self) -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn halt(&self) -> ! {
|
||||||
|
// Halting ends the run; there is no state after it to check.
|
||||||
|
kani::assume(false);
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
fn debug_putchar(&self, _byte: u8) {}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use domain::capability::{Capability, CapabilityTable};
|
|
||||||
use domain::endpoint::Endpoint;
|
|
||||||
use domain::resource::Resource;
|
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
|
use domain::capability::{Capability, CapabilityTable};
|
||||||
|
use domain::resource::Resource;
|
||||||
use types::generation::Generation;
|
use types::generation::Generation;
|
||||||
use types::id::EndpointId;
|
use types::id::EndpointId;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
@@ -62,77 +61,3 @@ fn grant_cap_preserves_resource_identity() {
|
|||||||
_ => panic!("resource type changed"),
|
_ => panic!("resource type changed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[kani::proof]
|
|
||||||
fn generation_revocation_invalidates_all_caps() {
|
|
||||||
let bits: u8 = kani::any();
|
|
||||||
let ep_id: u16 = kani::any();
|
|
||||||
kani::assume(ep_id < 256);
|
|
||||||
|
|
||||||
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
|
||||||
let original_gen = *endpoint.generation();
|
|
||||||
|
|
||||||
let cap = Capability::new(
|
|
||||||
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
|
||||||
PermissionSet::from_bits(bits),
|
|
||||||
original_gen,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut table = CapabilityTable::new();
|
|
||||||
let slot = insert_cap(&mut table, cap);
|
|
||||||
|
|
||||||
assert!(table.get(slot).unwrap().generation().is_valid_against(&original_gen));
|
|
||||||
|
|
||||||
endpoint.revoke();
|
|
||||||
let new_gen = *endpoint.generation();
|
|
||||||
|
|
||||||
assert!(!table.get(slot).unwrap().generation().is_valid_against(&new_gen));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[kani::proof]
|
|
||||||
fn cap_created_after_revoke_is_valid() {
|
|
||||||
let ep_id: u16 = kani::any();
|
|
||||||
kani::assume(ep_id < 256);
|
|
||||||
let bits: u8 = kani::any();
|
|
||||||
|
|
||||||
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
|
||||||
endpoint.revoke();
|
|
||||||
let current_gen = *endpoint.generation();
|
|
||||||
|
|
||||||
let cap = Capability::new(
|
|
||||||
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
|
||||||
PermissionSet::from_bits(bits),
|
|
||||||
current_gen,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(cap.generation().is_valid_against(¤t_gen));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[kani::proof]
|
|
||||||
fn multiple_revocations_invalidate_all_prior_caps() {
|
|
||||||
let ep_id: u16 = kani::any();
|
|
||||||
kani::assume(ep_id < 256);
|
|
||||||
|
|
||||||
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
|
||||||
|
|
||||||
let gen0 = *endpoint.generation();
|
|
||||||
let cap0 = Capability::new(
|
|
||||||
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
gen0,
|
|
||||||
);
|
|
||||||
|
|
||||||
endpoint.revoke();
|
|
||||||
let gen1 = *endpoint.generation();
|
|
||||||
let cap1 = Capability::new(
|
|
||||||
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
gen1,
|
|
||||||
);
|
|
||||||
|
|
||||||
endpoint.revoke();
|
|
||||||
let gen2 = *endpoint.generation();
|
|
||||||
|
|
||||||
assert!(!cap0.generation().is_valid_against(&gen2));
|
|
||||||
assert!(!cap1.generation().is_valid_against(&gen2));
|
|
||||||
}
|
|
||||||
|
|||||||
119
crates/kernel/src/smp.rs
Normal file
119
crates/kernel/src/smp.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
//! Cores. Every thread has a home core, chosen when it is created, and
|
||||||
|
//! only ever runs there: it waits in that core's run queue and is never
|
||||||
|
//! migrated. Making a thread ready from another core sends that core an
|
||||||
|
//! IPI when it has to look at its queue again (it is idle, or the thread
|
||||||
|
//! outranks what it is running), and so does taking away the thread a
|
||||||
|
//! core is running.
|
||||||
|
|
||||||
|
use domain::{
|
||||||
|
scheduler::{Scheduler, SchedulerDecision},
|
||||||
|
thread::ThreadState,
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
bounded::Priority,
|
||||||
|
id::{CoreId, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
|
impl<P: Platform> Kernel<P> {
|
||||||
|
/// The core `tid` runs on.
|
||||||
|
pub fn home(&self, tid: ThreadId) -> CoreId {
|
||||||
|
self.homes
|
||||||
|
.get(tid.raw() as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(CoreId::from_raw(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Homes a new thread on `core`.
|
||||||
|
pub(crate) fn set_home(&mut self, tid: ThreadId, core: CoreId) {
|
||||||
|
if let Some(h) = self.homes.get_mut(tid.raw() as usize) {
|
||||||
|
*h = core;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Homes a new thread on the core with the fewest live threads.
|
||||||
|
pub(crate) fn place(&mut self, tid: ThreadId) {
|
||||||
|
let cores = self.scheduler.num_cores().max(1);
|
||||||
|
let load = |core: CoreId| {
|
||||||
|
self.threads
|
||||||
|
.iter()
|
||||||
|
.filter(|(t, _)| *t != tid && self.home(*t) == core)
|
||||||
|
.count()
|
||||||
|
};
|
||||||
|
let best = (0..cores)
|
||||||
|
.map(CoreId::from_raw)
|
||||||
|
.min_by_key(|c| load(*c))
|
||||||
|
.unwrap_or(CoreId::from_raw(0));
|
||||||
|
self.set_home(tid, best);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queues a ready thread on its home core, and kicks that core if it
|
||||||
|
/// is another one that should look at its queue now.
|
||||||
|
pub(crate) fn make_ready(&mut self, tid: ThreadId, priority: Priority) {
|
||||||
|
let home = self.home(tid);
|
||||||
|
self.scheduler.add(tid, priority, home);
|
||||||
|
if home == self.platform.core_id() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let outranks = match self.scheduler.current(home) {
|
||||||
|
None => true,
|
||||||
|
Some(running) => self
|
||||||
|
.threads
|
||||||
|
.get(running)
|
||||||
|
.is_ok_and(|t| priority > t.priority()),
|
||||||
|
};
|
||||||
|
if outranks {
|
||||||
|
self.platform.send_ipi(home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Before `tid` is destroyed: if another core is running it, that core
|
||||||
|
/// must stop and pick something else.
|
||||||
|
pub(crate) fn evict(&mut self, tid: ThreadId) {
|
||||||
|
if let Ok(ThreadState::Running(core)) = self.threads.get(tid).map(|t| *t.state())
|
||||||
|
&& core != self.platform.core_id()
|
||||||
|
{
|
||||||
|
self.platform.send_ipi(core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What `core` should do after an IPI: keep running its thread, or
|
||||||
|
/// switch to a higher-priority one now waiting. When it has no thread
|
||||||
|
/// (it was idle, or its thread was destroyed) the caller picks the
|
||||||
|
/// next one itself.
|
||||||
|
pub fn reschedule(&mut self, core: CoreId) -> SchedulerDecision {
|
||||||
|
let Some(current) = self.scheduler.current(core) else {
|
||||||
|
return SchedulerDecision::Continue;
|
||||||
|
};
|
||||||
|
let Ok(thread) = self.threads.get(current) else {
|
||||||
|
return SchedulerDecision::Continue;
|
||||||
|
};
|
||||||
|
let priority = thread.priority();
|
||||||
|
if self
|
||||||
|
.scheduler
|
||||||
|
.highest_waiting(core)
|
||||||
|
.is_none_or(|p| p <= priority)
|
||||||
|
{
|
||||||
|
return SchedulerDecision::Continue;
|
||||||
|
}
|
||||||
|
let Some(next) = self.scheduler.next(core) else {
|
||||||
|
return SchedulerDecision::Continue;
|
||||||
|
};
|
||||||
|
if self
|
||||||
|
.threads
|
||||||
|
.get_mut(next)
|
||||||
|
.and_then(|t| t.set_running(core))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return SchedulerDecision::Continue;
|
||||||
|
}
|
||||||
|
if let Ok(t) = self.threads.get_mut(current)
|
||||||
|
&& t.preempt().is_ok()
|
||||||
|
{
|
||||||
|
self.scheduler.add(current, priority, core);
|
||||||
|
}
|
||||||
|
SchedulerDecision::Preempt(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,17 +11,18 @@ impl<P: Platform> Kernel<P> {
|
|||||||
to_endpoint: types::bounded::CapSlot,
|
to_endpoint: types::bounded::CapSlot,
|
||||||
restrict: types::permission::PermissionSet,
|
restrict: types::permission::PermissionSet,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let (resource, permissions) = match self.check_cap(
|
// A copy of the capability itself, so its badge and generation
|
||||||
caller_pid,
|
// travel with it; only the permissions narrow.
|
||||||
slot,
|
let copy = match self.check_cap(caller_pid, slot, types::permission::Permission::Delegate) {
|
||||||
types::permission::Permission::Delegate,
|
Ok(cap) => domain::capability::Capability::new(
|
||||||
) {
|
*cap.resource(),
|
||||||
Ok(cap) => (cap.resource().clone(), *cap.permissions()),
|
cap.permissions().restrict_to(&restrict),
|
||||||
|
*cap.generation(),
|
||||||
|
)
|
||||||
|
.with_badge(cap.badge()),
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let restricted = permissions.restrict_to(&restrict);
|
|
||||||
|
|
||||||
let target_pid = match self.check_cap(
|
let target_pid = match self.check_cap(
|
||||||
caller_pid,
|
caller_pid,
|
||||||
to_endpoint,
|
to_endpoint,
|
||||||
@@ -29,31 +30,24 @@ impl<P: Platform> Kernel<P> {
|
|||||||
) {
|
) {
|
||||||
Ok(cap) => match cap.resource() {
|
Ok(cap) => match cap.resource() {
|
||||||
domain::resource::Resource::Process(pid) => *pid,
|
domain::resource::Resource::Process(pid) => *pid,
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
_ => {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
domain::error::KernelError::PermissionDenied {
|
domain::error::KernelError::PermissionDenied {
|
||||||
slot: to_endpoint,
|
slot: to_endpoint,
|
||||||
needed: types::permission::Permission::Execute,
|
needed: types::permission::Permission::Execute,
|
||||||
},
|
},
|
||||||
)),
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = self.grant_cap(target_pid, resource.clone(), restricted) {
|
let parent = self.cap_ref(caller_pid, slot);
|
||||||
|
let inserted = self.insert_derived(target_pid, copy, parent);
|
||||||
|
if let Err(e) = inserted {
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let domain::resource::Resource::Memory(region_id) = &resource {
|
|
||||||
if let Some(region) = self.regions.get(*region_id) {
|
|
||||||
self.platform.map_shared(
|
|
||||||
target_pid,
|
|
||||||
region.base(),
|
|
||||||
region.size(),
|
|
||||||
restricted,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,16 +56,20 @@ impl<P: Platform> Kernel<P> {
|
|||||||
caller_pid: types::id::ProcessId,
|
caller_pid: types::id::ProcessId,
|
||||||
slot: types::bounded::CapSlot,
|
slot: types::bounded::CapSlot,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let resource = match self.check_cap(
|
SyscallOutcome::Complete(match self.revoke_subtree(caller_pid, slot) {
|
||||||
caller_pid,
|
Ok(()) => SyscallResult::Ok,
|
||||||
slot,
|
Err(e) => SyscallResult::Error(e),
|
||||||
types::permission::Permission::Revoke,
|
})
|
||||||
) {
|
}
|
||||||
Ok(cap) => cap.resource().clone(),
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.revoke_resource(&resource);
|
pub(crate) fn handle_drop_cap(
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
&mut self,
|
||||||
|
caller_pid: types::id::ProcessId,
|
||||||
|
slot: types::bounded::CapSlot,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
SyscallOutcome::Complete(match self.delete_cap(caller_pid, slot) {
|
||||||
|
Ok(()) => SyscallResult::Ok,
|
||||||
|
Err(e) => SyscallResult::Error(e),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,42 @@
|
|||||||
|
use domain::{fault::Fault, message::Message};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
|
use types::{id::ThreadId, permission::Permission};
|
||||||
|
|
||||||
|
use super::{Delivery, DeliveryKind, SyscallResult};
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub fn handle_fault(
|
/// A thread faulted. Report it to the process's fault endpoint (if one
|
||||||
&mut self,
|
/// is set, valid and has a receiver waiting), then destroy the process.
|
||||||
tid: types::id::ThreadId,
|
pub fn handle_fault(&mut self, tid: ThreadId, fault: Fault, fault_pc: usize) {
|
||||||
fault: domain::fault::Fault,
|
let Some(pid) = self.thread_process(tid) else {
|
||||||
fault_pc: usize,
|
return;
|
||||||
) {
|
|
||||||
let pid = match self.threads.get(tid) {
|
|
||||||
Ok(t) => t.process(),
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
};
|
||||||
|
let handler = self
|
||||||
// Try to deliver fault to a fault handler endpoint (if configured)
|
.processes
|
||||||
if let Ok(process) = self.processes.get(pid) {
|
.get(pid)
|
||||||
if let Some(handler_slot) = process.fault_handler() {
|
.ok()
|
||||||
if let Ok(cap) = process.capabilities().get(handler_slot) {
|
.and_then(|p| p.fault_handler())
|
||||||
if let domain::resource::Resource::Endpoint(ep_id) = cap.resource() {
|
.and_then(|slot| self.check_cap(pid, slot, Permission::Execute).ok())
|
||||||
let msg = domain::message::Message::from_words([
|
.and_then(|cap| match cap.resource() {
|
||||||
pid.raw() as usize,
|
domain::resource::Resource::Endpoint(ep) => Some((*ep, cap.badge())),
|
||||||
fault.code(),
|
_ => None,
|
||||||
fault.address(),
|
});
|
||||||
fault_pc,
|
if let Some((ep, badge)) = handler
|
||||||
]);
|
&& let Some(receiver) = self
|
||||||
|
.endpoints
|
||||||
if let Some(ep) = self.endpoints.get_mut(*ep_id) {
|
.get_mut(ep)
|
||||||
if ep.has_waiting_receiver() {
|
.and_then(|e| e.dequeue_receiver())
|
||||||
if let Some(receiver_tid) = ep.dequeue_receiver() {
|
{
|
||||||
self.pending_results[receiver_tid.raw() as usize] =
|
let report =
|
||||||
Some(crate::SyscallResult::Message(msg));
|
Message::from_words([pid.raw() as usize, fault.code(), fault.address(), fault_pc]);
|
||||||
|
let delivery = Delivery {
|
||||||
if let Ok(receiver) = self.threads.get_mut(receiver_tid) {
|
kind: DeliveryKind::Message,
|
||||||
let priority = receiver.priority();
|
badge: badge.raw(),
|
||||||
let _ = receiver.unblock();
|
words: *report.words(),
|
||||||
let core = self.platform.core_id();
|
cap: None,
|
||||||
use domain::scheduler::Scheduler;
|
};
|
||||||
self.scheduler.add(receiver_tid, priority, core);
|
self.wake(receiver, SyscallResult::Received(delivery));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.destroy_process(pid);
|
self.destroy_process(pid);
|
||||||
|
|||||||
@@ -1,336 +1,569 @@
|
|||||||
use platform::Platform;
|
//! Endpoint IPC, notifications and interrupt delivery. docs/ipc.md is the
|
||||||
|
//! specification; the comments below only point at the non-obvious rules.
|
||||||
|
|
||||||
use super::{SyscallOutcome, SyscallResult};
|
use domain::{
|
||||||
|
capability::Capability,
|
||||||
|
error::KernelError,
|
||||||
|
message::Message,
|
||||||
|
notification::SignalEffect,
|
||||||
|
resource::Resource,
|
||||||
|
scheduler::Scheduler,
|
||||||
|
thread::{BlockReason, ThreadState},
|
||||||
|
};
|
||||||
|
use platform::Platform;
|
||||||
|
use types::{
|
||||||
|
badge::Badge,
|
||||||
|
bounded::CapSlot,
|
||||||
|
generation::Generation,
|
||||||
|
id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId},
|
||||||
|
permission::{Permission, PermissionSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Delivery, DeliveryKind, SyscallOutcome, SyscallResult};
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
|
/// A thread named by id and generation, so a stale reference can be told
|
||||||
|
/// apart from a new thread that reused the id.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ThreadRef {
|
||||||
|
pub tid: ThreadId,
|
||||||
|
pub generation: Generation,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A message a blocked sender is waiting to hand over.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct Outgoing {
|
||||||
|
message: Message,
|
||||||
|
badge: Badge,
|
||||||
|
/// The sender's endpoint cap had `Delegate`, so a capability may travel.
|
||||||
|
grant: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-thread IPC bookkeeping.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct IpcState {
|
||||||
|
pub(crate) outgoing: Option<Outgoing>,
|
||||||
|
/// The caller this thread owes a reply to.
|
||||||
|
pub(crate) reply_to: Option<ThreadRef>,
|
||||||
|
pub(crate) bound: Option<NotificationId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rw_delegate() -> PermissionSet {
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Read)
|
||||||
|
.with(Permission::Write)
|
||||||
|
.with(Permission::Delegate)
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub(crate) fn handle_create_endpoint(
|
pub(crate) fn ipc(&self, tid: ThreadId) -> Option<&IpcState> {
|
||||||
|
self.ipc.get(tid.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ipc_mut(&mut self, tid: ThreadId) -> Option<&mut IpcState> {
|
||||||
|
self.ipc.get_mut(tid.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_pending_result(&mut self, tid: ThreadId, result: Option<SyscallResult>) {
|
||||||
|
if let Some(r) = self.pending_results.get_mut(tid.raw() as usize) {
|
||||||
|
*r = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_ref(&self, tid: ThreadId) -> Option<ThreadRef> {
|
||||||
|
Some(ThreadRef {
|
||||||
|
tid,
|
||||||
|
generation: self.threads.generation(tid)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_live(&self, r: ThreadRef) -> bool {
|
||||||
|
self.threads.generation(r.tid) == Some(r.generation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_blocked_as(&self, tid: ThreadId, f: impl Fn(&BlockReason) -> bool) -> bool {
|
||||||
|
matches!(self.threads.get(tid).map(|t| *t.state()), Ok(ThreadState::Blocked(r)) if f(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks the running caller.
|
||||||
|
pub(crate) fn block_caller(&mut self, tid: ThreadId, reason: BlockReason) -> SyscallOutcome {
|
||||||
|
if let Ok(t) = self.threads.get_mut(tid) {
|
||||||
|
let _ = t.block(reason);
|
||||||
|
}
|
||||||
|
self.scheduler.remove(tid);
|
||||||
|
SyscallOutcome::BlockCaller(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a blocked thread runnable with `result` as its syscall result.
|
||||||
|
pub(crate) fn wake(&mut self, tid: ThreadId, result: SyscallResult) {
|
||||||
|
let Ok(thread) = self.threads.get_mut(tid) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let priority = thread.priority();
|
||||||
|
if thread.unblock().is_ok() {
|
||||||
|
self.set_pending_result(tid, Some(result));
|
||||||
|
self.make_ready(tid, priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases a caller waiting on `reply` with `NoReply`, if it is still
|
||||||
|
/// that caller and still waiting.
|
||||||
|
pub(crate) fn release_caller(&mut self, reply: ThreadRef) {
|
||||||
|
if self.is_live(reply)
|
||||||
|
&& self.is_blocked_as(reply.tid, |r| *r == BlockReason::AwaitingReply)
|
||||||
|
{
|
||||||
|
self.wake(reply.tid, SyscallResult::Error(KernelError::NoReply));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint_cap(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
needed: Permission,
|
||||||
|
) -> Result<(EndpointId, Badge, bool), KernelError> {
|
||||||
|
let cap = self.check_cap(pid, slot, needed)?;
|
||||||
|
match cap.resource() {
|
||||||
|
Resource::Endpoint(id) => Ok((
|
||||||
|
*id,
|
||||||
|
cap.badge(),
|
||||||
|
cap.permissions().allows(Permission::Delegate),
|
||||||
|
)),
|
||||||
|
_ => Err(KernelError::WrongObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notification_cap(
|
||||||
|
&self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
needed: Permission,
|
||||||
|
) -> Result<(NotificationId, Badge, Generation), KernelError> {
|
||||||
|
let cap = self.check_cap(pid, slot, needed)?;
|
||||||
|
match cap.resource() {
|
||||||
|
Resource::Notification(id) => Ok((*id, cap.badge(), *cap.generation())),
|
||||||
|
_ => Err(KernelError::WrongObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies the capability in `slot` of `from` into `to`'s table, if the
|
||||||
|
/// transfer is allowed and still valid. Returns where it landed.
|
||||||
|
fn transfer_cap(
|
||||||
&mut self,
|
&mut self,
|
||||||
caller_pid: types::id::ProcessId,
|
from: ProcessId,
|
||||||
|
slot: Option<CapSlot>,
|
||||||
|
grant: bool,
|
||||||
|
to: ProcessId,
|
||||||
|
) -> Option<CapSlot> {
|
||||||
|
let slot = slot?;
|
||||||
|
if !grant {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let copy: Capability = self
|
||||||
|
.check_cap(from, slot, Permission::Delegate)
|
||||||
|
.ok()?
|
||||||
|
.clone();
|
||||||
|
let parent = self.cap_ref(from, slot);
|
||||||
|
self.insert_derived(to, copy, parent).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
|
||||||
|
/// reply slot now names the sender.
|
||||||
|
fn deliver(
|
||||||
|
&mut self,
|
||||||
|
sender: ThreadId,
|
||||||
|
out: Outgoing,
|
||||||
|
receiver: ThreadId,
|
||||||
|
kind: DeliveryKind,
|
||||||
|
) -> Delivery {
|
||||||
|
let from = self.thread_process(sender);
|
||||||
|
let to = self.thread_process(receiver);
|
||||||
|
let cap = match (from, to) {
|
||||||
|
(Some(from), Some(to)) => self.transfer_cap(from, out.message.cap(), out.grant, to),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if kind == DeliveryKind::Call {
|
||||||
|
let caller = self.thread_ref(sender);
|
||||||
|
if let Some(s) = self.ipc_mut(receiver) {
|
||||||
|
s.reply_to = caller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Delivery {
|
||||||
|
kind,
|
||||||
|
badge: out.badge.raw(),
|
||||||
|
words: *out.message.words(),
|
||||||
|
cap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- endpoints ----
|
||||||
|
|
||||||
|
pub(crate) fn send(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
is_call: bool,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let ep_id = match self.create_endpoint() {
|
let (ep, badge, grant) = match self.endpoint_cap(pid, slot, Permission::Execute) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
let out = Outgoing {
|
||||||
|
message,
|
||||||
|
badge,
|
||||||
|
grant,
|
||||||
|
};
|
||||||
|
let kind = if is_call {
|
||||||
|
DeliveryKind::Call
|
||||||
|
} else {
|
||||||
|
DeliveryKind::Message
|
||||||
|
};
|
||||||
|
|
||||||
|
let receiver = self
|
||||||
|
.endpoints
|
||||||
|
.get_mut(ep)
|
||||||
|
.and_then(|e| e.dequeue_receiver());
|
||||||
|
if let Some(receiver) = receiver {
|
||||||
|
let delivery = self.deliver(tid, out, receiver, kind);
|
||||||
|
self.wake(receiver, SyscallResult::Received(delivery));
|
||||||
|
if !is_call {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Ok);
|
||||||
|
}
|
||||||
|
self.block_caller(tid, BlockReason::AwaitingReply);
|
||||||
|
// Run the server straight away instead of waiting for its turn.
|
||||||
|
// Only works if it lives on this core (run_now finds it in this
|
||||||
|
// core's queue only); otherwise make_ready kicked its core.
|
||||||
|
let core = self.platform.core_id();
|
||||||
|
if self.scheduler.run_now(core, receiver)
|
||||||
|
&& self
|
||||||
|
.threads
|
||||||
|
.get_mut(receiver)
|
||||||
|
.and_then(|t| t.set_running(core))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return SyscallOutcome::SwitchTo(receiver);
|
||||||
|
}
|
||||||
|
return SyscallOutcome::BlockCaller(BlockReason::AwaitingReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
let queued = self
|
||||||
|
.endpoints
|
||||||
|
.get_mut(ep)
|
||||||
|
.is_some_and(|e| e.enqueue_sender(tid).is_some());
|
||||||
|
if !queued {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||||
|
}
|
||||||
|
if let Some(s) = self.ipc_mut(tid) {
|
||||||
|
s.outgoing = Some(out);
|
||||||
|
}
|
||||||
|
let reason = if is_call {
|
||||||
|
BlockReason::Calling(ep)
|
||||||
|
} else {
|
||||||
|
BlockReason::Sending(ep)
|
||||||
|
};
|
||||||
|
self.block_caller(tid, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `drop_reply`: a plain `Receive` abandons any pending reply.
|
||||||
|
pub(crate) fn receive(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
drop_reply: bool,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
let (ep, _, _) = match self.endpoint_cap(pid, slot, Permission::Read) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
|
};
|
||||||
|
if drop_reply && let Some(pending) = self.ipc_mut(tid).and_then(|s| s.reply_to.take()) {
|
||||||
|
self.release_caller(pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(n) = self.ipc(tid).and_then(|s| s.bound)
|
||||||
|
&& let Some(ntfn) = self.notifications.get_mut(n)
|
||||||
|
&& ntfn.has_bits()
|
||||||
|
{
|
||||||
|
let bits = ntfn.take_bits();
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sender = self.endpoints.get_mut(ep).and_then(|e| e.dequeue_sender());
|
||||||
|
if let Some(sender) = sender {
|
||||||
|
let is_call = self.is_blocked_as(sender, |r| matches!(r, BlockReason::Calling(_)));
|
||||||
|
let Some(out) = self.ipc_mut(sender).and_then(|s| s.outgoing.take()) else {
|
||||||
|
// A queued sender always has a message; treat a missing one
|
||||||
|
// as a sender that vanished.
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::WouldBlock));
|
||||||
|
};
|
||||||
|
let kind = if is_call {
|
||||||
|
DeliveryKind::Call
|
||||||
|
} else {
|
||||||
|
DeliveryKind::Message
|
||||||
|
};
|
||||||
|
let delivery = self.deliver(sender, out, tid, kind);
|
||||||
|
if is_call {
|
||||||
|
if let Ok(t) = self.threads.get_mut(sender) {
|
||||||
|
let _ = t.rewait(BlockReason::AwaitingReply);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.wake(sender, SyscallResult::Ok);
|
||||||
|
}
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Received(delivery));
|
||||||
|
}
|
||||||
|
|
||||||
|
let queued = self
|
||||||
|
.endpoints
|
||||||
|
.get_mut(ep)
|
||||||
|
.is_some_and(|e| e.enqueue_receiver(tid).is_some());
|
||||||
|
if !queued {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||||
|
}
|
||||||
|
self.block_caller(tid, BlockReason::Receiving(ep))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reply(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
message: Message,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let caller = self
|
||||||
|
.ipc_mut(tid)
|
||||||
|
.and_then(|s| s.reply_to.take())
|
||||||
|
.ok_or(KernelError::NoReply)?;
|
||||||
|
let waiting = self.is_live(caller)
|
||||||
|
&& self.is_blocked_as(caller.tid, |r| *r == BlockReason::AwaitingReply);
|
||||||
|
if !waiting {
|
||||||
|
// The caller died; the reply has nowhere to go.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let to = self.thread_process(caller.tid);
|
||||||
|
// A reply may always carry a capability the server can delegate.
|
||||||
|
let cap = to.and_then(|to| self.transfer_cap(pid, message.cap(), true, to));
|
||||||
|
let delivery = Delivery {
|
||||||
|
kind: DeliveryKind::Message,
|
||||||
|
badge: 0,
|
||||||
|
words: *message.words(),
|
||||||
|
cap,
|
||||||
|
};
|
||||||
|
self.wake(caller.tid, SyscallResult::Received(delivery));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reply_recv(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
message: Message,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
if let Err(e) = self.endpoint_cap(pid, slot, Permission::Read) {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
|
}
|
||||||
|
if self.ipc(tid).is_some_and(|s| s.reply_to.is_some()) {
|
||||||
|
let _ = self.reply(tid, pid, message);
|
||||||
|
}
|
||||||
|
self.receive(tid, pid, slot, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mint(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
badge: Badge,
|
||||||
|
permissions: PermissionSet,
|
||||||
|
) -> Result<CapSlot, KernelError> {
|
||||||
|
let source = self.check_cap(pid, slot, Permission::Delegate)?;
|
||||||
|
let badgeable = matches!(
|
||||||
|
source.resource(),
|
||||||
|
Resource::Endpoint(_) | Resource::Notification(_)
|
||||||
|
);
|
||||||
|
// Any capability can be copied with narrower rights; only those a
|
||||||
|
// receiver can observe the badge of take one, and only once.
|
||||||
|
if (!badgeable && !badge.is_none()) || !source.badge().is_none() {
|
||||||
|
return Err(KernelError::InvalidArgument);
|
||||||
|
}
|
||||||
|
let minted = Capability::new(
|
||||||
|
*source.resource(),
|
||||||
|
source.permissions().restrict_to(&permissions),
|
||||||
|
*source.generation(),
|
||||||
|
)
|
||||||
|
.with_badge(badge);
|
||||||
|
let parent = self.cap_ref(pid, slot);
|
||||||
|
self.insert_derived(pid, minted, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_create_endpoint(&mut self, pid: ProcessId) -> SyscallOutcome {
|
||||||
|
let id = match self.create_endpoint(pid) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
let perms = PermissionSet::empty()
|
||||||
let all_perms = types::permission::PermissionSet::empty()
|
.with(Permission::Read)
|
||||||
.with(types::permission::Permission::Read)
|
.with(Permission::Execute)
|
||||||
.with(types::permission::Permission::Write)
|
.with(Permission::Delegate);
|
||||||
.with(types::permission::Permission::Execute)
|
match self.grant_cap(pid, Resource::Endpoint(id), perms) {
|
||||||
.with(types::permission::Permission::Delegate);
|
|
||||||
|
|
||||||
match self.grant_cap(
|
|
||||||
caller_pid,
|
|
||||||
domain::resource::Resource::Endpoint(ep_id),
|
|
||||||
all_perms,
|
|
||||||
) {
|
|
||||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = self.endpoints.destroy(ep_id);
|
self.endpoints.destroy(id);
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_send(
|
// ---- notifications ----
|
||||||
|
|
||||||
|
pub fn create_notification(&mut self, owner: ProcessId) -> Result<NotificationId, KernelError> {
|
||||||
|
self.processes.get(owner)?;
|
||||||
|
self.notifications
|
||||||
|
.create(owner)
|
||||||
|
.ok_or(KernelError::NoFreeSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_notification_cap(
|
||||||
&mut self,
|
&mut self,
|
||||||
caller_tid: types::id::ThreadId,
|
pid: ProcessId,
|
||||||
caller_pid: types::id::ProcessId,
|
) -> Result<CapSlot, KernelError> {
|
||||||
endpoint_slot: types::bounded::CapSlot,
|
let id = self.create_notification(pid)?;
|
||||||
message: domain::message::Message,
|
self.grant_cap(pid, Resource::Notification(id), rw_delegate())
|
||||||
) -> SyscallOutcome {
|
.inspect_err(|_| {
|
||||||
use domain::scheduler::Scheduler;
|
self.notifications.destroy(id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let ep_id = match self.check_cap(
|
/// Signals `id` with `badge`; shared by `Signal` and interrupts.
|
||||||
caller_pid,
|
fn signal_notification(&mut self, id: NotificationId, badge: Badge) {
|
||||||
endpoint_slot,
|
let bound_receiving = self
|
||||||
types::permission::Permission::Execute,
|
.notifications
|
||||||
) {
|
.get(id)
|
||||||
Ok(cap) => match cap.resource() {
|
.and_then(|n| n.bound())
|
||||||
domain::resource::Resource::Endpoint(id) => *id,
|
.is_some_and(|t| self.is_blocked_as(t, |r| matches!(r, BlockReason::Receiving(_))));
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||||
domain::error::KernelError::InvalidEndpoint(types::id::EndpointId::from_raw(0)),
|
return;
|
||||||
)),
|
};
|
||||||
},
|
match ntfn.signal(badge, bound_receiving) {
|
||||||
|
SignalEffect::WakeWaiter(t, bits) => {
|
||||||
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
SignalEffect::WakeBound(t, bits) => {
|
||||||
|
self.endpoints.remove_thread_from_all(t);
|
||||||
|
self.wake(t, SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
SignalEffect::Stored => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn signal(&mut self, pid: ProcessId, slot: CapSlot) -> Result<(), KernelError> {
|
||||||
|
let (id, badge, _) = self.notification_cap(pid, slot, Permission::Write)?;
|
||||||
|
self.signal_notification(id, badge);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wait(&mut self, tid: ThreadId, pid: ProcessId, slot: CapSlot) -> SyscallOutcome {
|
||||||
|
let id = match self.notification_cap(pid, slot, Permission::Read) {
|
||||||
|
Ok((id, _, _)) => id,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
let Some(ntfn) = self.notifications.get_mut(id) else {
|
||||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::NoSuchObject));
|
||||||
Some(ep) => ep,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
|
if ntfn.has_bits() {
|
||||||
|
let bits = ntfn.take_bits();
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Received(Delivery::signal(bits)));
|
||||||
|
}
|
||||||
|
if !ntfn.add_waiter(tid) {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(KernelError::EndpointFull));
|
||||||
|
}
|
||||||
|
self.block_caller(tid, BlockReason::WaitingNotification(id))
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint.has_waiting_receiver() {
|
pub(crate) fn poll(&mut self, pid: ProcessId, slot: CapSlot) -> Result<usize, KernelError> {
|
||||||
let receiver_tid = match endpoint.dequeue_receiver() {
|
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||||
Some(t) => t,
|
self.notifications
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
.get_mut(id)
|
||||||
domain::error::KernelError::WouldBlock,
|
.map(|n| n.take_bits())
|
||||||
)),
|
.ok_or(KernelError::NoSuchObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_notification(
|
||||||
|
&mut self,
|
||||||
|
tid: ThreadId,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let (id, _, _) = self.notification_cap(pid, slot, Permission::Read)?;
|
||||||
|
if self.ipc(tid).is_none_or(|s| s.bound.is_some()) {
|
||||||
|
return Err(KernelError::InvalidArgument);
|
||||||
|
}
|
||||||
|
let ntfn = self
|
||||||
|
.notifications
|
||||||
|
.get_mut(id)
|
||||||
|
.ok_or(KernelError::NoSuchObject)?;
|
||||||
|
if !ntfn.bind(tid) {
|
||||||
|
return Err(KernelError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if let Some(s) = self.ipc_mut(tid) {
|
||||||
|
s.bound = Some(id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- interrupts ----
|
||||||
|
|
||||||
|
fn interrupt_cap(&self, pid: ProcessId, slot: CapSlot) -> Result<IrqId, KernelError> {
|
||||||
|
match self.check_cap(pid, slot, Permission::Execute)?.resource() {
|
||||||
|
Resource::Interrupt(irq) => Ok(*irq),
|
||||||
|
_ => Err(KernelError::WrongObject),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_irq_notification(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
irq_slot: CapSlot,
|
||||||
|
ntfn_slot: CapSlot,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||||
|
let (notification, badge, generation) =
|
||||||
|
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||||
|
let route = domain::irq::IrqRoute {
|
||||||
|
notification,
|
||||||
|
generation,
|
||||||
|
badge,
|
||||||
};
|
};
|
||||||
|
if self.irqs.set(irq, route) {
|
||||||
self.pending_results[receiver_tid.raw() as usize] =
|
Ok(())
|
||||||
Some(SyscallResult::Message(message));
|
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
|
||||||
|
|
||||||
let receiver = match self.threads.get_mut(receiver_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let _ = receiver.unblock();
|
|
||||||
let _ = receiver.set_running(core);
|
|
||||||
|
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let caller_priority = caller.priority();
|
|
||||||
let _ = caller.preempt();
|
|
||||||
self.scheduler.add(caller_tid, caller_priority, core);
|
|
||||||
|
|
||||||
self.scheduler.set_current(core, receiver_tid);
|
|
||||||
|
|
||||||
SyscallOutcome::SwitchTo(receiver_tid)
|
|
||||||
} else {
|
} else {
|
||||||
self.pending_messages[caller_tid.raw() as usize] = Some(message);
|
Err(KernelError::InvalidIrq(irq))
|
||||||
|
|
||||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
|
||||||
Some(ep) => ep,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
if endpoint.enqueue_sender(caller_tid).is_none() {
|
|
||||||
self.pending_messages[caller_tid.raw() as usize] = None;
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::WouldBlock,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let _ = caller.block(domain::thread::BlockReason::Sending(endpoint_slot));
|
|
||||||
self.scheduler.remove(caller_tid);
|
|
||||||
|
|
||||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Sending(endpoint_slot))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_receive(
|
pub(crate) fn ack_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
caller_tid: types::id::ThreadId,
|
pid: ProcessId,
|
||||||
caller_pid: types::id::ProcessId,
|
slot: CapSlot,
|
||||||
endpoint_slot: types::bounded::CapSlot,
|
) -> Result<(), KernelError> {
|
||||||
) -> SyscallOutcome {
|
let irq = self.interrupt_cap(pid, slot)?;
|
||||||
use domain::scheduler::Scheduler;
|
self.platform.unmask_irq(irq);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let ep_id = match self.check_cap(
|
/// An interrupt fired: mask the line and signal its notification. The
|
||||||
caller_pid,
|
/// line stays masked until the handler acknowledges it.
|
||||||
endpoint_slot,
|
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||||
types::permission::Permission::Read,
|
self.platform.mask_irq(irq);
|
||||||
) {
|
let Some(route) = self.irqs.route(irq) else {
|
||||||
Ok(cap) => match cap.resource() {
|
return;
|
||||||
domain::resource::Resource::Endpoint(id) => *id,
|
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidEndpoint(types::id::EndpointId::from_raw(0)),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
};
|
||||||
|
// A route set through a capability that has since been revoked, or
|
||||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
// to a notification whose slot was reused, is dead.
|
||||||
Some(ep) => ep,
|
if self.notifications.generation(route.notification) != Some(route.generation) {
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
self.irqs.clear(irq);
|
||||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
return;
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if endpoint.has_waiting_sender() {
|
|
||||||
let sender_tid = match endpoint.dequeue_sender() {
|
|
||||||
Some(t) => t,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::WouldBlock,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let message = self.pending_messages[sender_tid.raw() as usize].take();
|
|
||||||
debug_assert!(message.is_some(), "sender in wait queue had no pending message");
|
|
||||||
let message = message.unwrap_or_else(|| domain::message::Message::empty());
|
|
||||||
|
|
||||||
self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok);
|
|
||||||
|
|
||||||
let sender = match self.threads.get_mut(sender_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let priority = sender.priority();
|
|
||||||
let _ = sender.unblock();
|
|
||||||
let core = self.platform.core_id();
|
|
||||||
self.scheduler.add(sender_tid, priority, core);
|
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Message(message))
|
|
||||||
} else {
|
|
||||||
let endpoint = match self.endpoints.get_mut(ep_id) {
|
|
||||||
Some(ep) => ep,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidEndpoint(ep_id),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
if endpoint.enqueue_receiver(caller_tid).is_none() {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::WouldBlock,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
self.signal_notification(route.notification, route.badge);
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let _ = caller.block(domain::thread::BlockReason::Receiving(endpoint_slot));
|
|
||||||
self.scheduler.remove(caller_tid);
|
|
||||||
|
|
||||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::Receiving(endpoint_slot))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_wait_for_interrupt(
|
|
||||||
&mut self,
|
|
||||||
caller_tid: types::id::ThreadId,
|
|
||||||
caller_pid: types::id::ProcessId,
|
|
||||||
irq_slot: types::bounded::CapSlot,
|
|
||||||
) -> SyscallOutcome {
|
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
|
|
||||||
let irq_id = match self.check_cap(
|
|
||||||
caller_pid,
|
|
||||||
irq_slot,
|
|
||||||
types::permission::Permission::Read,
|
|
||||||
) {
|
|
||||||
Ok(cap) => match cap.resource() {
|
|
||||||
domain::resource::Resource::Interrupt(id) => *id,
|
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = self.irqs.register(irq_id, caller_tid);
|
|
||||||
|
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let _ = caller.block(domain::thread::BlockReason::WaitingForInterrupt(irq_slot));
|
|
||||||
self.scheduler.remove(caller_tid);
|
|
||||||
|
|
||||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingForInterrupt(irq_slot))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_ack_interrupt(
|
|
||||||
&mut self,
|
|
||||||
caller_pid: types::id::ProcessId,
|
|
||||||
irq_slot: types::bounded::CapSlot,
|
|
||||||
) -> SyscallOutcome {
|
|
||||||
let irq_id = match self.check_cap(
|
|
||||||
caller_pid,
|
|
||||||
irq_slot,
|
|
||||||
types::permission::Permission::Write,
|
|
||||||
) {
|
|
||||||
Ok(cap) => match cap.resource() {
|
|
||||||
domain::resource::Resource::Interrupt(id) => *id,
|
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.platform.unmask_irq(irq_id);
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_wait_any(
|
|
||||||
&mut self,
|
|
||||||
caller_tid: types::id::ThreadId,
|
|
||||||
caller_pid: types::id::ProcessId,
|
|
||||||
sources: domain::syscall::WaitSet,
|
|
||||||
) -> SyscallOutcome {
|
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
|
|
||||||
if sources.is_empty() {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::WouldBlock,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for ep_slot in sources.endpoints() {
|
|
||||||
if let Some(slot) = ep_slot {
|
|
||||||
let ep_id = match self.check_cap(
|
|
||||||
caller_pid,
|
|
||||||
*slot,
|
|
||||||
types::permission::Permission::Read,
|
|
||||||
) {
|
|
||||||
Ok(cap) => match cap.resource() {
|
|
||||||
domain::resource::Resource::Endpoint(id) => *id,
|
|
||||||
_ => continue,
|
|
||||||
},
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ep) = self.endpoints.get_mut(ep_id) {
|
|
||||||
if ep.has_waiting_sender() {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
|
||||||
domain::syscall::WakeReason::MessageReady(*slot),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for irq_slot in sources.interrupts() {
|
|
||||||
if let Some(slot) = irq_slot {
|
|
||||||
if let Ok(cap) = self.check_cap(
|
|
||||||
caller_pid,
|
|
||||||
*slot,
|
|
||||||
types::permission::Permission::Read,
|
|
||||||
) {
|
|
||||||
if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() {
|
|
||||||
if self.irqs.owner(*irq_id).is_some() {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::WakeReason(
|
|
||||||
domain::syscall::WakeReason::InterruptFired(*slot),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ep_slot in sources.endpoints() {
|
|
||||||
if let Some(slot) = ep_slot {
|
|
||||||
if let Ok(cap) = self.check_cap(caller_pid, *slot, types::permission::Permission::Read) {
|
|
||||||
if let domain::resource::Resource::Endpoint(id) = cap.resource() {
|
|
||||||
if let Some(ep) = self.endpoints.get_mut(*id) {
|
|
||||||
let _ = ep.enqueue_receiver(caller_tid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.wait_sets[caller_tid.raw() as usize] = Some(sources);
|
|
||||||
|
|
||||||
let caller = match self.threads.get_mut(caller_tid) {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
let _ = caller.block(domain::thread::BlockReason::WaitingAny);
|
|
||||||
self.scheduler.remove(caller_tid);
|
|
||||||
|
|
||||||
SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingAny)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use domain::process::MAX_THREADS_PER_PROCESS;
|
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
|
|
||||||
use super::{SyscallOutcome, SyscallResult};
|
use super::{SyscallOutcome, SyscallResult};
|
||||||
@@ -11,14 +10,16 @@ impl<P: Platform> Kernel<P> {
|
|||||||
entry: types::address::EntryPoint,
|
entry: types::address::EntryPoint,
|
||||||
stack: types::address::StackPointer,
|
stack: types::address::StackPointer,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
use domain::thread::SavedContext;
|
use domain::thread::SavedContext;
|
||||||
|
|
||||||
let priority = types::bounded::Priority::DEFAULT;
|
let priority = types::bounded::Priority::DEFAULT;
|
||||||
let time_slice = types::time::TimeSlice::DEFAULT;
|
let time_slice = types::time::TimeSlice::DEFAULT;
|
||||||
let context = SavedContext::with_entry_and_stack(entry, stack);
|
let context = SavedContext::with_entry_and_stack(entry, stack);
|
||||||
|
|
||||||
let tid = match self.threads.spawn_with_context(pid, priority, time_slice, None, context) {
|
let tid = match self
|
||||||
|
.threads
|
||||||
|
.spawn_with_context(pid, priority, time_slice, None, context)
|
||||||
|
{
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
@@ -28,8 +29,8 @@ impl<P: Platform> Kernel<P> {
|
|||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
self.place(tid);
|
||||||
self.scheduler.add(tid, priority, core);
|
self.make_ready(tid, priority);
|
||||||
SyscallOutcome::Complete(SyscallResult::Value(tid.raw() as usize))
|
SyscallOutcome::Complete(SyscallResult::Value(tid.raw() as usize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,33 +40,42 @@ impl<P: Platform> Kernel<P> {
|
|||||||
create_cap: types::bounded::CapSlot,
|
create_cap: types::bounded::CapSlot,
|
||||||
_image: types::address::DataRef,
|
_image: types::address::DataRef,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
if let Err(e) = self.check_cap(caller_pid, create_cap, types::permission::Permission::Execute) {
|
if let Err(e) = self.check_cap(
|
||||||
|
caller_pid,
|
||||||
|
create_cap,
|
||||||
|
types::permission::Permission::Execute,
|
||||||
|
) {
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_pid = match self.processes.create(
|
let new_pid = match self
|
||||||
domain::capability::CapabilityTable::new(),
|
.processes
|
||||||
None,
|
.create(domain::capability::CapabilityTable::new(), None)
|
||||||
) {
|
{
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.process_memory[new_pid.raw() as usize] =
|
if let Err(e) = self.create_address_space(new_pid) {
|
||||||
Some(domain::memory::process_memory::ProcessMemory::new(new_pid, domain::memory::process_memory::ProcessMemory::DEFAULT_PAGE_LIMIT));
|
let _ = self.processes.destroy(new_pid);
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
|
}
|
||||||
|
|
||||||
match self.grant_cap(
|
match self.grant_cap(
|
||||||
caller_pid,
|
caller_pid,
|
||||||
domain::resource::Resource::Process(new_pid),
|
domain::resource::Resource::Process(new_pid),
|
||||||
|
// The creator has full authority over what it creates,
|
||||||
|
// including handing that authority on.
|
||||||
types::permission::PermissionSet::empty()
|
types::permission::PermissionSet::empty()
|
||||||
.with(types::permission::Permission::Execute)
|
.with(types::permission::Permission::Execute)
|
||||||
.with(types::permission::Permission::Read),
|
.with(types::permission::Permission::Read)
|
||||||
|
.with(types::permission::Permission::Delegate),
|
||||||
) {
|
) {
|
||||||
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Rollback: destroy orphaned process
|
// Rollback: destroy orphaned process
|
||||||
|
self.teardown_memory(new_pid);
|
||||||
let _ = self.processes.destroy(new_pid);
|
let _ = self.processes.destroy(new_pid);
|
||||||
self.process_memory[new_pid.raw() as usize] = None;
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(e))
|
SyscallOutcome::Complete(SyscallResult::Error(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +88,6 @@ impl<P: Platform> Kernel<P> {
|
|||||||
entry: types::address::EntryPoint,
|
entry: types::address::EntryPoint,
|
||||||
stack: types::address::StackPointer,
|
stack: types::address::StackPointer,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
use domain::scheduler::Scheduler;
|
|
||||||
use domain::thread::SavedContext;
|
use domain::thread::SavedContext;
|
||||||
|
|
||||||
let target_pid = match self.check_cap(
|
let target_pid = match self.check_cap(
|
||||||
@@ -88,12 +97,14 @@ impl<P: Platform> Kernel<P> {
|
|||||||
) {
|
) {
|
||||||
Ok(cap) => match cap.resource() {
|
Ok(cap) => match cap.resource() {
|
||||||
domain::resource::Resource::Process(pid) => *pid,
|
domain::resource::Resource::Process(pid) => *pid,
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
_ => {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
domain::error::KernelError::PermissionDenied {
|
domain::error::KernelError::PermissionDenied {
|
||||||
slot: process_cap,
|
slot: process_cap,
|
||||||
needed: types::permission::Permission::Execute,
|
needed: types::permission::Permission::Execute,
|
||||||
},
|
},
|
||||||
)),
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
@@ -102,18 +113,25 @@ impl<P: Platform> Kernel<P> {
|
|||||||
let time_slice = types::time::TimeSlice::DEFAULT;
|
let time_slice = types::time::TimeSlice::DEFAULT;
|
||||||
let context = SavedContext::with_entry_and_stack(entry, stack);
|
let context = SavedContext::with_entry_and_stack(entry, stack);
|
||||||
|
|
||||||
let tid = match self.threads.spawn_with_context(target_pid, priority, time_slice, None, context) {
|
let tid = match self
|
||||||
|
.threads
|
||||||
|
.spawn_with_context(target_pid, priority, time_slice, None, context)
|
||||||
|
{
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = self.processes.get_mut(target_pid).and_then(|p| p.add_thread(tid)) {
|
if let Err(e) = self
|
||||||
|
.processes
|
||||||
|
.get_mut(target_pid)
|
||||||
|
.and_then(|p| p.add_thread(tid))
|
||||||
|
{
|
||||||
let _ = self.threads.destroy(tid);
|
let _ = self.threads.destroy(tid);
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
return SyscallOutcome::Complete(SyscallResult::Error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let core = self.platform.core_id();
|
self.place(tid);
|
||||||
self.scheduler.add(tid, priority, core);
|
self.make_ready(tid, priority);
|
||||||
SyscallOutcome::Complete(SyscallResult::Value(tid.raw() as usize))
|
SyscallOutcome::Complete(SyscallResult::Value(tid.raw() as usize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,20 +140,30 @@ impl<P: Platform> Kernel<P> {
|
|||||||
caller_pid: types::id::ProcessId,
|
caller_pid: types::id::ProcessId,
|
||||||
process_cap: types::bounded::CapSlot,
|
process_cap: types::bounded::CapSlot,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let target_pid = match self.check_cap(caller_pid, process_cap, types::permission::Permission::Execute) {
|
let target_pid = match self.check_cap(
|
||||||
|
caller_pid,
|
||||||
|
process_cap,
|
||||||
|
types::permission::Permission::Execute,
|
||||||
|
) {
|
||||||
Ok(cap) => match cap.resource() {
|
Ok(cap) => match cap.resource() {
|
||||||
domain::resource::Resource::Process(pid) => *pid,
|
domain::resource::Resource::Process(pid) => *pid,
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
_ => {
|
||||||
|
return SyscallOutcome::Complete(SyscallResult::Error(
|
||||||
domain::error::KernelError::PermissionDenied {
|
domain::error::KernelError::PermissionDenied {
|
||||||
slot: process_cap,
|
slot: process_cap,
|
||||||
needed: types::permission::Permission::Execute,
|
needed: types::permission::Permission::Execute,
|
||||||
},
|
},
|
||||||
)),
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.destroy_process(target_pid);
|
self.destroy_process(target_pid);
|
||||||
|
if target_pid == caller_pid {
|
||||||
|
// The caller no longer exists; nothing to return to.
|
||||||
|
return SyscallOutcome::TerminateCaller;
|
||||||
|
}
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,156 +1,135 @@
|
|||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::address::{PhysicalAddress, VirtualAddress};
|
use types::{
|
||||||
|
address::{PhysicalAddress, VirtualAddress},
|
||||||
|
bounded::CapSlot,
|
||||||
|
bounded::PageCount,
|
||||||
|
id::ProcessId,
|
||||||
|
mapping::PagePerms,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{SyscallOutcome, SyscallResult};
|
use super::{SyscallOutcome, SyscallResult};
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
use domain::{error::KernelError, resource::Resource};
|
||||||
|
use types::permission::Permission;
|
||||||
|
|
||||||
const USER_VA_LIMIT: usize = 0x80000000;
|
fn complete(result: Result<SyscallResult, KernelError>) -> SyscallOutcome {
|
||||||
|
SyscallOutcome::Complete(result.unwrap_or_else(SyscallResult::Error))
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub(crate) fn handle_request_pages(
|
pub(crate) fn handle_request_pages(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: types::id::ProcessId,
|
pid: ProcessId,
|
||||||
count: types::bounded::PageCount,
|
count: PageCount,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let pages = count.value() as usize;
|
let pages = count.value() as usize;
|
||||||
|
complete(
|
||||||
match &mut self.process_memory[pid.raw() as usize] {
|
self.find_free_heap_range(pid, pages)
|
||||||
Some(pm) => {
|
.ok_or(KernelError::OutOfMemory)
|
||||||
if pm.grant(pages).is_err() {
|
.and_then(|va| {
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
self.map_fresh_range(pid, va, pages, PagePerms::ReadWrite)
|
||||||
domain::error::KernelError::OutOfMemory,
|
.map(|()| SyscallResult::Value(va.raw()))
|
||||||
));
|
}),
|
||||||
}
|
)
|
||||||
}
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidProcess(pid),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
|
|
||||||
let phys_addr = match self.page_pool.allocate(pages) {
|
|
||||||
Some(a) => a,
|
|
||||||
None => {
|
|
||||||
if let Some(pm) = &mut self.process_memory[pid.raw() as usize] {
|
|
||||||
let _ = pm.release(pages);
|
|
||||||
}
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::OutOfMemory,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_va = match &mut self.process_memory[pid.raw() as usize] {
|
|
||||||
Some(pm) => {
|
|
||||||
let va = pm.alloc_user_va(pages);
|
|
||||||
pm.record_allocation(phys_addr, pages);
|
|
||||||
va
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.page_pool.free(phys_addr, pages);
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::InvalidProcess(pid),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for i in 0..pages {
|
|
||||||
let page_va = VirtualAddress::from_raw(user_va.raw() + i * 4096);
|
|
||||||
let page_pa = PhysicalAddress::from_raw(phys_addr.raw() + i * 4096);
|
|
||||||
self.platform.map_user_page(pid, page_va, page_pa, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let region = domain::memory::MemoryRegion::new(
|
|
||||||
types::id::RegionId::from_raw(0),
|
|
||||||
pid,
|
|
||||||
phys_addr,
|
|
||||||
count,
|
|
||||||
types::generation::Generation::initial(),
|
|
||||||
);
|
|
||||||
let _ = self.regions.insert(region);
|
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Value(user_va.raw()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_map_page(
|
|
||||||
&mut self,
|
|
||||||
caller_pid: types::id::ProcessId,
|
|
||||||
process_cap: types::bounded::CapSlot,
|
|
||||||
src_va: VirtualAddress,
|
|
||||||
dst_va: VirtualAddress,
|
|
||||||
executable: bool,
|
|
||||||
) -> SyscallOutcome {
|
|
||||||
// Prevent mapping kernel memory into userspace
|
|
||||||
if src_va.raw() >= USER_VA_LIMIT {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::PermissionDenied {
|
|
||||||
slot: process_cap,
|
|
||||||
needed: types::permission::Permission::Read,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_pid = match self.check_cap(
|
|
||||||
caller_pid,
|
|
||||||
process_cap,
|
|
||||||
types::permission::Permission::Execute,
|
|
||||||
) {
|
|
||||||
Ok(cap) => match cap.resource() {
|
|
||||||
domain::resource::Resource::Process(pid) => *pid,
|
|
||||||
_ => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::PermissionDenied {
|
|
||||||
slot: process_cap,
|
|
||||||
needed: types::permission::Permission::Execute,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pa = match self.platform.translate_va(caller_pid, src_va) {
|
|
||||||
Some(pa) => pa,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::OutOfMemory,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.platform.map_user_page(target_pid, dst_va, pa, !executable, executable);
|
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_release_pages(
|
pub(crate) fn handle_release_pages(
|
||||||
&mut self,
|
&mut self,
|
||||||
pid: types::id::ProcessId,
|
pid: ProcessId,
|
||||||
addr: VirtualAddress,
|
addr: VirtualAddress,
|
||||||
count: types::bounded::PageCount,
|
count: PageCount,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
let pages = count.value() as usize;
|
complete(
|
||||||
let phys = match self.platform.translate_va(pid, addr) {
|
self.release_range(pid, addr, count.value() as usize)
|
||||||
Some(pa) => pa,
|
.map(|()| SyscallResult::Ok),
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
)
|
||||||
domain::error::KernelError::OutOfMemory,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let region_id = match self.regions.find_by_base(pid, phys) {
|
|
||||||
Some((rid, _)) => rid,
|
|
||||||
None => return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::OutOfMemory,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.page_pool.free(phys, pages) {
|
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
|
||||||
domain::error::KernelError::OutOfMemory,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.regions.remove(region_id);
|
/// Moves one of the caller's pages into the process named by
|
||||||
|
/// `process_cap`. Executable pages are mapped read-execute, others
|
||||||
if let Some(pm) = &mut self.process_memory[pid.raw() as usize] {
|
/// read-write.
|
||||||
let _ = pm.release(pages);
|
pub(crate) fn handle_map_page(
|
||||||
|
&mut self,
|
||||||
|
caller_pid: ProcessId,
|
||||||
|
process_cap: CapSlot,
|
||||||
|
src_va: VirtualAddress,
|
||||||
|
dst_va: VirtualAddress,
|
||||||
|
executable: bool,
|
||||||
|
) -> SyscallOutcome {
|
||||||
|
let target = match self.check_cap(caller_pid, process_cap, Permission::Execute) {
|
||||||
|
Ok(cap) => match cap.resource() {
|
||||||
|
Resource::Process(pid) => *pid,
|
||||||
|
_ => {
|
||||||
|
return complete(Err(KernelError::PermissionDenied {
|
||||||
|
slot: process_cap,
|
||||||
|
needed: Permission::Execute,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => return complete(Err(e)),
|
||||||
|
};
|
||||||
|
let perms = if executable {
|
||||||
|
PagePerms::ReadExecute
|
||||||
|
} else {
|
||||||
|
PagePerms::ReadWrite
|
||||||
|
};
|
||||||
|
complete(
|
||||||
|
self.move_page(caller_pid, src_va, target, dst_va, perms)
|
||||||
|
.map(|()| SyscallResult::Ok),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
pub(crate) fn create_region_cap(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
pages: PageCount,
|
||||||
|
) -> Result<CapSlot, KernelError> {
|
||||||
|
let id = self.create_pool_region(pid, pages)?;
|
||||||
|
let all = types::permission::PermissionSet::empty()
|
||||||
|
.with(Permission::Read)
|
||||||
|
.with(Permission::Write)
|
||||||
|
.with(Permission::Execute)
|
||||||
|
.with(Permission::Delegate);
|
||||||
|
self.grant_cap(pid, Resource::Memory(id), all)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the region behind `slot` into the caller. The capability must
|
||||||
|
/// allow every access `perms` grants; the mapping lasts as long as
|
||||||
|
/// the capability.
|
||||||
|
pub(crate) fn map_region_cap(
|
||||||
|
&mut self,
|
||||||
|
pid: ProcessId,
|
||||||
|
slot: CapSlot,
|
||||||
|
va: VirtualAddress,
|
||||||
|
perms: PagePerms,
|
||||||
|
) -> Result<(), KernelError> {
|
||||||
|
let cap = self.check_cap(pid, slot, Permission::Read)?;
|
||||||
|
let needed = match perms {
|
||||||
|
PagePerms::ReadOnly => Permission::Read,
|
||||||
|
PagePerms::ReadWrite => Permission::Write,
|
||||||
|
PagePerms::ReadExecute => Permission::Execute,
|
||||||
|
};
|
||||||
|
if !cap.permissions().allows(needed) {
|
||||||
|
return Err(KernelError::PermissionDenied { slot, needed });
|
||||||
|
}
|
||||||
|
let Resource::Memory(id) = *cap.resource() else {
|
||||||
|
return Err(KernelError::WrongObject);
|
||||||
|
};
|
||||||
|
let via = self.cap_ref(pid, slot);
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,28 +5,66 @@ mod lifecycle;
|
|||||||
mod memory;
|
mod memory;
|
||||||
mod simple;
|
mod simple;
|
||||||
|
|
||||||
use domain::{error::KernelError, message::Message, syscall::Syscall};
|
use domain::{error::KernelError, syscall::Syscall};
|
||||||
use platform::Platform;
|
use platform::Platform;
|
||||||
use types::{bounded::CapSlot, id::ThreadId};
|
use types::{bounded::CapSlot, id::ThreadId, syscall_abi::ErrorCode};
|
||||||
|
|
||||||
use crate::Kernel;
|
use crate::Kernel;
|
||||||
|
|
||||||
|
pub use ipc::{IpcState, ThreadRef};
|
||||||
|
pub use types::syscall_abi::{Delivery, DeliveryKind};
|
||||||
|
|
||||||
pub enum SyscallOutcome {
|
pub enum SyscallOutcome {
|
||||||
Complete(SyscallResult),
|
Complete(SyscallResult),
|
||||||
BlockCaller(domain::thread::BlockReason),
|
BlockCaller(domain::thread::BlockReason),
|
||||||
|
/// The caller blocked and `ThreadId` should run next.
|
||||||
SwitchTo(ThreadId),
|
SwitchTo(ThreadId),
|
||||||
TerminateCaller,
|
TerminateCaller,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum SyscallResult {
|
pub enum SyscallResult {
|
||||||
Ok,
|
Ok,
|
||||||
Value(usize),
|
Value(usize),
|
||||||
Slot(CapSlot),
|
Slot(CapSlot),
|
||||||
Message(Message),
|
Received(Delivery),
|
||||||
WakeReason(domain::syscall::WakeReason),
|
|
||||||
Error(KernelError),
|
Error(KernelError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The status code userspace sees for a kernel error.
|
||||||
|
pub fn error_code(error: &KernelError) -> ErrorCode {
|
||||||
|
use KernelError::*;
|
||||||
|
match error {
|
||||||
|
InvalidSlot(_) | RevokedCapability(_) | NoSuchObject | InvalidEndpoint(_) => {
|
||||||
|
ErrorCode::InvalidCap
|
||||||
|
}
|
||||||
|
PermissionDenied { .. } | ProcessCreateDenied | WritableAndExecutable => {
|
||||||
|
ErrorCode::PermissionDenied
|
||||||
|
}
|
||||||
|
WrongObject => ErrorCode::WrongObject,
|
||||||
|
NoFreeSlot => ErrorCode::NoFreeSlot,
|
||||||
|
OutOfMemory => ErrorCode::OutOfMemory,
|
||||||
|
InvalidAddress => ErrorCode::InvalidAddress,
|
||||||
|
AddressInUse => ErrorCode::AddressInUse,
|
||||||
|
NoReply => ErrorCode::NoReply,
|
||||||
|
TooManyThreadsInProcess | TooManyProcesses | TooManyThreads | EndpointFull => {
|
||||||
|
ErrorCode::LimitReached
|
||||||
|
}
|
||||||
|
InvalidProcess(_)
|
||||||
|
| InvalidThread(_)
|
||||||
|
| InvalidStateTransition(_)
|
||||||
|
| InvalidIrq(_)
|
||||||
|
| InvalidRegion
|
||||||
|
| IrqAlreadyOwned(_)
|
||||||
|
| WouldBlock
|
||||||
|
| InvalidArgument => ErrorCode::InvalidArgument,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(result: Result<SyscallResult, KernelError>) -> SyscallOutcome {
|
||||||
|
SyscallOutcome::Complete(result.unwrap_or_else(SyscallResult::Error))
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Platform> Kernel<P> {
|
impl<P: Platform> Kernel<P> {
|
||||||
pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome {
|
pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome {
|
||||||
let (tid, pid) = match self.caller() {
|
let (tid, pid) = match self.caller() {
|
||||||
@@ -43,26 +81,81 @@ impl<P: Platform> Kernel<P> {
|
|||||||
Syscall::RequestPages { count } => self.handle_request_pages(pid, count),
|
Syscall::RequestPages { count } => self.handle_request_pages(pid, count),
|
||||||
Syscall::ReleasePages { addr, count } => self.handle_release_pages(pid, addr, count),
|
Syscall::ReleasePages { addr, count } => self.handle_release_pages(pid, addr, count),
|
||||||
Syscall::SpawnThread { entry, stack } => self.handle_spawn_thread(pid, entry, stack),
|
Syscall::SpawnThread { entry, stack } => self.handle_spawn_thread(pid, entry, stack),
|
||||||
Syscall::CreateProcess { create_cap, image } => self.handle_create_process(pid, create_cap, image),
|
Syscall::CreateProcess { create_cap, image } => {
|
||||||
|
self.handle_create_process(pid, create_cap, image)
|
||||||
|
}
|
||||||
Syscall::Kill { process } => self.handle_kill(pid, process),
|
Syscall::Kill { process } => self.handle_kill(pid, process),
|
||||||
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
|
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
|
||||||
Syscall::Delegate { slot, to_endpoint, permissions } => {
|
Syscall::DropCap { slot } => self.handle_drop_cap(pid, slot),
|
||||||
self.handle_delegate(pid, slot, to_endpoint, permissions)
|
Syscall::CreateRegion { pages } => {
|
||||||
|
complete(self.create_region_cap(pid, pages).map(SyscallResult::Slot))
|
||||||
}
|
}
|
||||||
Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message),
|
Syscall::MapRegion { region, va, perms } => complete(
|
||||||
Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint),
|
self.map_region_cap(pid, region, va, perms)
|
||||||
Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq),
|
.map(|()| SyscallResult::Ok),
|
||||||
Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq),
|
),
|
||||||
Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources),
|
Syscall::RegionAddress { region } => complete(
|
||||||
Syscall::Halt => self.handle_halt(pid),
|
self.region_address_cap(pid, region)
|
||||||
|
.map(|pa| SyscallResult::Value(pa.raw())),
|
||||||
|
),
|
||||||
|
Syscall::Delegate {
|
||||||
|
slot,
|
||||||
|
to_endpoint,
|
||||||
|
permissions,
|
||||||
|
} => self.handle_delegate(pid, slot, to_endpoint, permissions),
|
||||||
|
Syscall::Mint {
|
||||||
|
slot,
|
||||||
|
badge,
|
||||||
|
permissions,
|
||||||
|
} => complete(
|
||||||
|
self.mint(pid, slot, badge, permissions)
|
||||||
|
.map(SyscallResult::Slot),
|
||||||
|
),
|
||||||
|
Syscall::Send { endpoint, message } => self.send(tid, pid, endpoint, message, false),
|
||||||
|
Syscall::Call { endpoint, message } => self.send(tid, pid, endpoint, message, true),
|
||||||
|
Syscall::Receive { endpoint } => self.receive(tid, pid, endpoint, true),
|
||||||
|
Syscall::Reply { message } => {
|
||||||
|
complete(self.reply(tid, pid, message).map(|()| SyscallResult::Ok))
|
||||||
|
}
|
||||||
|
Syscall::ReplyRecv { endpoint, message } => {
|
||||||
|
self.reply_recv(tid, pid, endpoint, message)
|
||||||
|
}
|
||||||
|
Syscall::CreateNotification => {
|
||||||
|
complete(self.create_notification_cap(pid).map(SyscallResult::Slot))
|
||||||
|
}
|
||||||
|
Syscall::Signal { notification } => {
|
||||||
|
complete(self.signal(pid, notification).map(|()| SyscallResult::Ok))
|
||||||
|
}
|
||||||
|
Syscall::Wait { notification } => self.wait(tid, pid, notification),
|
||||||
|
Syscall::Poll { notification } => complete(
|
||||||
|
self.poll(pid, notification)
|
||||||
|
.map(|bits| SyscallResult::Received(Delivery::signal(bits))),
|
||||||
|
),
|
||||||
|
Syscall::BindNotification { notification } => complete(
|
||||||
|
self.bind_notification(tid, pid, notification)
|
||||||
|
.map(|()| SyscallResult::Ok),
|
||||||
|
),
|
||||||
|
Syscall::SetIrqNotification { irq, notification } => complete(
|
||||||
|
self.set_irq_notification(pid, irq, notification)
|
||||||
|
.map(|()| SyscallResult::Ok),
|
||||||
|
),
|
||||||
|
Syscall::AcknowledgeInterrupt { irq } => {
|
||||||
|
complete(self.ack_interrupt(pid, irq).map(|()| SyscallResult::Ok))
|
||||||
|
}
|
||||||
|
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 { process, src_va, dst_va, executable } => {
|
Syscall::MapPage {
|
||||||
self.handle_map_page(pid, process, src_va, dst_va, executable)
|
process,
|
||||||
}
|
src_va,
|
||||||
Syscall::SpawnThreadIn { process, entry, stack } => {
|
dst_va,
|
||||||
self.handle_spawn_thread_in(pid, process, entry, stack)
|
executable,
|
||||||
}
|
} => self.handle_map_page(pid, process, src_va, dst_va, executable),
|
||||||
|
Syscall::SpawnThreadIn {
|
||||||
|
process,
|
||||||
|
entry,
|
||||||
|
stack,
|
||||||
|
} => self.handle_spawn_thread_in(pid, process, entry, stack),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -31,15 +33,26 @@ impl<P: Platform> Kernel<P> {
|
|||||||
|
|
||||||
match self.scheduler.next(core) {
|
match self.scheduler.next(core) {
|
||||||
Some(next) if next != caller_tid => {
|
Some(next) if next != caller_tid => {
|
||||||
if self.threads.get_mut(next).and_then(|t| t.set_running(core)).is_ok() {
|
if self
|
||||||
|
.threads
|
||||||
|
.get_mut(next)
|
||||||
|
.and_then(|t| t.set_running(core))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
SyscallOutcome::SwitchTo(next)
|
SyscallOutcome::SwitchTo(next)
|
||||||
} else {
|
} else {
|
||||||
let _ = self.threads.get_mut(caller_tid).map(|t| t.set_running(core));
|
let _ = self
|
||||||
|
.threads
|
||||||
|
.get_mut(caller_tid)
|
||||||
|
.map(|t| t.set_running(core));
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let _ = self.threads.get_mut(caller_tid).map(|t| t.set_running(core));
|
let _ = self
|
||||||
|
.threads
|
||||||
|
.get_mut(caller_tid)
|
||||||
|
.map(|t| t.set_running(core));
|
||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,10 +72,7 @@ impl<P: Platform> Kernel<P> {
|
|||||||
caller_tid: types::id::ThreadId,
|
caller_tid: types::id::ThreadId,
|
||||||
pid: types::id::ProcessId,
|
pid: types::id::ProcessId,
|
||||||
) -> SyscallOutcome {
|
) -> SyscallOutcome {
|
||||||
use domain::scheduler::Scheduler;
|
self.cleanup_threads(&[Some(caller_tid)]);
|
||||||
|
|
||||||
self.scheduler.remove(caller_tid);
|
|
||||||
let _ = self.threads.destroy(caller_tid);
|
|
||||||
|
|
||||||
if let Ok(process) = self.processes.get_mut(pid) {
|
if let Ok(process) = self.processes.get_mut(pid) {
|
||||||
process.remove_thread(caller_tid);
|
process.remove_thread(caller_tid);
|
||||||
@@ -79,15 +89,13 @@ impl<P: Platform> Kernel<P> {
|
|||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_halt(
|
/// Halts the machine for a holder of a SystemControl capability with
|
||||||
&mut self,
|
/// Execute; anyone else gets an error and nothing happens.
|
||||||
pid: types::id::ProcessId,
|
pub(crate) fn handle_halt(&mut self, pid: ProcessId, control: CapSlot) -> SyscallOutcome {
|
||||||
) -> SyscallOutcome {
|
match self.check_cap(pid, control, Permission::Execute) {
|
||||||
if pid.raw() != 0 {
|
Ok(cap) if *cap.resource() == Resource::SystemControl => self.platform.halt(),
|
||||||
return SyscallOutcome::Complete(SyscallResult::Error(
|
Ok(_) => SyscallOutcome::Complete(SyscallResult::Error(KernelError::WrongObject)),
|
||||||
domain::error::KernelError::ProcessCreateDenied,
|
Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)),
|
||||||
));
|
|
||||||
}
|
}
|
||||||
self.platform.halt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
crates/kernel/src/tests/abi.rs
Normal file
100
crates/kernel/src/tests/abi.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use crate::abi::{decode, encode, unknown_syscall};
|
||||||
|
use crate::syscall::SyscallResult;
|
||||||
|
use domain::syscall::Syscall;
|
||||||
|
use types::syscall_abi::*;
|
||||||
|
|
||||||
|
/// Every syscall number the ABI defines decodes with ordinary arguments,
|
||||||
|
/// so the decoder cannot silently drop one.
|
||||||
|
#[test]
|
||||||
|
fn every_defined_syscall_decodes() {
|
||||||
|
let numbers = [
|
||||||
|
SYS_SEND,
|
||||||
|
SYS_RECEIVE,
|
||||||
|
SYS_DELEGATE,
|
||||||
|
SYS_ACKNOWLEDGE_INTERRUPT,
|
||||||
|
SYS_CREATE_PROCESS,
|
||||||
|
SYS_KILL,
|
||||||
|
SYS_REVOKE,
|
||||||
|
SYS_SPAWN_THREAD,
|
||||||
|
SYS_EXIT_THREAD,
|
||||||
|
SYS_REQUEST_PAGES,
|
||||||
|
SYS_RELEASE_PAGES,
|
||||||
|
SYS_GET_TIME,
|
||||||
|
SYS_GET_PROCESS_ID,
|
||||||
|
SYS_YIELD,
|
||||||
|
SYS_EXIT,
|
||||||
|
SYS_HALT,
|
||||||
|
SYS_CREATE_ENDPOINT,
|
||||||
|
SYS_DEBUG_PUTCHAR,
|
||||||
|
SYS_MAP_PAGE,
|
||||||
|
SYS_SPAWN_THREAD_IN,
|
||||||
|
SYS_DROP_CAP,
|
||||||
|
SYS_CALL,
|
||||||
|
SYS_REPLY,
|
||||||
|
SYS_REPLY_RECV,
|
||||||
|
SYS_MINT,
|
||||||
|
SYS_CREATE_NOTIFICATION,
|
||||||
|
SYS_SIGNAL,
|
||||||
|
SYS_WAIT,
|
||||||
|
SYS_POLL,
|
||||||
|
SYS_BIND_NOTIFICATION,
|
||||||
|
SYS_SET_IRQ_NOTIFICATION,
|
||||||
|
SYS_CREATE_REGION,
|
||||||
|
SYS_MAP_REGION,
|
||||||
|
SYS_REGION_ADDRESS,
|
||||||
|
];
|
||||||
|
// slot 1, slot 2, small values, no cap
|
||||||
|
let args = [1, 2, 1, 0, 0, NONE];
|
||||||
|
for nr in numbers {
|
||||||
|
assert!(decode(nr, &args).is_some(), "syscall {nr} does not decode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retired_and_unknown_numbers_do_not_decode() {
|
||||||
|
for nr in [3, 5, 36, 1000, usize::MAX] {
|
||||||
|
assert!(decode(nr, &[0; 6]).is_none(), "{nr} decoded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn out_of_range_arguments_do_not_decode() {
|
||||||
|
assert!(decode(SYS_RECEIVE, &[64, 0, 0, 0, 0, 0]).is_none());
|
||||||
|
assert!(
|
||||||
|
decode(SYS_SEND, &[1, 0, 0, 0, 0, 64]).is_none(),
|
||||||
|
"bad cap slot"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
decode(SYS_REQUEST_PAGES, &[0, 0, 0, 0, 0, 0]).is_none(),
|
||||||
|
"zero pages"
|
||||||
|
);
|
||||||
|
assert!(decode(SYS_REQUEST_PAGES, &[1025, 0, 0, 0, 0, 0]).is_none());
|
||||||
|
assert!(
|
||||||
|
decode(SYS_MAP_REGION, &[1, 0x30000, 3, 0, 0, 0]).is_none(),
|
||||||
|
"bad perms"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
decode(SYS_DELEGATE, &[1, 2, 256, 0, 0, 0]).is_none(),
|
||||||
|
"perm bits overflow u8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn none_means_no_capability() {
|
||||||
|
match decode(SYS_SEND, &[1, 9, 8, 7, 6, NONE]) {
|
||||||
|
Some(Syscall::Send { message, .. }) => {
|
||||||
|
assert_eq!(message.cap(), None);
|
||||||
|
assert_eq!(message.words(), &[9, 8, 7, 6]);
|
||||||
|
}
|
||||||
|
_ => panic!("send did not decode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn results_follow_the_status_convention() {
|
||||||
|
assert_eq!(encode(&SyscallResult::Ok), [0; 8]);
|
||||||
|
assert_eq!(encode(&SyscallResult::Value(7))[..2], [0, 7]);
|
||||||
|
let err = encode(&SyscallResult::Error(domain::error::KernelError::NoReply));
|
||||||
|
assert_eq!(err[0], ErrorCode::NoReply as usize);
|
||||||
|
assert_eq!(unknown_syscall()[0], ErrorCode::UnknownSyscall as usize);
|
||||||
|
}
|
||||||
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));
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
use domain::resource::Resource;
|
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::Syscall;
|
||||||
use types::bounded::CapSlot;
|
|
||||||
use types::permission::{Permission, PermissionSet};
|
|
||||||
use types::address::DataRef;
|
use types::address::DataRef;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
use types::permission::PermissionSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delegate_without_delegate_permission_fails() {
|
fn delegate_without_delegate_permission_fails() {
|
||||||
@@ -58,131 +57,16 @@ fn delegate_with_valid_cap_succeeds() {
|
|||||||
// --- Generation-based revocation ---
|
// --- Generation-based revocation ---
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn revoke_resource_invalidates_all_caps() {
|
fn every_representable_slot_is_rejected_not_panicking() {
|
||||||
let (mut k, pid) = kernel_with_process();
|
let (mut k, _) = kernel_with_process();
|
||||||
let ep_id = k.create_endpoint_for_test();
|
for raw in 1..=CapSlot::MAX {
|
||||||
|
let outcome = k.handle_syscall(Syscall::Send {
|
||||||
let cap_slot = k.grant_cap_for_test(
|
endpoint: CapSlot::new(raw).unwrap(),
|
||||||
pid,
|
message: domain::message::Message::empty(),
|
||||||
Resource::Endpoint(ep_id),
|
});
|
||||||
PermissionSet::empty().with(Permission::Read),
|
assert!(
|
||||||
|
matches!(outcome, SyscallOutcome::Complete(SyscallResult::Error(_))),
|
||||||
|
"slot {raw} should be an error"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
// Revoke the endpoint
|
|
||||||
k.revoke_resource(&Resource::Endpoint(ep_id));
|
|
||||||
|
|
||||||
// Cap is now invalid — check_cap should reject with RevokedCapability
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_slot });
|
|
||||||
assert!(matches!(
|
|
||||||
outcome,
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_cap_after_revoke_uses_current_generation() {
|
|
||||||
let (mut k, pid) = kernel_with_process();
|
|
||||||
let ep_id = k.create_endpoint_for_test();
|
|
||||||
|
|
||||||
// Grant, revoke, grant again
|
|
||||||
let old_slot = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep_id),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
);
|
|
||||||
|
|
||||||
k.revoke_resource(&Resource::Endpoint(ep_id));
|
|
||||||
|
|
||||||
// Old cap is dead
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: old_slot });
|
|
||||||
assert!(matches!(
|
|
||||||
outcome,
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
|
||||||
));
|
|
||||||
|
|
||||||
// New cap with current generation works
|
|
||||||
let new_slot = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep_id),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
);
|
|
||||||
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: new_slot });
|
|
||||||
// Should block (no sender), not error — proving the cap is valid
|
|
||||||
assert!(matches!(outcome, SyscallOutcome::BlockCaller(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn revoke_does_not_affect_other_resources() {
|
|
||||||
let (mut k, pid) = kernel_with_process();
|
|
||||||
let ep1 = k.create_endpoint_for_test();
|
|
||||||
let ep2 = k.create_endpoint_for_test();
|
|
||||||
|
|
||||||
let slot1 = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep1),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
);
|
|
||||||
let slot2 = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep2),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Revoke only ep1
|
|
||||||
k.revoke_resource(&Resource::Endpoint(ep1));
|
|
||||||
|
|
||||||
// ep1 cap is revoked
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: slot1 });
|
|
||||||
assert!(matches!(
|
|
||||||
outcome,
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
|
||||||
));
|
|
||||||
|
|
||||||
// ep2 cap still works
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: slot2 });
|
|
||||||
assert!(matches!(outcome, SyscallOutcome::BlockCaller(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn revoke_syscall_invalidates_via_generation() {
|
|
||||||
let (mut k, pid) = kernel_with_process();
|
|
||||||
let ep_id = k.create_endpoint_for_test();
|
|
||||||
|
|
||||||
let cap_slot = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep_id),
|
|
||||||
PermissionSet::empty()
|
|
||||||
.with(Permission::Read)
|
|
||||||
.with(Permission::Revoke),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Revoke via syscall
|
|
||||||
let outcome = k.handle_syscall(Syscall::Revoke { slot: cap_slot });
|
|
||||||
assert!(matches!(outcome, SyscallOutcome::Complete(SyscallResult::Ok)));
|
|
||||||
|
|
||||||
// The cap itself is now revoked (generation mismatch)
|
|
||||||
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_slot });
|
|
||||||
assert!(matches!(
|
|
||||||
outcome,
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn revoke_syscall_without_revoke_permission_fails() {
|
|
||||||
let (mut k, pid) = kernel_with_process();
|
|
||||||
let ep_id = k.create_endpoint_for_test();
|
|
||||||
|
|
||||||
let cap_slot = k.grant_cap_for_test(
|
|
||||||
pid,
|
|
||||||
Resource::Endpoint(ep_id),
|
|
||||||
PermissionSet::empty().with(Permission::Read),
|
|
||||||
);
|
|
||||||
|
|
||||||
let outcome = k.handle_syscall(Syscall::Revoke { slot: cap_slot });
|
|
||||||
assert!(matches!(
|
|
||||||
outcome,
|
|
||||||
SyscallOutcome::Complete(SyscallResult::Error(_))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|||||||
326
crates/kernel/src/tests/captree_model.rs
Normal file
326
crates/kernel/src/tests/captree_model.rs
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
//! Randomized check of the derivation tree against an independent model.
|
||||||
|
//! The model applies the rules of docs/ipc.md (derive records a parent,
|
||||||
|
//! delete re-parents, revoke removes the subtree, death deletes everything
|
||||||
|
//! a process holds) to its own data; after every step the kernel must hold
|
||||||
|
//! exactly the capabilities the model says are alive.
|
||||||
|
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
|
use domain::syscall::Syscall;
|
||||||
|
use types::address::{DataRef, VirtualAddress};
|
||||||
|
use types::badge::Badge;
|
||||||
|
use types::bounded::{CapSlot, PageCount};
|
||||||
|
use types::id::ThreadId;
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
|
const STEPS: usize = 1500;
|
||||||
|
|
||||||
|
struct Rng(u64);
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
fn next(&mut self) -> u64 {
|
||||||
|
self.0 ^= self.0 << 13;
|
||||||
|
self.0 ^= self.0 >> 7;
|
||||||
|
self.0 ^= self.0 << 17;
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn below(&mut self, n: usize) -> usize {
|
||||||
|
(self.next() % n as u64) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick<T: Copy>(&mut self, xs: &[T]) -> Option<T> {
|
||||||
|
(!xs.is_empty()).then(|| xs[self.below(xs.len())])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Node {
|
||||||
|
parent: Option<usize>,
|
||||||
|
is_region: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reference model: logical capabilities and where each one lives.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Model {
|
||||||
|
nodes: Vec<Node>,
|
||||||
|
alive: HashMap<usize, (ProcessId, CapSlot)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
fn add(&mut self, at: (ProcessId, CapSlot), parent: Option<usize>, is_region: bool) -> usize {
|
||||||
|
self.nodes.push(Node { parent, is_region });
|
||||||
|
let id = self.nodes.len() - 1;
|
||||||
|
self.alive.insert(id, at);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&mut self, id: usize) {
|
||||||
|
let grandparent = self.nodes[id].parent;
|
||||||
|
for n in 0..self.nodes.len() {
|
||||||
|
if self.alive.contains_key(&n) && self.nodes[n].parent == Some(id) {
|
||||||
|
self.nodes[n].parent = grandparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.alive.remove(&id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_descendant(&self, mut n: usize, root: usize) -> bool {
|
||||||
|
while let Some(p) = self.nodes[n].parent {
|
||||||
|
if p == root {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
n = p;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revoke(&mut self, root: usize) {
|
||||||
|
let doomed: Vec<usize> = self
|
||||||
|
.alive
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.filter(|&n| self.is_descendant(n, root))
|
||||||
|
.collect();
|
||||||
|
for n in doomed {
|
||||||
|
self.alive.remove(&n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn held_by(&self, pid: ProcessId) -> Vec<(usize, CapSlot)> {
|
||||||
|
self.alive
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, (p, _))| *p == pid)
|
||||||
|
.map(|(id, (_, s))| (*id, *s))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all() -> PermissionSet {
|
||||||
|
PermissionSet::empty()
|
||||||
|
.with(Permission::Read)
|
||||||
|
.with(Permission::Write)
|
||||||
|
.with(Permission::Execute)
|
||||||
|
.with(Permission::Delegate)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct World {
|
||||||
|
k: Kernel<MockPlatform>,
|
||||||
|
m: Model,
|
||||||
|
pids: [ProcessId; 3],
|
||||||
|
tids: [ThreadId; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (k, a) = kernel_with_process();
|
||||||
|
let ta = k.current_thread().unwrap();
|
||||||
|
let mut w = Self {
|
||||||
|
k,
|
||||||
|
m: Model::default(),
|
||||||
|
pids: [a; 3],
|
||||||
|
tids: [ta; 3],
|
||||||
|
};
|
||||||
|
// init's own boot capability (ProcessCreate) is a kernel root.
|
||||||
|
let create = CapSlot::new(0).unwrap();
|
||||||
|
w.m.add((a, create), None, false);
|
||||||
|
for i in 1..3 {
|
||||||
|
w.spawn(i);
|
||||||
|
}
|
||||||
|
for call in [Syscall::CreateEndpoint, Syscall::CreateNotification] {
|
||||||
|
let s = w.slot(0, call);
|
||||||
|
w.m.add((a, s), None, false);
|
||||||
|
}
|
||||||
|
let r = w.slot(
|
||||||
|
0,
|
||||||
|
Syscall::CreateRegion {
|
||||||
|
pages: PageCount::new(1).unwrap(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
w.m.add((a, r), None, true);
|
||||||
|
w
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slot(&mut self, who: usize, call: Syscall) -> CapSlot {
|
||||||
|
self.k.switch_to_thread_for_test(self.tids[who]);
|
||||||
|
match self.k.handle_syscall(call) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
|
_ => panic!("expected a slot"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (Re)creates process `i` from init; init gets a root process cap.
|
||||||
|
fn spawn(&mut self, i: usize) {
|
||||||
|
let cap = self.slot(
|
||||||
|
0,
|
||||||
|
Syscall::CreateProcess {
|
||||||
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
|
image: DataRef::empty(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.pids[i] = self.k.process_of_cap_for_test(cap);
|
||||||
|
self.tids[i] =
|
||||||
|
self.k
|
||||||
|
.spawn_thread_for_test(self.pids[i], Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
|
self.m.add((self.pids[0], cap), None, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, who: usize, call: Syscall) -> bool {
|
||||||
|
self.k.switch_to_thread_for_test(self.tids[who]);
|
||||||
|
!matches!(
|
||||||
|
self.k.handle_syscall(call),
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self, rng: &mut Rng) {
|
||||||
|
let who = rng.below(3);
|
||||||
|
let pid = self.pids[who];
|
||||||
|
let held = self.m.held_by(pid);
|
||||||
|
let Some((id, slot)) = rng.pick(&held) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match rng.below(10) {
|
||||||
|
0..=2 => {
|
||||||
|
// Badges only go on endpoints and notifications; the kernel
|
||||||
|
// must refuse the rest, so try both.
|
||||||
|
let badge = Badge::new(rng.below(2));
|
||||||
|
let before = self.k.cap_slots_for_test(pid);
|
||||||
|
let ok = self.run(
|
||||||
|
who,
|
||||||
|
Syscall::Mint {
|
||||||
|
slot,
|
||||||
|
badge,
|
||||||
|
permissions: all(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if ok {
|
||||||
|
let new = newly_used(before, self.k.cap_slots_for_test(pid));
|
||||||
|
let is_region = self.m.nodes[id].is_region;
|
||||||
|
self.m.add((pid, new), Some(id), is_region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3..=4 => {
|
||||||
|
// Delegate through any process cap the caller holds.
|
||||||
|
let proc_caps: Vec<(CapSlot, ProcessId)> = held
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, s)| {
|
||||||
|
self.k.process_cap_target_for_test(pid, *s).map(|t| (*s, t))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if let Some((to, target)) = rng.pick(&proc_caps) {
|
||||||
|
let before = self.k.cap_slots_for_test(target);
|
||||||
|
let ok = self.run(
|
||||||
|
who,
|
||||||
|
Syscall::Delegate {
|
||||||
|
slot,
|
||||||
|
to_endpoint: to,
|
||||||
|
permissions: all(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if ok {
|
||||||
|
let new = newly_used(before, self.k.cap_slots_for_test(target));
|
||||||
|
let is_region = self.m.nodes[id].is_region;
|
||||||
|
self.m.add((target, new), Some(id), is_region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// init keeps its ProcessCreate cap so processes can be re-made.
|
||||||
|
5 if !(who == 0 && slot == CapSlot::new(0).unwrap()) => {
|
||||||
|
if self.run(who, Syscall::DropCap { slot }) {
|
||||||
|
self.m.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6..=7 => {
|
||||||
|
assert!(
|
||||||
|
self.run(who, Syscall::Revoke { slot }),
|
||||||
|
"revoking a held cap failed"
|
||||||
|
);
|
||||||
|
self.m.revoke(id);
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
let va = VirtualAddress::from_raw(0x30000 + rng.below(16) * 0x1000);
|
||||||
|
let _ = self.run(
|
||||||
|
who,
|
||||||
|
Syscall::MapRegion {
|
||||||
|
region: slot,
|
||||||
|
va,
|
||||||
|
perms: PagePerms::ReadWrite,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ if who == 0 && rng.below(4) == 0 => {
|
||||||
|
let victim = 1 + rng.below(2);
|
||||||
|
let vp = self.pids[victim];
|
||||||
|
// init's cap to the victim may already be revoked; kill it
|
||||||
|
// directly, as a supervisor holding a cap would.
|
||||||
|
for (n, _) in self.m.held_by(vp) {
|
||||||
|
self.m.delete(n);
|
||||||
|
}
|
||||||
|
self.k.destroy_process(vp);
|
||||||
|
self.spawn(victim);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) {
|
||||||
|
for &pid in &self.pids {
|
||||||
|
let used = self.k.cap_slots_for_test(pid);
|
||||||
|
let mut expected = [false; 64];
|
||||||
|
for (_, s) in self.m.held_by(pid) {
|
||||||
|
expected[s.value() as usize] = true;
|
||||||
|
}
|
||||||
|
assert_eq!(used, expected, "{pid:?} holds the wrong capabilities");
|
||||||
|
}
|
||||||
|
// Every capability-backed mapping is backed by a live capability
|
||||||
|
// of the mapping process.
|
||||||
|
for (_, region) in self.k.regions.iter() {
|
||||||
|
for m in region.mappings() {
|
||||||
|
if let Some(via) = m.via {
|
||||||
|
assert!(self.k.resolves(via), "mapping outlived its capability");
|
||||||
|
assert_eq!(via.process, m.process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newly_used(before: [bool; 64], after: [bool; 64]) -> CapSlot {
|
||||||
|
(0..64)
|
||||||
|
.find(|&i| after[i] && !before[i])
|
||||||
|
.and_then(|i| CapSlot::new(i as u8))
|
||||||
|
.expect("a new slot")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derivation_tree_matches_the_model() {
|
||||||
|
for seed in [5u64, 0xBADC0DE, 31337, 0x5EED, 2024] {
|
||||||
|
let mut world = World::new();
|
||||||
|
let mut rng = Rng(seed);
|
||||||
|
let mut revokes = 0;
|
||||||
|
for _ in 0..STEPS {
|
||||||
|
let before = world.m.alive.len();
|
||||||
|
world.step(&mut rng);
|
||||||
|
if world.m.alive.len() < before {
|
||||||
|
revokes += 1;
|
||||||
|
}
|
||||||
|
world.check();
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
revokes > 20,
|
||||||
|
"seed {seed:#x}: too little deletion exercised"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
world.m.nodes.len() > 100,
|
||||||
|
"seed {seed:#x}: too little derivation"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::SyscallOutcome;
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::Syscall;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -21,5 +21,5 @@ fn exit_thread_last_thread_destroys_process() {
|
|||||||
let (mut k, pid) = kernel_with_process();
|
let (mut k, pid) = kernel_with_process();
|
||||||
k.handle_syscall(Syscall::ExitThread);
|
k.handle_syscall(Syscall::ExitThread);
|
||||||
// Process should be gone since it had only one thread
|
// Process should be gone since it had only one thread
|
||||||
assert!(k.process_exists(pid) == false);
|
assert!(!k.process_exists(pid));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use domain::memory::page_pool::PagePool;
|
|
||||||
use domain::resource::Resource;
|
use domain::resource::Resource;
|
||||||
use domain::scheduler::Scheduler;
|
use domain::scheduler::Scheduler;
|
||||||
use domain::thread::ThreadState;
|
use domain::thread::ThreadState;
|
||||||
@@ -6,25 +5,22 @@ use platform::Platform;
|
|||||||
use types::{
|
use types::{
|
||||||
address::PhysicalAddress,
|
address::PhysicalAddress,
|
||||||
bounded::{CapSlot, Priority},
|
bounded::{CapSlot, Priority},
|
||||||
id::{EndpointId, IrqId, ProcessId, ThreadId},
|
id::{EndpointId, ProcessId, ThreadId},
|
||||||
permission::PermissionSet,
|
permission::PermissionSet,
|
||||||
time::TimeSlice,
|
time::TimeSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::Kernel;
|
|
||||||
use super::mock_platform::MockPlatform;
|
use super::mock_platform::MockPlatform;
|
||||||
|
use crate::Kernel;
|
||||||
|
|
||||||
pub fn test_kernel() -> Kernel<MockPlatform> {
|
pub fn test_kernel() -> Kernel<MockPlatform> {
|
||||||
let mock = MockPlatform::new(1);
|
let mock = MockPlatform::new(1);
|
||||||
let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024);
|
Kernel::new(mock, PhysicalAddress::from_raw(0x8400_0000), 1024)
|
||||||
Kernel::new(mock, pool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kernel_with_process() -> (Kernel<MockPlatform>, ProcessId) {
|
pub fn kernel_with_process() -> (Kernel<MockPlatform>, ProcessId) {
|
||||||
let mut kernel = test_kernel();
|
let mut kernel = test_kernel();
|
||||||
let (pid, _tid) = kernel
|
let (pid, _tid) = kernel.boot(Priority::DEFAULT, TimeSlice::DEFAULT).unwrap();
|
||||||
.boot(Priority::DEFAULT, TimeSlice::DEFAULT)
|
|
||||||
.unwrap();
|
|
||||||
(kernel, pid)
|
(kernel, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,22 +31,18 @@ impl Kernel<MockPlatform> {
|
|||||||
priority: Priority,
|
priority: Priority,
|
||||||
time_slice: TimeSlice,
|
time_slice: TimeSlice,
|
||||||
) -> ThreadId {
|
) -> ThreadId {
|
||||||
let tid = self.threads.spawn(pid, priority, time_slice, None).expect("spawn");
|
let tid = self
|
||||||
|
.threads
|
||||||
|
.spawn(pid, priority, time_slice, None)
|
||||||
|
.expect("spawn");
|
||||||
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
|
let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid));
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
|
self.set_home(tid, core);
|
||||||
self.scheduler.add(tid, priority, core);
|
self.scheduler.add(tid, priority, core);
|
||||||
tid
|
tid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_irq_for_test(&mut self, irq: IrqId, thread: ThreadId) {
|
pub fn block_thread_for_test(&mut self, tid: ThreadId, reason: domain::thread::BlockReason) {
|
||||||
let _ = self.irqs.register(irq, thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_thread_for_test(
|
|
||||||
&mut self,
|
|
||||||
tid: ThreadId,
|
|
||||||
reason: domain::thread::BlockReason,
|
|
||||||
) {
|
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
if let Ok(t) = self.threads.get_mut(tid) {
|
if let Ok(t) = self.threads.get_mut(tid) {
|
||||||
if matches!(t.state(), ThreadState::Ready) {
|
if matches!(t.state(), ThreadState::Ready) {
|
||||||
@@ -63,30 +55,44 @@ impl Kernel<MockPlatform> {
|
|||||||
|
|
||||||
pub fn switch_to_thread_for_test(&mut self, tid: ThreadId) {
|
pub fn switch_to_thread_for_test(&mut self, tid: ThreadId) {
|
||||||
let core = self.platform.core_id();
|
let core = self.platform.core_id();
|
||||||
|
assert_eq!(
|
||||||
|
self.home(tid),
|
||||||
|
core,
|
||||||
|
"a thread only ever runs on its home core"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only a thread ready in this core's queue can be switched to;
|
||||||
|
// otherwise leave the core as it is.
|
||||||
|
let switchable = self.scheduler.queued_on(tid) == Some(core)
|
||||||
|
&& self
|
||||||
|
.threads
|
||||||
|
.get(tid)
|
||||||
|
.is_ok_and(|t| matches!(t.state(), ThreadState::Ready));
|
||||||
if let Some(current) = self.scheduler.current(core) {
|
if let Some(current) = self.scheduler.current(core) {
|
||||||
if current == tid {
|
if current == tid || !switchable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Ok(t) = self.threads.get_mut(current) {
|
if let Ok(t) = self.threads.get_mut(current)
|
||||||
if matches!(t.state(), ThreadState::Running(_)) {
|
&& matches!(t.state(), ThreadState::Running(_))
|
||||||
|
{
|
||||||
let priority = t.priority();
|
let priority = t.priority();
|
||||||
let _ = t.preempt();
|
let _ = t.preempt();
|
||||||
self.scheduler.add(current, priority, core);
|
self.scheduler.add(current, priority, core);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(t) = self.threads.get(tid) {
|
if let Ok(t) = self.threads.get(tid)
|
||||||
if matches!(t.state(), ThreadState::Ready) {
|
&& matches!(t.state(), ThreadState::Ready)
|
||||||
self.scheduler.next(core);
|
&& self.scheduler.run_now(core, tid)
|
||||||
|
{
|
||||||
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
let _ = self.threads.get_mut(tid).map(|t| t.set_running(core));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_endpoint_for_test(&mut self) -> EndpointId {
|
pub fn create_endpoint_for_test(&mut self) -> EndpointId {
|
||||||
self.endpoints.create().expect("create endpoint")
|
self.endpoints
|
||||||
|
.create(ProcessId::from_raw(0))
|
||||||
|
.expect("create endpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grant_cap_for_test(
|
pub fn grant_cap_for_test(
|
||||||
@@ -95,6 +101,95 @@ impl Kernel<MockPlatform> {
|
|||||||
resource: Resource,
|
resource: Resource,
|
||||||
permissions: PermissionSet,
|
permissions: PermissionSet,
|
||||||
) -> CapSlot {
|
) -> CapSlot {
|
||||||
self.grant_cap(pid, resource, permissions).expect("grant cap")
|
self.grant_cap(pid, resource, permissions)
|
||||||
|
.expect("grant cap")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endpoint_of_cap_for_test(&self, pid: ProcessId, slot: CapSlot) -> EndpointId {
|
||||||
|
match self
|
||||||
|
.processes
|
||||||
|
.get(pid)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.get(slot)
|
||||||
|
.unwrap()
|
||||||
|
.resource()
|
||||||
|
{
|
||||||
|
Resource::Endpoint(id) => *id,
|
||||||
|
_ => panic!("not an endpoint cap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The process a Process cap in init's table points at.
|
||||||
|
pub fn process_of_cap_for_test(&self, slot: CapSlot) -> ProcessId {
|
||||||
|
let init = ProcessId::from_raw(0);
|
||||||
|
match self
|
||||||
|
.processes
|
||||||
|
.get(init)
|
||||||
|
.unwrap()
|
||||||
|
.capabilities()
|
||||||
|
.get(slot)
|
||||||
|
.unwrap()
|
||||||
|
.resource()
|
||||||
|
{
|
||||||
|
Resource::Process(pid) => *pid,
|
||||||
|
_ => panic!("not a process cap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy_process_notifications_for_test(&mut self, owner: ProcessId) {
|
||||||
|
self.destroy_notifications_owned_by(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The process a Process cap in `pid`'s table names, if it is one.
|
||||||
|
pub fn process_cap_target_for_test(&self, pid: ProcessId, slot: CapSlot) -> Option<ProcessId> {
|
||||||
|
match self
|
||||||
|
.processes
|
||||||
|
.get(pid)
|
||||||
|
.ok()?
|
||||||
|
.capabilities()
|
||||||
|
.get(slot)
|
||||||
|
.ok()?
|
||||||
|
.resource()
|
||||||
|
{
|
||||||
|
Resource::Process(target) if self.process_exists(*target) => Some(*target),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `slot` of `pid` holds a capability whose object is gone.
|
||||||
|
pub fn is_dead_cap_for_test(&self, pid: ProcessId, slot: CapSlot) -> bool {
|
||||||
|
let Ok(p) = self.processes.get(pid) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
p.capabilities()
|
||||||
|
.get(slot)
|
||||||
|
.is_ok_and(|c| self.resource_generation(c.resource()) != Some(*c.generation()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `slot` of `pid` holds a capability to a process that is gone.
|
||||||
|
pub fn is_dead_process_cap_for_test(&self, pid: ProcessId, slot: CapSlot) -> bool {
|
||||||
|
let Ok(p) = self.processes.get(pid) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
match p.capabilities().get(slot).map(|c| *c.resource()) {
|
||||||
|
Ok(Resource::Process(target)) => !self.process_exists(target),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which slots of `pid`'s capability table are occupied.
|
||||||
|
pub fn cap_slots_for_test(&self, pid: ProcessId) -> [bool; 64] {
|
||||||
|
let mut used = [false; 64];
|
||||||
|
if let Ok(p) = self.processes.get(pid) {
|
||||||
|
for (slot, _) in p.capabilities().iter() {
|
||||||
|
used[slot.value() as usize] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn thread_state_for_test(&self, tid: ThreadId) -> Option<ThreadState> {
|
||||||
|
self.threads.get(tid).ok().map(|t| *t.state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
299
crates/kernel/src/tests/invariants.rs
Normal file
299
crates/kernel/src/tests/invariants.rs
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
//! Randomized whole-kernel check of the memory model. Three processes issue
|
||||||
|
//! thousands of random (often hostile) memory operations; after every step
|
||||||
|
//! the kernel's records, the quotas and the mock "hardware" must agree.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
|
use domain::memory::{RegionBacking, frame::FrameUse};
|
||||||
|
use domain::syscall::Syscall;
|
||||||
|
use platform::Platform;
|
||||||
|
use types::address::{DataRef, VirtualAddress};
|
||||||
|
use types::bounded::{CapSlot, PageCount};
|
||||||
|
use types::id::ThreadId;
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
|
const PAGE: usize = 4096;
|
||||||
|
const PROCS: usize = 3;
|
||||||
|
const STEPS: usize = 4000;
|
||||||
|
|
||||||
|
struct Rng(u64);
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
fn next(&mut self) -> u64 {
|
||||||
|
// xorshift64
|
||||||
|
self.0 ^= self.0 << 13;
|
||||||
|
self.0 ^= self.0 >> 7;
|
||||||
|
self.0 ^= self.0 << 17;
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn below(&mut self, n: usize) -> usize {
|
||||||
|
(self.next() % n as u64) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A VA that is usually plausible and sometimes plain hostile.
|
||||||
|
fn va(&mut self, known: &[usize]) -> usize {
|
||||||
|
match self.below(6) {
|
||||||
|
0 | 1 if !known.is_empty() => known[self.below(known.len())],
|
||||||
|
2 => self.below(0x0300_0000) & !(PAGE - 1),
|
||||||
|
3 => self.next() as usize,
|
||||||
|
4 => 0x8020_0000 + self.below(64) * PAGE,
|
||||||
|
_ => 0x10000 + self.below(64) * PAGE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct World {
|
||||||
|
k: Kernel<MockPlatform>,
|
||||||
|
pids: [ProcessId; PROCS],
|
||||||
|
tids: [ThreadId; PROCS],
|
||||||
|
alive: [bool; PROCS],
|
||||||
|
/// A's process caps for B and C.
|
||||||
|
caps: [CapSlot; PROCS],
|
||||||
|
known: [[usize; 64]; PROCS],
|
||||||
|
known_len: [usize; PROCS],
|
||||||
|
rebirths: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (mut k, pid_a) = kernel_with_process();
|
||||||
|
let tid_a = k.current_thread().unwrap();
|
||||||
|
let mut pids = [pid_a; PROCS];
|
||||||
|
let mut tids = [tid_a; PROCS];
|
||||||
|
let mut caps = [CapSlot::new(0).unwrap(); PROCS];
|
||||||
|
for i in 1..PROCS {
|
||||||
|
caps[i] = match k.handle_syscall(Syscall::CreateProcess {
|
||||||
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
|
image: DataRef::empty(),
|
||||||
|
}) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
|
_ => panic!("create process"),
|
||||||
|
};
|
||||||
|
pids[i] = ProcessId::from_raw(i as u16);
|
||||||
|
tids[i] = k.spawn_thread_for_test(pids[i], Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
k,
|
||||||
|
pids,
|
||||||
|
tids,
|
||||||
|
alive: [true; PROCS],
|
||||||
|
caps,
|
||||||
|
known: [[0; 64]; PROCS],
|
||||||
|
known_len: [0; PROCS],
|
||||||
|
rebirths: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remember(&mut self, who: usize, va: usize) {
|
||||||
|
let n = self.known_len[who];
|
||||||
|
self.known[who][n % 64] = va;
|
||||||
|
self.known_len[who] = (n + 1).min(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn known(&self, who: usize) -> &[usize] {
|
||||||
|
&self.known[who][..self.known_len[who]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self, rng: &mut Rng) {
|
||||||
|
let who = rng.below(PROCS);
|
||||||
|
if !self.alive[who] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.k.switch_to_thread_for_test(self.tids[who]);
|
||||||
|
let pid = self.pids[who];
|
||||||
|
let known: [usize; 64] = self.known[who];
|
||||||
|
let known = &known[..self.known_len[who]];
|
||||||
|
|
||||||
|
match rng.below(10) {
|
||||||
|
0..=2 => {
|
||||||
|
let n = 1 + rng.below(4) as u16;
|
||||||
|
if let SyscallOutcome::Complete(SyscallResult::Value(va)) =
|
||||||
|
self.k.handle_syscall(Syscall::RequestPages {
|
||||||
|
count: PageCount::new(n).unwrap(),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for i in 0..n as usize {
|
||||||
|
self.remember(who, va + i * PAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3..=4 => {
|
||||||
|
let addr = rng.va(known);
|
||||||
|
let count = PageCount::new(1 + rng.below(5) as u16).unwrap();
|
||||||
|
self.k.handle_syscall(Syscall::ReleasePages {
|
||||||
|
addr: VirtualAddress::from_raw(addr),
|
||||||
|
count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
5..=6 if who == 0 => {
|
||||||
|
let target = 1 + rng.below(PROCS - 1);
|
||||||
|
let src = rng.va(known);
|
||||||
|
let dst = rng.va(self.known(target));
|
||||||
|
let outcome = self.k.handle_syscall(Syscall::MapPage {
|
||||||
|
process: self.caps[target],
|
||||||
|
src_va: VirtualAddress::from_raw(src),
|
||||||
|
dst_va: VirtualAddress::from_raw(dst),
|
||||||
|
executable: rng.below(2) == 0,
|
||||||
|
});
|
||||||
|
if matches!(outcome, SyscallOutcome::Complete(SyscallResult::Ok)) {
|
||||||
|
self.remember(target, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
7 => {
|
||||||
|
let pages = PageCount::new(1 + rng.below(3) as u16).unwrap();
|
||||||
|
if let Ok(id) = self.k.create_pool_region(pid, pages) {
|
||||||
|
let other = rng.below(PROCS);
|
||||||
|
for p in [who, other] {
|
||||||
|
if self.alive[p] {
|
||||||
|
let va = VirtualAddress::from_raw(rng.va(self.known(p)));
|
||||||
|
let _ = self
|
||||||
|
.k
|
||||||
|
.map_region(id, self.pids[p], va, PagePerms::ReadWrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
8 if who == 0 && rng.below(8) == 0 => {
|
||||||
|
let victim = 1 + rng.below(PROCS - 1);
|
||||||
|
let stale = self.caps[victim];
|
||||||
|
self.k.handle_syscall(Syscall::Kill { process: stale });
|
||||||
|
self.rebirths += 1;
|
||||||
|
|
||||||
|
// Re-create it; the pid slot is reused.
|
||||||
|
let fresh = match self.k.handle_syscall(Syscall::CreateProcess {
|
||||||
|
create_cap: CapSlot::new(0).unwrap(),
|
||||||
|
image: DataRef::empty(),
|
||||||
|
}) {
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Slot(s)) => s,
|
||||||
|
_ => panic!("re-create process"),
|
||||||
|
};
|
||||||
|
assert_eq!(self.k.process_of_cap_for_test(fresh), self.pids[victim]);
|
||||||
|
self.tids[victim] = self.k.spawn_thread_for_test(
|
||||||
|
self.pids[victim],
|
||||||
|
Priority::DEFAULT,
|
||||||
|
TimeSlice::DEFAULT,
|
||||||
|
);
|
||||||
|
self.known_len[victim] = 0;
|
||||||
|
|
||||||
|
// The old cap names the same pid but must reach nothing.
|
||||||
|
let src = rng.va(known);
|
||||||
|
let outcome = self.k.handle_syscall(Syscall::MapPage {
|
||||||
|
process: stale,
|
||||||
|
src_va: VirtualAddress::from_raw(src),
|
||||||
|
dst_va: VirtualAddress::from_raw(0x10000),
|
||||||
|
executable: false,
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
matches!(outcome, SyscallOutcome::Complete(SyscallResult::Error(_))),
|
||||||
|
"stale process cap still works"
|
||||||
|
);
|
||||||
|
assert!(matches!(
|
||||||
|
self.k.handle_syscall(Syscall::Kill { process: stale }),
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(_))
|
||||||
|
));
|
||||||
|
self.k.handle_syscall(Syscall::DropCap { slot: stale });
|
||||||
|
self.caps[victim] = fresh;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) {
|
||||||
|
let k = &self.k;
|
||||||
|
let window = MockPlatform::USER_VA_START..MockPlatform::USER_VA_END;
|
||||||
|
|
||||||
|
// Every frame is either free or charged to exactly one quota.
|
||||||
|
let charged: usize = self.pids.iter().map(|&p| k.page_usage(p)).sum();
|
||||||
|
assert_eq!(
|
||||||
|
charged + k.free_page_count(),
|
||||||
|
k.frames.capacity(),
|
||||||
|
"frames leaked or double counted"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, &pid) in self.pids.iter().enumerate() {
|
||||||
|
let frames_owned = k
|
||||||
|
.frames
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, u)| {
|
||||||
|
matches!(u, FrameUse::User { owner, .. } | FrameUse::PageTable { owner } if *owner == pid)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
let region_pages: usize = k
|
||||||
|
.regions
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, r)| r.owner() == pid && r.backing() == RegionBacking::Pool)
|
||||||
|
.map(|(_, r)| r.pages().value() as usize)
|
||||||
|
.sum();
|
||||||
|
assert_eq!(
|
||||||
|
k.page_usage(pid),
|
||||||
|
frames_owned + region_pages,
|
||||||
|
"quota of {pid:?} drifted"
|
||||||
|
);
|
||||||
|
if !self.alive[i] && i != 0 {
|
||||||
|
assert_eq!(k.page_usage(pid), 0);
|
||||||
|
assert_eq!(
|
||||||
|
k.platform.mapping_count(pid),
|
||||||
|
0,
|
||||||
|
"dead {pid:?} still mapped"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware -> kernel: every live mapping is one the kernel recorded.
|
||||||
|
for (pid, va, pa) in k.platform.mappings() {
|
||||||
|
assert!(
|
||||||
|
window.contains(&va),
|
||||||
|
"{pid:?} mapped outside window at {va:#x}"
|
||||||
|
);
|
||||||
|
let v = VirtualAddress::from_raw(va);
|
||||||
|
let as_user = k.frames.find_user(pid, v).map(|(p, _)| p.raw());
|
||||||
|
let as_region = k.regions.covering(pid, v).and_then(|id| {
|
||||||
|
let r = k.regions.get(id)?;
|
||||||
|
let m = r.mappings().find(|m| m.process == pid)?;
|
||||||
|
Some(r.base().raw() + (va - m.va.raw()))
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
as_user == Some(pa) || as_region == Some(pa),
|
||||||
|
"{pid:?} has unrecorded mapping {va:#x} -> {pa:#x}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel -> hardware: every recorded user page is really mapped.
|
||||||
|
for (pa, usage) in k.frames.iter() {
|
||||||
|
if let FrameUse::User { owner, page, .. } = usage {
|
||||||
|
assert_eq!(
|
||||||
|
k.platform.mapping(*owner, *page as usize * PAGE),
|
||||||
|
Some(pa.raw()),
|
||||||
|
"recorded page not mapped"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (_, r) in k.regions.iter() {
|
||||||
|
for m in r.mappings() {
|
||||||
|
for i in 0..r.pages().value() as usize {
|
||||||
|
assert_eq!(
|
||||||
|
k.platform.mapping(m.process, m.va.raw() + i * PAGE),
|
||||||
|
Some(r.base().raw() + i * PAGE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn memory_model_holds_under_random_hostile_operations() {
|
||||||
|
for seed in [1u64, 0xDEAD_BEEF, 0x1234_5678_9ABC, 42, 7777] {
|
||||||
|
let mut world = World::new();
|
||||||
|
let mut rng = Rng(seed);
|
||||||
|
for _ in 0..STEPS {
|
||||||
|
world.step(&mut rng);
|
||||||
|
world.check();
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
world.rebirths >= 5,
|
||||||
|
"seed {seed:#x} barely exercised kill/re-create"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user