128 lines
5.5 KiB
Zig
128 lines
5.5 KiB
Zig
//! 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;
|
|
}
|
|
|
|
/// 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, 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 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,
|
|
/// 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 };
|
|
}
|