//! User-space IPC helpers over the kernel's synchronous IPC syscalls. A client //! `call`s an endpoint (send + block for reply); the VFS server and drivers are //! reached this way. The server side (`replyWait`, which returns two values) is //! added with the first server binary. const abi = @import("abi"); const sc = @import("system-call.zig"); /// A small-int handle into the calling process's handle table. pub const Handle = usize; /// A fixed-size, register-friendly message payload. Server protocols (VFS, driver) /// layer their own wire format on top of the bytes a call carries. pub const Message = extern struct { tag: u64 = 0, a: u64 = 0, b: u64 = 0, c: u64 = 0, }; /// Whether a system_call return value is a wrapped -errno (lands in the top page). inline fn failed(r: usize) bool { return r > ~@as(usize, 0) - 4095; } /// Create a new endpoint owned by this process; returns its handle. pub fn createIpcEndpoint() ?Handle { const r = sc.systemCall0(.create_ipc_endpoint); return if (failed(r)) null else r; } /// Publish endpoint `h` under a well-known service id so other processes find it. pub fn register(id: abi.ServiceId, h: Handle) bool { return !failed(sc.systemCall2(.ipc_register, @intFromEnum(id), h)); } /// Find the endpoint published under `id`, installing a handle to it in this /// process. pub fn lookup(id: abi.ServiceId) ?Handle { const r = sc.systemCall1(.ipc_lookup, @intFromEnum(id)); return if (failed(r)) null else r; } pub const CallError = error{Failed}; /// The result of a capability-passing `callCap`: the reply length, and the handle of /// an endpoint the server sent back (e.g. a per-device channel), or null. pub const Reply = struct { len: usize, cap: ?Handle, }; /// Send `message` to endpoint `h` and block until the server replies into `reply`, /// optionally handing the server a capability (`send_cap`) and receiving one back. /// This is the class-driver "open" primitive: call a bus with `send_cap = null`, get a /// private per-device endpoint back in `.cap`. Two return values (reply length in rax, /// received handle in r8) need a hand-written stub — r8 is read-write (in: reply /// capacity, arg #4; out: the received handle). pub fn callCap(h: Handle, message: []const u8, reply: []u8, send_cap: ?Handle) CallError!Reply { var rax: usize = undefined; var r8: usize = reply.len; // in: reply capacity (arg #4); out: received capability handle asm volatile ("syscall" : [rax] "={rax}" (rax), [r8] "+{r8}" (r8), : [n] "{rax}" (@intFromEnum(abi.SystemCall.ipc_call)), [a0] "{rdi}" (h), [a1] "{rsi}" (@intFromPtr(message.ptr)), [a2] "{rdx}" (message.len), [a3] "{r10}" (@intFromPtr(reply.ptr)), [a5] "{r9}" (send_cap orelse abi.no_cap), : .{ .rcx = true, .r11 = true, .memory = true }); if (failed(rax)) return error.Failed; return .{ .len = rax, .cap = if (r8 == abi.no_cap) null else r8 }; } /// Send `message` to endpoint `h` and block until the server replies into `reply`. /// Returns the reply length. The common case: no capability passed either way. pub fn call(h: Handle, message: []const u8, reply: []u8) CallError!usize { return (try callCap(h, message, reply, null)).len; } /// Post `message` to endpoint `h`'s asynchronous queue and return immediately — no /// rendezvous, no reply, no blocking. The receiver picks it up through `replyWait` as a /// buffered message (`Received.isMessage`). Unlike `call`, this **cannot hang on a dead /// or slow peer**, which is why a broadcaster (the input service) delivers events this /// way. The payload must fit an endpoint slot (64 bytes); a full queue drops the oldest /// message. Returns false on failure (bad handle, oversized payload, bad buffer). pub fn send(h: Handle, message: []const u8) bool { return !failed(sc.systemCall3(.ipc_send, h, @intFromPtr(message.ptr), message.len)); } /// Set in `Received.badge` when what arrived is an asynchronous notification — a /// bound device interrupt — rather than a client's message. The low bits carry the /// GSI. See `isNotification`. pub const notify_badge_bit: u64 = abi.notify_badge_bit; /// Set alongside `notify_badge_bit` when the notification is a **signal** — the /// lifecycle vocabulary of docs/process-lifecycle.md, delivered to the endpoint /// nominated with `process.bindSignals`. Decode with `process.signalsFrom`. pub const notify_signal_bit: u64 = abi.notify_signal_bit; /// Set alongside `notify_badge_bit` when the notification is a **one-shot timer** /// landing (`system.timerOnce`). pub const notify_timer_bit: u64 = abi.notify_timer_bit; /// Set alongside `notify_badge_bit` when the notification is a **child-exit /// notice** — a process this one spawned (with an exit endpoint) has ended — /// rather than a device interrupt. The low bits carry the child's process id. pub const notify_exit_bit: u64 = abi.notify_exit_bit; /// Set alongside `notify_badge_bit` when the wake-up is a **buffered message** — a payload /// posted with `send` (`ipc_send`) — rather than a bare device interrupt or child-exit /// notice. The payload is in the `replyWait` receive buffer (`Received.len` bytes); the /// low bits of the badge carry the sender's task id. See `Received.isMessage`. pub const notify_message_bit: u64 = abi.notify_message_bit; /// The result of a `replyWait`: the request length, the sender's badge (a task id, or /// an IRQ notification if the high bit is set), and any capability the request carried. pub const Received = struct { len: usize, badge: u64, cap: ?Handle, /// True if this wake-up was an asynchronous notification (a device interrupt /// or a child-exit notice), not a client request. An event loop branches on /// this; there is no reply owed on the notification path. pub fn isNotification(self: Received) bool { return self.badge & notify_badge_bit != 0; } /// True if this wake-up tells of a supervised child's end — the notification /// requested by passing an exit endpoint to `system.spawnSupervised`. pub fn isChildExit(self: Received) bool { return self.isNotification() and self.badge & notify_exit_bit != 0; } /// True if this wake-up is a **buffered message** posted with `send` (`ipc_send`): /// there is a payload in the receive buffer (`self.len` bytes) and no reply is owed. /// The subscriber side of a broadcast branches on this. pub fn isMessage(self: Received) bool { return self.isNotification() and self.badge & notify_message_bit != 0; } /// The task id of whoever posted a buffered message, meaningful only when /// Whether this arrival is a signal notification — decode the set with /// `process.signalsFrom(badge)`. pub fn isSignal(self: Received) bool { return self.isNotification() and self.badge & notify_signal_bit != 0; } /// Whether this arrival is a one-shot timer landing (`system.timerOnce`). pub fn isTimer(self: Received) bool { return self.isNotification() and self.badge & notify_timer_bit != 0; } /// `isMessage`. (The badge's low bits, with the three high marker bits masked off.) pub fn senderTaskId(self: Received) u32 { return @intCast(self.badge & ~(notify_badge_bit | notify_exit_bit | notify_message_bit)); } /// The interrupt source (a GSI), meaningful only when `isNotification` and /// not `isChildExit`. pub fn source(self: Received) u64 { return self.badge & ~notify_badge_bit; } /// The ended child's process id, meaningful only when `isChildExit`. pub fn childProcessId(self: Received) u32 { return @intCast(self.badge & ~(notify_badge_bit | notify_exit_bit)); } }; /// Server side of IPC_ReplyWait: deliver `reply` to the client last received (if any, /// optionally handing it `send_cap`), then block until the next request arrives in /// `receive`. Returns its length, the sender badge, and any capability the request /// carried (in `.cap`). Three return values — length in rax, badge in rdx, received /// handle in r8 — so it needs a hand-written stub: rdx is read-write (in: reply length, /// arg #3; out: badge) and r8 is read-write (in: receive capacity, arg #4; out: handle). pub fn replyWait(h: Handle, reply: []const u8, receive: []u8, send_cap: ?Handle) Received { var rax: usize = undefined; var rdx: usize = reply.len; // in: reply_len (arg #3); out: badge var r8: usize = receive.len; // in: receive capacity (arg #4); out: received capability handle asm volatile ("syscall" : [rax] "={rax}" (rax), [rdx] "+{rdx}" (rdx), [r8] "+{r8}" (r8), : [n] "{rax}" (@intFromEnum(abi.SystemCall.ipc_reply_wait)), [a0] "{rdi}" (h), [a1] "{rsi}" (@intFromPtr(reply.ptr)), [a3] "{r10}" (@intFromPtr(receive.ptr)), [a5] "{r9}" (send_cap orelse abi.no_cap), : .{ .rcx = true, .r11 = true, .memory = true }); return .{ .len = rax, .badge = rdx, .cap = if (r8 == abi.no_cap) null else r8 }; }