refactor(userspace): split syscall/fat16/ext4 by concern; trim comments

This commit is contained in:
2026-10-27 13:21:53 +01:00
parent 99273db6d3
commit b4390b7b6d
25 changed files with 1283 additions and 1229 deletions

View File

@@ -1,8 +1,5 @@
//! Handing devices to drivers. Init holds every device's capabilities
//! (see `types::bootinfo`) and gives each driver its own over the driver's
//! endpoint, one capability per `Call`; the driver collects them before it
//! starts serving. Drivers then map their MMIO and allocate DMA memory
//! themselves; nothing is set up for them behind their back.
//! Init hands each driver its device's capabilities, one per `Call` on the
//! driver's endpoint; drivers map MMIO and allocate DMA themselves.
use crate::syscall::{self, Received, Result};
use types::address::VirtualAddress;

View File

@@ -1,346 +0,0 @@
//! Thin wrappers over the kernel's register ABI (docs/ipc.md). Every call
//! returns a status in `a0`; results follow in `a1..`.
use crate::arch;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount};
use types::id::ProcessId;
use types::mapping::PagePerms;
use types::permission::PermissionSet;
use types::syscall_abi::*;
use types::time::Ticks;
pub use types::syscall_abi::ErrorCode as Error;
pub type Result<T> = core::result::Result<T, Error>;
fn raw(nr: usize, args: [usize; 6]) -> Result<[usize; 8]> {
let r = arch::syscall(nr, args);
match r[0] {
0 => Ok(r),
code => Err(Error::from_raw(code).unwrap_or(Error::InvalidArgument)),
}
}
fn call0(nr: usize) -> Result<[usize; 8]> {
raw(nr, [0; 6])
}
fn slot_result(r: usize) -> Option<CapSlot> {
reg_slot(r).flatten()
}
/// What a receiving call got.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Received {
/// A `Send`. `badge` identifies the sender's capability.
Message {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// A `Call`; answer it with `reply` or `reply_recv`.
Call {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// Bits from a notification.
Signal { bits: usize },
}
impl Received {
fn decode(r: [usize; 8]) -> Result<Self> {
let d = regs_delivery(&r).ok_or(Error::InvalidArgument)?;
let (badge, words, cap) = (d.badge, d.words, d.cap);
Ok(match d.kind {
DeliveryKind::Message => Received::Message { badge, words, cap },
DeliveryKind::Call => Received::Call { badge, words, cap },
DeliveryKind::Signal => Received::Signal { bits: badge },
})
}
pub fn words(&self) -> [usize; 4] {
match self {
Received::Message { words, .. } | Received::Call { words, .. } => *words,
Received::Signal { .. } => [0; 4],
}
}
}
fn slot_of(r: Result<[usize; 8]>) -> Result<CapSlot> {
r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument))
}
// ---- threads, processes, time ----
pub fn exit() -> ! {
let _ = call0(SYS_EXIT);
loop {}
}
pub fn exit_thread() -> ! {
let _ = call0(SYS_EXIT_THREAD);
loop {}
}
pub fn yield_now() {
let _ = call0(SYS_YIELD);
}
/// Stops the machine. Needs a SystemControl capability; without one the
/// call fails and this spins, since there is nothing sensible to return.
pub fn halt(control: CapSlot) -> ! {
let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]);
loop {}
}
pub fn get_time() -> Ticks {
Ticks::from_raw(call0(SYS_GET_TIME).map_or(0, |r| r[1]) as u64)
}
pub fn get_process_id() -> ProcessId {
ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16)
}
pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
slot_of(raw(
SYS_CREATE_PROCESS,
[create_cap.value() as usize, 0, 0, 0, 0, 0],
))
.ok()
}
pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize {
raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1])
}
pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize {
raw(
SYS_SPAWN_THREAD_IN,
[
process_cap.value() as usize,
entry.raw(),
stack.raw(),
0,
0,
0,
],
)
.map_or(NONE, |r| r[1])
}
pub fn kill(process_cap: CapSlot) -> bool {
raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
// ---- memory ----
pub fn request_pages(count: PageCount) -> Option<VirtualAddress> {
raw(SYS_REQUEST_PAGES, [count.value() as usize, 0, 0, 0, 0, 0])
.ok()
.map(|r| VirtualAddress::from_raw(r[1]))
}
pub fn map_page(
process_cap: CapSlot,
src_va: VirtualAddress,
dst_va: VirtualAddress,
executable: bool,
) -> bool {
raw(
SYS_MAP_PAGE,
[
process_cap.value() as usize,
src_va.raw(),
dst_va.raw(),
executable as usize,
0,
0,
],
)
.is_ok()
}
/// Allocates `pages` of physically contiguous shared memory, charged to
/// this process, and returns a capability to it.
pub fn create_region(pages: PageCount) -> Result<CapSlot> {
slot_of(raw(
SYS_CREATE_REGION,
[pages.value() as usize, 0, 0, 0, 0, 0],
))
}
/// Maps the region behind `region` at `va`. The mapping lasts as long as
/// the capability: dropping or having it revoked unmaps it.
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> {
let perms = match perms {
PagePerms::ReadOnly => PERMS_READ_ONLY,
PagePerms::ReadWrite => PERMS_READ_WRITE,
PagePerms::ReadExecute => PERMS_READ_EXECUTE,
};
raw(
SYS_MAP_REGION,
[region.value() as usize, va.raw(), perms, 0, 0, 0],
)
.map(|_| ())
}
/// The physical base of the region behind `region`, for programming a
/// device's DMA. Pool regions are physically contiguous.
pub fn region_address(region: CapSlot) -> Result<usize> {
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
// ---- capabilities ----
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
raw(
SYS_DELEGATE,
[
slot.value() as usize,
to.value() as usize,
permissions.bits() as usize,
0,
0,
0,
],
)
.is_ok()
}
/// Copies an unbadged endpoint or notification capability with `badge`
/// and at most `permissions`.
pub fn mint(slot: CapSlot, badge: Badge, permissions: PermissionSet) -> Result<CapSlot> {
slot_of(raw(
SYS_MINT,
[
slot.value() as usize,
badge.raw(),
permissions.bits() as usize,
0,
0,
0,
],
))
}
/// Deletes every capability derived from `slot` (in any process) and keeps
/// `slot` itself.
pub fn revoke(slot: CapSlot) -> bool {
raw(SYS_REVOKE, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
/// Removes a capability from this process's own table, freeing the slot.
pub fn drop_cap(slot: CapSlot) -> bool {
raw(SYS_DROP_CAP, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
// ---- endpoints ----
pub fn create_endpoint() -> Option<CapSlot> {
slot_of(call0(SYS_CREATE_ENDPOINT)).ok()
}
pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(
SYS_SEND,
message_regs(endpoint.value() as usize, words, cap),
)
.map(|_| ())
}
pub fn receive_msg(endpoint: CapSlot) -> Result<Received> {
raw(SYS_RECEIVE, [endpoint.value() as usize, 0, 0, 0, 0, 0]).and_then(Received::decode)
}
/// Sends and waits for the reply.
pub fn call(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_CALL,
message_regs(endpoint.value() as usize, words, cap),
)
.and_then(Received::decode)
}
/// Calls a server with four words and returns its reply's words. Needs
/// only Execute on the endpoint; the reply comes back through the
/// kernel's reply slot, so it can never reach another client.
pub fn request(endpoint: CapSlot, words: [usize; 4]) -> Result<[usize; 4]> {
match call(endpoint, words, None)? {
Received::Signal { .. } => Err(Error::InvalidArgument),
reply => Ok(reply.words()),
}
}
/// Answers the last `Call` this thread received.
pub fn reply(words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ())
}
/// Replies to the pending `Call` (if any), then receives the next message.
pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_REPLY_RECV,
message_regs(endpoint.value() as usize, words, cap),
)
.and_then(Received::decode)
}
// ---- notifications and interrupts ----
pub fn create_notification() -> Result<CapSlot> {
slot_of(call0(SYS_CREATE_NOTIFICATION))
}
pub fn signal(notification: CapSlot) -> Result<()> {
raw(SYS_SIGNAL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|_| ())
}
/// Blocks until the notification has bits, then returns and clears them.
pub fn wait(notification: CapSlot) -> Result<usize> {
raw(SYS_WAIT, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// Returns and clears the bits without blocking.
pub fn poll(notification: CapSlot) -> Result<usize> {
raw(SYS_POLL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// After this, `receive` also returns when the notification is signalled.
pub fn bind_notification(notification: CapSlot) -> Result<()> {
raw(
SYS_BIND_NOTIFICATION,
[notification.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
}
/// Delivers interrupt `irq` as a signal on `notification`.
pub fn set_irq_notification(irq: CapSlot, notification: CapSlot) -> Result<()> {
raw(
SYS_SET_IRQ_NOTIFICATION,
[
irq.value() as usize,
notification.value() as usize,
0,
0,
0,
0,
],
)
.map(|_| ())
}
pub fn ack_interrupt(irq: CapSlot) -> Result<()> {
raw(
SYS_ACKNOWLEDGE_INTERRUPT,
[irq.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
}
pub fn debug_putchar(byte: u8) {
let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]);
}

View File

@@ -0,0 +1,51 @@
//! Capability management.
use super::{raw, slot_of, Result};
use types::badge::Badge;
use types::bounded::CapSlot;
use types::permission::PermissionSet;
use types::syscall_abi::*;
// ---- capabilities ----
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
raw(
SYS_DELEGATE,
[
slot.value() as usize,
to.value() as usize,
permissions.bits() as usize,
0,
0,
0,
],
)
.is_ok()
}
/// Copies an unbadged endpoint or notification capability with `badge`
/// and at most `permissions`.
pub fn mint(slot: CapSlot, badge: Badge, permissions: PermissionSet) -> Result<CapSlot> {
slot_of(raw(
SYS_MINT,
[
slot.value() as usize,
badge.raw(),
permissions.bits() as usize,
0,
0,
0,
],
))
}
/// Deletes every capability derived from `slot` (in any process) and keeps
/// `slot` itself.
pub fn revoke(slot: CapSlot) -> bool {
raw(SYS_REVOKE, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
/// Removes a capability from this process's own table, freeing the slot.
pub fn drop_cap(slot: CapSlot) -> bool {
raw(SYS_DROP_CAP, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}

View File

@@ -0,0 +1,94 @@
//! Endpoints and message passing.
use super::{call0, raw, slot_of, Error, Result};
use types::bounded::CapSlot;
use types::syscall_abi::*;
/// What a receiving call got.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Received {
/// A `Send`. `badge` identifies the sender's capability.
Message {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// A `Call`; answer it with `reply` or `reply_recv`.
Call {
badge: usize,
words: [usize; 4],
cap: Option<CapSlot>,
},
/// Bits from a notification.
Signal { bits: usize },
}
impl Received {
fn decode(r: [usize; 8]) -> Result<Self> {
let d = regs_delivery(&r).ok_or(Error::InvalidArgument)?;
let (badge, words, cap) = (d.badge, d.words, d.cap);
Ok(match d.kind {
DeliveryKind::Message => Received::Message { badge, words, cap },
DeliveryKind::Call => Received::Call { badge, words, cap },
DeliveryKind::Signal => Received::Signal { bits: badge },
})
}
pub fn words(&self) -> [usize; 4] {
match self {
Received::Message { words, .. } | Received::Call { words, .. } => *words,
Received::Signal { .. } => [0; 4],
}
}
}
// ---- endpoints ----
pub fn create_endpoint() -> Option<CapSlot> {
slot_of(call0(SYS_CREATE_ENDPOINT)).ok()
}
pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(
SYS_SEND,
message_regs(endpoint.value() as usize, words, cap),
)
.map(|_| ())
}
pub fn receive_msg(endpoint: CapSlot) -> Result<Received> {
raw(SYS_RECEIVE, [endpoint.value() as usize, 0, 0, 0, 0, 0]).and_then(Received::decode)
}
/// Sends and waits for the reply.
pub fn call(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_CALL,
message_regs(endpoint.value() as usize, words, cap),
)
.and_then(Received::decode)
}
/// Calls a server with four words and returns its reply's words. Needs
/// only Execute on the endpoint; the reply comes back through the
/// kernel's reply slot, so it can never reach another client.
pub fn request(endpoint: CapSlot, words: [usize; 4]) -> Result<[usize; 4]> {
match call(endpoint, words, None)? {
Received::Signal { .. } => Err(Error::InvalidArgument),
reply => Ok(reply.words()),
}
}
/// Answers the last `Call` this thread received.
pub fn reply(words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ())
}
/// Replies to the pending `Call` (if any), then receives the next message.
pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<Received> {
raw(
SYS_REPLY_RECV,
message_regs(endpoint.value() as usize, words, cap),
)
.and_then(Received::decode)
}

View File

@@ -0,0 +1,65 @@
//! Pages and shared regions.
use super::{raw, slot_of, Result};
use types::address::VirtualAddress;
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
use types::syscall_abi::*;
// ---- memory ----
pub fn request_pages(count: PageCount) -> Option<VirtualAddress> {
raw(SYS_REQUEST_PAGES, [count.value() as usize, 0, 0, 0, 0, 0])
.ok()
.map(|r| VirtualAddress::from_raw(r[1]))
}
pub fn map_page(
process_cap: CapSlot,
src_va: VirtualAddress,
dst_va: VirtualAddress,
executable: bool,
) -> bool {
raw(
SYS_MAP_PAGE,
[
process_cap.value() as usize,
src_va.raw(),
dst_va.raw(),
executable as usize,
0,
0,
],
)
.is_ok()
}
/// Allocates `pages` of physically contiguous shared memory, charged to
/// this process, and returns a capability to it.
pub fn create_region(pages: PageCount) -> Result<CapSlot> {
slot_of(raw(
SYS_CREATE_REGION,
[pages.value() as usize, 0, 0, 0, 0, 0],
))
}
/// Maps the region behind `region` at `va`. The mapping lasts as long as
/// the capability: dropping or having it revoked unmaps it.
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> {
let perms = match perms {
PagePerms::ReadOnly => PERMS_READ_ONLY,
PagePerms::ReadWrite => PERMS_READ_WRITE,
PagePerms::ReadExecute => PERMS_READ_EXECUTE,
};
raw(
SYS_MAP_REGION,
[region.value() as usize, va.raw(), perms, 0, 0, 0],
)
.map(|_| ())
}
/// The physical base of the region behind `region`, for programming a
/// device's DMA. Pool regions are physically contiguous.
pub fn region_address(region: CapSlot) -> Result<usize> {
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}

View File

@@ -0,0 +1,42 @@
//! Thin wrappers over the kernel's register ABI (docs/ipc.md). Every call
//! returns a status in `a0`; results follow in `a1..`.
mod caps;
mod ipc;
mod memory;
mod notify;
mod process;
pub use caps::*;
pub use ipc::*;
pub use memory::*;
pub use notify::*;
pub use process::*;
use crate::arch;
use types::bounded::CapSlot;
use types::syscall_abi::*;
pub use types::syscall_abi::ErrorCode as Error;
pub type Result<T> = core::result::Result<T, Error>;
fn raw(nr: usize, args: [usize; 6]) -> Result<[usize; 8]> {
let r = arch::syscall(nr, args);
match r[0] {
0 => Ok(r),
code => Err(Error::from_raw(code).unwrap_or(Error::InvalidArgument)),
}
}
fn call0(nr: usize) -> Result<[usize; 8]> {
raw(nr, [0; 6])
}
fn slot_result(r: usize) -> Option<CapSlot> {
reg_slot(r).flatten()
}
fn slot_of(r: Result<[usize; 8]>) -> Result<CapSlot> {
r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument))
}

View File

@@ -0,0 +1,58 @@
//! Notifications and interrupts.
use super::{call0, raw, slot_of, Result};
use types::bounded::CapSlot;
use types::syscall_abi::*;
// ---- notifications and interrupts ----
pub fn create_notification() -> Result<CapSlot> {
slot_of(call0(SYS_CREATE_NOTIFICATION))
}
pub fn signal(notification: CapSlot) -> Result<()> {
raw(SYS_SIGNAL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|_| ())
}
/// Blocks until the notification has bits, then returns and clears them.
pub fn wait(notification: CapSlot) -> Result<usize> {
raw(SYS_WAIT, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// Returns and clears the bits without blocking.
pub fn poll(notification: CapSlot) -> Result<usize> {
raw(SYS_POLL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
}
/// After this, `receive` also returns when the notification is signalled.
pub fn bind_notification(notification: CapSlot) -> Result<()> {
raw(
SYS_BIND_NOTIFICATION,
[notification.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
}
/// Delivers interrupt `irq` as a signal on `notification`.
pub fn set_irq_notification(irq: CapSlot, notification: CapSlot) -> Result<()> {
raw(
SYS_SET_IRQ_NOTIFICATION,
[
irq.value() as usize,
notification.value() as usize,
0,
0,
0,
0,
],
)
.map(|_| ())
}
pub fn ack_interrupt(irq: CapSlot) -> Result<()> {
raw(
SYS_ACKNOWLEDGE_INTERRUPT,
[irq.value() as usize, 0, 0, 0, 0, 0],
)
.map(|_| ())
}

View File

@@ -0,0 +1,74 @@
//! Threads, processes, time and debug output.
use super::{call0, raw, slot_of};
use types::address::{EntryPoint, StackPointer};
use types::bounded::CapSlot;
use types::id::ProcessId;
use types::syscall_abi::*;
use types::time::Ticks;
// ---- threads, processes, time ----
pub fn exit() -> ! {
let _ = call0(SYS_EXIT);
loop {}
}
pub fn exit_thread() -> ! {
let _ = call0(SYS_EXIT_THREAD);
loop {}
}
pub fn yield_now() {
let _ = call0(SYS_YIELD);
}
/// Stops the machine. Needs a SystemControl capability; without one the
/// call fails and this spins, since there is nothing sensible to return.
pub fn halt(control: CapSlot) -> ! {
let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]);
loop {}
}
pub fn get_time() -> Ticks {
Ticks::from_raw(call0(SYS_GET_TIME).map_or(0, |r| r[1]) as u64)
}
pub fn get_process_id() -> ProcessId {
ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16)
}
pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
slot_of(raw(
SYS_CREATE_PROCESS,
[create_cap.value() as usize, 0, 0, 0, 0, 0],
))
.ok()
}
pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize {
raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1])
}
pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize {
raw(
SYS_SPAWN_THREAD_IN,
[
process_cap.value() as usize,
entry.raw(),
stack.raw(),
0,
0,
0,
],
)
.map_or(NONE, |r| r[1])
}
pub fn kill(process_cap: CapSlot) -> bool {
raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok()
}
pub fn debug_putchar(byte: u8) {
let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]);
}