61 lines
3.7 KiB
Zig
61 lines
3.7 KiB
Zig
//! The **kernel ↔ user** ABI: the core contract every user program speaks to the
|
|
//! kernel — the system_call numbers, `mmap` protection flags, the page size those
|
|
//! calls work in, and the IPC name-registry ids and notification bit. Shared by the
|
|
//! kernel dispatcher (system/kernel/process.zig) and the user runtime library
|
|
//! (library/runtime/), so the two can never drift.
|
|
//!
|
|
//! This is the *core* ABI; the device half — `DeviceDescriptor` and friends, which
|
|
//! also cross this boundary — lives with the device sub-project as [[device-abi]]
|
|
//! (system/devices/device-abi.zig). The loader↔kernel handoff is [[boot-handoff]].
|
|
|
|
/// Page size every `mmap`/`munmap` grant and the boot memory map are measured in.
|
|
/// 4 KiB on every architecture danos targets so far. Part of the ABI because user
|
|
/// code aligns to it (grants are page-granular) and the kernel guarantees it.
|
|
pub const page_size = 4096;
|
|
|
|
/// The kernel system_call numbers — the single source of truth shared by the kernel
|
|
/// dispatcher (system/kernel/process.zig) and the user runtime library, so the two
|
|
/// can never drift. The set is deliberately microkernel-minimal: file/device I/O
|
|
/// is not here — it lives in user-space servers reached through the IPC calls.
|
|
/// The table grows one milestone at a time; see docs/syscall.md.
|
|
pub const SystemCall = enum(u64) {
|
|
exit = 0, // exit(code): end the calling process
|
|
yield = 1, // yield(): give up the rest of this quantum
|
|
debug_write = 2, // debug_write(ptr, len): raw bytes to the kernel log (bring-up only)
|
|
sleep = 3, // sleep(ms): block the caller for ms milliseconds
|
|
mmap = 4, // mmap(len, prot) -> base: grant zeroed, page-aligned user pages
|
|
munmap = 5, // munmap(base, len): release pages from a prior mmap
|
|
create_endpoint = 6, // create_endpoint() -> handle: a new IPC endpoint
|
|
ipc_register = 7, // ipc_register(service_id, handle): publish an endpoint by well-known id
|
|
ipc_lookup = 8, // ipc_lookup(service_id) -> handle: find a published endpoint
|
|
ipc_call = 9, // ipc_call(h, message, len, reply, cap) -> reply_len: send + block for reply
|
|
ipc_reply_wait = 10, // ipc_reply_wait(h, reply, len, receive, cap) -> receive_len (+badge in rdx)
|
|
device_enumerate = 11, // device_enumerate(buffer, maximum) -> count: snapshot the device table
|
|
device_claim = 12, // device_claim(id) -> ok: take exclusive ownership of a device
|
|
mmio_map = 13, // mmio_map(id, resource_index) -> vaddr: map a claimed device's MMIO into this AS
|
|
irq_bind = 14, // irq_bind(id, resource_index, endpoint): deliver a device IRQ as an IPC notification
|
|
irq_ack = 15, // irq_ack(id, resource_index): re-arm a bound IRQ after servicing it
|
|
device_register = 16, // device_register(parent_id, descriptor) -> id: publish a child of a device you claimed
|
|
_,
|
|
};
|
|
|
|
/// Set in the badge returned by `ipc_reply_wait` when what arrived is an
|
|
/// **asynchronous notification** (today: a device interrupt bound with `irq_bind`)
|
|
/// rather than a message from a client. There is no payload and no reply owed; the
|
|
/// low bits carry the source, a GSI. Shared so the kernel's ISR and the driver's
|
|
/// event loop can't disagree about which bit means "the hardware spoke".
|
|
pub const notify_badge_bit: u64 = 1 << 63;
|
|
|
|
/// Well-known IPC service ids for the bootstrap name registry (create_endpoint +
|
|
/// ipc_register/ipc_lookup). Small integers, so no string interning is needed
|
|
/// during bring-up. The VFS server registers under `vfs`; clients look it up.
|
|
pub const ServiceId = enum(u32) {
|
|
vfs = 1,
|
|
_,
|
|
};
|
|
|
|
/// Protection flags for `mmap` (matching the usual C bit values).
|
|
pub const prot_read: u64 = 1;
|
|
pub const prot_write: u64 = 2;
|
|
pub const prot_exec: u64 = 4;
|