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:
+81
-2
@@ -82,6 +82,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
ipcTest();
|
||||
} else if (eql(case, "ipc-call")) {
|
||||
ipcCallTest();
|
||||
} else if (eql(case, "ipc-cap")) {
|
||||
capabilityTest();
|
||||
} else if (eql(case, "smp")) {
|
||||
smpTest();
|
||||
} else if (eql(case, "affinity")) {
|
||||
@@ -808,9 +810,10 @@ fn ipcServer() void {
|
||||
var reply_buffer: [8]u8 = undefined;
|
||||
var reply_len: u64 = 0;
|
||||
var badge: u64 = 0;
|
||||
var received_cap: u64 = abi.no_cap;
|
||||
while (true) {
|
||||
var receive: [8]u8 = undefined;
|
||||
const n = ipcsync.replyWait(ipc_endpoint, @intFromPtr(&reply_buffer), reply_len, @intFromPtr(&receive), receive.len, &badge);
|
||||
const n = ipcsync.replyWait(ipc_endpoint, @intFromPtr(&reply_buffer), reply_len, @intFromPtr(&receive), receive.len, abi.no_cap, &badge, &received_cap);
|
||||
if (n < 0) scheduler.exit();
|
||||
const v = std.mem.readInt(u64, receive[0..8], .little);
|
||||
std.mem.writeInt(u64, reply_buffer[0..8], v + 1, .little);
|
||||
@@ -826,7 +829,8 @@ fn ipcClient() void {
|
||||
var message: [8]u8 = undefined;
|
||||
std.mem.writeInt(u64, message[0..8], i, .little);
|
||||
var reply: [8]u8 = undefined;
|
||||
const n = ipcsync.call(ipc_endpoint, @intFromPtr(&message), 8, @intFromPtr(&reply), reply.len);
|
||||
var received_cap: u64 = abi.no_cap;
|
||||
const n = ipcsync.call(ipc_endpoint, @intFromPtr(&message), 8, @intFromPtr(&reply), reply.len, abi.no_cap, &received_cap);
|
||||
if (n != 8 or std.mem.readInt(u64, reply[0..8], .little) != i + 1) ok = false;
|
||||
}
|
||||
ipc_replies_ok = ok;
|
||||
@@ -856,6 +860,81 @@ fn ipcCallTest() void {
|
||||
result();
|
||||
}
|
||||
|
||||
// --- IPC capability passing (M13) -------------------------------------------
|
||||
|
||||
var cap_endpoint: *ipcsync.Endpoint = undefined;
|
||||
var cap_ep_x: *ipcsync.Endpoint = undefined; // client mints, sends to the server
|
||||
var cap_ep_y: *ipcsync.Endpoint = undefined; // server mints, sends back to the client
|
||||
var cap_server_got_x: bool = false;
|
||||
var cap_client_got_y: bool = false;
|
||||
var cap_done: bool = false;
|
||||
|
||||
/// Server half of the "open" pattern: receive one request carrying a capability,
|
||||
/// verify it, then reply handing back a capability of its own.
|
||||
fn capServer() void {
|
||||
const me = scheduler.current();
|
||||
cap_ep_y = ipcsync.createEndpoint().?;
|
||||
const h_y = ipcsync.installHandle(me, cap_ep_y); // the handle to send back in the reply
|
||||
|
||||
var reply_buffer: [8]u8 = .{0} ** 8;
|
||||
var receive: [8]u8 = undefined;
|
||||
var badge: u64 = 0;
|
||||
var received: u64 = abi.no_cap;
|
||||
|
||||
// Phase 1: no reply owed yet — receive the client's request, which carries ep_x.
|
||||
_ = ipcsync.replyWait(cap_endpoint, @intFromPtr(&reply_buffer), 0, @intFromPtr(&receive), receive.len, abi.no_cap, &badge, &received);
|
||||
cap_server_got_x = received != abi.no_cap and
|
||||
ipcsync.resolveHandle(me, received) == cap_ep_x and
|
||||
cap_ep_x.refcount == 2; // shared (client's handle + this one), not moved
|
||||
|
||||
// Phase 2: reply to the held client, handing it ep_y; then block for a next
|
||||
// request that never comes (so this replyWait does not return).
|
||||
_ = ipcsync.replyWait(cap_endpoint, @intFromPtr(&reply_buffer), 8, @intFromPtr(&receive), receive.len, @intCast(h_y), &badge, &received);
|
||||
scheduler.exit();
|
||||
}
|
||||
|
||||
/// Client half of "open": mint a capability, send it in a call, receive one back.
|
||||
fn capClient() void {
|
||||
const me = scheduler.current();
|
||||
cap_ep_x = ipcsync.createEndpoint().?;
|
||||
const h_x = ipcsync.installHandle(me, cap_ep_x);
|
||||
|
||||
var message: [8]u8 = .{0} ** 8;
|
||||
var reply: [8]u8 = undefined;
|
||||
var received: u64 = abi.no_cap;
|
||||
_ = ipcsync.call(cap_endpoint, @intFromPtr(&message), 8, @intFromPtr(&reply), reply.len, @intCast(h_x), &received);
|
||||
cap_client_got_y = received != abi.no_cap and
|
||||
ipcsync.resolveHandle(me, received) == cap_ep_y and
|
||||
cap_ep_y.refcount == 2;
|
||||
|
||||
cap_done = true;
|
||||
scheduler.exit();
|
||||
}
|
||||
|
||||
/// IPC capability passing: a client hands the server an endpoint in a `call`, and the
|
||||
/// server hands one back in its reply — the primitive that lets a bus driver give a
|
||||
/// class driver a private channel to one device (M13). Two kernel tasks (no user ELF);
|
||||
/// each verifies the endpoint it received is the *same* object the peer sent (resolves
|
||||
/// equal) and was *shared*, not moved (refcount bumped to 2).
|
||||
fn capabilityTest() void {
|
||||
log("DANOS-TEST-BEGIN: ipc-cap\n", .{});
|
||||
cap_endpoint = ipcsync.createEndpoint().?;
|
||||
cap_server_got_x = false;
|
||||
cap_client_got_y = false;
|
||||
cap_done = false;
|
||||
scheduler.spawn(capServer, 5);
|
||||
scheduler.spawn(capClient, 5);
|
||||
|
||||
const done: *volatile bool = &cap_done;
|
||||
var spins: u64 = 0;
|
||||
while (!done.* and spins < 100_000_000) : (spins += 1) scheduler.yield();
|
||||
|
||||
check("capability test completed", cap_done);
|
||||
check("server received the client's endpoint (same object, shared not moved)", cap_server_got_x);
|
||||
check("client received the server's endpoint back (same object, shared not moved)", cap_client_got_y);
|
||||
result();
|
||||
}
|
||||
|
||||
var proc_worker_run: bool = true;
|
||||
var proc_worker_ran: bool = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user