M11–M12: IRQ-as-IPC and bus drivers; expand names tree-wide
Two driver-model milestones plus a tree-wide naming pass. Suite 35/35 (QEMU) + host tests green. M11 — IRQ-as-IPC. A ring-3 driver now sleeps until its device interrupts it. New src/kernel/irq.zig: per-GSI endpoint bindings, comptime per-vector trampolines, dispatch = mask GSI -> LAPIC EOI -> notifyLocked, all under one lock region. irq_bind/irq_ack syscalls, gated by the device claim like mmio_map. interruptDispatch no longer EOIs — each handler owns its EOI, because a level line must be masked before it is acknowledged (irq_ack is the unmask). Bindings are keyed on the owning task and released on exit (a shared endpoint's siblings survive). hpetd rewritten interrupt-driven. Tests: hpet (rewritten, reads back the I/O APIC routing) and irqfree. M12 — bus drivers. DeviceDesc gains a parent, making the device table a tree. dev_register (device_register) lets a process publish children below a device it claimed; the kernel enforces resource containment (a child's resources must nest in its parent's), so a descriptor can't fabricate a window over kernel RAM. Descriptor copied in via copyFromUser (physmap walk — an unmapped user pointer fails the call instead of faulting the kernel). Per-parent child cap bounds table exhaustion. sbin/busd.zig is a worked bus driver. Test: bus. Naming — per docs/coding-standards.md: non-acronym abbreviations spelled out (message, descriptor, device_service, scheduler, runtime, physical, interpreter, ...); acronyms kept (IPC, MMIO, DMA, HCD, ...); files are kebab-case (ipc-synchronous.zig, device-service.zig, vfs-protocol.zig, ...). Exceptions: POSIX/C ABI names and Zig idioms (init/len/ptr) kept. Module collisions resolved by specific naming (config -> parameters, device.zig alias -> device_model). AML op/Op disambiguated: op = opcode, Op = operation; per-opcode parse handlers renamed opX -> parseX. New driver docs: drivers.md, driver-model.md (bus/class/HCD shapes + the proposed M13–M16 ABI), coding-standards.md.
This commit is contained in:
@@ -51,13 +51,13 @@ fn timestamp(b: *std.Build) []const u8 {
|
||||
/// Build one user-space binary the same way for every program (init, and later
|
||||
/// the VFS server + drivers): freestanding, ReleaseSmall, `.large` code model
|
||||
/// (the image base is above 4 GiB — smaller models emit 32-bit relocations that
|
||||
/// can't reach), linked against the `rt` runtime library with the shared user
|
||||
/// can't reach), linked against the `runtime` runtime library with the shared user
|
||||
/// link script. Pinned to LLVM + LLD so the script's PHDRS (segment permissions)
|
||||
/// are authoritative — the kernel's W^X user-ELF loader requires exact perms.
|
||||
fn addUserBinary(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
rt_mod: *std.Build.Module,
|
||||
runtime_module: *std.Build.Module,
|
||||
name: []const u8,
|
||||
root: []const u8,
|
||||
) *std.Build.Step.Compile {
|
||||
@@ -73,7 +73,7 @@ fn addUserBinary(
|
||||
.stack_check = false,
|
||||
.stack_protector = false,
|
||||
.imports = &.{
|
||||
.{ .name = "rt", .module = rt_mod },
|
||||
.{ .name = "runtime", .module = runtime_module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -91,74 +91,74 @@ pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Shared handoff definitions (BootInfo, Framebuffer, ...). No target is set,
|
||||
// Shared handoff definitions (BootInformation, Framebuffer, ...). No target is set,
|
||||
// so the module inherits the target of whichever binary imports it — the
|
||||
// freestanding kernel or the UEFI bootloader.
|
||||
const mod = b.addModule("danos", .{
|
||||
const danos_module = b.addModule("danos", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
});
|
||||
|
||||
// Kernel tunables (max_cpus, stack sizes, tick rate). A dependency-free module of
|
||||
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
|
||||
// compile-time constants, imported wherever a knob is read; keeps the trade-offs
|
||||
// in one place instead of scattered across the tree. See src/config.zig.
|
||||
const config_mod = b.addModule("config", .{
|
||||
.root_source_file = b.path("src/config.zig"),
|
||||
// in one place instead of scattered across the tree. See src/configuration.zig.
|
||||
const parameters_module = b.addModule("parameters", .{
|
||||
.root_source_file = b.path("src/parameters.zig"),
|
||||
});
|
||||
|
||||
// Architecture-specific kernel code (CPU ops, entry, later GDT/IDT/paging).
|
||||
// The generic kernel imports this as "arch" and never names x86_64, so a new
|
||||
// The generic kernel imports this as "architecture" and never names x86_64, so a new
|
||||
// architecture is a matter of pointing this module at a different directory.
|
||||
const arch_mod = b.addModule("arch", .{
|
||||
const architecture_module = b.addModule("architecture", .{
|
||||
.root_source_file = b.path("src/kernel/arch/x86_64/cpu.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod }, // paging uses the shared BootInfo/memory-map types
|
||||
.{ .name = "config", .module = config_mod }, // max_cpus, ist_stack_size, timer_hz
|
||||
.{ .name = "danos", .module = danos_module }, // paging uses the shared BootInformation/memory-map types
|
||||
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus, ist_stack_size, timer_hz
|
||||
},
|
||||
});
|
||||
// CPU-exception stubs — real assembly, since they need cross-symbol
|
||||
// jumps/calls that Zig inline asm can't express (see the file's header).
|
||||
arch_mod.addAssemblyFile(b.path("src/kernel/arch/x86_64/isr.s"));
|
||||
architecture_module.addAssemblyFile(b.path("src/kernel/arch/x86_64/isr.s"));
|
||||
// The AP bring-up trampoline: 16-/32-/64-bit mode-switch code that can't be
|
||||
// inline asm (it runs relocated to a low page, not at its link address).
|
||||
arch_mod.addAssemblyFile(b.path("src/kernel/arch/x86_64/trampoline.s"));
|
||||
architecture_module.addAssemblyFile(b.path("src/kernel/arch/x86_64/trampoline.s"));
|
||||
|
||||
// Firmware-agnostic device discovery. The generic kernel imports this as
|
||||
// "platform" and asks it to enumerate hardware into a backend-neutral device
|
||||
// tree, never naming ACPI (or, later, device-tree) — the same discipline the
|
||||
// arch module applies to CPU code. The backend is selected at runtime from
|
||||
// architecture module applies to CPU code. The backend is selected at runtime from
|
||||
// the boot handoff (see src/device/platform.zig).
|
||||
const platform_mod = b.addModule("platform", .{
|
||||
const platform_module = b.addModule("platform", .{
|
||||
.root_source_file = b.path("src/device/platform.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod }, // BootInfo (carries the ACPI RSDP)
|
||||
.{ .name = "config", .module = config_mod }, // max_cpus (the discovery pool)
|
||||
.{ .name = "danos", .module = danos_module }, // BootInformation (carries the ACPI RSDP)
|
||||
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
|
||||
},
|
||||
});
|
||||
|
||||
// The user-space runtime library (a nascent libc): syscall wrappers, the
|
||||
// The user-space runtime library (a nascent libc): system_call wrappers, the
|
||||
// C-convention heap, IPC helpers, the process start shim. Compiled into every
|
||||
// user binary (see addUserBinary), so it inherits each exe's `.large` code
|
||||
// model — do NOT set a target/code_model here. It imports `danos` for the
|
||||
// shared Syscall numbers.
|
||||
const rt_mod = b.addModule("rt", .{
|
||||
.root_source_file = b.path("lib/rt.zig"),
|
||||
// shared SystemCall numbers.
|
||||
const runtime_module = b.addModule("runtime", .{
|
||||
.root_source_file = b.path("lib/runtime.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod },
|
||||
.{ .name = "danos", .module = danos_module },
|
||||
},
|
||||
});
|
||||
|
||||
// The initrd container format, shared by the kernel (unpacks it) and the
|
||||
// build-time packer tools/mkinitrd.zig (produces it). No dependencies.
|
||||
const initrd_mod = b.addModule("initrd", .{
|
||||
.root_source_file = b.path("src/user/proto/initrd.zig"),
|
||||
const initrd_module = b.addModule("initrd", .{
|
||||
.root_source_file = b.path("src/user/protocol/initrd.zig"),
|
||||
});
|
||||
|
||||
// Compile-time config the kernel reads as `@import("build_options")`. The
|
||||
// Compile-time configuration the kernel reads as `@import("build_options")`. The
|
||||
// QEMU test harness sets -Dtest-case=<name> to run one self-test at boot.
|
||||
const test_case = b.option([]const u8, "test-case", "Kernel self-test case to run at boot (see src/kernel/tests.zig)");
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption(?[]const u8, "test_case", test_case);
|
||||
const build_options_mod = build_options.createModule();
|
||||
const build_options_module = build_options.createModule();
|
||||
|
||||
// --- Kernel: freestanding x86_64 ELF, jumped to by the bootloader ---
|
||||
// SSE2 is part of the x86_64 baseline and UEFI leaves it enabled at handoff,
|
||||
@@ -177,18 +177,18 @@ pub fn build(b: *std.Build) void {
|
||||
.target = kernel_target,
|
||||
.optimize = optimize,
|
||||
.code_model = .kernel, // kernel runs in the top 2 GiB (higher half)
|
||||
.red_zone = false, // interrupts would corrupt the SysV red zone
|
||||
.red_zone = false, // interrupts would corrupt the SystemV red zone
|
||||
.single_threaded = false, // SMP: the big kernel lock's atomics must be real across cores
|
||||
.sanitize_c = .off, // the UBSan runtime needs f128/SSE support we don't provide
|
||||
.stack_check = false, // stack-probe calls have no runtime to land in
|
||||
.stack_protector = false,
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod },
|
||||
.{ .name = "arch", .module = arch_mod },
|
||||
.{ .name = "platform", .module = platform_mod },
|
||||
.{ .name = "config", .module = config_mod },
|
||||
.{ .name = "build_options", .module = build_options_mod },
|
||||
.{ .name = "initrd", .module = initrd_mod },
|
||||
.{ .name = "danos", .module = danos_module },
|
||||
.{ .name = "architecture", .module = architecture_module },
|
||||
.{ .name = "platform", .module = platform_module },
|
||||
.{ .name = "parameters", .module = parameters_module },
|
||||
.{ .name = "build_options", .module = build_options_module },
|
||||
.{ .name = "initrd", .module = initrd_module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -208,18 +208,19 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
// --- /sbin/init: the first user-space program ---
|
||||
// Built by the shared user-binary recipe (see addUserBinary): freestanding,
|
||||
// linked into the kernel's user region against the `rt` runtime library, and
|
||||
// linked into the kernel's user region against the `runtime` runtime library, and
|
||||
// started in ring 3 by the kernel's user-ELF loader.
|
||||
const init_exe = addUserBinary(b, kernel_target, rt_mod, "init", "sbin/init.zig");
|
||||
const init_exe = addUserBinary(b, kernel_target, runtime_module, "init", "sbin/init.zig");
|
||||
b.installArtifact(init_exe);
|
||||
|
||||
// --- initrd: a bundle of extra user binaries (VFS server + drivers) ---
|
||||
// Each is built by the same user-binary recipe, then packed into one image by
|
||||
// the host-side mkinitrd tool. The bootloader ferries the image to the kernel,
|
||||
// which unpacks it and spawns each program (src/user/proto/initrd.zig).
|
||||
const vfs_exe = addUserBinary(b, kernel_target, rt_mod, "vfs", "sbin/vfs.zig");
|
||||
const vfstest_exe = addUserBinary(b, kernel_target, rt_mod, "vfstest", "sbin/vfstest.zig");
|
||||
const hpetd_exe = addUserBinary(b, kernel_target, rt_mod, "hpetd", "sbin/hpetd.zig");
|
||||
// which unpacks it and spawns each program (src/user/protocol/initrd.zig).
|
||||
const vfs_exe = addUserBinary(b, kernel_target, runtime_module, "vfs", "sbin/vfs.zig");
|
||||
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, "vfs-test", "sbin/vfs-test.zig");
|
||||
const hpetd_exe = addUserBinary(b, kernel_target, runtime_module, "hpetd", "sbin/hpetd.zig");
|
||||
const busd_exe = addUserBinary(b, kernel_target, runtime_module, "busd", "sbin/busd.zig");
|
||||
|
||||
// Pack the user binaries into the initrd image with the host-side Python tool
|
||||
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
||||
@@ -229,10 +230,12 @@ pub fn build(b: *std.Build) void {
|
||||
const initrd_img = mk_run.addOutputFileArg("initrd.img");
|
||||
mk_run.addArg("vfs");
|
||||
mk_run.addFileArg(vfs_exe.getEmittedBin());
|
||||
mk_run.addArg("vfstest");
|
||||
mk_run.addArg("vfs-test");
|
||||
mk_run.addFileArg(vfstest_exe.getEmittedBin());
|
||||
mk_run.addArg("hpetd");
|
||||
mk_run.addFileArg(hpetd_exe.getEmittedBin());
|
||||
mk_run.addArg("busd");
|
||||
mk_run.addFileArg(busd_exe.getEmittedBin());
|
||||
|
||||
// Install the image to zig-out/bin (so the QEMU test harness picks it up like
|
||||
// the other binaries). The run-x86-64 ESP install is added below.
|
||||
@@ -252,7 +255,7 @@ pub fn build(b: *std.Build) void {
|
||||
}),
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "danos", .module = mod },
|
||||
.{ .name = "danos", .module = danos_module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -261,14 +264,14 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
// --- run-x86-64: boot the x86-64 kernel in QEMU via UEFI/OVMF ---
|
||||
// Firmware lives in different places per OS/distro, so probe the known
|
||||
// layouts (Arch, Debian/Ubuntu, Fedora, macOS Homebrew) and use the first
|
||||
// layouts (Architecture, Debian/Ubuntu, Fedora, macOS Homebrew) and use the first
|
||||
// that exists. Override with -Dovmf-code / -Dovmf-vars if yours is elsewhere.
|
||||
const ovmf_code = b.option(
|
||||
[]const u8,
|
||||
"ovmf-code",
|
||||
"Path to the OVMF_CODE firmware image",
|
||||
) orelse firstExisting(b.graph.io, &.{
|
||||
"/usr/share/edk2/x64/OVMF_CODE.4m.fd", // Arch
|
||||
"/usr/share/edk2/x64/OVMF_CODE.4m.fd", // Architecture
|
||||
"/usr/share/OVMF/OVMF_CODE_4M.fd", // Debian/Ubuntu
|
||||
"/usr/share/OVMF/OVMF_CODE.fd", // older Debian/Ubuntu
|
||||
"/usr/share/edk2-ovmf/x64/OVMF_CODE.fd", // Fedora
|
||||
@@ -280,7 +283,7 @@ pub fn build(b: *std.Build) void {
|
||||
"ovmf-vars",
|
||||
"Path to the OVMF_VARS firmware image (a writable copy is made)",
|
||||
) orelse firstExisting(b.graph.io, &.{
|
||||
"/usr/share/edk2/x64/OVMF_VARS.4m.fd", // Arch
|
||||
"/usr/share/edk2/x64/OVMF_VARS.4m.fd", // Architecture
|
||||
"/usr/share/OVMF/OVMF_VARS_4M.fd", // Debian/Ubuntu
|
||||
"/usr/share/OVMF/OVMF_VARS.fd", // older Debian/Ubuntu
|
||||
"/usr/share/edk2-ovmf/x64/OVMF_VARS.fd", // Fedora
|
||||
|
||||
Reference in New Issue
Block a user