//! Typed system_call surface for user space — thin wrappers over the raw `system_call` //! stubs, one per kernel call. Numbers come from `abi.SystemCall`, the single //! source of truth shared with the kernel dispatcher. const std = @import("std"); const abi = @import("abi"); const sc = @import("system-call.zig"); /// `mmap` protection flags (matching the usual C bit values). Grants are always /// readable+writable today; the kernel does not yet honour finer prot. pub const PROT_READ: usize = abi.prot_read; pub const PROT_WRITE: usize = abi.prot_write; pub const PROT_EXEC: usize = abi.prot_exec; /// One `processes` entry — re-exported from the shared ABI so a user program can /// declare its snapshot buffer without importing `abi` itself. pub const ProcessDescriptor = abi.ProcessDescriptor; /// Give up the rest of this quantum. pub fn yield() void { _ = sc.systemCall0(.yield); } /// Write raw bytes to the kernel log (a bring-up diagnostic; real output goes /// through the console/VFS later). Returns the byte count, or a wrapped -1. pub fn write(message: []const u8) usize { return sc.systemCall2(.debug_write, @intFromPtr(message.ptr), message.len); } /// Block the caller for `ms` milliseconds. pub fn sleep(ms: usize) void { _ = sc.systemCall1(.sleep, ms); } /// Arm a one-shot timer: after `ms` milliseconds the kernel posts a timer /// notification (`ipc.Received.isTimer`) to `endpoint`. The timed wait of /// docs/process-lifecycle.md — a service arms a deadline and keeps serving, /// instead of blocking in sleep; what stop-sequence escalation, hello deadlines, /// and restart backoff are built from. pub fn timerOnce(endpoint: usize, ms: u64) bool { return sc.systemCall2(.timer_bind, endpoint, ms) == 0; } /// Monotonic nanoseconds since boot — a time source for timeouts and short delays. It /// only ever moves forward. This is *not* wall-clock time (no date, no timezone — that /// is a user-space service layered on top). Deadline pattern for a bounded poll loop: /// /// const deadline = clock() + timeout_ns; /// while (clock() < deadline) { ... } pub fn clock() u64 { return @intCast(sc.systemCall0(.clock)); } /// Copy bytes out of the kernel's in-memory diagnostic log — the accumulated /// stream of everything `write` (and the kernel itself) has emitted — starting at /// `offset`, into `out`. Returns the number of bytes copied (0 at end of buffer). /// A program reads the whole log by looping from offset 0, advancing by the return /// value, until it gets 0. This is how the boot log is persisted to disk on a /// headless/real machine where serial output is otherwise lost. pub fn klogRead(offset: usize, out: []u8) usize { return sc.systemCall3(.klog_read, offset, @intFromPtr(out.ptr), out.len); } /// End the process. Never returns. pub fn exit(code: usize) noreturn { _ = sc.systemCall1(.exit, code); unreachable; // the kernel never returns from exit } /// Start the binary bundled in the initial-ramdisk under `name` as a new ring-3 /// process, returning the child's process id (or null on failure). The child's /// argv[0] is `name`, and the caller becomes its **supervisor** — the only process /// allowed to `kill` it. This is how a supervisor (the device manager) launches a /// driver it matched — danos-native, not POSIX (a spawn/exec family comes with the /// POSIX layer later). pub fn spawn(name: []const u8) ?u32 { return spawnSupervised(name, &.{}, null); } /// Like `spawn`, but hands the child command-line arguments: they arrive as /// argv[1..] on its System V entry stack (argv[0] is still `name`). pub fn spawnWithArguments(name: []const u8, arguments: []const []const u8) ?u32 { return spawnSupervised(name, arguments, null); } /// The full spawn: command-line arguments for the child, and an optional endpoint /// (a handle from `ipc.createIpcEndpoint`) the kernel notifies when the child ends /// — any way it ends: clean exit, fault, or `kill`. The notification arrives via /// `ipc.replyWait` as a badge with the child-exit bit set and the child's id in /// the low bits (`ipc.Received.isChildExit`/`childProcessId`), so one endpoint can /// supervise many children. Arguments are marshalled to the kernel as one /// NUL-separated blob; the combined arguments must fit `blob` (the kernel caps the /// blob at 256 bytes and argc at 8 anyway). Returns the child's process id, or /// null on failure. pub fn spawnSupervised(name: []const u8, arguments: []const []const u8, exit_endpoint: ?usize) ?u32 { var blob: [256]u8 = undefined; var len: usize = 0; for (arguments, 0..) |argument, i| { if (i != 0) { if (len >= blob.len) return null; blob[len] = 0; len += 1; } if (len + argument.len > blob.len) return null; @memcpy(blob[len..][0..argument.len], argument); len += argument.len; } const r = sc.systemCall5(.system_spawn, @intFromPtr(name.ptr), name.len, if (len == 0) 0 else @intFromPtr(&blob), len, exit_endpoint orelse abi.no_cap); if (r > ~@as(usize, 0) - 4095) return null; // a wrapped -errno return @intCast(r); } /// Snapshot the process table into `out` (up to its length) and return the total /// number of live processes — which may exceed `out.len`; call again with a larger /// buffer for the full listing. Kernel tasks are included, with an empty name. /// The primitive `ps` is built on. pub fn processes(out: []abi.ProcessDescriptor) usize { return sc.systemCall2(.process_enumerate, @intFromPtr(out.ptr), out.len); } /// Whether a process spawned under `name` (its argv[0]) is currently alive. pub fn isProcessRunning(name: []const u8) bool { var table: [32]ProcessDescriptor = undefined; const total = processes(&table); for (table[0..@min(total, table.len)]) |descriptor| { if (std.mem.eql(u8, descriptor.name[0..descriptor.name_length], name)) return true; } return false; } /// End process `id`. Only its supervisor — the process that spawned it — may; /// anyone else gets false, as does a stale or unknown id (ids are never reused). /// Delivery is prompt but asynchronous, like a signal: a target caught running on /// another core dies at its next system call or timer tick. True means the kill /// is accepted and irrevocable; the exit notification (if an endpoint was given /// at spawn) confirms completion. pub fn kill(id: u32) bool { return sc.systemCall1(.process_kill, id) == 0; } /// Grant `len` bytes (rounded up to whole pages) of fresh, zeroed, writable /// memory and return the base virtual address. On failure returns a value in the /// top page (see `mmapFailed`). The user heap grows through this call. pub fn mmap(len: usize, prot: usize) usize { return sc.systemCall2(.mmap, len, prot); } /// Release a range previously handed out by `mmap`. pub fn munmap(base: usize, len: usize) usize { return sc.systemCall2(.munmap, base, len); } /// Whether an `mmap` return value is an error (the kernel returns a wrapped /// -errno, which lands in the top page — no real grant base is ever that high). pub inline fn mmapFailed(ret: usize) bool { return ret > ~@as(usize, 0) - 4095; }