M13: IPC capability passing
ipc_call and ipc_reply_wait grow a `send_cap` argument (r9) and a `received_cap` return (r8): an endpoint travels alongside a message, installed into the receiver's handle table. The transfer is a share, not a move — the endpoint's refcount is bumped and the sender keeps its handle. If the receiver's table is full the call fails -ENOSPC and the message is NOT delivered (a half-delivered capability is worse than a failed send); a bad handle fails -EBADF. Both directions carry a cap: a client's call hands one to the server (seen in the server's replyWait), and the server's reply hands one back (seen in the client's call return). This is the "open" primitive the driver model was blocked on: a bus driver mints a per-device endpoint and hands it to a class driver, giving it a private channel to one device without the 8-slot global name registry. Kernel: shareCapability in ipc-synchronous.zig at both copy points; new setSystemCallResult3 (r8, saved/restored by the syscall stub); Task gains ipc_send_cap / ipc_received_cap. Runtime: callCap + Reply, replyWait gains send_cap and Received.cap; plain call/replyWait delegate with no_cap. New abi.no_cap. New ipc-cap test (two kernel tasks exercise both directions, each verifying the endpoint it received is the same object shared, refcount bumped to 2). No class driver consumes callCap yet — it lands with the first one. Suite 37/37 plus host tests.
This commit is contained in:
@@ -157,10 +157,28 @@ pub fn copyFromUser(user_as: u64, user_va: u64, destination: []u8) bool {
|
||||
|
||||
// --- the two IPC operations -------------------------------------------------
|
||||
|
||||
/// Share the capability named by handle `cap` in `from`'s table into `to`'s table,
|
||||
/// bumping the endpoint's refcount (the sender keeps its handle — this is a copy, not
|
||||
/// a move). Returns the handle it landed at in `to` (>= 0), or `-EBADF` if `cap` names
|
||||
/// no live handle, or `-ENOSPC` if `to`'s table is full. Callers only invoke this when
|
||||
/// `cap != no_cap`. Used by both IPC directions to carry an endpoint with a message.
|
||||
fn shareCapability(from: *Task, to: *Task, cap: u64) i64 {
|
||||
const endpoint = resolveHandle(from, cap) orelse return -EBADF;
|
||||
endpoint.refcount += 1;
|
||||
const handle = installHandle(to, endpoint);
|
||||
if (handle < 0) {
|
||||
dropRef(endpoint); // undo the bump; the receiver had no room
|
||||
return -ENOSPC;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// Client side of IPC_Call: send `[message_ptr, message_len)` to `endpoint` and block until a
|
||||
/// server replies into `[reply_ptr, reply_cap)`. Returns the reply length, or a
|
||||
/// negative errno. Runs as the current task.
|
||||
pub fn call(endpoint: *Endpoint, message_ptr: u64, message_len: u64, reply_ptr: u64, reply_cap: u64) i64 {
|
||||
/// negative errno. `send_cap` (a handle, or `no_cap`) is an endpoint transferred to the
|
||||
/// server with the request; `out_received_cap` receives the handle of an endpoint the
|
||||
/// server sent back in its reply, or `no_cap`. Runs as the current task.
|
||||
pub fn call(endpoint: *Endpoint, message_ptr: u64, message_len: u64, reply_ptr: u64, reply_cap: u64, send_cap: u64, out_received_cap: *u64) i64 {
|
||||
if (message_len > MESSAGE_MAXIMUM or reply_cap > MESSAGE_MAXIMUM) return -E2BIG;
|
||||
const flags = sync.enter();
|
||||
defer sync.leave(flags);
|
||||
@@ -170,12 +188,15 @@ pub fn call(endpoint: *Endpoint, message_ptr: u64, message_len: u64, reply_ptr:
|
||||
me.ipc_send_len = message_len;
|
||||
me.ipc_reply_ptr = reply_ptr;
|
||||
me.ipc_reply_cap = reply_cap;
|
||||
me.ipc_send_cap = send_cap;
|
||||
me.ipc_received_cap = abi.no_cap;
|
||||
me.ipc_status = 0;
|
||||
|
||||
enqueueSender(endpoint, me); // join the FIFO, then...
|
||||
scheduler.wakeLocked(&endpoint.receive_wait_queue); // ...wake a waiting server (no-op if none)
|
||||
scheduler.blockCurrentLocked(); // block until the reply readies us again
|
||||
|
||||
out_received_cap.* = me.ipc_received_cap; // a capability the replier sent back, or no_cap
|
||||
return me.ipc_status; // reply length or -errno, written by the replier
|
||||
}
|
||||
|
||||
@@ -185,21 +206,33 @@ pub fn call(endpoint: *Endpoint, message_ptr: u64, message_len: u64, reply_ptr:
|
||||
/// to `out_badge` and returns the request length, or a negative errno. A pending
|
||||
/// notification is delivered ahead of client requests (length 0, badge with
|
||||
/// `notify_badge_bit` set, no reply owed).
|
||||
pub fn replyWait(endpoint: *Endpoint, reply_ptr: u64, reply_len: u64, receive_ptr: u64, receive_cap: u64, out_badge: *u64) i64 {
|
||||
pub fn replyWait(endpoint: *Endpoint, reply_ptr: u64, reply_len: u64, receive_ptr: u64, receive_cap: u64, send_cap: u64, out_badge: *u64, out_received_cap: *u64) i64 {
|
||||
if (reply_len > MESSAGE_MAXIMUM or receive_cap > MESSAGE_MAXIMUM) return -E2BIG;
|
||||
const flags = sync.enter();
|
||||
defer sync.leave(flags);
|
||||
|
||||
const me = scheduler.current();
|
||||
out_received_cap.* = abi.no_cap; // no capability received unless a request delivers one
|
||||
|
||||
// (1) Reply to the client we're still holding, if any.
|
||||
// (1) Reply to the client we're still holding, if any — carrying `send_cap` to it.
|
||||
if (me.ipc_client) |client| {
|
||||
me.ipc_client = null;
|
||||
const n = @min(reply_len, client.ipc_reply_cap);
|
||||
if (copyAcross(me.aspace, reply_ptr, client.aspace, client.ipc_reply_ptr, n)) {
|
||||
client.ipc_status = @intCast(n);
|
||||
} else {
|
||||
client.ipc_received_cap = abi.no_cap;
|
||||
if (!copyAcross(me.aspace, reply_ptr, client.aspace, client.ipc_reply_ptr, n)) {
|
||||
client.ipc_status = -EFAULT;
|
||||
} else if (send_cap != abi.no_cap) {
|
||||
// Transfer the reply's capability into the client. A failure fails the
|
||||
// client's `call` rather than delivering a reply without its promised cap.
|
||||
const shared = shareCapability(me, client, send_cap);
|
||||
if (shared < 0) {
|
||||
client.ipc_status = shared; // -EBADF (bad handle) or -ENOSPC (client table full)
|
||||
} else {
|
||||
client.ipc_received_cap = @intCast(shared);
|
||||
client.ipc_status = @intCast(n);
|
||||
}
|
||||
} else {
|
||||
client.ipc_status = @intCast(n);
|
||||
}
|
||||
scheduler.readyLocked(client); // its `call` now returns
|
||||
}
|
||||
@@ -208,7 +241,7 @@ pub fn replyWait(endpoint: *Endpoint, reply_ptr: u64, reply_len: u64, receive_pt
|
||||
while (true) {
|
||||
if (popNotify(endpoint)) |badge| {
|
||||
out_badge.* = badge | notify_badge_bit;
|
||||
return 0; // notification: no payload, no reply owed
|
||||
return 0; // notification: no payload, no reply owed, no cap
|
||||
}
|
||||
if (dequeueSender(endpoint)) |caller| {
|
||||
const n = @min(caller.ipc_send_len, receive_cap);
|
||||
@@ -217,6 +250,17 @@ pub fn replyWait(endpoint: *Endpoint, reply_ptr: u64, reply_len: u64, receive_pt
|
||||
scheduler.readyLocked(caller);
|
||||
continue;
|
||||
}
|
||||
// Install the capability the caller sent, if any, into my table. A failure
|
||||
// fails the caller's `call` and does not deliver — no half-delivered cap.
|
||||
if (caller.ipc_send_cap != abi.no_cap) {
|
||||
const shared = shareCapability(caller, me, caller.ipc_send_cap);
|
||||
if (shared < 0) {
|
||||
caller.ipc_status = shared; // -EBADF or -ENOSPC
|
||||
scheduler.readyLocked(caller);
|
||||
continue;
|
||||
}
|
||||
out_received_cap.* = @intCast(shared);
|
||||
}
|
||||
me.ipc_client = caller; // remember who to reply to
|
||||
out_badge.* = caller.id;
|
||||
return @intCast(n);
|
||||
|
||||
Reference in New Issue
Block a user