Post-reorg cleanup: POSIX layer, and naming fixes
Follow-up to the monorepo re-org. Suite 35/35 plus host tests green. POSIX compatibility is now its own library, library/posix/ (unistd, stdio), layered strictly over the runtime — it calls the runtime's IPC/heap, never system calls directly. The runtime is now POSIX-free (the danos-native application ABI). The VFS wire protocol is danos-native throughout (Stat -> FileStatus, .stat -> .status, O_CREAT -> create); the POSIX layer maps the POSIX spellings at the boundary. The coding standard's ABI-name exception is scoped to one place: a file is allowed POSIX spellings only if it lives under library/posix/ — everywhere else, danos naming with no exception. Naming fixes, all mechanical: - initrd -> initial-ramdisk: the source file, the module, the tool (make-initial-ramdisk.py), the artifact (initial-ramdisk.img, including the bootloader's load path), and the identifiers. - system/kernel/device-service.zig -> devices-broker.zig: it is ring-0 kernel code (the trusted device table + claim capability), not a ring-3 service. The future user-space device *manager* (policy) will live in system/services/. - Dropped the daemon `d` suffix: hpetd -> hpet, busd -> bus. A driver lives in system/drivers/, so the folder already says what it is; encoding the role in the name too is redundant. The coding standard drops that exception. - system/devices/aml/interp.zig -> interpreter.zig (the type was already Interpreter).
This commit is contained in:
+53
-53
@@ -12,7 +12,7 @@
|
||||
const std = @import("std");
|
||||
const danos = @import("danos");
|
||||
const architecture = @import("architecture");
|
||||
const device_service = @import("device-service.zig");
|
||||
const devices_broker = @import("devices-broker.zig");
|
||||
const platform = @import("platform");
|
||||
const pmm = @import("pmm.zig");
|
||||
const heap = @import("heap.zig");
|
||||
@@ -22,7 +22,7 @@ const ipcsync = @import("ipc-synchronous.zig");
|
||||
const irq = @import("irq.zig");
|
||||
const sync = @import("sync.zig");
|
||||
const process = @import("process.zig");
|
||||
const initrd = @import("initrd");
|
||||
const initial_ramdisk = @import("initial-ramdisk");
|
||||
|
||||
/// Formatted write straight to serial, independent of the framebuffer console.
|
||||
fn log(comptime fmt: []const u8, args: anytype) void {
|
||||
@@ -108,8 +108,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
initTest(boot_information);
|
||||
} else if (eql(case, "process")) {
|
||||
processTest(boot_information);
|
||||
} else if (eql(case, "initrd")) {
|
||||
initrdTest(boot_information);
|
||||
} else if (eql(case, "initial-ramdisk")) {
|
||||
initialRamdiskTest(boot_information);
|
||||
} else if (eql(case, "vfs")) {
|
||||
vfsTest(boot_information);
|
||||
} else if (eql(case, "hpet")) {
|
||||
@@ -952,24 +952,24 @@ fn initTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// The initrd path: the bootloader handed over an image bundling extra user
|
||||
/// The initial_ramdisk path: the bootloader handed over an image bundling extra user
|
||||
/// binaries; parse it, spawn every program, and confirm one (the vfs stub)
|
||||
/// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline.
|
||||
fn initrdTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: initrd\n", .{});
|
||||
check("bootloader handed over an initrd", boot_information.initrd_len != 0);
|
||||
if (boot_information.initrd_len == 0) {
|
||||
fn initialRamdiskTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: initial_ramdisk\n", .{});
|
||||
check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
|
||||
const rd = initrd.Reader.init(image) orelse {
|
||||
check("initrd image is valid", false);
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
check("initrd image is valid", true);
|
||||
check("initrd contains at least one binary", rd.count >= 1);
|
||||
check("initial_ramdisk image is valid", true);
|
||||
check("initial_ramdisk contains at least one binary", rd.count >= 1);
|
||||
|
||||
process.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
@@ -981,7 +981,7 @@ fn initrdTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-INITRD-ERR: {s}: {s}\n", .{ item.name, @errorName(err) });
|
||||
}
|
||||
}
|
||||
check("every initrd binary spawned", spawned == rd.count);
|
||||
check("every initial_ramdisk binary spawned", spawned == rd.count);
|
||||
|
||||
// Wait for the spawned programs to run and make syscalls (they write + sleep).
|
||||
scheduler.setPriority(1);
|
||||
@@ -989,33 +989,33 @@ fn initrdTest(boot_information: *const BootInformation) void {
|
||||
while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield();
|
||||
scheduler.setPriority(4);
|
||||
|
||||
check("initrd processes ran and made syscalls (>=2)", process.write_count >= 2);
|
||||
check("initial_ramdisk processes ran and made syscalls (>=2)", process.write_count >= 2);
|
||||
check("syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||
result();
|
||||
}
|
||||
|
||||
/// The full VFS path: spawn the user-space VFS server and a client from the
|
||||
/// initrd. The client opens a file through the runtime file API, writes, seeks, reads
|
||||
/// initial_ramdisk. The client opens a file through the runtime file API, writes, seeks, reads
|
||||
/// it back, and — only if the round trip matched — heartbeats "vfstest: ok". So
|
||||
/// seeing that marker proves client open/write/read reached the server over IPC
|
||||
/// and came back correct. (The client retries until the server registers.)
|
||||
fn vfsTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: vfs\n", .{});
|
||||
if (boot_information.initrd_len == 0) {
|
||||
check("bootloader handed over an initrd", false);
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
|
||||
const rd = initrd.Reader.init(image) orelse {
|
||||
check("initrd image is valid", false);
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
process.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
// Spawn just the server and its client (other initrd binaries would write to
|
||||
// Spawn just the server and its client (other initial_ramdisk binaries would write to
|
||||
// the shared evidence buffer and confuse the marker check).
|
||||
_ = spawnNamed(rd, "vfs");
|
||||
_ = spawnNamed(rd, "vfs-test");
|
||||
@@ -1037,9 +1037,9 @@ fn vfsTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Spawn the initrd binary named `name` as a ring-3 process. Returns false if it
|
||||
/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it
|
||||
/// isn't in the image or fails to load.
|
||||
fn spawnNamed(rd: initrd.Reader, name: []const u8) bool {
|
||||
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
@@ -1051,36 +1051,36 @@ fn spawnNamed(rd: initrd.Reader, name: []const u8) bool {
|
||||
}
|
||||
|
||||
/// IO passthrough + IRQ-as-IPC: a user-space driver drives real hardware and is
|
||||
/// *woken by it*. Spawn hpetd, which claims the HPET, maps its registers into its
|
||||
/// *woken by it*. Spawn hpet, which claims the HPET, maps its registers into its
|
||||
/// own ring-3 address space, arms a level-triggered comparator, binds the interrupt
|
||||
/// to an IPC endpoint, and then blocks. It prints "hpetd: ok" only after being woken
|
||||
/// to an IPC endpoint, and then blocks. It prints "hpet: ok" only after being woken
|
||||
/// `target_ticks` times — it cannot reach that line by polling, because the loop's
|
||||
/// only exit is through `replyWait` returning a notification badge.
|
||||
///
|
||||
/// The interesting assertion is the last one, which doesn't trust hpetd at all: it
|
||||
/// The interesting assertion is the last one, which doesn't trust hpet at all: it
|
||||
/// reads the I/O APIC's redirection entry back and checks the kernel really routed
|
||||
/// the line (our vector, level-triggered) and really left it unmasked after the
|
||||
/// driver's final `irq_ack`. hpetd disables its comparator on the last interrupt, so
|
||||
/// driver's final `irq_ack`. hpet disables its comparator on the last interrupt, so
|
||||
/// that state is quiescent and not a race.
|
||||
fn hpetTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: hpet\n", .{});
|
||||
if (boot_information.initrd_len == 0) {
|
||||
check("bootloader handed over an initrd", false);
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
|
||||
const rd = initrd.Reader.init(image) orelse {
|
||||
check("initrd image is valid", false);
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
process.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
check("hpetd spawned from the initrd", spawnNamed(rd, "hpetd"));
|
||||
check("hpet spawned from the initial_ramdisk", spawnNamed(rd, "hpet"));
|
||||
|
||||
const prefix = "hpetd: ok";
|
||||
const prefix = "hpet: ok";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
while (architecture.millis() < deadline) {
|
||||
@@ -1114,7 +1114,7 @@ fn hpetRouteOk() bool {
|
||||
/// The GSI discovery recorded for the HPET, from the same device table the driver saw.
|
||||
fn hpetGsi() ?u32 {
|
||||
var buffer: [16]danos.DeviceDescriptor = undefined;
|
||||
const n = @min(device_service.enumerate(&buffer), buffer.len);
|
||||
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
|
||||
for (buffer[0..n]) |d| {
|
||||
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
|
||||
if (d.parent != danos.no_parent) continue; // the block, not a comparator child
|
||||
@@ -1130,34 +1130,34 @@ fn hpetGsi() ?u32 {
|
||||
/// them from the hardware, and publishes each as a child via `device_register` — the
|
||||
/// primitive a PCI bridge or USB hub driver is built from.
|
||||
///
|
||||
/// `busd` treats the HPET's register block as a bus and its comparators as children,
|
||||
/// `bus` treats the HPET's register block as a bus and its comparators as children,
|
||||
/// giving each a 0x20 sub-window. It checks its own work (children come back from the
|
||||
/// table with the right parent and a strictly narrower window) and, importantly, that
|
||||
/// the kernel **refuses** a child whose window escapes the parent's — without that,
|
||||
/// `device_register` would be a system_call for mapping arbitrary physical memory. It prints
|
||||
/// "busd: ok" only if all of that holds.
|
||||
/// "bus: ok" only if all of that holds.
|
||||
///
|
||||
/// The kernel-side check here is the one busd can't make: that the children really did
|
||||
/// The kernel-side check here is the one bus can't make: that the children really did
|
||||
/// land in the device table with the containment invariant intact.
|
||||
fn busTest(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: bus\n", .{});
|
||||
if (boot_information.initrd_len == 0) {
|
||||
check("bootloader handed over an initrd", false);
|
||||
if (boot_information.initial_ramdisk_len == 0) {
|
||||
check("bootloader handed over an initial_ramdisk", false);
|
||||
result();
|
||||
return;
|
||||
}
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initrd_base)))[0..boot_information.initrd_len];
|
||||
const rd = initrd.Reader.init(image) orelse {
|
||||
check("initrd image is valid", false);
|
||||
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse {
|
||||
check("initial_ramdisk image is valid", false);
|
||||
result();
|
||||
return;
|
||||
};
|
||||
|
||||
process.write_count = 0;
|
||||
process.write_from_user = false;
|
||||
check("busd spawned from the initrd", spawnNamed(rd, "busd"));
|
||||
check("bus spawned from the initial_ramdisk", spawnNamed(rd, "bus"));
|
||||
|
||||
const prefix = "busd: ok";
|
||||
const prefix = "bus: ok";
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
while (architecture.millis() < deadline) {
|
||||
@@ -1173,7 +1173,7 @@ fn busTest(boot_information: *const BootInformation) void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Every child `busd` registered must have each of its resources inside a parent
|
||||
/// Every child `bus` registered must have each of its resources inside a parent
|
||||
/// resource of the same kind — the invariant `device_register` exists to maintain,
|
||||
/// checked from the kernel's own table rather than the driver's word for it.
|
||||
///
|
||||
@@ -1182,7 +1182,7 @@ fn busTest(boot_information: *const BootInformation) void {
|
||||
/// bridge's `bus_range`, because a bus-number range isn't an address window.
|
||||
fn childrenContained() bool {
|
||||
var buffer: [64]danos.DeviceDescriptor = undefined;
|
||||
const n = @min(device_service.enumerate(&buffer), buffer.len);
|
||||
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
|
||||
|
||||
const bus_id = hpetDeviceId() orelse return false;
|
||||
const p = buffer[@intCast(bus_id)];
|
||||
@@ -1205,13 +1205,13 @@ fn childrenContained() bool {
|
||||
if (!ok) return false;
|
||||
}
|
||||
}
|
||||
return children > 0; // busd must have published at least one
|
||||
return children > 0; // bus must have published at least one
|
||||
}
|
||||
|
||||
/// Device id of the HPET (the bus busd claims), from the same table drivers see.
|
||||
/// Device id of the HPET (the bus bus claims), from the same table drivers see.
|
||||
fn hpetDeviceId() ?u64 {
|
||||
var buffer: [64]danos.DeviceDescriptor = undefined;
|
||||
const n = @min(device_service.enumerate(&buffer), buffer.len);
|
||||
const n = @min(devices_broker.enumerate(&buffer), buffer.len);
|
||||
for (buffer[0..n]) |d| {
|
||||
if (d.class != @intFromEnum(danos.DeviceClass.timer)) continue;
|
||||
if (d.parent != danos.no_parent) continue; // a comparator child, not the block
|
||||
@@ -1226,7 +1226,7 @@ fn hpetDeviceId() ?u64 {
|
||||
/// (so a dead driver's device goes quiet instead of storming) and the slot cleared
|
||||
/// (so an ISR never posts a notification into the endpoint that is about to be freed).
|
||||
///
|
||||
/// This is the path `hpetd` never takes — it runs forever — so it gets its own test.
|
||||
/// This is the path `hpet` never takes — it runs forever — so it gets its own test.
|
||||
/// Two properties, both read back from the hardware rather than from our own state:
|
||||
///
|
||||
/// 1. A bound GSI is routed and unmasked.
|
||||
|
||||
Reference in New Issue
Block a user