boot: the /system tree is the system image — loader-built ramdisk, spawn by path

Retire the build-time ramdisk packer and the packed initial-ramdisk.img.
make-fat-image.py now lays every user binary out at its FHS path on the
boot volume (system/services, system/drivers, system/tests), and the EFI
loader walks \system at boot, packing what it finds into an in-RAM v2
initial_ramdisk whose entry names are full FHS paths. init rides the
table like every other binary: its dedicated handoff fields are gone and
the kernel spawns PID 1 via the same lookup as everyone else
(process.spawnBundled).

system_spawn resolves names by exact path first, then unique basename,
and normalizes argv[0] to the stored path — so task names (and, next,
the tagged log ring's attribution) are honest binary paths everywhere.
initrd v2 rejects the old magic so a stale image fails loudly.

Groundwork for per-process logging (/var/log/<boot-stamp>/<binary-path>.log)
and the kernel-VFS /system mount.
This commit is contained in:
Daniel Samson
2026-07-21 14:47:26 +01:00
parent ec6e888076
commit 2d0858caf6
11 changed files with 413 additions and 324 deletions
+126 -43
View File
@@ -2,6 +2,7 @@ const std = @import("std");
const uefi = std.os.uefi; const uefi = std.os.uefi;
const elf = std.elf; const elf = std.elf;
const boot_handoff = @import("boot-handoff"); const boot_handoff = @import("boot-handoff");
const initial_ramdisk = @import("initial-ramdisk");
const build_options = @import("build_options"); const build_options = @import("build_options");
const BootInformation = boot_handoff.BootInformation; const BootInformation = boot_handoff.BootInformation;
const GraphicsOutput = uefi.protocol.GraphicsOutput; const GraphicsOutput = uefi.protocol.GraphicsOutput;
@@ -15,11 +16,11 @@ const MemoryMapSlice = uefi.tables.MemoryMapSlice;
/// The kernel image: /system/kernel. /// The kernel image: /system/kernel.
const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("system\\kernel"); const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("system\\kernel");
/// The init program: /system/services/init. /// The user binaries: everything under /system except the kernel itself. The
const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("system\\services\\init"); /// loader walks this tree and packs it into the in-RAM initial_ramdisk image —
/// the volume's file structure is the single source of truth (no packed image
/// The initial-ramdisk (the VFS server + drivers), in /boot. /// artifact on disk).
const initial_ramdisk_file_name = std.unicode.utf8ToUtf16LeStringLiteral("boot\\initial-ramdisk.img"); const system_directory_name = std.unicode.utf8ToUtf16LeStringLiteral("system");
/// Physical page size, and the sentinel UEFI uses to seek to end-of-file. /// Physical page size, and the sentinel UEFI uses to seek to end-of-file.
const page_size = 4096; const page_size = 4096;
@@ -64,20 +65,14 @@ fn boot() !noreturn {
const entry = try loadKernel(bs, &boot_information); const entry = try loadKernel(bs, &boot_information);
// Best effort: a volume without /system/services/init still boots (kernel-only). // Best effort: a volume without a /system tree of user binaries still boots
loadInit(bs, &boot_information) catch |err| { // (kernel-only). The tree — init included — becomes the initial_ramdisk.
log("EFI: no /system/services/init ("); loadSystemTree(bs, &boot_information) catch |err| {
log("EFI: no /system binaries (");
logBytes(@errorName(err)); logBytes(@errorName(err));
log(") - booting without user space\r\n"); log(") - booting without user space\r\n");
}; };
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
loadInitialRamdisk(bs, &boot_information) catch |err| {
log("EFI: no initial_ramdisk (");
logBytes(@errorName(err));
log(")\r\n");
};
// Build the page tables the kernel starts life on: identity + a physmap of // Build the page tables the kernel starts life on: identity + a physmap of
// low RAM, plus the higher-half kernel image once it links high. Allocated // low RAM, plus the higher-half kernel image once it links high. Allocated
// now, while boot services (and the memory map) are still stable — nothing // now, while boot services (and the memory map) are still stable — nothing
@@ -369,11 +364,28 @@ fn handoff(cr3: u64, entry: usize, boot_information: *const BootInformation) nor
unreachable; unreachable;
} }
/// Read a whole file off the boot volume into a pool buffer that outlives the // --- the /system tree -> initial_ramdisk ------------------------------------
/// loader. The buffer is deliberately NOT freed: it's LoaderData, which the
/// memory-map conversion classifies as reserved, so the kernel identity-maps it /// Cap on bundled binaries. Generous: the tree carries ~30 today.
/// and reads from there. Returns the buffer (pointer + length). const maximum_bundled = 64;
fn loadFile(bs: *uefi.tables.BootServices, name: [*:0]const u16) ![]u8 {
/// How deep the walk goes below /system ("/system/services/x" is depth 1).
const maximum_tree_depth = 3;
/// One binary discovered under /system: its FHS path (UTF-8, '/'-separated,
/// NUL-free) and its contents in a transient pool buffer.
const Bundled = struct {
path: [initial_ramdisk.maximum_name]u8,
path_len: usize,
data: []align(8) u8,
};
/// Walk the boot volume's /system tree and pack every regular file (except the
/// kernel image itself — the only top-level file) into an in-RAM v2
/// initial_ramdisk image, entries named by full FHS path. This is what makes the
/// volume's file structure the single source of truth: there is no packed
/// ramdisk artifact on disk, and init travels in the table like everything else.
fn loadSystemTree(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const loaded = (try bs.handleProtocol(uefi.protocol.LoadedImage, uefi.handle)) orelse const loaded = (try bs.handleProtocol(uefi.protocol.LoadedImage, uefi.handle)) orelse
return error.NoLoadedImage; return error.NoLoadedImage;
const device = loaded.device_handle orelse return error.NoBootDevice; const device = loaded.device_handle orelse return error.NoBootDevice;
@@ -383,40 +395,111 @@ fn loadFile(bs: *uefi.tables.BootServices, name: [*:0]const u16) ![]u8 {
const root = try fs.openVolume(); const root = try fs.openVolume();
defer _ = root.close() catch {}; defer _ = root.close() catch {};
const file = try root.open(name, .read, .{}); const system_directory = try root.open(system_directory_name, .read, .{});
defer _ = file.close() catch {}; defer _ = system_directory.close() catch {};
var list: [maximum_bundled]Bundled = undefined;
var count: usize = 0;
try walkDirectory(bs, system_directory, "/system", 0, &list, &count);
if (count == 0) return error.NoBinaries;
// Assemble the v2 image: header, entry table, then the blobs.
const table_end = @sizeOf(initial_ramdisk.Header) + count * @sizeOf(initial_ramdisk.Entry);
var total: usize = table_end;
for (list[0..count]) |e| total += e.data.len;
const image = try bs.allocatePool(.loader_data, total); // survives the handoff
std.mem.bytesAsValue(initial_ramdisk.Header, image[0..@sizeOf(initial_ramdisk.Header)]).* = .{
.magic = initial_ramdisk.magic,
.count = @intCast(count),
};
var offset: usize = table_end;
for (list[0..count], 0..) |e, i| {
var record = initial_ramdisk.Entry{ .name = @splat(0), .offset = offset, .len = e.data.len };
@memcpy(record.name[0..e.path_len], e.path[0..e.path_len]);
const slot = image[@sizeOf(initial_ramdisk.Header) + i * @sizeOf(initial_ramdisk.Entry) ..][0..@sizeOf(initial_ramdisk.Entry)];
std.mem.bytesAsValue(initial_ramdisk.Entry, slot).* = record;
@memcpy(image[offset..][0..e.data.len], e.data);
offset += e.data.len;
_ = bs.freePool(e.data.ptr) catch {};
}
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
boot_information.initial_ramdisk_len = total;
progress("EFI: /system tree loaded\r\n");
}
/// Recursively collect the regular files below `directory` into `list`. Top-level
/// files (depth 0) are skipped: the only one is /system/kernel, which loadKernel
/// has already consumed and which is not a spawnable user binary.
fn walkDirectory(
bs: *uefi.tables.BootServices,
directory: *uefi.protocol.File,
prefix: []const u8,
depth: usize,
list: *[maximum_bundled]Bundled,
count: *usize,
) !void {
// Each read() on a directory yields one EFI_FILE_INFO; zero bytes means done.
var info_buffer: [1024]u8 align(8) = undefined;
while (true) {
const n = try directory.read(&info_buffer);
if (n == 0) return;
const info: *const uefi.protocol.File.Info.File = @ptrCast(@alignCast(&info_buffer));
const name16 = info.getFileName();
// Convert the (ASCII in practice) UTF-16 name; skip "." and "..".
var name_buffer: [initial_ramdisk.maximum_name]u8 = undefined;
var name_length: usize = 0;
while (name16[name_length] != 0) : (name_length += 1) {
if (name_length == name_buffer.len) return error.NameTooLong;
const c = name16[name_length];
if (c > 0x7F) return error.UnsupportedName;
name_buffer[name_length] = @intCast(c);
}
const name = name_buffer[0..name_length];
if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
if (info.attribute.directory) {
if (depth == maximum_tree_depth) continue;
var child_prefix: [initial_ramdisk.maximum_name]u8 = undefined;
const child = try std.fmt.bufPrint(&child_prefix, "{s}/{s}", .{ prefix, name });
const child_directory = try directory.open(name16, .read, .{});
defer _ = child_directory.close() catch {};
try walkDirectory(bs, child_directory, child, depth + 1, list, count);
continue;
}
if (depth == 0) continue; // /system/kernel — already loaded, not bundled
if (count.* == maximum_bundled) return error.TooManyBinaries;
var entry: *Bundled = &list[count.*];
const path = try std.fmt.bufPrint(&entry.path, "{s}/{s}", .{ prefix, name });
entry.path_len = path.len;
const file = try directory.open(name16, .read, .{});
defer _ = file.close() catch {};
entry.data = try readWholeFile(bs, file);
count.* += 1;
}
}
/// Read an open file completely into a fresh pool buffer that survives the
/// handoff (LoaderData is classified reserved, so the kernel identity-maps it).
fn readWholeFile(bs: *uefi.tables.BootServices, file: *uefi.protocol.File) ![]align(8) u8 {
try file.setPosition(seek_end); try file.setPosition(seek_end);
const size: usize = @intCast(try file.getPosition()); const size: usize = @intCast(try file.getPosition());
try file.setPosition(0); try file.setPosition(0);
if (size == 0) return error.EmptyFile; if (size == 0) return error.EmptyFile;
const image = try bs.allocatePool(.loader_data, size); // survives the handoff const buffer = try bs.allocatePool(.loader_data, size);
var read_total: usize = 0; var read_total: usize = 0;
while (read_total < size) { while (read_total < size) {
const n = try file.read(image[read_total..]); const n = try file.read(buffer[read_total..]);
if (n == 0) return error.UnexpectedEof; if (n == 0) return error.UnexpectedEof;
read_total += n; read_total += n;
} }
return image[0..size]; return buffer[0..size];
}
/// Ferry the init program (/system/services/init) to the kernel. The kernel does the ELF
/// loading itself (into ring-3 mappings) — the loader just carries the bytes.
fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const image = try loadFile(bs, init_file_name);
boot_information.init_base = @intFromPtr(image.ptr);
boot_information.init_len = image.len;
progress("EFI: /system/services/init loaded\r\n");
}
/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void {
const image = try loadFile(bs, initial_ramdisk_file_name);
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
boot_information.initial_ramdisk_len = image.len;
progress("EFI: initial_ramdisk loaded\r\n");
} }
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and /// Validate the ELF, copy every PT_LOAD segment to its physical address, and
+73 -112
View File
@@ -221,18 +221,21 @@ fn addKernel(
return exe; return exe;
} }
/// Assemble the bootable FAT32 image (the in-repo Python builder) holding what /// One user binary and its FHS home on the boot volume (and in zig-out).
/// the firmware and loader need off the ESP: the EFI stub, `kernel`, `init`, and const BundledBinary = struct { path: []const u8, binary: std.Build.LazyPath };
/// the initial-ramdisk. Factored so the serial-enabled `run-x86-64` variant can
/// bundle its own serial kernel while sharing the loader, init, and ramdisk — all /// Assemble the bootable FAT32 image (the in-repo Python builder) holding the
/// built once per invocation (the loader's boot breadcrumbs and init's heartbeat /// EFI stub, the kernel, and every user binary at its FHS path — the volume's
/// both follow the top-level -Dserial). Returns the image's LazyPath. /// /system tree IS the system image; the EFI loader walks it at boot and builds
/// the in-RAM initial_ramdisk from it. Factored so the serial-enabled
/// `run-x86-64` variant can bundle its own serial kernel while sharing the
/// loader and user tree (the loader's boot breadcrumbs and init's heartbeat both
/// follow the top-level -Dserial). Returns the image's LazyPath.
fn addBootImage( fn addBootImage(
b: *std.Build, b: *std.Build,
kernel_bin: std.Build.LazyPath, kernel_bin: std.Build.LazyPath,
efi_bin: std.Build.LazyPath, efi_bin: std.Build.LazyPath,
init_bin: std.Build.LazyPath, bundled: []const BundledBinary,
initial_ramdisk_img: std.Build.LazyPath,
) std.Build.LazyPath { ) std.Build.LazyPath {
const mk_fat = b.addSystemCommand(&.{"python3"}); const mk_fat = b.addSystemCommand(&.{"python3"});
mk_fat.addFileArg(b.path("tools/make-fat-image.py")); mk_fat.addFileArg(b.path("tools/make-fat-image.py"));
@@ -242,10 +245,10 @@ fn addBootImage(
mk_fat.addFileArg(efi_bin); mk_fat.addFileArg(efi_bin);
mk_fat.addArg("system/kernel"); mk_fat.addArg("system/kernel");
mk_fat.addFileArg(kernel_bin); mk_fat.addFileArg(kernel_bin);
mk_fat.addArg("system/services/init"); for (bundled) |item| {
mk_fat.addFileArg(init_bin); mk_fat.addArg(item.path);
mk_fat.addArg("boot/initial-ramdisk.img"); mk_fat.addFileArg(item.binary);
mk_fat.addFileArg(initial_ramdisk_img); }
return fat_image; return fat_image;
} }
@@ -443,8 +446,9 @@ pub fn build(b: *std.Build) void {
}, },
}); });
// The initial_ramdisk container format, shared by the kernel (unpacks it) and the // The initial_ramdisk container format, shared by the kernel (unpacks it) and
// build-time packer tools/make-initial-ramdisk.py (produces it). No dependencies. // the EFI loader (packs it in RAM from the boot volume's /system tree). No
// dependencies.
const initial_ramdisk_module = b.addModule("initial-ramdisk", .{ const initial_ramdisk_module = b.addModule("initial-ramdisk", .{
.root_source_file = b.path("system/initial-ramdisk.zig"), .root_source_file = b.path("system/initial-ramdisk.zig"),
}); });
@@ -502,13 +506,11 @@ pub fn build(b: *std.Build) void {
const init_options = b.addOptions(); const init_options = b.addOptions();
init_options.addOption(bool, "serial", serial); init_options.addOption(bool, "serial", serial);
programModule(init_exe).addImport("build_options", init_options.createModule()); programModule(init_exe).addImport("build_options", init_options.createModule());
const init_install = b.addInstallArtifact(init_exe, .{ .dest_dir = .{ .override = .{ .custom = "system/services" } } });
b.getInstallStep().dependOn(&init_install.step);
// --- initial_ramdisk: a bundle of extra user binaries (VFS server + drivers) --- // --- the rest of the /system tree: services, drivers, test fixtures ---
// Each is built by the same user-binary recipe, then packed into one image by // Each is built by the same user-binary recipe and laid out at its FHS path on
// the host-side make-initial-ramdisk tool. The bootloader ferries the image to the kernel, // the boot volume (see `bundled` below). The EFI loader walks the tree at boot
// which unpacks it and spawns each program (system/initial-ramdisk.zig). // and hands the kernel an in-RAM initial_ramdisk of it (system/initial-ramdisk.zig).
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs", "system/services/vfs/vfs.zig"); const vfs_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs", "system/services/vfs/vfs.zig");
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs-test", "system/services/vfs/vfs-test.zig"); const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "vfs-test", "system/services/vfs/vfs-test.zig");
const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig"); const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig");
@@ -584,94 +586,51 @@ pub fn build(b: *std.Build) void {
// (docs/threading.md). Built threaded so its shared-memory poll is real. // (docs/threading.md). Built threaded so its shared-memory poll is real.
const thread_test_exe = addThreadedUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "thread-test", "system/services/thread-test/thread-test.zig"); const thread_test_exe = addThreadedUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "thread-test", "system/services/thread-test/thread-test.zig");
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool // Every user binary and its FHS home on the boot volume. There is no packed
// (the container format is trivial, and Python sidesteps std API churn). Args: // ramdisk artifact any more: make-fat-image.py lays each binary out at this
// make-initial-ramdisk.py <out> [<name> <file>]... — one name/file pair per binary. // path on the image, and the EFI loader walks /system at boot and builds the
const mk_run = b.addSystemCommand(&.{"python3"}); // in-RAM initial_ramdisk table from the tree — the volume's file structure is
mk_run.addFileArg(b.path("tools/make-initial-ramdisk.py")); // the single source of truth. Entry names (and hence argv[0] and task names)
const initial_ramdisk_img = mk_run.addOutputFileArg("initial-ramdisk.img"); // are these paths with a leading slash.
mk_run.addArg("vfs"); const bundled = [_]BundledBinary{
mk_run.addFileArg(vfs_exe.getEmittedBin()); .{ .path = "system/services/init", .binary = init_exe.getEmittedBin() },
mk_run.addArg("vfs-test"); .{ .path = "system/services/vfs", .binary = vfs_exe.getEmittedBin() },
mk_run.addFileArg(vfstest_exe.getEmittedBin()); .{ .path = "system/services/fat", .binary = fat_exe.getEmittedBin() },
mk_run.addArg("ps2-bus"); .{ .path = "system/services/display", .binary = display_exe.getEmittedBin() },
mk_run.addFileArg(ps2_bus_exe.getEmittedBin()); .{ .path = "system/services/display-demo", .binary = display_demo_exe.getEmittedBin() },
mk_run.addArg("ps2-keyboard"); .{ .path = "system/services/device-manager", .binary = device_manager_exe.getEmittedBin() },
mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin()); .{ .path = "system/services/input", .binary = input_exe.getEmittedBin() },
mk_run.addArg("ps2-mouse"); .{ .path = "system/services/discovery", .binary = discovery_exe.getEmittedBin() },
mk_run.addFileArg(ps2_mouse_exe.getEmittedBin()); .{ .path = "system/services/log-flush", .binary = log_flush_exe.getEmittedBin() },
mk_run.addArg("usb-xhci-bus"); .{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() },
mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin()); .{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() },
mk_run.addArg("usb-hid-keyboard"); .{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() },
mk_run.addFileArg(usb_hid_keyboard_exe.getEmittedBin()); .{ .path = "system/drivers/usb-xhci-bus", .binary = usb_xhci_bus_exe.getEmittedBin() },
mk_run.addArg("usb-hid-mouse"); .{ .path = "system/drivers/usb-hid-keyboard", .binary = usb_hid_keyboard_exe.getEmittedBin() },
mk_run.addFileArg(usb_hid_mouse_exe.getEmittedBin()); .{ .path = "system/drivers/usb-hid-mouse", .binary = usb_hid_mouse_exe.getEmittedBin() },
mk_run.addArg("usb-storage"); .{ .path = "system/drivers/usb-storage", .binary = usb_storage_exe.getEmittedBin() },
mk_run.addFileArg(usb_storage_exe.getEmittedBin()); .{ .path = "system/drivers/virtio-gpu", .binary = virtio_gpu_exe.getEmittedBin() },
mk_run.addArg("fat"); .{ .path = "system/drivers/pci-bus", .binary = pci_bus_exe.getEmittedBin() },
mk_run.addFileArg(fat_exe.getEmittedBin()); .{ .path = "system/tests/vfs-test", .binary = vfstest_exe.getEmittedBin() },
mk_run.addArg("fat-test"); .{ .path = "system/tests/fat-test", .binary = fat_test_exe.getEmittedBin() },
mk_run.addFileArg(fat_test_exe.getEmittedBin()); .{ .path = "system/tests/shm-server", .binary = shm_server_exe.getEmittedBin() },
mk_run.addArg("display"); .{ .path = "system/tests/shm-client", .binary = shm_client_exe.getEmittedBin() },
mk_run.addFileArg(display_exe.getEmittedBin()); .{ .path = "system/tests/crash-test", .binary = crash_test_exe.getEmittedBin() },
mk_run.addArg("display-demo"); .{ .path = "system/tests/device-list", .binary = device_list_exe.getEmittedBin() },
mk_run.addFileArg(display_demo_exe.getEmittedBin()); .{ .path = "system/tests/input-source", .binary = input_source_exe.getEmittedBin() },
mk_run.addArg("virtio-gpu"); .{ .path = "system/tests/input-test", .binary = input_test_exe.getEmittedBin() },
mk_run.addFileArg(virtio_gpu_exe.getEmittedBin()); .{ .path = "system/tests/args-echo", .binary = args_echo_exe.getEmittedBin() },
mk_run.addArg("shm-server"); .{ .path = "system/tests/process-test", .binary = process_test_exe.getEmittedBin() },
mk_run.addFileArg(shm_server_exe.getEmittedBin()); .{ .path = "system/tests/thread-test", .binary = thread_test_exe.getEmittedBin() },
mk_run.addArg("shm-client"); };
mk_run.addFileArg(shm_client_exe.getEmittedBin());
mk_run.addArg("pci-bus");
mk_run.addFileArg(pci_bus_exe.getEmittedBin());
mk_run.addArg("crash-test");
mk_run.addFileArg(crash_test_exe.getEmittedBin());
mk_run.addArg("thread-test");
mk_run.addFileArg(thread_test_exe.getEmittedBin());
mk_run.addArg("device-list");
mk_run.addFileArg(device_list_exe.getEmittedBin());
mk_run.addArg("discovery");
mk_run.addFileArg(discovery_exe.getEmittedBin());
mk_run.addArg("device-manager");
mk_run.addFileArg(device_manager_exe.getEmittedBin());
mk_run.addArg("input");
mk_run.addFileArg(input_exe.getEmittedBin());
mk_run.addArg("input-source");
mk_run.addFileArg(input_source_exe.getEmittedBin());
mk_run.addArg("input-test");
mk_run.addFileArg(input_test_exe.getEmittedBin());
mk_run.addArg("args-echo");
mk_run.addFileArg(args_echo_exe.getEmittedBin());
mk_run.addArg("process-test");
mk_run.addFileArg(process_test_exe.getEmittedBin());
mk_run.addArg("log-flush");
mk_run.addFileArg(log_flush_exe.getEmittedBin());
// Also install the packed binaries to their FHS homes, so zig-out is a true image // Install every bundled binary to its FHS home, so zig-out is a true image of
// of the filesystem — even though at boot they arrive inside the initial-ramdisk. // the filesystem — the same tree make-fat-image.py lays out on the boot volume.
for ([_]struct { *std.Build.Step.Compile, []const u8 }{ for (bundled) |item| {
.{ vfs_exe, "system/services" }, const install = b.addInstallFileWithDir(item.binary, .prefix, item.path);
.{ device_manager_exe, "system/services" }, b.getInstallStep().dependOn(&install.step);
.{ input_exe, "system/services" },
.{ ps2_bus_exe, "system/drivers" },
.{ ps2_keyboard_exe, "system/drivers" },
.{ ps2_mouse_exe, "system/drivers" },
.{ usb_xhci_bus_exe, "system/drivers" },
.{ usb_hid_keyboard_exe, "system/drivers" },
.{ usb_hid_mouse_exe, "system/drivers" },
.{ usb_storage_exe, "system/drivers" },
.{ fat_exe, "system/services" },
.{ display_exe, "system/services" },
.{ log_flush_exe, "system/services" },
}) |entry| {
const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } });
b.getInstallStep().dependOn(&step.step);
} }
// The initial-ramdisk itself installs to /boot (with the loaders).
const initial_ramdisk_install = b.addInstallFile(initial_ramdisk_img, "boot/initial-ramdisk.img");
b.getInstallStep().dependOn(&initial_ramdisk_install.step);
// Boot methods live in boot/, one per way of getting the kernel running. // Boot methods live in boot/, one per way of getting the kernel running.
// Each is its own binary/entry (a loader is built for its own target); today // Each is its own binary/entry (a loader is built for its own target); today
// that's UEFI for x86-64, with room for e.g. a device-tree path for the Pis. // that's UEFI for x86-64, with room for e.g. a device-tree path for the Pis.
@@ -692,8 +651,10 @@ pub fn build(b: *std.Build) void {
}), }),
.optimize = optimize, .optimize = optimize,
.imports = &.{ .imports = &.{
// The bootloader speaks only the handoff contract — never the user ABI. // The bootloader speaks the handoff contract and the ramdisk
// container it packs the /system tree into — never the user ABI.
.{ .name = "boot-handoff", .module = boot_handoff_module }, .{ .name = "boot-handoff", .module = boot_handoff_module },
.{ .name = "initial-ramdisk", .module = initial_ramdisk_module },
.{ .name = "build_options", .module = loader_options_module }, .{ .name = "build_options", .module = loader_options_module },
}, },
}), }),
@@ -706,11 +667,11 @@ pub fn build(b: *std.Build) void {
// --- danos-usb.img: the bootable FAT32 USB image --- // --- danos-usb.img: the bootable FAT32 USB image ---
// Format a real FAT32 image (the in-repo Python builder, no external tools) // Format a real FAT32 image (the in-repo Python builder, no external tools)
// holding exactly what the firmware and bootloader need off the ESP: the EFI // holding the EFI stub, the kernel, and the whole /system tree of user
// stub, the kernel, init, and the initial-ramdisk. QEMU presents this image as // binaries at their FHS paths. QEMU presents this image as a USB mass-storage
// a USB mass-storage device the guest boots from (see run-x86-64 and the test // device the guest boots from (see run-x86-64 and the test harness), and the
// harness), and the danos fat driver mounts the same image at /mnt/usb. // danos fat driver mounts the same image at /mnt/usb.
const fat_image = addBootImage(b, exe.getEmittedBin(), efiexe.getEmittedBin(), init_exe.getEmittedBin(), initial_ramdisk_img); const fat_image = addBootImage(b, exe.getEmittedBin(), efiexe.getEmittedBin(), &bundled);
const fat_image_install = b.addInstallFile(fat_image, "danos-usb.img"); const fat_image_install = b.addInstallFile(fat_image, "danos-usb.img");
b.getInstallStep().dependOn(&fat_image_install.step); b.getInstallStep().dependOn(&fat_image_install.step);
@@ -719,7 +680,7 @@ pub fn build(b: *std.Build) void {
// log captured to serial0 — without baking serial into the image users flash. // log captured to serial0 — without baking serial into the image users flash.
// Built lazily (only when `run-x86-64` is requested), and never installed. // Built lazily (only when `run-x86-64` is requested), and never installed.
const exe_serial = addKernel(b, kernel_target, optimize, kernel_modules, test_case, true); const exe_serial = addKernel(b, kernel_target, optimize, kernel_modules, test_case, true);
const fat_image_serial = addBootImage(b, exe_serial.getEmittedBin(), efiexe.getEmittedBin(), init_exe.getEmittedBin(), initial_ramdisk_img); const fat_image_serial = addBootImage(b, exe_serial.getEmittedBin(), efiexe.getEmittedBin(), &bundled);
// `zig build check-fat-image` — validate the produced image is a real FAT32 // `zig build check-fat-image` — validate the produced image is a real FAT32
// with the EFI stub present (the builder's own --verify, no external tools). // with the EFI stub present (the builder's own --verify, no external tools).
+5 -9
View File
@@ -147,15 +147,11 @@ pub const BootInformation = extern struct {
/// A device-tree boot path leaves this 0 and (later) fills a `device_tree_blob` /// A device-tree boot path leaves this 0 and (later) fills a `device_tree_blob`
/// field instead, so the kernel discovers devices without knowing what booted it. /// field instead, so the kernel discovers devices without knowing what booted it.
acpi_rsdp: u64 = 0, acpi_rsdp: u64 = 0,
/// The raw `/system/services/init` ELF image, read off the boot volume by the loader /// The initial_ramdisk image: every user binary from the boot volume's /system
/// into memory that survives the handoff (classified reserved, so the kernel /// tree (init included), packed by the loader into memory that survives the
/// identity-maps it and never allocates over it). 0/0 = no init found — the /// handoff (classified reserved, so the kernel identity-maps it and never
/// kernel boots without user space. Grows into a full initial_ramdisk handoff later. /// allocates over it). Entries are named by full FHS path. 0/0 = no binaries
init_base: u64 = 0, /// found — the kernel boots without user space. See system/initial-ramdisk.zig.
init_len: u64 = 0,
/// The initial_ramdisk image (a bundle of extra user binaries — the VFS server and
/// device drivers), read off the boot volume into memory that survives the
/// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig.
initial_ramdisk_base: u64 = 0, initial_ramdisk_base: u64 = 0,
initial_ramdisk_len: u64 = 0, initial_ramdisk_len: u64 = 0,
}; };
+89 -8
View File
@@ -1,7 +1,13 @@
//! The initial_ramdisk (initial ramdisk) container format — shared by the build-time //! The initial_ramdisk (initial ramdisk) container format — built in RAM by the
//! packer (tools/make-initial-ramdisk.py) and the kernel that unpacks it. Deliberately //! bootloader (boot/efi.zig walks the boot volume's /system tree) and unpacked by
//! trivial: a header, a table of fixed-size entries, then the concatenated file //! the kernel. Deliberately trivial: a header, a table of fixed-size entries, then
//! blobs. We own both producer and consumer, so it need be no fancier. //! the concatenated file blobs. We own both producer and consumer, so it need be
//! no fancier.
//!
//! v2: entry names are full FHS paths ("/system/services/init"), 64 bytes — the
//! same limit as a task name (abi.maximum_process_name), so a path-named task is
//! never truncated. The boot volume's file tree is the single source of truth;
//! this image is only the loader→kernel handoff snapshot of it.
//! //!
//! Layout: //! Layout:
//! Header (magic, count) //! Header (magic, count)
@@ -10,8 +16,14 @@
const std = @import("std"); const std = @import("std");
/// "DNRD" — identifies a danos initial_ramdisk image. /// "DNR2" — identifies a danos initial_ramdisk image, format v2 (path names).
pub const magic: u32 = 0x444E5244; /// The v1 magic ("DNRD", basename entries) is rejected: a stale image should
/// fail loudly at Reader.init, not misparse names.
pub const magic: u32 = 0x32524E44;
/// Entry name capacity. Matches abi.maximum_process_name so a spawned task can
/// always carry its full binary path as its name.
pub const maximum_name = 64;
pub const Header = extern struct { pub const Header = extern struct {
magic: u32, magic: u32,
@@ -19,11 +31,17 @@ pub const Header = extern struct {
}; };
pub const Entry = extern struct { pub const Entry = extern struct {
name: [32]u8, // NUL-padded file name (basename) name: [maximum_name]u8, // NUL-padded FHS path, e.g. "/system/services/init"
offset: u64, // byte offset of the blob within the image offset: u64, // byte offset of the blob within the image
len: u64, // blob length in bytes len: u64, // blob length in bytes
}; };
/// The basename of a path: the final component after the last '/'.
pub fn basename(path: []const u8) []const u8 {
const i = std.mem.lastIndexOfScalar(u8, path, '/') orelse return path;
return path[i + 1 ..];
}
/// A validated view over an initial_ramdisk image. `init` checks the magic and that the /// A validated view over an initial_ramdisk image. `init` checks the magic and that the
/// entry table fits; `entry` bounds-checks each blob against the image. /// entry table fits; `entry` bounds-checks each blob against the image.
pub const Reader = struct { pub const Reader = struct {
@@ -48,11 +66,74 @@ pub const Reader = struct {
if (e.offset > self.image.len or e.len > self.image.len - e.offset) return null; if (e.offset > self.image.len or e.len > self.image.len - e.offset) return null;
// The name is stored in the entry's fixed field; return a stable slice // The name is stored in the entry's fixed field; return a stable slice
// into the image (not the value copy) up to the NUL terminator. // into the image (not the value copy) up to the NUL terminator.
const name_field = self.image[off .. off + 32]; const name_field = self.image[off .. off + maximum_name];
const nlen = std.mem.indexOfScalar(u8, name_field, 0) orelse name_field.len; const nlen = std.mem.indexOfScalar(u8, name_field, 0) orelse name_field.len;
return .{ return .{
.name = name_field[0..nlen], .name = name_field[0..nlen],
.blob = self.image[@intCast(e.offset)..][0..@intCast(e.len)], .blob = self.image[@intCast(e.offset)..][0..@intCast(e.len)],
}; };
} }
/// Look a binary up by name: an exact path match wins; otherwise a unique
/// basename match ("fat" finds "/system/services/fat") keeps pre-path callers
/// working. The returned Item's name is always the stored full path.
pub fn find(self: Reader, name: []const u8) ?Item {
var i: u32 = 0;
while (i < self.count) : (i += 1) {
const item = self.entry(i) orelse continue;
if (std.mem.eql(u8, item.name, name)) return item;
}
i = 0;
while (i < self.count) : (i += 1) {
const item = self.entry(i) orelse continue;
if (std.mem.eql(u8, basename(item.name), name)) return item;
}
return null;
}
}; };
// --- tests (host) -----------------------------------------------------------
fn testImage(buffer: []u8, entries: []const struct { name: []const u8, blob: []const u8 }) []const u8 {
const table_end = @sizeOf(Header) + entries.len * @sizeOf(Entry);
var offset: usize = table_end;
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = magic, .count = @intCast(entries.len) };
for (entries, 0..) |e, i| {
var record = Entry{ .name = @splat(0), .offset = offset, .len = e.blob.len };
@memcpy(record.name[0..e.name.len], e.name);
std.mem.bytesAsValue(Entry, buffer[@sizeOf(Header) + i * @sizeOf(Entry) ..][0..@sizeOf(Entry)]).* = record;
@memcpy(buffer[offset..][0..e.blob.len], e.blob);
offset += e.blob.len;
}
return buffer[0..offset];
}
test "find matches exact path, then unique basename; name is the stored path" {
var buffer: [1024]u8 = undefined;
const image = testImage(&buffer, &.{
.{ .name = "/system/services/init", .blob = "INIT" },
.{ .name = "/system/drivers/ps2-bus", .blob = "PS2" },
});
const rd = Reader.init(image).?;
const by_path = rd.find("/system/services/init").?;
try std.testing.expectEqualStrings("/system/services/init", by_path.name);
try std.testing.expectEqualStrings("INIT", by_path.blob);
const by_base = rd.find("ps2-bus").?;
try std.testing.expectEqualStrings("/system/drivers/ps2-bus", by_base.name);
try std.testing.expectEqualStrings("PS2", by_base.blob);
try std.testing.expect(rd.find("no-such-binary") == null);
}
test "v1 magic is rejected" {
var buffer: [64]u8 = @splat(0);
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = 0x444E5244, .count = 0 };
try std.testing.expect(Reader.init(&buffer) == null);
}
test "basename" {
try std.testing.expectEqualStrings("fat", basename("/system/services/fat"));
try std.testing.expectEqualStrings("fat", basename("fat"));
}
+10 -14
View File
@@ -329,20 +329,16 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// service supervisor and the device manager spawns the drivers it discovers. // service supervisor and the device manager spawns the drivers it discovers.
publishInitialRamdisk(boot_information); publishInitialRamdisk(boot_information);
// Hand over to user space: load /system/services/init (read off the boot volume by // Hand over to user space: spawn /system/services/init out of the ramdisk as a
// the loader) and spawn it as a real ring-3 process, PID 1. As the supervisor it // real ring-3 process, PID 1 — it rides the same table as every other binary.
// brings up the system services (the VFS server, the device manager); the device // As the supervisor it brings up the system services (the VFS server, the device
// manager then discovers the hardware and spawns each driver. init runs on its own // manager); the device manager then discovers the hardware and spawns each
// address space, preemptively — this boot context becomes the BSP's idle loop. // driver. init runs on its own address space, preemptively — this boot context
if (boot_information.init_len != 0) { // becomes the BSP's idle loop.
status("/system/kernel: starting /system/services/init...\n"); status("/system/kernel: starting /system/services/init...\n");
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; process.spawnBundled("/system/services/init") catch |err| {
process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| { statusPrint("/system/kernel: /system/services/init failed to start: {s}\n", .{@errorName(err)});
statusPrint("/system/kernel: /system/services/init failed to load: {s}\n", .{@errorName(err)}); };
};
} else {
status("no /system/services/init on the boot volume.\n");
}
// Become the idle task: drop below every real task and halt until an // Become the idle task: drop below every real task and halt until an
// interrupt. The timer keeps preempting into init and any other work. // interrupt. The timer keeps preempting into init and any other work.
+16 -10
View File
@@ -140,6 +140,16 @@ pub fn setInitialRamdisk(image: []const u8) void {
ramdisk_image = image; ramdisk_image = image;
} }
/// Spawn a bundled binary from the kernel by path. Used exactly once, to start
/// /system/services/init (PID 1) — every other spawn goes through the
/// `system_spawn` syscall.
pub fn spawnBundled(name: []const u8) !void {
const image = ramdisk_image orelse return error.NoInitialRamdisk;
const rd = initial_ramdisk.Reader.init(image) orelse return error.BadInitialRamdisk;
const item = rd.find(name) orelse return error.NotBundled;
try spawnProcess(item.blob, 4, &.{item.name});
}
/// The system_call surface, dispatched on the saved system_call number (`abi.SystemCall`). /// The system_call surface, dispatched on the saved system_call number (`abi.SystemCall`).
/// This is the microkernel-minimal set — memory + scheduling only; file/device /// This is the microkernel-minimal set — memory + scheduling only; file/device
/// I/O will arrive as IPC to user-space servers (docs/syscall.md). The result is /// I/O will arrive as IPC to user-space servers (docs/syscall.md). The result is
@@ -656,8 +666,11 @@ fn systemSpawn(state: *architecture.CpuState) void {
const rd = initial_ramdisk.Reader.init(image) orelse return fail(state); const rd = initial_ramdisk.Reader.init(image) orelse return fail(state);
const name = @as([*]const u8, @ptrFromInt(ptr))[0..len]; const name = @as([*]const u8, @ptrFromInt(ptr))[0..len];
// Exact path first, basename fallback second; either way argv[0] (and hence
// the task name, and the log ring's attribution) is the stored full path.
const item = rd.find(name) orelse return fail(state); // no bundled binary by that name
var argv: [maximum_arguments][]const u8 = undefined; var argv: [maximum_arguments][]const u8 = undefined;
argv[0] = name; argv[0] = item.name;
var argc: usize = 1; var argc: usize = 1;
if (arguments_len != 0) { if (arguments_len != 0) {
const blob = @as([*]const u8, @ptrFromInt(arguments_ptr))[0..arguments_len]; const blob = @as([*]const u8, @ptrFromInt(arguments_ptr))[0..arguments_len];
@@ -669,15 +682,8 @@ fn systemSpawn(state: *architecture.CpuState) void {
} }
} }
var i: u32 = 0; const child = spawnProcessSupervised(item.blob, 4, argv[0..argc], t.id, exit_endpoint) catch return fail(state);
while (i < rd.count) : (i += 1) { architecture.setSystemCallResult(state, child);
const item = rd.entry(i) orelse continue;
if (!std.mem.eql(u8, item.name, name)) continue;
const child = spawnProcessSupervised(item.blob, 4, argv[0..argc], t.id, exit_endpoint) catch return fail(state);
architecture.setSystemCallResult(state, child);
return;
}
fail(state); // no bundled binary by that name
} }
/// thread_spawn(entry, stack_top, arg) -> tid: start a task that shares the **caller's** /// thread_spawn(entry, stack_top, arg) -> tid: start a task that shares the **caller's**
+67 -61
View File
@@ -1327,12 +1327,12 @@ fn procWorker() void {
/// strongest cheap proof of address-space isolation. /// strongest cheap proof of address-space isolation.
fn processTest(boot_information: *const BootInformation) void { fn processTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process\n", .{}); log("DANOS-TEST-BEGIN: process\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0) { check("initial_ramdisk carries /system/services/init", false);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; process.write_from_user = false;
@@ -1414,12 +1414,12 @@ fn spawnFaultingProcess() ?u32 {
/// time the harness out. /// time the harness out.
fn faultRecoveryTest(boot_information: *const BootInformation) void { fn faultRecoveryTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: fault-recovery\n", .{}); log("DANOS-TEST-BEGIN: fault-recovery\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0) { check("initial_ramdisk carries /system/services/init", false);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0; process.write_count = 0;
process.fault_kill_count = 0; process.fault_kill_count = 0;
@@ -1550,7 +1550,7 @@ fn threadJoinTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "join" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "join" })) true else |_| false;
break; break;
} }
@@ -1594,7 +1594,7 @@ fn threadFutexTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "futex" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "futex" })) true else |_| false;
break; break;
} }
@@ -1642,7 +1642,7 @@ fn threadMutexTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "mutex" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "mutex" })) true else |_| false;
break; break;
} }
@@ -1683,7 +1683,7 @@ fn threadIdTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "id" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "id" })) true else |_| false;
break; break;
} }
@@ -1726,7 +1726,7 @@ fn threadAllocTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "alloc" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "alloc" })) true else |_| false;
break; break;
} }
@@ -1769,7 +1769,7 @@ fn threadTlsTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "tls" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "tls" })) true else |_| false;
break; break;
} }
@@ -1811,7 +1811,7 @@ fn threadRwlockTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "thread-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "rwlock" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "rwlock" })) true else |_| false;
break; break;
} }
@@ -1876,12 +1876,12 @@ fn taskReapTest(boot_information: *const BootInformation) void {
/// (write + sleep), and stays alive rather than exiting. /// (write + sleep), and stays alive rather than exiting.
fn initTest(boot_information: *const BootInformation) void { fn initTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: init\n", .{}); log("DANOS-TEST-BEGIN: init\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0) { check("initial_ramdisk carries /system/services/init", false);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
process.write_count = 0; process.write_count = 0;
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |err| blk: { const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |err| blk: {
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)}); log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
@@ -1912,12 +1912,12 @@ fn initTest(boot_information: *const BootInformation) void {
/// learns how big a buffer to bring). /// learns how big a buffer to bring).
fn processListTest(boot_information: *const BootInformation) void { fn processListTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process-list\n", .{}); log("DANOS-TEST-BEGIN: process-list\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0) { check("initial_ramdisk carries /system/services/init", false);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
var spawned: u32 = 0; var spawned: u32 = 0;
if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {} if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
@@ -1963,13 +1963,12 @@ fn processListTest(boot_information: *const BootInformation) void {
/// harness out rather than passing vacuously. /// harness out rather than passing vacuously.
fn processKillTest(boot_information: *const BootInformation) void { fn processKillTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: process-kill\n", .{}); log("DANOS-TEST-BEGIN: process-kill\n", .{});
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) { check("initial_ramdisk carries /system/services/init", false);
check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(ramdisk) orelse { const rd = initial_ramdisk.Reader.init(ramdisk) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
@@ -2024,7 +2023,7 @@ fn processKillTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
spinner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "spinner" }, me, endpoint) catch 0; spinner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "spinner" }, me, endpoint) catch 0;
break; break;
} }
@@ -2041,7 +2040,7 @@ fn processKillTest(boot_information: *const BootInformation) void {
i = 0; i = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "args-echo")) continue; if (!eql(initial_ramdisk.basename(item.name), "args-echo")) continue;
clean = process.spawnProcessSupervised(item.blob, 4, &.{ "args-echo", "clean-exit" }, me, endpoint) catch 0; clean = process.spawnProcessSupervised(item.blob, 4, &.{ "args-echo", "clean-exit" }, me, endpoint) catch 0;
break; break;
} }
@@ -2089,12 +2088,12 @@ fn claimReleaseTest(boot_information: *const BootInformation) void {
check("cleanup released owner 222", devices_broker.ownerOf(1) == null); check("cleanup released owner 222", devices_broker.ownerOf(1) == null);
// The death-path wiring: a real process dies holding a claim. // The death-path wiring: a real process dies holding a claim.
check("bootloader handed over /system/services/init", boot_information.init_len != 0); const image = bundledInit(boot_information) orelse {
if (boot_information.init_len == 0) { check("initial_ramdisk carries /system/services/init", false);
result(); result();
return; return;
} };
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; check("initial_ramdisk carries /system/services/init", true);
const me = scheduler.currentId(); const me = scheduler.currentId();
const endpoint = ipcsync.createIpcEndpoint() orelse { const endpoint = ipcsync.createIpcEndpoint() orelse {
check("exit endpoint allocated", false); check("exit endpoint allocated", false);
@@ -2148,7 +2147,7 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "vfs-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "vfs-test")) continue;
client = process.spawnProcessSupervised(item.blob, 4, &.{ "vfs-test", "park" }, me, endpoint) catch 0; client = process.spawnProcessSupervised(item.blob, 4, &.{ "vfs-test", "park" }, me, endpoint) catch 0;
break; break;
} }
@@ -2209,7 +2208,7 @@ fn signalsTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
runner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "signal-run" }, scheduler.currentId(), null) catch 0; runner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "signal-run" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2257,7 +2256,7 @@ fn driverRestartTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-restart" }, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-restart" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2295,7 +2294,7 @@ fn usbReportTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2327,7 +2326,7 @@ fn deviceListTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2368,7 +2367,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-pci-restart" }, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-pci-restart" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2452,8 +2451,8 @@ fn usbStorageTest(boot_information: *const BootInformation) void {
/// the fat mount and the client's success. /// the fat mount and the client's success.
fn fatMountTest(boot_information: *const BootInformation) void { fn fatMountTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: fat-mount\n", .{}); log("DANOS-TEST-BEGIN: fat-mount\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false); check("bootloader handed over the initial_ramdisk", false);
result(); result();
return; return;
} }
@@ -2464,8 +2463,7 @@ fn fatMountTest(boot_information: *const BootInformation) void {
return; return;
}; };
process.setInitialRamdisk(ramdisk); process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const init_ok = if (process.spawnBundled("/system/services/init")) true else |_| false;
const init_ok = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
check("init spawned (boots the tree, incl. the fat server)", init_ok); check("init spawned (boots the tree, incl. the fat server)", init_ok);
check("fat-test client spawned", spawnNamed(rd, "fat-test")); check("fat-test client spawned", spawnNamed(rd, "fat-test"));
result(); result();
@@ -2473,30 +2471,28 @@ fn fatMountTest(boot_information: *const BootInformation) void {
fn bootServiceTreeTest(boot_information: *const BootInformation, comptime label: []const u8) void { fn bootServiceTreeTest(boot_information: *const BootInformation, comptime label: []const u8) void {
log("DANOS-TEST-BEGIN: " ++ label ++ "\n", .{}); log("DANOS-TEST-BEGIN: " ++ label ++ "\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false); check("bootloader handed over the initial_ramdisk", false);
result(); result();
return; return;
} }
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(ramdisk); process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
check("init spawned (boots vfs, input, device-manager, and the USB chain)", spawned); check("init spawned (boots vfs, input, device-manager, and the USB chain)", spawned);
result(); result();
} }
fn orderlyShutdownTest(boot_information: *const BootInformation) void { fn orderlyShutdownTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: orderly-shutdown\n", .{}); log("DANOS-TEST-BEGIN: orderly-shutdown\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) { if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false); check("bootloader handed over the initial_ramdisk", false);
result(); result();
return; return;
} }
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(ramdisk); process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
check("init spawned as PID root of user space", spawned); check("init spawned as PID root of user space", spawned);
result(); result();
} }
@@ -2524,7 +2520,7 @@ fn acpiReportTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
_ = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0; _ = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
spawned = true; spawned = true;
break; break;
@@ -2562,7 +2558,7 @@ fn acpiParseTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "discovery")) continue; if (!eql(initial_ramdisk.basename(item.name), "discovery")) continue;
_ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", "1" }, scheduler.currentId(), null) catch 0; _ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", "1" }, scheduler.currentId(), null) catch 0;
spawned = true; spawned = true;
break; break;
@@ -2597,7 +2593,7 @@ fn supervisionTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "process-test")) continue; if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
started = if (process.spawnProcess(item.blob, 4, &.{ "process-test", "run" })) true else |_| false; started = if (process.spawnProcess(item.blob, 4, &.{ "process-test", "run" })) true else |_| false;
break; break;
} }
@@ -2921,7 +2917,7 @@ fn virtioGpuTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -2962,7 +2958,7 @@ fn displayNativeTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -3006,7 +3002,7 @@ fn displayReattachTest(boot_information: *const BootInformation) void {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (!eql(item.name, "device-manager")) continue; if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-scanout-restart" }, scheduler.currentId(), null) catch 0; manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-scanout-restart" }, scheduler.currentId(), null) catch 0;
break; break;
} }
@@ -3055,7 +3051,7 @@ fn argsTest(boot_information: *const BootInformation) void {
while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield(); while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
scheduler.setPriority(4); scheduler.setPriority(4);
const expected = "args: args-echo alpha beta-42\n"; const expected = "args: /system/tests/args-echo alpha beta-42\n";
const echoed = process.write_len == expected.len and eql(process.write_buffer[0..process.write_len], expected); const echoed = process.write_len == expected.len and eql(process.write_buffer[0..process.write_len], expected);
if (!echoed and process.write_len > 0) log("DANOS-ARGS: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]}); if (!echoed and process.write_len > 0) log("DANOS-ARGS: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
check("argv arrived intact (argv[0] = name, argv[1..] = spawn arguments)", echoed); check("argv arrived intact (argv[0] = name, argv[1..] = spawn arguments)", echoed);
@@ -3065,11 +3061,21 @@ fn argsTest(boot_information: *const BootInformation) void {
/// Spawn the initial_ramdisk 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. /// isn't in the image or fails to load.
/// The init ELF image out of the initial_ramdisk — init rides the table like
/// every other binary since the loader packs the whole /system tree.
fn bundledInit(boot_information: *const BootInformation) ?[]const u8 {
if (boot_information.initial_ramdisk_len == 0) return null;
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
const rd = initial_ramdisk.Reader.init(image) orelse return null;
const item = rd.find("/system/services/init") orelse return null;
return item.blob;
}
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool { fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (eql(item.name, name)) { if (eql(initial_ramdisk.basename(item.name), name)) {
return if (process.spawnProcess(item.blob, 4, &.{item.name})) true else |_| false; return if (process.spawnProcess(item.blob, 4, &.{item.name})) true else |_| false;
} }
} }
@@ -3082,7 +3088,7 @@ fn spawnNamedWithArg(rd: initial_ramdisk.Reader, name: []const u8, arg: []const
var i: u32 = 0; var i: u32 = 0;
while (i < rd.count) : (i += 1) { while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue; const item = rd.entry(i) orelse continue;
if (eql(item.name, name)) { if (eql(initial_ramdisk.basename(item.name), name)) {
return if (process.spawnProcess(item.blob, 4, &.{ item.name, arg })) true else |_| false; return if (process.spawnProcess(item.blob, 4, &.{ item.name, arg })) true else |_| false;
} }
} }
@@ -56,8 +56,8 @@ const virtio_gpu_pci_class: u64 = pci_class.ClassCode.pack(.{
/// its registered id as argv[1]. /// its registered id as argv[1].
fn pciDriverForIdentity(identity: u64) ?[]const u8 { fn pciDriverForIdentity(identity: u64) ?[]const u8 {
return switch (identity) { return switch (identity) {
xhci_pci_class => "usb-xhci-bus", xhci_pci_class => "/system/drivers/usb-xhci-bus",
virtio_gpu_pci_class => "virtio-gpu", virtio_gpu_pci_class => "/system/drivers/virtio-gpu",
else => null, else => null,
}; };
} }
@@ -67,8 +67,8 @@ fn pciDriverForIdentity(identity: u64) ?[]const u8 {
/// nodes the kernel used to build). ps2-bus is a singleton that finds both its /// nodes the kernel used to build). ps2-bus is a singleton that finds both its
/// devices by hid once spawned, so keyboard and mouse map to the same name. /// devices by hid once spawned, so keyboard and mouse map to the same name.
fn hidDriverFor(hid: []const u8) ?[]const u8 { fn hidDriverFor(hid: []const u8) ?[]const u8 {
if (std.mem.eql(u8, hid, "PNP0303")) return "ps2-bus"; // PS/2 keyboard if (std.mem.eql(u8, hid, "PNP0303")) return "/system/drivers/ps2-bus"; // PS/2 keyboard
if (std.mem.eql(u8, hid, "PNP0F13")) return "ps2-bus"; // PS/2 mouse if (std.mem.eql(u8, hid, "PNP0F13")) return "/system/drivers/ps2-bus"; // PS/2 mouse
return null; return null;
} }
@@ -95,9 +95,9 @@ fn usbDriverForIdentity(identity: u64) ?[]const u8 {
@intFromEnum(usb_ids.mass_storage.Protocol.bulk_only), @intFromEnum(usb_ids.mass_storage.Protocol.bulk_only),
); );
return switch (identity) { return switch (identity) {
keyboard => "usb-hid-keyboard", keyboard => "/system/drivers/usb-hid-keyboard",
mouse => "usb-hid-mouse", mouse => "/system/drivers/usb-hid-mouse",
storage => "usb-storage", storage => "/system/drivers/usb-storage",
else => null, else => null,
}; };
} }
@@ -429,7 +429,7 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
writeLine("/system/services/device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id }); writeLine("/system/services/device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id });
// Resilience drill (V6): once, kill the virtio-gpu driver a moment after it hellos, so // Resilience drill (V6): once, kill the virtio-gpu driver a moment after it hellos, so
// the normal restart policy respawns it — the compositor must survive and re-attach. // the normal restart policy respawns it — the compositor must survive and re-attach.
if (test_scanout_restart_mode and !test_scanout_killed and std.mem.eql(u8, driver.name(), "virtio-gpu")) { if (test_scanout_restart_mode and !test_scanout_killed and std.mem.eql(u8, driver.name(), "/system/drivers/virtio-gpu")) {
test_scanout_killed = true; test_scanout_killed = true;
test_kill_pid = sender; test_kill_pid = sender;
test_kill_due_ns = system.clock() + 1_500_000_000; test_kill_due_ns = system.clock() + 1_500_000_000;
@@ -498,7 +498,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
// Only the xHCI reporter is the drill's victim — pci-bus also reports // Only the xHCI reporter is the drill's victim — pci-bus also reports
// now, and whichever finishes second must not trigger the kill. // now, and whichever finishes second must not trigger the kill.
if (driverByProcess(sender)) |driver| { if (driverByProcess(sender)) |driver| {
if (std.mem.eql(u8, driver.name(), "usb-xhci-bus")) { if (std.mem.eql(u8, driver.name(), "/system/drivers/usb-xhci-bus")) {
// Delayed, not immediate: the device-list scenario's subscriber // Delayed, not immediate: the device-list scenario's subscriber
// needs a window to enumerate and subscribe before the events. // needs a window to enumerate and subscribe before the events.
test_usb_killed = true; test_usb_killed = true;
+14 -6
View File
@@ -27,11 +27,19 @@ const build_options = @import("build_options");
/// the log-flush one-shot writes it once at boot. /// the log-flush one-shot writes it once at boot.
const log_path = "/mnt/usb/DANOS.LOG"; const log_path = "/mnt/usb/DANOS.LOG";
/// The system services init brings up at boot, in order. This is init's policy — the /// The system services init brings up at boot, in order, by binary path. This is
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent /// init's policy — the microkernel keeps such choices in user space, not the
/// on purpose: the device manager owns those. (A future init reads this from a /// kernel. Drivers are absent on purpose: the device manager owns those. (A
/// manifest under /system/services instead of a hardcoded list.) /// future init reads this from a manifest under /system/services instead of a
const boot_services = [_][]const u8{ "vfs", "input", "device-manager", "fat", "display", "display-demo" }; /// hardcoded list.)
const boot_services = [_][]const u8{
"/system/services/vfs",
"/system/services/input",
"/system/services/device-manager",
"/system/services/fat",
"/system/services/display",
"/system/services/display-demo",
};
/// The live process id of each boot service (0 = not running), indexed by its position /// The live process id of each boot service (0 = not running), indexed by its position
/// in `boot_services`, plus how many times init has restarted it. init supervises these: /// in `boot_services`, plus how many times init has restarted it. init supervises these:
@@ -84,7 +92,7 @@ pub fn main() void {
// and forget: it polls for the mount itself, and is deliberately NOT one of // and forget: it polls for the mount itself, and is deliberately NOT one of
// init's supervised children (a transient one-shot must not be stopped-and- // init's supervised children (a transient one-shot must not be stopped-and-
// waited-for during shutdown). // waited-for during shutdown).
_ = runtime.system.spawn("log-flush"); _ = runtime.system.spawn("/system/services/log-flush");
// Subscribe to power events (retry: the power service registers well after // Subscribe to power events (retry: the power service registers well after
// init starts). Best-effort — without it, a `terminate` signal still // init starts). Best-effort — without it, a `terminate` signal still
@@ -147,8 +147,8 @@ pub fn main(init: runtime.process.Init) void {
const spinner = runtime.system.spawnSupervised("process-test", &.{"spinner"}, endpoint) orelse fail("spawn spinner"); const spinner = runtime.system.spawnSupervised("process-test", &.{"spinner"}, endpoint) orelse fail("spawn spinner");
runtime.system.sleep(100); // let the sleeper block and the spinner get a core runtime.system.sleep(100); // let the sleeper block and the spinner get a core
if (!listed(sleeper, "process-test")) fail("sleeper not in process_enumerate"); if (!listed(sleeper, "/system/tests/process-test")) fail("sleeper not in process_enumerate");
if (!listed(spinner, "process-test")) fail("spinner not in process_enumerate"); if (!listed(spinner, "/system/tests/process-test")) fail("spinner not in process_enumerate");
// Kills that must be refused: a kernel task (id 0), and an id that was never // Kills that must be refused: a kernel task (id 0), and an id that was never
// issued — both -ESRCH. (-EPERM needs a second supervisor; the kernel-level // issued — both -ESRCH. (-EPERM needs a second supervisor; the kernel-level
@@ -167,8 +167,8 @@ pub fn main(init: runtime.process.Init) void {
if (!runtime.system.kill(spinner)) fail("kill spinner"); if (!runtime.system.kill(spinner)) fail("kill spinner");
if (awaitChildExit(endpoint) != spinner) fail("spinner exit notification"); if (awaitChildExit(endpoint) != spinner) fail("spinner exit notification");
if (listed(sleeper, "process-test")) fail("sleeper still listed after kill"); if (listed(sleeper, "/system/tests/process-test")) fail("sleeper still listed after kill");
if (listed(spinner, "process-test")) fail("spinner still listed after kill"); if (listed(spinner, "/system/tests/process-test")) fail("spinner still listed after kill");
// M17.2: both children were killed by us, and the reason says so — the whole // M17.2: both children were killed by us, and the reason says so — the whole
// restart-policy input, read through the runtime like a real supervisor would. // restart-policy input, read through the runtime like a real supervisor would.
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env python3
"""Build-time initial_ramdisk packer. Concatenates user binaries into one image the
bootloader ferries to the kernel.
Usage: make-initial-ramdisk.py <out.img> [<name> <file>]...
Image layout (little-endian), mirroring src/user/proto/initial-ramdisk.zig:
Header : magic u32 ("DNRD"=0x444E5244), count u32
Entry*N : name [32]u8 (NUL-padded), offset u64, len u64
blobs : each entry's file bytes at its offset
"""
import struct
import sys
MAGIC = 0x444E5244
HEADER = struct.Struct("<II") # magic, count
ENTRY = struct.Struct("<32sQQ") # name[32], offset, len
def main() -> int:
out_path = sys.argv[1]
rest = sys.argv[2:]
if len(rest) % 2 != 0:
sys.stderr.write("usage: make-initial-ramdisk.py <out.img> [<name> <file>]...\n")
return 2
items = [(rest[i], rest[i + 1]) for i in range(0, len(rest), 2)]
table_end = HEADER.size + len(items) * ENTRY.size
entries = b""
blobs = []
off = table_end
for name, path in items:
with open(path, "rb") as f:
data = f.read()
entries += ENTRY.pack(name.encode()[:31], off, len(data))
blobs.append(data)
off += len(data)
with open(out_path, "wb") as f:
f.write(HEADER.pack(MAGIC, len(items)))
f.write(entries)
for b in blobs:
f.write(b)
return 0
if __name__ == "__main__":
sys.exit(main())