M11–M12: IRQ-as-IPC and bus drivers; expand names tree-wide

Two driver-model milestones plus a tree-wide naming pass. Suite 35/35
(QEMU) + host tests green.

M11 — IRQ-as-IPC. A ring-3 driver now sleeps until its device interrupts
it. New src/kernel/irq.zig: per-GSI endpoint bindings, comptime per-vector
trampolines, dispatch = mask GSI -> LAPIC EOI -> notifyLocked, all under one
lock region. irq_bind/irq_ack syscalls, gated by the device claim like
mmio_map. interruptDispatch no longer EOIs — each handler owns its EOI,
because a level line must be masked before it is acknowledged (irq_ack is
the unmask). Bindings are keyed on the owning task and released on exit
(a shared endpoint's siblings survive). hpetd rewritten interrupt-driven.
Tests: hpet (rewritten, reads back the I/O APIC routing) and irqfree.

M12 — bus drivers. DeviceDesc gains a parent, making the device table a
tree. dev_register (device_register) lets a process publish children below
a device it claimed; the kernel enforces resource containment (a child's
resources must nest in its parent's), so a descriptor can't fabricate a
window over kernel RAM. Descriptor copied in via copyFromUser (physmap
walk — an unmapped user pointer fails the call instead of faulting the
kernel). Per-parent child cap bounds table exhaustion. sbin/busd.zig is a
worked bus driver. Test: bus.

Naming — per docs/coding-standards.md: non-acronym abbreviations spelled
out (message, descriptor, device_service, scheduler, runtime, physical,
interpreter, ...); acronyms kept (IPC, MMIO, DMA, HCD, ...); files are
kebab-case (ipc-synchronous.zig, device-service.zig, vfs-protocol.zig, ...).
Exceptions: POSIX/C ABI names and Zig idioms (init/len/ptr) kept. Module
collisions resolved by specific naming (config -> parameters, device.zig
alias -> device_model). AML op/Op disambiguated: op = opcode, Op =
operation; per-opcode parse handlers renamed opX -> parseX.

New driver docs: drivers.md, driver-model.md (bus/class/HCD shapes + the
proposed M13–M16 ABI), coding-standards.md.
This commit is contained in:
Daniel Samson
2026-07-10 11:39:56 +01:00
parent 83881641ca
commit 15b70856c9
63 changed files with 4722 additions and 2690 deletions
+28 -28
View File
@@ -1,16 +1,16 @@
//! /sbin/vfs — the user-space VFS server. Shipped in the initrd, spawned as a
//! ring-3 process, and reached by every other process through IPC (the `rt`
//! ring-3 process, and reached by every other process through IPC (the `runtime`
//! file API marshals open/read/write/stat/close into calls to this server's
//! endpoint, published under the well-known `vfs` service id).
//!
//! For now the namespace is a small in-memory ramfs (opening a name creates it):
//! enough to prove the whole path — client file API -> IPC -> server dispatch ->
//! reply. Device nodes backed by user-space drivers (/dev) layer on top in M10,
//! where `open` on a /dev name forwards to the owning driver's endpoint.
//! reply. Device nodes backed by user-space drivers (/device) layer on top in M10,
//! where `open` on a /device name forwards to the owning driver's endpoint.
const std = @import("std");
const rt = @import("rt");
const proto = rt.vfsproto;
const runtime = @import("runtime");
const protocol = runtime.vfs_protocol;
const Node = struct {
used: bool = false,
@@ -54,11 +54,11 @@ fn openAt(id: u64) ?*OpenFile {
}
/// Serialise a reply header + payload into `out`; returns the total length.
fn writeReply(out: []u8, reply: proto.Reply, payload: []const u8) usize {
@memcpy(out[0..proto.reply_size], std.mem.asBytes(&reply));
const n = @min(payload.len, out.len - proto.reply_size);
@memcpy(out[proto.reply_size..][0..n], payload[0..n]);
return proto.reply_size + n;
fn writeReply(out: []u8, reply: protocol.Reply, payload: []const u8) usize {
@memcpy(out[0..protocol.reply_size], std.mem.asBytes(&reply));
const n = @min(payload.len, out.len - protocol.reply_size);
@memcpy(out[protocol.reply_size..][0..n], payload[0..n]);
return protocol.reply_size + n;
}
fn fail(out: []u8) usize {
@@ -66,10 +66,10 @@ fn fail(out: []u8) usize {
}
/// Handle one request; write the reply into `out`, return its length.
fn handle(msg: []const u8, out: []u8) usize {
if (msg.len < proto.req_size) return fail(out);
const req = std.mem.bytesToValue(proto.Request, msg[0..proto.req_size]);
const payload = msg[proto.req_size..];
fn handle(message: []const u8, out: []u8) usize {
if (message.len < protocol.req_size) return fail(out);
const req = std.mem.bytesToValue(protocol.Request, message[0..protocol.req_size]);
const payload = message[protocol.req_size..];
switch (req.op) {
.open => {
@@ -88,7 +88,7 @@ fn handle(msg: []const u8, out: []u8) usize {
const nd = &nodes[of.node];
const off: usize = @intCast(req.offset);
if (off >= nd.size) return writeReply(out, .{ .status = 0, .len = 0 }, &.{}); // EOF
const n = @min(@min(nd.size - off, req.len), proto.max_payload);
const n = @min(@min(nd.size - off, req.len), protocol.maximum_payload);
return writeReply(out, .{ .status = 0, .len = @intCast(n) }, nd.data[off .. off + n]);
},
.write => {
@@ -103,8 +103,8 @@ fn handle(msg: []const u8, out: []u8) usize {
},
.stat => {
const of = openAt(req.node) orelse return fail(out);
const st = proto.Stat{ .size = nodes[of.node].size, .kind = 0 };
return writeReply(out, .{ .status = 0, .len = @sizeOf(proto.Stat) }, std.mem.asBytes(&st));
const st = protocol.Stat{ .size = nodes[of.node].size, .kind = 0 };
return writeReply(out, .{ .status = 0, .len = @sizeOf(protocol.Stat) }, std.mem.asBytes(&st));
},
.close => {
if (req.node < opens.len) opens[@intCast(req.node)].used = false;
@@ -114,27 +114,27 @@ fn handle(msg: []const u8, out: []u8) usize {
}
pub fn main() void {
const ep = rt.ipc.createEndpoint() orelse {
_ = rt.sys.write("vfs: no endpoint\n");
const endpoint = runtime.ipc.createEndpoint() orelse {
_ = runtime.system.write("vfs: no endpoint\n");
return;
};
if (!rt.ipc.register(.vfs, ep)) {
_ = rt.sys.write("vfs: register failed\n");
if (!runtime.ipc.register(.vfs, endpoint)) {
_ = runtime.system.write("vfs: register failed\n");
return;
}
_ = rt.sys.write("vfs: ready\n");
_ = runtime.system.write("vfs: ready\n");
var reply_buf: [proto.msg_max]u8 = undefined;
var reply_buffer: [protocol.message_maximum]u8 = undefined;
var reply_len: usize = 0;
var recv: [proto.msg_max]u8 = undefined;
var receive: [protocol.message_maximum]u8 = undefined;
while (true) {
const got = rt.ipc.replyWait(ep, reply_buf[0..reply_len], &recv);
const got = runtime.ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive);
// Ignore notifications (none expected here); handle a request.
reply_len = handle(recv[0..got.len], &reply_buf);
reply_len = handle(receive[0..got.len], &reply_buffer);
}
}
pub const panic = rt.panic;
pub const panic = runtime.panic;
comptime {
_ = &rt.start._start;
_ = &runtime.start._start;
}