//! 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 createEndpoint() ?Handle { const r = sc.systemCall0(.create_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}; /// Send `message` to endpoint `h` and block until the server replies into `reply`. /// Returns the reply length. pub fn call(h: Handle, message: []const u8, reply: []u8) CallError!usize { const r = sc.systemCall5(.ipc_call, h, @intFromPtr(message.ptr), message.len, @intFromPtr(reply.ptr), reply.len); return if (failed(r)) error.Failed else r; } /// 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; /// The result of a `replyWait`: the request length and the sender's badge (a /// task id, or an IRQ notification if the high bit is set). pub const Received = struct { len: usize, badge: u64, /// True if this wake-up was a device interrupt, not a client request. A driver's /// 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; } /// The interrupt source (a GSI), meaningful only when `isNotification`. pub fn source(self: Received) u64 { return self.badge & ~notify_badge_bit; } }; /// Server side of IPC_ReplyWait: deliver `reply` to the client last received (if /// any), then block until the next request arrives in `receive`. Returns its length /// and the sender badge. This system_call returns two values — the length in rax and /// the badge in rdx — so it needs a hand-written stub: rdx is a read-write /// operand (input = reply length, arg #3; output = badge). pub fn replyWait(h: Handle, reply: []const u8, receive: []u8) Received { var rax: usize = undefined; var rdx: usize = reply.len; // in: reply_len (arg #3 -> rdx); out: badge asm volatile ("syscall" : [rax] "={rax}" (rax), [rdx] "+{rdx}" (rdx), : [n] "{rax}" (@intFromEnum(abi.SystemCall.ipc_reply_wait)), [a0] "{rdi}" (h), [a1] "{rsi}" (@intFromPtr(reply.ptr)), [a3] "{r10}" (@intFromPtr(receive.ptr)), [a4] "{r8}" (receive.len), : .{ .rcx = true, .r11 = true, .memory = true }); return .{ .len = rax, .badge = rdx }; }