Split the system contract into boot-handoff / abi / device-abi

The `system` module (formerly `danos`) had become a grab-bag: it held the
loader<->kernel handoff *and* the kernel<->user ABI *and* the device wire types, in
one module three different audiences imported. Usage proved the seam — the
bootloader never touched the syscall/device ABI, and user space never touched the
boot handoff — so split it by audience, one module per contract:

  system/boot-handoff.zig       loader <-> kernel: BootInformation, Framebuffer,
                                MemoryMap, the VM layout + physicalToVirtual, kernel_abi
  system/abi.zig                kernel <-> user, core: SystemCall, mmap prot flags,
                                page_size, notify_badge_bit, ServiceId
  system/devices/device-abi.zig kernel <-> user, devices: DeviceDescriptor,
                                DeviceClass, ResourceDescriptor, ResourceKind, ...

device-abi is the devices sub-project's public interface, exposed as its own module
the way vfs exposes vfs-protocol — importable by user space, unlike the
kernel-internal device model it also feeds. That collapses a real duplication:
DeviceClass and ResourceKind were defined twice (device-model.zig and the contract,
kept "in sync by hand"); device-model now re-exports them from device-abi, so the
enum a driver matches on and the one the kernel classifies with are one type.

Each import now declares which contract it speaks: the bootloader imports only
boot-handoff; a driver only abi + device-abi (via the runtime); the kernel all
three. This also retires the `system` / `runtime.system` name overlap. page_size
lands in abi (it's part of the mmap contract user space aligns to); the bootloader
keeps its own local 4 KiB constant so it depends on nothing but the handoff.

All 21 importers rewired, docs updated to keep /system mapping to source. Build,
host tests, and the QEMU suite (36/36) all green.
This commit is contained in:
Daniel Samson
2026-07-10 18:08:51 +01:00
parent 47610e8ee2
commit be81394be3
37 changed files with 395 additions and 337 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ Codename: Shodan
Version: 1 Version: 1
A small operating system, written from scratch in Zig — a bootloader (`boot/`) A small operating system, written from scratch in Zig — a bootloader (`boot/`)
and a microkernel (`system/kernel/`), sharing a neutral handoff contract (`system/system.zig`). and a microkernel (`system/kernel/`), sharing a neutral handoff contract (`system/boot-handoff.zig`).
It boots x86-64 via UEFI, and so far has a framebuffer console, a physical frame It boots x86-64 via UEFI, and so far has a framebuffer console, a physical frame
allocator, its own paging with W^X permissions, interrupt/exception handling, a allocator, its own paging with W^X permissions, interrupt/exception handling, a
LAPIC timer, a kernel heap, a fixed-priority preemptive scheduler, and in-kernel IPC LAPIC timer, a kernel heap, a fixed-priority preemptive scheduler, and in-kernel IPC
+16 -16
View File
@@ -1,8 +1,8 @@
const std = @import("std"); const std = @import("std");
const uefi = std.os.uefi; const uefi = std.os.uefi;
const elf = std.elf; const elf = std.elf;
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const BootInformation = system.BootInformation; const BootInformation = boot_handoff.BootInformation;
const GraphicsOutput = uefi.protocol.GraphicsOutput; const GraphicsOutput = uefi.protocol.GraphicsOutput;
const EdidActive = uefi.protocol.edid.Active; const EdidActive = uefi.protocol.edid.Active;
const MemoryMapSlice = uefi.tables.MemoryMapSlice; const MemoryMapSlice = uefi.tables.MemoryMapSlice;
@@ -45,7 +45,7 @@ fn boot() !noreturn {
var boot_information: BootInformation = .{ var boot_information: BootInformation = .{
// A missing GOP (a headless machine) is not fatal — hand the kernel a // A missing GOP (a headless machine) is not fatal — hand the kernel a
// "no framebuffer" descriptor (base 0) and let it log to serial instead. // "no framebuffer" descriptor (base 0) and let it log to serial instead.
.framebuffer = queryFramebuffer(bs) catch system.Framebuffer{ .framebuffer = queryFramebuffer(bs) catch boot_handoff.Framebuffer{
.base = 0, .base = 0,
.width = 0, .width = 0,
.height = 0, .height = 0,
@@ -100,7 +100,7 @@ const Resolution = struct { width: u32, height: u32 };
/// Switch the GPU to the monitor's native resolution (when we can determine it) /// Switch the GPU to the monitor's native resolution (when we can determine it)
/// and read the resulting graphics mode into our own framebuffer description. /// and read the resulting graphics mode into our own framebuffer description.
fn queryFramebuffer(bs: *uefi.tables.BootServices) !system.Framebuffer { fn queryFramebuffer(bs: *uefi.tables.BootServices) !boot_handoff.Framebuffer {
// Enumerate the handles carrying the Graphics Output Protocol. We go through // Enumerate the handles carrying the Graphics Output Protocol. We go through
// handles (rather than locateProtocol) so we can also ask them for their EDID, // handles (rather than locateProtocol) so we can also ask them for their EDID,
// which is what tells us the panel's native resolution. // which is what tells us the panel's native resolution.
@@ -132,7 +132,7 @@ fn queryFramebuffer(bs: *uefi.tables.BootServices) !system.Framebuffer {
/// Map a GOP pixel format to ours. bit_mask / blt_only have no linear 32bpp /// Map a GOP pixel format to ours. bit_mask / blt_only have no linear 32bpp
/// layout we can paint into, so they're rejected. /// layout we can paint into, so they're rejected.
fn pixelFormat(fmt: GraphicsOutput.PixelFormat) !system.PixelFormat { fn pixelFormat(fmt: GraphicsOutput.PixelFormat) !boot_handoff.PixelFormat {
return switch (fmt) { return switch (fmt) {
.red_green_blue_reserved_8_bit_per_color => .rgbx, .red_green_blue_reserved_8_bit_per_color => .rgbx,
.blue_green_red_reserved_8_bit_per_color => .bgrx, .blue_green_red_reserved_8_bit_per_color => .bgrx,
@@ -235,7 +235,7 @@ fn loadKernel(bs: *uefi.tables.BootServices, boot_information: *BootInformation)
// the first set of real page tables and switches CR3 before jumping in. They // the first set of real page tables and switches CR3 before jumping in. They
// carry: an identity map of low RAM (so the loader's own code/stack executing // carry: an identity map of low RAM (so the loader's own code/stack executing
// the switch stays valid, and the low-linked kernel keeps working during the // the switch stays valid, and the low-linked kernel keeps working during the
// staged move), a physmap at system.physmap_base (the kernel's permanent way to // staged move), a physmap at boot_handoff.physmap_base (the kernel's permanent way to
// reach physical memory), and 4 KiB mappings of any higher-half kernel segment. // reach physical memory), and 4 KiB mappings of any higher-half kernel segment.
// The kernel later builds its own precise tables (paging.init) and abandons // The kernel later builds its own precise tables (paging.init) and abandons
// these; they leak as reserved LoaderData (~a handful of frames). // these; they leak as reserved LoaderData (~a handful of frames).
@@ -308,7 +308,7 @@ fn buildBootstrapTables(bs: *uefi.tables.BootServices, boot_information: *const
var address: u64 = 0; var address: u64 = 0;
while (address < 4 * gib) : (address += 2 << 20) { while (address < 4 * gib) : (address += 2 << 20) {
try pool.map2M(pml4, address, address); // identity try pool.map2M(pml4, address, address); // identity
try pool.map2M(pml4, system.physicalToVirtual(address), address); // physmap try pool.map2M(pml4, boot_handoff.physicalToVirtual(address), address); // physmap
} }
// A framebuffer above the 4 GiB window needs its own identity + physmap // A framebuffer above the 4 GiB window needs its own identity + physmap
@@ -319,7 +319,7 @@ fn buildBootstrapTables(bs: *uefi.tables.BootServices, boot_information: *const
const fb_end = fb.base + @as(u64, fb.pitch) * fb.height; const fb_end = fb.base + @as(u64, fb.pitch) * fb.height;
while (p < fb_end) : (p += 2 << 20) { while (p < fb_end) : (p += 2 << 20) {
try pool.map2M(pml4, p, p); try pool.map2M(pml4, p, p);
try pool.map2M(pml4, system.physicalToVirtual(p), p); try pool.map2M(pml4, boot_handoff.physicalToVirtual(p), p);
} }
} }
@@ -328,7 +328,7 @@ fn buildBootstrapTables(bs: *uefi.tables.BootServices, boot_information: *const
// (and 4 KiB-mapping them would collide with the 2 MiB identity leaves), so // (and 4 KiB-mapping them would collide with the 2 MiB identity leaves), so
// only map segments that actually live in the higher half. // only map segments that actually live in the higher half.
for (boot_information.kernel_segments[0..boot_information.kernel_segment_count]) |seg| { for (boot_information.kernel_segments[0..boot_information.kernel_segment_count]) |seg| {
if (seg.virtual < system.kernel_virt_base) continue; if (seg.virtual < boot_handoff.kernel_virt_base) continue;
var off: u64 = 0; var off: u64 = 0;
while (off < seg.pages * page_size) : (off += page_size) { while (off < seg.pages * page_size) : (off += page_size) {
try pool.map4K(pml4, seg.virtual + off, seg.physical + off); try pool.map4K(pml4, seg.virtual + off, seg.physical + off);
@@ -463,14 +463,14 @@ fn loadElf(bs: *uefi.tables.BootServices, image: []u8, boot_information: *BootIn
/// neutral form. Allocating the buffers can itself change the map (invalidating /// neutral form. Allocating the buffers can itself change the map (invalidating
/// the key), so retry until it takes. Both buffers are LoaderData, which survives /// the key), so retry until it takes. Both buffers are LoaderData, which survives
/// the exit, so the returned map stays valid for the kernel. /// the exit, so the returned map stays valid for the kernel.
fn exitBootServices(bs: *uefi.tables.BootServices) !system.MemoryMap { fn exitBootServices(bs: *uefi.tables.BootServices) !boot_handoff.MemoryMap {
var attempts: usize = 0; var attempts: usize = 0;
while (attempts < 8) : (attempts += 1) { while (attempts < 8) : (attempts += 1) {
const info = try bs.getMemoryMapInfo(); const info = try bs.getMemoryMapInfo();
// Spare descriptors to absorb the growth from the allocations below. // Spare descriptors to absorb the growth from the allocations below.
const cap = info.len + 8; const cap = info.len + 8;
const map_buffer = try bs.allocatePool(.loader_data, cap * info.descriptor_size); const map_buffer = try bs.allocatePool(.loader_data, cap * info.descriptor_size);
const regions_buffer = try bs.allocatePool(.loader_data, cap * @sizeOf(system.MemoryRegion)); const regions_buffer = try bs.allocatePool(.loader_data, cap * @sizeOf(boot_handoff.MemoryRegion));
const map = bs.getMemoryMap(map_buffer) catch { const map = bs.getMemoryMap(map_buffer) catch {
_ = bs.freePool(map_buffer.ptr) catch {}; _ = bs.freePool(map_buffer.ptr) catch {};
_ = bs.freePool(regions_buffer.ptr) catch {}; _ = bs.freePool(regions_buffer.ptr) catch {};
@@ -492,8 +492,8 @@ fn exitBootServices(bs: *uefi.tables.BootServices) !system.MemoryMap {
/// into `out` (sized for at least `map.info.len` regions). Adjacent regions of /// into `out` (sized for at least `map.info.len` regions). Adjacent regions of
/// the same kind are coalesced. This is the loader's job precisely so the kernel /// the same kind are coalesced. This is the loader's job precisely so the kernel
/// never sees UEFI's vocabulary — the same seam the framebuffer already uses. /// never sees UEFI's vocabulary — the same seam the framebuffer already uses.
fn convertMemoryMap(map: MemoryMapSlice, out: []u8) system.MemoryMap { fn convertMemoryMap(map: MemoryMapSlice, out: []u8) boot_handoff.MemoryMap {
const regions: [*]system.MemoryRegion = @ptrCast(@alignCast(out.ptr)); const regions: [*]boot_handoff.MemoryRegion = @ptrCast(@alignCast(out.ptr));
// We're about to call boot-services memory `usable`, but our own stack lives // We're about to call boot-services memory `usable`, but our own stack lives
// in it and the kernel starts out running on it. Keep the region holding the // in it and the kernel starts out running on it. Keep the region holding the
// current stack pointer reserved so it's never handed out. // current stack pointer reserved so it's never handed out.
@@ -510,14 +510,14 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) system.MemoryMap {
if (d.number_of_pages == 0) continue; if (d.number_of_pages == 0) continue;
var kind = classify(d); var kind = classify(d);
// The descriptor we're executing on stays reserved (see rsp above). // The descriptor we're executing on stays reserved (see rsp above).
const region_end = d.physical_start + d.number_of_pages * system.page_size; const region_end = d.physical_start + d.number_of_pages * page_size;
if (kind == .usable and rsp >= d.physical_start and rsp < region_end) kind = .reserved; if (kind == .usable and rsp >= d.physical_start and rsp < region_end) kind = .reserved;
// Coalesce with the previous region if it's the same kind and contiguous. // Coalesce with the previous region if it's the same kind and contiguous.
if (count > 0) { if (count > 0) {
const previous = &regions[count - 1]; const previous = &regions[count - 1];
if (previous.kind == kind and if (previous.kind == kind and
previous.base + previous.pages * system.page_size == d.physical_start) previous.base + previous.pages * page_size == d.physical_start)
{ {
previous.pages += d.number_of_pages; previous.pages += d.number_of_pages;
continue; continue;
@@ -544,7 +544,7 @@ fn convertMemoryMap(map: MemoryMapSlice, out: []u8) system.MemoryMap {
/// ever the firmware's (the one live piece, our stack, is reserved by the caller). /// ever the firmware's (the one live piece, our stack, is reserved by the caller).
/// Anything unrecognised is `reserved` — the safe default; our own LoaderData (the /// Anything unrecognised is `reserved` — the safe default; our own LoaderData (the
/// kernel image and these buffers) lands there and stays reserved. /// kernel image and these buffers) lands there and stays reserved.
fn classify(d: *const uefi.tables.MemoryDescriptor) system.MemoryKind { fn classify(d: *const uefi.tables.MemoryDescriptor) boot_handoff.MemoryKind {
if (!d.attribute.wb) return .mmio; if (!d.attribute.wb) return .mmio;
return switch (d.type) { return switch (d.type) {
.conventional_memory, .boot_services_code, .boot_services_data => .usable, .conventional_memory, .boot_services_code, .boot_services_data => .usable,
+51 -27
View File
@@ -95,11 +95,23 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
// Shared handoff definitions (BootInformation, Framebuffer, ...). No target is set, // The three shared contracts, each with its own audience so every import
// so the module inherits the target of whichever binary imports it — the // declares which one it speaks (no target is set, so each inherits the target of
// freestanding kernel or the UEFI bootloader. // whichever binary imports it). See docs/coding-standards.md.
const system_module = b.addModule("system", .{ // boot-handoff : loader <-> kernel (BootInformation, framebuffer, VM layout)
.root_source_file = b.path("system/system.zig"), // abi : kernel <-> user, core (SystemCall, mmap prot flags, page_size)
// device-abi : kernel <-> user, devices (DeviceDescriptor, DeviceClass, ...)
const boot_handoff_module = b.addModule("boot-handoff", .{
.root_source_file = b.path("system/boot-handoff.zig"),
});
const abi_module = b.addModule("abi", .{
.root_source_file = b.path("system/abi.zig"),
});
// The devices sub-project's public interface (the flat wire types), exposed as
// its own module like vfs-protocol — importable by user space, unlike the
// kernel-internal device model it also feeds (system/devices/device-model.zig).
const device_abi_module = b.addModule("device-abi", .{
.root_source_file = b.path("system/devices/device-abi.zig"),
}); });
// Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of // Kernel tunables (maximum_cpus, stack sizes, tick rate). A dependency-free module of
@@ -115,7 +127,8 @@ pub fn build(b: *std.Build) void {
const architecture_module = b.addModule("architecture", .{ const architecture_module = b.addModule("architecture", .{
.root_source_file = b.path("system/kernel/architecture/x86_64/cpu.zig"), .root_source_file = b.path("system/kernel/architecture/x86_64/cpu.zig"),
.imports = &.{ .imports = &.{
.{ .name = "system", .module = system_module }, // paging uses the shared BootInformation/memory-map types .{ .name = "boot-handoff", .module = boot_handoff_module }, // paging uses BootInformation/memory-map + physicalToVirtual
.{ .name = "abi", .module = abi_module }, // paging works in page_size units
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus, ist_stack_size, timer_hz .{ .name = "parameters", .module = parameters_module }, // maximum_cpus, ist_stack_size, timer_hz
}, },
}); });
@@ -134,7 +147,9 @@ pub fn build(b: *std.Build) void {
const platform_module = b.addModule("platform", .{ const platform_module = b.addModule("platform", .{
.root_source_file = b.path("system/devices/platform.zig"), .root_source_file = b.path("system/devices/platform.zig"),
.imports = &.{ .imports = &.{
.{ .name = "system", .module = system_module }, // BootInformation (carries the ACPI RSDP) .{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units
.{ .name = "device-abi", .module = device_abi_module }, // device-model's DeviceClass/ResourceKind live here
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool) .{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
}, },
}); });
@@ -152,13 +167,16 @@ pub fn build(b: *std.Build) void {
// heap, IPC helpers, the process start shim, device access. This is the stable // heap, IPC helpers, the process start shim, device access. This is the stable
// application ABI; POSIX compatibility is a separate library on top (see below). // application ABI; POSIX compatibility is a separate library on top (see below).
// Compiled into every user binary (see addUserBinary), so it inherits each exe's // 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` // `.large` code model — do NOT set a target/code_model here. It imports `abi`
// for the shared SystemCall numbers and re-exports `vfs-protocol` for the VFS // for the shared SystemCall numbers / mmap flags, `device-abi` for the device
// server. // types its `device` helper wraps, and re-exports `vfs-protocol` for the VFS
// server. It never touches `boot-handoff` — user space has no business with the
// loader↔kernel handoff.
const runtime_module = b.addModule("runtime", .{ const runtime_module = b.addModule("runtime", .{
.root_source_file = b.path("library/runtime/runtime.zig"), .root_source_file = b.path("library/runtime/runtime.zig"),
.imports = &.{ .imports = &.{
.{ .name = "system", .module = system_module }, .{ .name = "abi", .module = abi_module },
.{ .name = "device-abi", .module = device_abi_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module }, .{ .name = "vfs-protocol", .module = vfs_protocol_module },
}, },
}); });
@@ -172,7 +190,6 @@ pub fn build(b: *std.Build) void {
.imports = &.{ .imports = &.{
.{ .name = "runtime", .module = runtime_module }, .{ .name = "runtime", .module = runtime_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module }, .{ .name = "vfs-protocol", .module = vfs_protocol_module },
.{ .name = "system", .module = system_module },
}, },
}); });
@@ -212,7 +229,9 @@ pub fn build(b: *std.Build) void {
.stack_check = false, // stack-probe calls have no runtime to land in .stack_check = false, // stack-probe calls have no runtime to land in
.stack_protector = false, .stack_protector = false,
.imports = &.{ .imports = &.{
.{ .name = "system", .module = system_module }, .{ .name = "boot-handoff", .module = boot_handoff_module },
.{ .name = "abi", .module = abi_module },
.{ .name = "device-abi", .module = device_abi_module },
.{ .name = "architecture", .module = architecture_module }, .{ .name = "architecture", .module = architecture_module },
.{ .name = "platform", .module = platform_module }, .{ .name = "platform", .module = platform_module },
.{ .name = "parameters", .module = parameters_module }, .{ .name = "parameters", .module = parameters_module },
@@ -305,7 +324,8 @@ pub fn build(b: *std.Build) void {
}), }),
.optimize = optimize, .optimize = optimize,
.imports = &.{ .imports = &.{
.{ .name = "system", .module = system_module }, // The bootloader speaks only the handoff contract — never the user ABI.
.{ .name = "boot-handoff", .module = boot_handoff_module },
}, },
}), }),
}); });
@@ -401,18 +421,22 @@ pub fn build(b: *std.Build) void {
// } // }
// Tests run on the host. The kernel and bootloader target freestanding/UEFI // Tests run on the host. The kernel and bootloader target freestanding/UEFI
// and can't be executed natively, so only the shared module is unit-tested // and can't be executed natively, so only the shared contracts are unit-tested
// here (compiled for the host rather than inheriting a freestanding target). // here (compiled for the host rather than inheriting a freestanding target) —
const mod_tests = b.addTest(.{ // which also compile-checks that the three-way split stays self-consistent.
.root_module = b.createModule(.{
.root_source_file = b.path("system/system.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step); for ([_][]const u8{
"system/boot-handoff.zig",
"system/abi.zig",
"system/devices/device-abi.zig",
}) |root| {
const mod_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path(root),
.target = target,
.optimize = optimize,
}),
});
test_step.dependOn(&b.addRunArtifact(mod_tests).step);
}
} }
+6 -2
View File
@@ -146,11 +146,13 @@ A sub-project's extra files are reached through the module, never as separate pa
``` ```
system/ → /system danos's own internals (the self-representation) system/ → /system danos's own internals (the self-representation)
system.zig the kernel↔user ABI contract (the `system` module) boot-handoff.zig the loader↔kernel contract (the `boot-handoff` module)
abi.zig the core kernel↔user ABI (the `abi` module)
parameters.zig initial-ramdisk.zig shared contracts parameters.zig initial-ramdisk.zig shared contracts
kernel/ IPC, memory, scheduling, the private syscall dispatch kernel/ IPC, memory, scheduling, the private syscall dispatch
architecture/x86_64/ the `architecture` module (never named by generic code) architecture/x86_64/ the `architecture` module (never named by generic code)
devices/ the device model /system/devices reflects (+ aml/) devices/ the device model /system/devices reflects (+ aml/)
device-abi.zig the device wire types (the `device-abi` module)
drivers/ hpet/ bus/ one sub-project per driver → /system/drivers drivers/ hpet/ bus/ one sub-project per driver → /system/drivers
services/ init/ vfs/ device-manager/ system servers → /system/services (vfs/ holds services/ init/ vfs/ device-manager/ system servers → /system/services (vfs/ holds
vfs.zig, vfs-test.zig, protocol.zig) vfs.zig, vfs-test.zig, protocol.zig)
@@ -177,7 +179,9 @@ appears in the private-ABI path.
|------|------| |------|------|
| Boot methods (one per way of booting the kernel) | `boot/` — `efi.zig` (UEFI) → `BOOTX64.efi` | | Boot methods (one per way of booting the kernel) | `boot/` — `efi.zig` (UEFI) → `BOOTX64.efi` |
| Kernel entry, panic, bring-up | `system/kernel/kernel.zig` | | Kernel entry, panic, bring-up | `system/kernel/kernel.zig` |
| Shared loader↔kernel contract (`BootInfo`, `Framebuffer`, `MemoryMap`, `Syscall`, ABI) | `system/system.zig` | | Loader↔kernel handoff (`BootInfo`, `Framebuffer`, `MemoryMap`, VM layout) | `system/boot-handoff.zig` |
| Core kernel↔user ABI (`SystemCall`, mmap prot flags, `page_size`) | `system/abi.zig` |
| Device wire types (`DeviceDescriptor`, `DeviceClass`, …) | `system/devices/device-abi.zig` |
| Physical frame allocator | `system/kernel/pmm.zig` | | Physical frame allocator | `system/kernel/pmm.zig` |
| Kernel heap (`std.mem.Allocator`) | `system/kernel/heap.zig` | | Kernel heap (`std.mem.Allocator`) | `system/kernel/heap.zig` |
| Scheduler (fixed-priority preemptive; blocking, wait queues) | `system/kernel/scheduler.zig` | | Scheduler (fixed-priority preemptive; blocking, wait queues) | `system/kernel/scheduler.zig` |
+2 -2
View File
@@ -16,7 +16,7 @@ the RSDT's address is a field *inside* the RSDP. The platform follows that point
UEFI configuration table UEFI configuration table
│ the loader reads the RSDP's physical address │ the loader reads the RSDP's physical address
▼ ▼
BootInfo.acpi_rsdp (u64, in the shared `system` module) system/system.zig BootInfo.acpi_rsdp (u64, in the loader↔kernel handoff) system/boot-handoff.zig
│ the kernel forwards the whole BootInfo │ the kernel forwards the whole BootInfo
▼ ▼
platform.discover(boot_info, …) system/devices/platform.zig platform.discover(boot_info, …) system/devices/platform.zig
@@ -44,7 +44,7 @@ the [memory map](memory-map.md).
The loader can't just call the device module: the bootloader binary and the kernel The loader can't just call the device module: the bootloader binary and the kernel
binary are compiled separately, and **the loader isn't linked against the `platform` binary are compiled separately, and **the loader isn't linked against the `platform`
module at all** (it imports only the shared `system` module). So instead of a call, it module at all** (it imports only the `boot-handoff` contract). So instead of a call, it
deposits a value in the handoff struct: deposits a value in the handoff struct:
```zig ```zig
+2 -1
View File
@@ -22,7 +22,8 @@ say.*
## The capability: claim before touch ## The capability: claim before touch
The five driver syscalls (`system/system.zig`, dispatched in `system/kernel/process.zig`): The driver syscall numbers (`system/abi.zig`) with the device types they carry
(`system/devices/device-abi.zig`), dispatched in `system/kernel/process.zig`:
| # | Call | Meaning | | # | Call | Meaning |
|---|------|---------| |---|------|---------|
+8 -6
View File
@@ -124,7 +124,7 @@ entirely ours.
### 4. Jump to the kernel ### 4. Jump to the kernel
```zig ```zig
const kernel: *const fn (*const BootInfo) callconv(system.kernel_abi) noreturn = const kernel: *const fn (*const BootInfo) callconv(boot_handoff.kernel_abi) noreturn =
@ptrFromInt(entry); @ptrFromInt(entry);
kernel(&boot_info); kernel(&boot_info);
``` ```
@@ -142,17 +142,19 @@ kernel is freestanding and uses the **SysV AMD64** convention (first argument in
read garbage. read garbage.
So both sides pin the convention explicitly to SysV via the shared So both sides pin the convention explicitly to SysV via the shared
`system.kernel_abi` (defined in `system/system.zig`). The loader's function-pointer type `boot_handoff.kernel_abi` (defined in `system/boot-handoff.zig`). The loader's
and the kernel's `_start` both reference it, so the pointer lands in the register function-pointer type and the kernel's `_start` both reference it, so the pointer lands
the kernel expects. This is the whole reason `kernel_abi` lives in the shared in the register the kernel expects. This is the whole reason `kernel_abi` lives in the
`system` module: it's a contract both binaries must agree on. See shared `boot-handoff` module: it's a contract both binaries must agree on. See
[sysv.md](sysv.md) for what "SysV" means and where else it shows up. [sysv.md](sysv.md) for what "SysV" means and where else it shows up.
## The handoff contract ## The handoff contract
The loader and kernel are two *separate* binaries built for two different targets, The loader and kernel are two *separate* binaries built for two different targets,
so everything they exchange must have an identically-defined memory layout. That's so everything they exchange must have an identically-defined memory layout. That's
what `system/system.zig` provides — imported by both as the `system` module: what `system/boot-handoff.zig` provides — imported by both as the `boot-handoff` module.
It is *only* the handoff: the kernel↔user ABI (`system/abi.zig`) and the device types
(`system/devices/device-abi.zig`) are separate contracts the bootloader never sees.
- `BootInfo` — the top-level struct passed to the kernel (currently just the - `BootInfo` — the top-level struct passed to the kernel (currently just the
framebuffer; this is where future handoff data like the memory map will go). framebuffer; this is where future handoff data like the memory map will go).
+1 -1
View File
@@ -12,7 +12,7 @@ exactly what `Console.pixel` does:
self.rowPtr(y)[x] = color; // system/kernel/console.zig self.rowPtr(y)[x] = color; // system/kernel/console.zig
``` ```
Our `Framebuffer` struct (`system/system.zig`) is the four facts you need to Our `Framebuffer` struct (`system/boot-handoff.zig`) is the four facts you need to
address it: address it:
| Field | Meaning | | Field | Meaning |
+1 -1
View File
@@ -58,7 +58,7 @@ screen. `console.write` is a no-op when the firmware gave us no framebuffer.
A framebuffer is not guaranteed — a headless server exposes no UEFI Graphics Output A framebuffer is not guaranteed — a headless server exposes no UEFI Graphics Output
Protocol. That used to be *fatal* (the loader failed the boot). Now the loader hands Protocol. That used to be *fatal* (the loader failed the boot). Now the loader hands
over a "no framebuffer" descriptor (`base == 0`) rather than failing, and over a "no framebuffer" descriptor (`base == 0`) rather than failing, and
`Framebuffer.present()` (in `system/system.zig`) gates every on-screen path. A headless, `Framebuffer.present()` (in `system/boot-handoff.zig`) gates every on-screen path. A headless,
serial-less machine boots and runs correctly — it just goes quiet. serial-less machine boots and runs correctly — it just goes quiet.
## Last-resort channels (no text output at all) ## Last-resort channels (no text output at all)
+1 -1
View File
@@ -27,7 +27,7 @@ danos's own neutral format, and the kernel only ever sees that.**
## The neutral format ## The neutral format
Defined in `system/system.zig`, the shared loader↔kernel contract: Defined in `system/boot-handoff.zig`, the shared loader↔kernel contract:
```zig ```zig
pub const MemoryKind = enum(u32) { pub const MemoryKind = enum(u32) {
+1 -1
View File
@@ -27,7 +27,7 @@ address to the low load address in its bootstrap tables and jumps in). The entir
alongside a **physmap** — a straight window onto all of physical memory at alongside a **physmap** — a straight window onto all of physical memory at
`physmap_base + phys`. Wherever the kernel needs to touch a physical address (a `physmap_base + phys`. Wherever the kernel needs to touch a physical address (a
page-table frame, an ACPI table, a device register), it adds that constant: page-table frame, an ACPI table, a device register), it adds that constant:
`system.physToVirt(phys)`. The layout constants live in `system/system.zig`: `system.physToVirt(phys)`. The layout constants live in `system/boot-handoff.zig`:
| region | virtual base | PML4 slot | | region | virtual base | PML4 slot |
|--------|--------------|-----------| |--------|--------------|-----------|
+1 -1
View File
@@ -1,6 +1,6 @@
# SysV: the kernel's calling convention # SysV: the kernel's calling convention
Several places in danos say "the kernel is SysV" — most visibly `system/system.zig`: Several places in danos say "the kernel is SysV" — most visibly `system/boot-handoff.zig`:
```zig ```zig
pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} }; pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} };
+3 -2
View File
@@ -8,8 +8,9 @@ without a human staring at the screen.
There are two layers: There are two layers:
- **Host unit tests** (`zig build test`) — for pure, platform-independent logic in - **Host unit tests** (`zig build test`) — for pure, platform-independent logic in
the shared `system` module (the handoff layout in `system/system.zig`). These compile the shared contracts (`system/boot-handoff.zig`, `system/abi.zig`,
for the host and run natively. `system/devices/device-abi.zig`), which also compile-checks the three-way split
stays self-consistent. These compile for the host and run natively.
- **QEMU integration tests** (`python3 test/qemu_test.py`) — boot the real kernel - **QEMU integration tests** (`python3 test/qemu_test.py`) — boot the real kernel
and check its behaviour. This is the interesting part. and check its behaviour. This is the interesting part.
-1
View File
@@ -6,7 +6,6 @@
const std = @import("std"); const std = @import("std");
const protocol = @import("vfs-protocol"); const protocol = @import("vfs-protocol");
const ipc = @import("runtime").ipc; const ipc = @import("runtime").ipc;
const system = @import("system");
pub const O_CREAT = protocol.create; pub const O_CREAT = protocol.create;
pub const SEEK_SET: u32 = 0; pub const SEEK_SET: u32 = 0;
+6 -6
View File
@@ -3,13 +3,13 @@
//! ownership of its hardware; the claim is the capability the kernel checks before //! ownership of its hardware; the claim is the capability the kernel checks before
//! mapping registers or routing an IRQ. //! mapping registers or routing an IRQ.
const system = @import("system"); const device_abi = @import("device-abi");
const sc = @import("system-call.zig"); const sc = @import("system-call.zig");
pub const DeviceDescriptor = system.DeviceDescriptor; pub const DeviceDescriptor = device_abi.DeviceDescriptor;
pub const ResourceDescriptor = system.ResourceDescriptor; pub const ResourceDescriptor = device_abi.ResourceDescriptor;
pub const DeviceClass = system.DeviceClass; pub const DeviceClass = device_abi.DeviceClass;
pub const ResourceKind = system.ResourceKind; pub const ResourceKind = device_abi.ResourceKind;
inline fn failed(r: usize) bool { inline fn failed(r: usize) bool {
return r > ~@as(usize, 0) - 4095; return r > ~@as(usize, 0) - 4095;
@@ -33,7 +33,7 @@ pub fn mmioMap(device_id: u64, resource_index: u64) ?usize {
} }
/// `DeviceDescriptor.parent` for a device with no parent. /// `DeviceDescriptor.parent` for a device with no parent.
pub const no_parent = system.no_parent; pub const no_parent = device_abi.no_parent;
/// Publish `descriptor` as a child of `parent_id`, which this process must have claimed. /// Publish `descriptor` as a child of `parent_id`, which this process must have claimed.
/// Returns the new device id. The child is left unclaimed, so whichever driver owns /// Returns the new device id. The child is left unclaimed, so whichever driver owns
+2 -2
View File
@@ -13,10 +13,10 @@
//! lock and larger alignments come when user programs gain threads. //! lock and larger alignments come when user programs gain threads.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const abi = @import("abi");
const system_calls = @import("system.zig"); const system_calls = @import("system.zig");
const page_size = system.page_size; const page_size = abi.page_size;
/// A block header, at the start of every block; while free it also links the /// A block header, at the start of every block; while free it also links the
/// free list via `next`. /// free list via `next`.
+5 -5
View File
@@ -3,7 +3,7 @@
//! reached this way. The server side (`replyWait`, which returns two values) is //! reached this way. The server side (`replyWait`, which returns two values) is
//! added with the first server binary. //! added with the first server binary.
const system = @import("system"); const abi = @import("abi");
const sc = @import("system-call.zig"); const sc = @import("system-call.zig");
/// A small-int handle into the calling process's handle table. /// A small-int handle into the calling process's handle table.
@@ -30,13 +30,13 @@ pub fn createEndpoint() ?Handle {
} }
/// Publish endpoint `h` under a well-known service id so other processes find it. /// Publish endpoint `h` under a well-known service id so other processes find it.
pub fn register(id: system.ServiceId, h: Handle) bool { pub fn register(id: abi.ServiceId, h: Handle) bool {
return !failed(sc.systemCall2(.ipc_register, @intFromEnum(id), h)); return !failed(sc.systemCall2(.ipc_register, @intFromEnum(id), h));
} }
/// Find the endpoint published under `id`, installing a handle to it in this /// Find the endpoint published under `id`, installing a handle to it in this
/// process. /// process.
pub fn lookup(id: system.ServiceId) ?Handle { pub fn lookup(id: abi.ServiceId) ?Handle {
const r = sc.systemCall1(.ipc_lookup, @intFromEnum(id)); const r = sc.systemCall1(.ipc_lookup, @intFromEnum(id));
return if (failed(r)) null else r; return if (failed(r)) null else r;
} }
@@ -53,7 +53,7 @@ pub fn call(h: Handle, message: []const u8, reply: []u8) CallError!usize {
/// Set in `Received.badge` when what arrived is an asynchronous notification — a /// Set in `Received.badge` when what arrived is an asynchronous notification — a
/// bound device interrupt — rather than a client's message. The low bits carry the /// bound device interrupt — rather than a client's message. The low bits carry the
/// GSI. See `isNotification`. /// GSI. See `isNotification`.
pub const notify_badge_bit: u64 = system.notify_badge_bit; pub const notify_badge_bit: u64 = abi.notify_badge_bit;
/// The result of a `replyWait`: the request length and the sender's badge (a /// The result of a `replyWait`: the request length and the sender's badge (a
/// task id, or an IRQ notification if the high bit is set). /// task id, or an IRQ notification if the high bit is set).
@@ -84,7 +84,7 @@ pub fn replyWait(h: Handle, reply: []const u8, receive: []u8) Received {
asm volatile ("syscall" asm volatile ("syscall"
: [rax] "={rax}" (rax), : [rax] "={rax}" (rax),
[rdx] "+{rdx}" (rdx), [rdx] "+{rdx}" (rdx),
: [n] "{rax}" (@intFromEnum(system.SystemCall.ipc_reply_wait)), : [n] "{rax}" (@intFromEnum(abi.SystemCall.ipc_reply_wait)),
[a0] "{rdi}" (h), [a0] "{rdi}" (h),
[a1] "{rsi}" (@intFromPtr(reply.ptr)), [a1] "{rsi}" (@intFromPtr(reply.ptr)),
[a3] "{r10}" (@intFromPtr(receive.ptr)), [a3] "{r10}" (@intFromPtr(receive.ptr)),
+2 -2
View File
@@ -6,8 +6,8 @@
//! Note argument #3 goes in **r10, not rcx** — rcx is unavailable across the //! Note argument #3 goes in **r10, not rcx** — rcx is unavailable across the
//! instruction, so the kernel reads the 4th argument from r10. //! instruction, so the kernel reads the 4th argument from r10.
const system = @import("system"); const abi = @import("abi");
const SystemCall = system.SystemCall; const SystemCall = abi.SystemCall;
pub inline fn systemCall0(n: SystemCall) usize { pub inline fn systemCall0(n: SystemCall) usize {
return asm volatile ("syscall" return asm volatile ("syscall"
+5 -5
View File
@@ -1,15 +1,15 @@
//! Typed system_call surface for user space — thin wrappers over the raw `system_call` //! Typed system_call surface for user space — thin wrappers over the raw `system_call`
//! stubs, one per kernel call. Numbers come from `system.SystemCall`, the single //! stubs, one per kernel call. Numbers come from `abi.SystemCall`, the single
//! source of truth shared with the kernel dispatcher. //! source of truth shared with the kernel dispatcher.
const system = @import("system"); const abi = @import("abi");
const sc = @import("system-call.zig"); const sc = @import("system-call.zig");
/// `mmap` protection flags (matching the usual C bit values). Grants are always /// `mmap` protection flags (matching the usual C bit values). Grants are always
/// readable+writable today; the kernel does not yet honour finer prot. /// readable+writable today; the kernel does not yet honour finer prot.
pub const PROT_READ: usize = system.prot_read; pub const PROT_READ: usize = abi.prot_read;
pub const PROT_WRITE: usize = system.prot_write; pub const PROT_WRITE: usize = abi.prot_write;
pub const PROT_EXEC: usize = system.prot_exec; pub const PROT_EXEC: usize = abi.prot_exec;
/// Give up the rest of this quantum. /// Give up the rest of this quantum.
pub fn yield() void { pub fn yield() void {
+60
View File
@@ -0,0 +1,60 @@
//! The **kernel ↔ user** ABI: the core contract every user program speaks to the
//! kernel — the system_call numbers, `mmap` protection flags, the page size those
//! calls work in, and the IPC name-registry ids and notification bit. Shared by the
//! kernel dispatcher (system/kernel/process.zig) and the user runtime library
//! (library/runtime/), so the two can never drift.
//!
//! This is the *core* ABI; the device half — `DeviceDescriptor` and friends, which
//! also cross this boundary — lives with the device sub-project as [[device-abi]]
//! (system/devices/device-abi.zig). The loader↔kernel handoff is [[boot-handoff]].
/// Page size every `mmap`/`munmap` grant and the boot memory map are measured in.
/// 4 KiB on every architecture danos targets so far. Part of the ABI because user
/// code aligns to it (grants are page-granular) and the kernel guarantees it.
pub const page_size = 4096;
/// The kernel system_call numbers — the single source of truth shared by the kernel
/// dispatcher (system/kernel/process.zig) and the user runtime library, so the two
/// can never drift. The set is deliberately microkernel-minimal: file/device I/O
/// is not here — it lives in user-space servers reached through the IPC calls.
/// The table grows one milestone at a time; see docs/syscall.md.
pub const SystemCall = enum(u64) {
exit = 0, // exit(code): end the calling process
yield = 1, // yield(): give up the rest of this quantum
debug_write = 2, // debug_write(ptr, len): raw bytes to the kernel log (bring-up only)
sleep = 3, // sleep(ms): block the caller for ms milliseconds
mmap = 4, // mmap(len, prot) -> base: grant zeroed, page-aligned user pages
munmap = 5, // munmap(base, len): release pages from a prior mmap
create_endpoint = 6, // create_endpoint() -> handle: a new IPC endpoint
ipc_register = 7, // ipc_register(service_id, handle): publish an endpoint by well-known id
ipc_lookup = 8, // ipc_lookup(service_id) -> handle: find a published endpoint
ipc_call = 9, // ipc_call(h, message, len, reply, cap) -> reply_len: send + block for reply
ipc_reply_wait = 10, // ipc_reply_wait(h, reply, len, receive, cap) -> receive_len (+badge in rdx)
device_enumerate = 11, // device_enumerate(buffer, maximum) -> count: snapshot the device table
device_claim = 12, // device_claim(id) -> ok: take exclusive ownership of a device
mmio_map = 13, // mmio_map(id, resource_index) -> vaddr: map a claimed device's MMIO into this AS
irq_bind = 14, // irq_bind(id, resource_index, endpoint): deliver a device IRQ as an IPC notification
irq_ack = 15, // irq_ack(id, resource_index): re-arm a bound IRQ after servicing it
device_register = 16, // device_register(parent_id, descriptor) -> id: publish a child of a device you claimed
_,
};
/// Set in the badge returned by `ipc_reply_wait` when what arrived is an
/// **asynchronous notification** (today: a device interrupt bound with `irq_bind`)
/// rather than a message from a client. There is no payload and no reply owed; the
/// low bits carry the source, a GSI. Shared so the kernel's ISR and the driver's
/// event loop can't disagree about which bit means "the hardware spoke".
pub const notify_badge_bit: u64 = 1 << 63;
/// Well-known IPC service ids for the bootstrap name registry (create_endpoint +
/// ipc_register/ipc_lookup). Small integers, so no string interning is needed
/// during bring-up. The VFS server registers under `vfs`; clients look it up.
pub const ServiceId = enum(u32) {
vfs = 1,
_,
};
/// Protection flags for `mmap` (matching the usual C bit values).
pub const prot_read: u64 = 1;
pub const prot_write: u64 = 2;
pub const prot_exec: u64 = 4;
+8 -108
View File
@@ -1,8 +1,11 @@
//! Shared definitions that form the contract between a bootloader //! The **loader ↔ kernel** contract: everything a bootloader (boot/, e.g. efi.zig
//! (boot/, e.g. efi.zig built as BOOTX64.efi) and the kernel (system/kernel/kernel.zig). //! built as BOOTX64.efi) and the kernel (system/kernel/kernel.zig) must agree on to
//! hand control over — the handoff structures the loader fills in, plus the kernel's
//! virtual-memory layout and the physical↔virtual addressing both sides use.
//! //!
//! Both binaries import this as the "danos" module, so the handoff layout is //! Both binaries import this as the `boot-handoff` module, so the layout is defined
//! defined in exactly one place. //! in exactly one place. **User space never sees this** — the kernel↔user contract is
//! [[abi]] (system/abi.zig); device types are [[device-abi]] (system/devices/device-abi.zig).
const std = @import("std"); const std = @import("std");
@@ -41,10 +44,6 @@ pub const Framebuffer = extern struct {
} }
}; };
/// Page size the memory map is measured in. 4 KiB on every architecture danos
/// targets so far.
pub const page_size = 4096;
/// The kernel's virtual-memory layout (higher-half). The kernel is linked at /// The kernel's virtual-memory layout (higher-half). The kernel is linked at
/// `kernel_virt_base` but loaded at a low physical address; all of RAM (and the /// `kernel_virt_base` but loaded at a low physical address; all of RAM (and the
/// device MMIO windows) is also mapped at `physmap_base + physical`, so the kernel /// device MMIO windows) is also mapped at `physmap_base + physical`, so the kernel
@@ -58,105 +57,6 @@ pub const page_size = 4096;
pub const physmap_base: u64 = 0xFFFF_8800_0000_0000; pub const physmap_base: u64 = 0xFFFF_8800_0000_0000;
pub const kernel_virt_base: u64 = 0xFFFF_FFFF_8000_0000; pub const kernel_virt_base: u64 = 0xFFFF_FFFF_8000_0000;
/// The kernel system_call numbers — the single source of truth shared by the kernel
/// dispatcher (system/kernel/process.zig) and the user runtime library, so the two
/// can never drift. The set is deliberately microkernel-minimal: file/device I/O
/// is not here — it lives in user-space servers reached through the IPC calls.
/// The table grows one milestone at a time; see docs/syscall.md.
pub const SystemCall = enum(u64) {
exit = 0, // exit(code): end the calling process
yield = 1, // yield(): give up the rest of this quantum
debug_write = 2, // debug_write(ptr, len): raw bytes to the kernel log (bring-up only)
sleep = 3, // sleep(ms): block the caller for ms milliseconds
mmap = 4, // mmap(len, prot) -> base: grant zeroed, page-aligned user pages
munmap = 5, // munmap(base, len): release pages from a prior mmap
create_endpoint = 6, // create_endpoint() -> handle: a new IPC endpoint
ipc_register = 7, // ipc_register(service_id, handle): publish an endpoint by well-known id
ipc_lookup = 8, // ipc_lookup(service_id) -> handle: find a published endpoint
ipc_call = 9, // ipc_call(h, message, len, reply, cap) -> reply_len: send + block for reply
ipc_reply_wait = 10, // ipc_reply_wait(h, reply, len, receive, cap) -> receive_len (+badge in rdx)
device_enumerate = 11, // device_enumerate(buffer, maximum) -> count: snapshot the device table
device_claim = 12, // device_claim(id) -> ok: take exclusive ownership of a device
mmio_map = 13, // mmio_map(id, resource_index) -> vaddr: map a claimed device's MMIO into this AS
irq_bind = 14, // irq_bind(id, resource_index, endpoint): deliver a device IRQ as an IPC notification
irq_ack = 15, // irq_ack(id, resource_index): re-arm a bound IRQ after servicing it
device_register = 16, // device_register(parent_id, descriptor) -> id: publish a child of a device you claimed
_,
};
/// Set in the badge returned by `ipc_reply_wait` when what arrived is an
/// **asynchronous notification** (today: a device interrupt bound with `irq_bind`)
/// rather than a message from a client. There is no payload and no reply owed; the
/// low bits carry the source, a GSI. Shared so the kernel's ISR and the driver's
/// event loop can't disagree about which bit means "the hardware spoke".
pub const notify_badge_bit: u64 = 1 << 63;
/// A device class, mirroring system/devices/device-model.zig's `DeviceClass` **in order**
/// (its `@intFromEnum` values cross the system_call boundary in `DeviceDescriptor.class`).
/// Keep the two in sync.
pub const DeviceClass = enum(u32) {
root,
processor,
interrupt_controller,
timer,
pci_host_bridge,
pci_device,
acpi_device,
unknown,
};
/// A resource kind, mirroring system/devices/device-model.zig's `ResourceKind` in order.
pub const ResourceKind = enum(u32) {
memory,
io_port,
irq,
bus_range,
};
/// One device resource, as handed to a user-space driver (flat, extern).
pub const ResourceDescriptor = extern struct {
kind: u64, // a ResourceKind value
start: u64,
len: u64,
};
pub const maximum_device_resources = 8;
/// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree.
pub const no_parent: u64 = ~@as(u64, 0);
/// A device, as snapshotted for user space by `device_enumerate`. A driver scans
/// these to find the hardware it owns, claims it, and maps its MMIO.
///
/// `parent` makes the table a tree rather than a list, which is what a **bus driver**
/// needs: it claims the bus, finds the devices below it, and publishes any it
/// discovers itself with `device_register`. A registered child's resources must lie
/// within its parent's (the kernel enforces this) — that containment is what makes
/// delegation safe, since a device descriptor is otherwise a licence to map physical
/// memory.
pub const DeviceDescriptor = extern struct {
id: u64,
parent: u64, // a device id, or `no_parent`
class: u64, // a DeviceClass value
hid_len: u64,
resource_count: u64,
hid: [8]u8,
resources: [maximum_device_resources]ResourceDescriptor,
};
/// Well-known IPC service ids for the bootstrap name registry (create_endpoint +
/// ipc_register/ipc_lookup). Small integers, so no string interning is needed
/// during bring-up. The VFS server registers under `vfs`; clients look it up.
pub const ServiceId = enum(u32) {
vfs = 1,
_,
};
/// Protection flags for `mmap` (matching the usual C bit values).
pub const prot_read: u64 = 1;
pub const prot_write: u64 = 2;
pub const prot_exec: u64 = 4;
/// Physical address -> its virtual address in the physmap. The single way the /// Physical address -> its virtual address in the physmap. The single way the
/// kernel dereferences a physical address once paging is up. /// kernel dereferences a physical address once paging is up.
/// ///
@@ -204,7 +104,7 @@ pub const MemoryKind = enum(u32) {
/// firmware's variable descriptor-stride to worry about. /// firmware's variable descriptor-stride to worry about.
pub const MemoryRegion = extern struct { pub const MemoryRegion = extern struct {
base: u64, // physical start address base: u64, // physical start address
pages: u64, // length in `page_size` units pages: u64, // length in 4 KiB pages (the [[abi]] `page_size` unit)
kind: MemoryKind, kind: MemoryKind,
_pad: u32 = 0, _pad: u32 = 0,
}; };
+13 -12
View File
@@ -15,7 +15,8 @@
//! the `Hal.mapMmio` callback the caller supplies (the architecture VMM's map primitive). //! the `Hal.mapMmio` callback the caller supplies (the architecture VMM's map primitive).
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const parameters = @import("parameters"); const parameters = @import("parameters");
const device_model = @import("device-model.zig"); const device_model = @import("device-model.zig");
const aml = @import("aml/aml.zig"); const aml = @import("aml/aml.zig");
@@ -147,7 +148,7 @@ var aml_block_count: usize = 0;
fn addAmlBlock(sdt_physical: u64) void { fn addAmlBlock(sdt_physical: u64) void {
if (aml_block_count >= aml_block_physical.len or sdt_physical == 0) return; if (aml_block_count >= aml_block_physical.len or sdt_physical == 0) return;
const h: *const SystemDescriptorTableHeader = @ptrFromInt(system.physicalToVirtual(sdt_physical)); const h: *const SystemDescriptorTableHeader = @ptrFromInt(boot_handoff.physicalToVirtual(sdt_physical));
if (h.length <= @sizeOf(SystemDescriptorTableHeader)) return; if (h.length <= @sizeOf(SystemDescriptorTableHeader)) return;
aml_block_physical[aml_block_count] = sdt_physical + @sizeOf(SystemDescriptorTableHeader); aml_block_physical[aml_block_count] = sdt_physical + @sizeOf(SystemDescriptorTableHeader);
aml_block_len[aml_block_count] = h.length - @sizeOf(SystemDescriptorTableHeader); aml_block_len[aml_block_count] = h.length - @sizeOf(SystemDescriptorTableHeader);
@@ -376,14 +377,14 @@ pub fn discover(rsdp_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
dsdt_physical = 0; dsdt_physical = 0;
aml_block_count = 0; aml_block_count = 0;
const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(system.physicalToVirtual(rsdp_physical)); const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(boot_handoff.physicalToVirtual(rsdp_physical));
if (!std.mem.eql(u8, &rsdp.signature, "RSD PTR ")) return error.BadRsdpSignature; if (!std.mem.eql(u8, &rsdp.signature, "RSD PTR ")) return error.BadRsdpSignature;
// Revision 0 checksums only the first 20 bytes (the v1.0 RSDP). // Revision 0 checksums only the first 20 bytes (the v1.0 RSDP).
if (!checksumOk(@ptrFromInt(system.physicalToVirtual(rsdp_physical)), 20)) return error.BadRsdpChecksum; if (!checksumOk(@ptrFromInt(boot_handoff.physicalToVirtual(rsdp_physical)), 20)) return error.BadRsdpChecksum;
if (rsdp.revision >= 2) { if (rsdp.revision >= 2) {
const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(system.physicalToVirtual(rsdp_physical)); const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(boot_handoff.physicalToVirtual(rsdp_physical));
if (!checksumOk(@ptrFromInt(system.physicalToVirtual(rsdp_physical)), xsdp.length)) return error.BadXsdpChecksum; if (!checksumOk(@ptrFromInt(boot_handoff.physicalToVirtual(rsdp_physical)), xsdp.length)) return error.BadXsdpChecksum;
try walkRoot(u64, xsdp.extended_system_descriptor_table_address, device_tree, hal); try walkRoot(u64, xsdp.extended_system_descriptor_table_address, device_tree, hal);
} else { } else {
try walkRoot(u32, rsdp.root_system_description_table_address, device_tree, hal); try walkRoot(u32, rsdp.root_system_description_table_address, device_tree, hal);
@@ -393,7 +394,7 @@ pub fn discover(rsdp_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
// read the sleep types from it. // read the sleep types from it.
var blocks: [aml_block_physical.len][]const u8 = undefined; var blocks: [aml_block_physical.len][]const u8 = undefined;
for (0..aml_block_count) |i| { for (0..aml_block_count) |i| {
blocks[i] = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]]; blocks[i] = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]];
} }
const active = blocks[0..aml_block_count]; const active = blocks[0..aml_block_count];
if (aml.parse(device_tree.allocator, active)) |pr| { if (aml.parse(device_tree.allocator, active)) |pr| {
@@ -412,11 +413,11 @@ pub fn discover(rsdp_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
/// Walk the RSDT (Entry = u32) or XSDT (Entry = u64): validate it, then dispatch /// Walk the RSDT (Entry = u32) or XSDT (Entry = u64): validate it, then dispatch
/// each SDT it points at. A bad individual table is skipped, not fatal. /// each SDT it points at. A bad individual table is skipped, not fatal.
fn walkRoot(comptime Entry: type, root_physical: u64, device_tree: *DeviceTree, hal: Hal) !void { fn walkRoot(comptime Entry: type, root_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
const header: *const SystemDescriptorTableHeader = @ptrFromInt(system.physicalToVirtual(root_physical)); const header: *const SystemDescriptorTableHeader = @ptrFromInt(boot_handoff.physicalToVirtual(root_physical));
if (!checksumOk(@ptrFromInt(system.physicalToVirtual(root_physical)), header.length)) return error.BadRootChecksum; if (!checksumOk(@ptrFromInt(boot_handoff.physicalToVirtual(root_physical)), header.length)) return error.BadRootChecksum;
const count = (header.length - @sizeOf(SystemDescriptorTableHeader)) / @sizeOf(Entry); const count = (header.length - @sizeOf(SystemDescriptorTableHeader)) / @sizeOf(Entry);
const base: [*]const u8 = @ptrFromInt(system.physicalToVirtual(root_physical)); const base: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(root_physical));
const entries: [*]align(1) const Entry = @ptrCast(base + @sizeOf(SystemDescriptorTableHeader)); const entries: [*]align(1) const Entry = @ptrCast(base + @sizeOf(SystemDescriptorTableHeader));
for (entries[0..count]) |ent| { for (entries[0..count]) |ent| {
@@ -427,7 +428,7 @@ fn walkRoot(comptime Entry: type, root_physical: u64, device_tree: *DeviceTree,
/// Dispatch a single SDT on its signature. /// Dispatch a single SDT on its signature.
fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void { fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
const header: *const SystemDescriptorTableHeader = @ptrFromInt(system.physicalToVirtual(sdt_physical)); const header: *const SystemDescriptorTableHeader = @ptrFromInt(boot_handoff.physicalToVirtual(sdt_physical));
const sig = header.signature; const sig = header.signature;
if (std.mem.eql(u8, &sig, &APIC)) { if (std.mem.eql(u8, &sig, &APIC)) {
try parseMadt(device_tree, header); try parseMadt(device_tree, header);
@@ -1120,7 +1121,7 @@ fn pciConfigurationPtr(alloc: McfgAllocation, hal: Hal, bus: u8, device: u8, fun
(@as(u64, function) << 12); (@as(u64, function) << 12);
// Map the configuration page (writable, for BAR sizing) and use the virtual // Map the configuration page (writable, for BAR sizing) and use the virtual
// address the HAL hands back. // address the HAL hands back.
return @ptrFromInt(hal.mapMmio(physical, system.page_size, true)); return @ptrFromInt(hal.mapMmio(physical, abi.page_size, true));
} }
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer. /// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
+76
View File
@@ -0,0 +1,76 @@
//! The **device ABI**: the flat, `extern` device types that cross the system_call
//! boundary — what `device_enumerate` hands a user-space driver, what
//! `device_register` takes back. This is the devices sub-project's *public
//! interface*, exposed as its own `device-abi` module the same way the VFS server
//! exposes `vfs-protocol` — so both the kernel and user space depend on the contract
//! by name, and neither reaches into the other's files.
//!
//! It is also the **single source of truth** for `DeviceClass` and `ResourceKind`:
//! the kernel's rich, pointer-based device tree (system/devices/device-model.zig,
//! which user space must never import) re-exports these, so the enum that a driver
//! matches on and the enum the kernel classifies with are the *same* type — no
//! hand-kept "mirror in order" to drift. The core kernel↔user ABI is [[abi]]; the
//! loader↔kernel handoff is [[boot-handoff]].
/// A coarse classification of a device, independent of the describing firmware.
/// Kept small on purpose; refine as real drivers arrive. `enum(u32)` because the
/// `@intFromEnum` value crosses the system_call boundary in `DeviceDescriptor.class`.
pub const DeviceClass = enum(u32) {
/// The synthetic root every discovered device hangs beneath.
root,
processor,
interrupt_controller,
timer,
/// A PCI(e) host bridge — the root of a PCI segment (owns an ECAM window).
pci_host_bridge,
/// A single PCI function.
pci_device,
/// A device named in the ACPI namespace (from the DSDT/SSDT), carrying a
/// hardware ID (`_HID`) and, where static, current resource settings (`_CRS`).
acpi_device,
unknown,
};
/// The kind of hardware resource a device occupies. `enum(u32)` for the same
/// boundary-crossing reason as `DeviceClass` (see `ResourceDescriptor.kind`).
pub const ResourceKind = enum(u32) {
/// A memory-mapped I/O window: `start` is the physical base, `len` its size.
memory,
/// A legacy I/O-port range: `start` is the first port, `len` the count.
io_port,
/// An interrupt: `start` is the global system interrupt (GSI), `len` is 1.
irq,
/// A range of bus numbers owned by a bridge: `start`..`start+len`.
bus_range,
};
/// One device resource, as handed to a user-space driver (flat, extern).
pub const ResourceDescriptor = extern struct {
kind: u64, // a ResourceKind value
start: u64,
len: u64,
};
pub const maximum_device_resources = 8;
/// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree.
pub const no_parent: u64 = ~@as(u64, 0);
/// A device, as snapshotted for user space by `device_enumerate`. A driver scans
/// these to find the hardware it owns, claims it, and maps its MMIO.
///
/// `parent` makes the table a tree rather than a list, which is what a **bus driver**
/// needs: it claims the bus, finds the devices below it, and publishes any it
/// discovers itself with `device_register`. A registered child's resources must lie
/// within its parent's (the kernel enforces this) — that containment is what makes
/// delegation safe, since a device descriptor is otherwise a licence to map physical
/// memory.
pub const DeviceDescriptor = extern struct {
id: u64,
parent: u64, // a device id, or `no_parent`
class: u64, // a DeviceClass value
hid_len: u64,
resource_count: u64,
hid: [8]u8,
resources: [maximum_device_resources]ResourceDescriptor,
};
+9 -27
View File
@@ -12,6 +12,7 @@
//! on top of this — nothing here presumes them. //! on top of this — nothing here presumes them.
const std = @import("std"); const std = @import("std");
const device_abi = @import("device-abi");
/// The hardware primitives a discovery backend needs but can't express portably. /// The hardware primitives a discovery backend needs but can't express portably.
/// The kernel injects an implementation (the architecture VMM + port I/O), so the device /// The kernel injects an implementation (the architecture VMM + port I/O), so the device
@@ -26,17 +27,10 @@ pub const Hal = struct {
pioWrite: *const fn (width: u8, port: u16, value: u32) void, pioWrite: *const fn (width: u8, port: u16, value: u32) void,
}; };
/// The kind of hardware resource a device occupies. /// The kind of hardware resource a device occupies. Canonically defined by the
pub const ResourceKind = enum { /// device ABI (system/devices/device-abi.zig) and re-exported here, so the kernel's
/// A memory-mapped I/O window: `start` is the physical base, `len` its size. /// internal tree and the descriptors it hands user space share one enum.
memory, pub const ResourceKind = device_abi.ResourceKind;
/// A legacy I/O-port range: `start` is the first port, `len` the count.
io_port,
/// An interrupt: `start` is the global system interrupt (GSI), `len` is 1.
irq,
/// A range of bus numbers owned by a bridge: `start`..`start+len`.
bus_range,
};
/// One hardware resource claimed by a device. /// One hardware resource claimed by a device.
pub const Resource = struct { pub const Resource = struct {
@@ -46,22 +40,10 @@ pub const Resource = struct {
}; };
/// A coarse classification of a device, independent of the describing firmware. /// A coarse classification of a device, independent of the describing firmware.
/// Kept small on purpose; refine as real drivers arrive. /// Canonically defined by the device ABI (system/devices/device-abi.zig) and
pub const DeviceClass = enum { /// re-exported here — the enum a driver matches on and the one the kernel classifies
/// The synthetic root every discovered device hangs beneath. /// with are the same type. Kept small on purpose; refine as real drivers arrive.
root, pub const DeviceClass = device_abi.DeviceClass;
processor,
interrupt_controller,
timer,
/// A PCI(e) host bridge — the root of a PCI segment (owns an ECAM window).
pci_host_bridge,
/// A single PCI function.
pci_device,
/// A device named in the ACPI namespace (from the DSDT/SSDT), carrying a
/// hardware ID (`_HID`) and, where static, current resource settings (`_CRS`).
acpi_device,
unknown,
};
/// Firmware-independent identity. Each backend fills only the fields it knows; /// Firmware-independent identity. Each backend fills only the fields it knows;
/// the rest stay null. The generic layer never branches on *how* an id was /// the rest stay null. The generic layer never branches on *how* an id was
+2 -2
View File
@@ -9,7 +9,7 @@
//! compile-time choice. //! compile-time choice.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const device_model = @import("device-model.zig"); const device_model = @import("device-model.zig");
const acpi = @import("acpi.zig"); const acpi = @import("acpi.zig");
const power = @import("power.zig"); const power = @import("power.zig");
@@ -65,7 +65,7 @@ pub fn cpusDropped() usize {
/// ACPI registers); pass the architecture implementation. Errors leave nothing to clean up /// ACPI registers); pass the architecture implementation. Errors leave nothing to clean up
/// beyond the tree's own allocations. /// beyond the tree's own allocations.
pub fn discover( pub fn discover(
boot_information: *const system.BootInformation, boot_information: *const boot_handoff.BootInformation,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
hal: Hal, hal: Hal,
) !DeviceTree { ) !DeviceTree {
+2 -2
View File
@@ -9,7 +9,7 @@
//! map). Every interrupt must be acknowledged with an end-of-interrupt write, or //! map). Every interrupt must be acknowledged with an end-of-interrupt write, or
//! the LAPIC won't deliver the next one. //! the LAPIC won't deliver the next one.
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const io = @import("io.zig"); const io = @import("io.zig");
const paging = @import("paging.zig"); const paging = @import("paging.zig");
@@ -121,7 +121,7 @@ pub fn init() void {
const msr = io.rdmsr(ia32_apic_base_msr); const msr = io.rdmsr(ia32_apic_base_msr);
// Reach the LAPIC through the physmap (paging.init maps its page there). // Reach the LAPIC through the physmap (paging.init maps its page there).
base = @intCast(system.physicalToVirtual(msr & 0xFFFFF000)); // physical base is bits 12+ base = @intCast(boot_handoff.physicalToVirtual(msr & 0xFFFFF000)); // physical base is bits 12+
io.wrmsr(ia32_apic_base_msr, msr | (1 << 11)); // global enable io.wrmsr(ia32_apic_base_msr, msr | (1 << 11)); // global enable
write(register_spurious, 0x100 | spurious_vector); // bit 8 = software enable write(register_spurious, 0x100 | spurious_vector); // bit 8 = software enable
+2 -2
View File
@@ -4,7 +4,7 @@
//! build.zig — no change to the generic code. Keep everything CPU-specific here //! build.zig — no change to the generic code. Keep everything CPU-specific here
//! (halt, the descriptor tables, later paging), and nothing generic. //! (halt, the descriptor tables, later paging), and nothing generic.
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const parameters = @import("parameters"); const parameters = @import("parameters");
const gdt = @import("gdt.zig"); const gdt = @import("gdt.zig");
const tss = @import("tss.zig"); const tss = @import("tss.zig");
@@ -132,7 +132,7 @@ pub fn init() void {
/// Build the kernel's own page tables (with real permissions) and switch onto /// Build the kernel's own page tables (with real permissions) and switch onto
/// them. Needs the frame allocator and the boot info (for the memory map and the /// them. Needs the frame allocator and the boot info (for the memory map and the
/// kernel's segment layout). Call once the frame allocator is up. /// kernel's segment layout). Call once the frame allocator is up.
pub fn enablePaging(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const system.BootInformation) void { pub fn enablePaging(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const boot_handoff.BootInformation) void {
paging.init(allocFrame, freeFrame, boot_information); paging.init(allocFrame, freeFrame, boot_information);
} }
+11 -10
View File
@@ -10,10 +10,11 @@
//! Everything is 4 KiB pages — precise and simple; the extra table memory is //! Everything is 4 KiB pages — precise and simple; the extra table memory is
//! negligible against available RAM. //! negligible against available RAM.
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const io = @import("io.zig"); const io = @import("io.zig");
const page_size = system.page_size; const page_size = abi.page_size;
// Page-table entry bits. // Page-table entry bits.
const present: u64 = 1 << 0; const present: u64 = 1 << 0;
@@ -58,7 +59,7 @@ const bootstrap_physmap_limit: u64 = 4 << 30;
/// both the loader's bootstrap tables and the kernel's own, which share the /// both the loader's bootstrap tables and the kernel's own, which share the
/// physmap base. /// physmap base.
fn tableAt(physical: u64) *[512]u64 { fn tableAt(physical: u64) *[512]u64 {
return @ptrFromInt(system.physicalToVirtual(physical)); return @ptrFromInt(boot_handoff.physicalToVirtual(physical));
} }
fn allocTable() u64 { fn allocTable() u64 {
@@ -102,12 +103,12 @@ fn mapRangePhysmap(pml4: u64, physical_base: u64, len: u64, flags: u64) void {
var address = physical_base & ~@as(u64, page_size - 1); var address = physical_base & ~@as(u64, page_size - 1);
const end = physical_base + len; const end = physical_base + len;
while (address < end) : (address += page_size) { while (address < end) : (address += page_size) {
mapPage(pml4, system.physicalToVirtual(address), address, flags); mapPage(pml4, boot_handoff.physicalToVirtual(address), address, flags);
} }
} }
fn regions(mm: system.MemoryMap) []const system.MemoryRegion { fn regions(mm: boot_handoff.MemoryMap) []const boot_handoff.MemoryRegion {
return @as([*]const system.MemoryRegion, @ptrFromInt(system.physicalToVirtual(mm.regions)))[0..mm.len]; return @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(mm.regions)))[0..mm.len];
} }
/// Enable the NX bit in the page-table format (EFER.NXE). Must happen before we /// Enable the NX bit in the page-table format (EFER.NXE). Must happen before we
@@ -118,7 +119,7 @@ fn enableNx() void {
} }
/// Build the address space and switch onto it. /// Build the address space and switch onto it.
pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const system.BootInformation) void { pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const boot_handoff.BootInformation) void {
alloc_frame = allocFrame; alloc_frame = allocFrame;
free_frame = freeFrame; free_frame = freeFrame;
enableNx(); enableNx();
@@ -136,7 +137,7 @@ pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot
// the kernel touches directly), RW + NX. // the kernel touches directly), RW + NX.
const fb = boot_information.framebuffer; const fb = boot_information.framebuffer;
mapRangePhysmap(pml4, fb.base, @as(u64, fb.height) * fb.pitch, present | writable | no_execute); mapRangePhysmap(pml4, fb.base, @as(u64, fb.height) * fb.pitch, present | writable | no_execute);
mapPage(pml4, system.physicalToVirtual(0xFEE00000), 0xFEE00000, present | writable | no_execute); mapPage(pml4, boot_handoff.physicalToVirtual(0xFEE00000), 0xFEE00000, present | writable | no_execute);
// 3. The kernel's own segments at their higher-half link addresses, mapped // 3. The kernel's own segments at their higher-half link addresses, mapped
// to their low physical load addresses with real ELF permissions: code // to their low physical load addresses with real ELF permissions: code
@@ -206,11 +207,11 @@ pub fn mapMmio(physical: u64, len: u64, writable_page: bool) u64 {
const last = physical + (if (len == 0) 1 else len) - 1; const last = physical + (if (len == 0) 1 else len) - 1;
var address = first; var address = first;
while (address <= (last & ~@as(u64, page_size - 1))) : (address += page_size) { while (address <= (last & ~@as(u64, page_size - 1))) : (address += page_size) {
const virtual = system.physicalToVirtual(address); const virtual = boot_handoff.physicalToVirtual(address);
mapPage(kernel_pml4, virtual, address, flags); mapPage(kernel_pml4, virtual, address, flags);
invalidate(virtual); invalidate(virtual);
} }
return system.physicalToVirtual(physical); return boot_handoff.physicalToVirtual(physical);
} }
/// Like `descend`, but also sets the U/S bit on the intermediate entry (new or /// Like `descend`, but also sets the U/S bit on the intermediate entry (new or
+4 -4
View File
@@ -13,7 +13,7 @@
//! Once a core has its own descriptor tables, LAPIC, and timer, it calls the generic //! Once a core has its own descriptor tables, LAPIC, and timer, it calls the generic
//! scheduler entry and joins the run loop — mechanism here, policy there. //! scheduler entry and joins the run loop — mechanism here, policy there.
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const io = @import("io.zig"); const io = @import("io.zig");
const gdt = @import("gdt.zig"); const gdt = @import("gdt.zig");
const tss = @import("tss.zig"); const tss = @import("tss.zig");
@@ -85,7 +85,7 @@ fn arm() void {
const start = @extern([*]const u8, .{ .name = "ap_trampoline_start" }); const start = @extern([*]const u8, .{ .name = "ap_trampoline_start" });
const end = @extern([*]const u8, .{ .name = "ap_trampoline_end" }); const end = @extern([*]const u8, .{ .name = "ap_trampoline_end" });
const len = @intFromPtr(end) - @intFromPtr(start); const len = @intFromPtr(end) - @intFromPtr(start);
const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(tramp_physical)); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(tramp_physical));
@memcpy(destination[0..len], start[0..len]); @memcpy(destination[0..len], start[0..len]);
} }
@@ -95,7 +95,7 @@ fn arm() void {
/// reported in — it's long past the trampoline by then, in the kernel image; a /// reported in — it's long past the trampoline by then, in the kernel image; a
/// core that never answered is dead and can't be mid-climb. /// core that never answered is dead and can't be mid-climb.
fn disarm() void { fn disarm() void {
const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(tramp_physical)); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(tramp_physical));
@memset(destination[0..page_size], 0); @memset(destination[0..page_size], 0);
paging.unmap(tramp_physical); // drop the transient low identity mapping paging.unmap(tramp_physical); // drop the transient low identity mapping
} }
@@ -107,7 +107,7 @@ fn disarm() void {
fn param(comptime name: []const u8) *align(1) volatile u64 { fn param(comptime name: []const u8) *align(1) volatile u64 {
const start = @intFromPtr(@extern([*]const u8, .{ .name = "ap_trampoline_start" })); const start = @intFromPtr(@extern([*]const u8, .{ .name = "ap_trampoline_start" }));
const sym = @intFromPtr(@extern([*]const u8, .{ .name = name })); const sym = @intFromPtr(@extern([*]const u8, .{ .name = name }));
return @ptrFromInt(system.physicalToVirtual(tramp_physical + (sym - start))); return @ptrFromInt(boot_handoff.physicalToVirtual(tramp_physical + (sym - start)));
} }
/// Wake the core with Local APIC id `apic_id` as dense CPU `index`, hand it /// Wake the core with Local APIC id `apic_id` as dense CPU `index`, hand it
+5 -5
View File
@@ -14,7 +14,7 @@
//! never assumes a display exists. //! never assumes a display exists.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
/// The one framebuffer console, valid only when `con_present`. /// The one framebuffer console, valid only when `con_present`.
var con: Console = undefined; var con: Console = undefined;
@@ -22,7 +22,7 @@ var con_present: bool = false;
/// Set up the console over `fb`, or mark it absent if there's no usable /// Set up the console over `fb`, or mark it absent if there's no usable
/// framebuffer. Clears the screen when present. /// framebuffer. Clears the screen when present.
pub fn init(fb: system.Framebuffer) void { pub fn init(fb: boot_handoff.Framebuffer) void {
if (!fb.present()) { if (!fb.present()) {
con_present = false; con_present = false;
return; return;
@@ -58,7 +58,7 @@ const glyph_bytes = glyph_h; // 8 pixels wide => 1 byte per row
const glyph_data = 32; // PSF2 header size const glyph_data = 32; // PSF2 header size
pub const Console = struct { pub const Console = struct {
fb: system.Framebuffer, fb: boot_handoff.Framebuffer,
cols: u32, cols: u32,
rows: u32, rows: u32,
col: u32 = 0, col: u32 = 0,
@@ -66,12 +66,12 @@ pub const Console = struct {
fg: u32 = 0x00c8_c8c8, // light grey fg: u32 = 0x00c8_c8c8, // light grey
bg: u32 = 0x0000_0000, // black bg: u32 = 0x0000_0000, // black
pub fn init(fb: system.Framebuffer) Console { pub fn init(fb: boot_handoff.Framebuffer) Console {
// Reach the framebuffer through the physmap, so the pointer stays valid // Reach the framebuffer through the physmap, so the pointer stays valid
// once the low identity map is gone. The base is mapped by both the // once the low identity map is gone. The base is mapped by both the
// loader's bootstrap tables and paging.init. // loader's bootstrap tables and paging.init.
var mapped = fb; var mapped = fb;
if (fb.base != 0) mapped.base = system.physicalToVirtual(fb.base); if (fb.base != 0) mapped.base = boot_handoff.physicalToVirtual(fb.base);
return .{ return .{
.fb = mapped, .fb = mapped,
.cols = fb.width / glyph_w, .cols = fb.width / glyph_w,
+14 -14
View File
@@ -20,7 +20,7 @@
const std = @import("std"); const std = @import("std");
const platform = @import("platform"); const platform = @import("platform");
const system = @import("system"); const device_abi = @import("device-abi");
const maximum_devices = 64; const maximum_devices = 64;
@@ -32,7 +32,7 @@ const maximum_devices = 64;
/// `device_release` to reclaim on exit) is future work — see docs/driver-model.md. /// `device_release` to reclaim on exit) is future work — see docs/driver-model.md.
const maximum_children_per_parent = 16; const maximum_children_per_parent = 16;
var devices: [maximum_devices]system.DeviceDescriptor = undefined; var devices: [maximum_devices]device_abi.DeviceDescriptor = undefined;
var claimed: [maximum_devices]?u32 = .{null} ** maximum_devices; // owner task id, or null var claimed: [maximum_devices]?u32 = .{null} ** maximum_devices; // owner task id, or null
var count: usize = 0; var count: usize = 0;
@@ -46,13 +46,13 @@ pub fn init(device_tree: *const platform.DeviceTree) void {
count = 0; count = 0;
dropped = 0; dropped = 0;
for (&claimed) |*c| c.* = null; for (&claimed) |*c| c.* = null;
walk(device_tree.root, system.no_parent); walk(device_tree.root, device_abi.no_parent);
} }
/// Record `node` (unless it's the synthetic root) and recurse, threading the id we /// Record `node` (unless it's the synthetic root) and recurse, threading the id we
/// assigned it down to its children as their parent. /// assigned it down to its children as their parent.
fn walk(node: *platform.Device, parent_id: u64) void { fn walk(node: *platform.Device, parent_id: u64) void {
const id = if (node.class == .root) system.no_parent else record(node, parent_id); const id = if (node.class == .root) device_abi.no_parent else record(node, parent_id);
var child = node.first_child; var child = node.first_child;
while (child) |c| : (child = c.next_sibling) walk(c, id); while (child) |c| : (child = c.next_sibling) walk(c, id);
} }
@@ -60,16 +60,16 @@ fn walk(node: *platform.Device, parent_id: u64) void {
fn record(node: *platform.Device, parent_id: u64) u64 { fn record(node: *platform.Device, parent_id: u64) u64 {
if (count >= maximum_devices) { if (count >= maximum_devices) {
dropped += 1; dropped += 1;
return system.no_parent; // children of a dropped node become roots, not orphans return device_abi.no_parent; // children of a dropped node become roots, not orphans
} }
var d = std.mem.zeroes(system.DeviceDescriptor); var d = std.mem.zeroes(device_abi.DeviceDescriptor);
d.id = count; d.id = count;
d.parent = parent_id; d.parent = parent_id;
d.class = @intFromEnum(node.class); d.class = @intFromEnum(node.class);
const h = node.hid(); const h = node.hid();
d.hid_len = @min(h.len, d.hid.len); d.hid_len = @min(h.len, d.hid.len);
@memcpy(d.hid[0..d.hid_len], h[0..d.hid_len]); @memcpy(d.hid[0..d.hid_len], h[0..d.hid_len]);
const rc = @min(node.resource_count, system.maximum_device_resources); const rc = @min(node.resource_count, device_abi.maximum_device_resources);
d.resource_count = rc; d.resource_count = rc;
for (0..rc) |i| { for (0..rc) |i| {
const r = node.resources[i]; const r = node.resources[i];
@@ -82,7 +82,7 @@ fn record(node: *platform.Device, parent_id: u64) u64 {
/// Copy up to `out.len` device descriptors into `out`; returns the total count /// Copy up to `out.len` device descriptors into `out`; returns the total count
/// available (which may exceed `out.len`). /// available (which may exceed `out.len`).
pub fn enumerate(out: []system.DeviceDescriptor) usize { pub fn enumerate(out: []device_abi.DeviceDescriptor) usize {
const n = @min(count, out.len); const n = @min(count, out.len);
@memcpy(out[0..n], devices[0..n]); @memcpy(out[0..n], devices[0..n]);
return count; return count;
@@ -104,7 +104,7 @@ pub fn ownerOf(id: u64) ?u32 {
} }
/// Resource `index` of device `id`, or null if out of range. /// Resource `index` of device `id`, or null if out of range.
pub fn resourceOf(id: u64, index: u64) ?system.ResourceDescriptor { pub fn resourceOf(id: u64, index: u64) ?device_abi.ResourceDescriptor {
if (id >= count) return null; if (id >= count) return null;
const d = &devices[@intCast(id)]; const d = &devices[@intCast(id)];
if (index >= d.resource_count) return null; if (index >= d.resource_count) return null;
@@ -115,9 +115,9 @@ pub fn resourceOf(id: u64, index: u64) ?system.ResourceDescriptor {
/// interval containment; for an irq it's equality, since an interrupt line is not /// interval containment; for an irq it's equality, since an interrupt line is not
/// divisible. Zero-length child ranges are refused — an empty window is meaningless /// divisible. Zero-length child ranges are refused — an empty window is meaningless
/// and would otherwise vacuously "fit" anywhere. /// and would otherwise vacuously "fit" anywhere.
fn contains(parent: system.ResourceDescriptor, child: system.ResourceDescriptor) bool { fn contains(parent: device_abi.ResourceDescriptor, child: device_abi.ResourceDescriptor) bool {
if (parent.kind != child.kind) return false; if (parent.kind != child.kind) return false;
if (child.kind == @intFromEnum(system.ResourceKind.irq)) return parent.start == child.start; if (child.kind == @intFromEnum(device_abi.ResourceKind.irq)) return parent.start == child.start;
if (child.len == 0 or parent.len == 0) return false; if (child.len == 0 or parent.len == 0) return false;
// No overflow: a resource that wraps the address space is not containable. // No overflow: a resource that wraps the address space is not containable.
const child_end = std.math.add(u64, child.start, child.len) catch return false; const child_end = std.math.add(u64, child.start, child.len) catch return false;
@@ -149,10 +149,10 @@ fn childCount(parent_id: u64) usize {
/// `owner` must have claimed `parent_id`, and every resource in `descriptor` must be /// `owner` must have claimed `parent_id`, and every resource in `descriptor` must be
/// contained in a parent resource of the same kind. A device with no resources is /// contained in a parent resource of the same kind. A device with no resources is
/// fine and common: a USB device is addressed through its controller, not by MMIO. /// fine and common: a USB device is addressed through its controller, not by MMIO.
pub fn register(parent_id: u64, owner: u32, descriptor: *const system.DeviceDescriptor) RegisterError!u64 { pub fn register(parent_id: u64, owner: u32, descriptor: *const device_abi.DeviceDescriptor) RegisterError!u64 {
const parent_owner = ownerOf(parent_id) orelse return error.BadParent; const parent_owner = ownerOf(parent_id) orelse return error.BadParent;
if (parent_owner != owner) return error.BadParent; if (parent_owner != owner) return error.BadParent;
if (descriptor.resource_count > system.maximum_device_resources) return error.TooManyResources; if (descriptor.resource_count > device_abi.maximum_device_resources) return error.TooManyResources;
if (childCount(parent_id) >= maximum_children_per_parent) return error.TooManyChildren; if (childCount(parent_id) >= maximum_children_per_parent) return error.TooManyChildren;
if (count >= maximum_devices) return error.NoSpace; if (count >= maximum_devices) return error.NoSpace;
@@ -166,7 +166,7 @@ pub fn register(parent_id: u64, owner: u32, descriptor: *const system.DeviceDesc
if (!ok) return error.NotContained; if (!ok) return error.NotContained;
} }
var d = std.mem.zeroes(system.DeviceDescriptor); var d = std.mem.zeroes(device_abi.DeviceDescriptor);
d.id = count; d.id = count;
d.parent = parent_id; d.parent = parent_id;
d.class = descriptor.class; d.class = descriptor.class;
+2 -2
View File
@@ -13,11 +13,11 @@
//! interrupt handlers (ours don't). A lock comes with threads/SMP. //! interrupt handlers (ours don't). A lock comes with threads/SMP.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const abi = @import("abi");
const architecture = @import("architecture"); const architecture = @import("architecture");
const pmm = @import("pmm.zig"); const pmm = @import("pmm.zig");
const page_size = system.page_size; const page_size = abi.page_size;
/// Virtual base of the heap: the start of the higher half, which is unmapped and /// Virtual base of the heap: the start of the higher half, which is unmapped and
/// well clear of the identity-mapped low half. (Canonical on x86_64; an architecture that /// well clear of the identity-mapped low half. (Canonical on x86_64; an architecture that
+8 -7
View File
@@ -22,13 +22,14 @@
//! copy is a later security-track item, matching the existing debug_write gap. //! copy is a later security-track item, matching the existing debug_write gap.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const architecture = @import("architecture"); const architecture = @import("architecture");
const scheduler = @import("scheduler.zig"); const scheduler = @import("scheduler.zig");
const sync = @import("sync.zig"); const sync = @import("sync.zig");
const heap = @import("heap.zig"); const heap = @import("heap.zig");
const page_size = system.page_size; const page_size = abi.page_size;
const Task = scheduler.Task; const Task = scheduler.Task;
/// Largest message a single call/reply may carry. Bumping it is trivial; kept /// Largest message a single call/reply may carry. Bumping it is trivial; kept
@@ -50,8 +51,8 @@ pub const ENOMEM: i64 = 6; // out of memory
/// message from a client — there is no reply owed. The low bits carry the source /// message from a client — there is no reply owed. The low bits carry the source
/// (a GSI for IRQs). Posted by `notifyFromIsr`, from the ISR in system/kernel/irq.zig; /// (a GSI for IRQs). Posted by `notifyFromIsr`, from the ISR in system/kernel/irq.zig;
/// the message path uses a plain task-id badge with this bit clear. Defined in the /// the message path uses a plain task-id badge with this bit clear. Defined in the
/// shared contract (system/system.zig), because ring 3 has to test the same bit. /// shared kernel↔user ABI (system/abi.zig), because ring 3 has to test the same bit.
pub const notify_badge_bit: u64 = system.notify_badge_bit; pub const notify_badge_bit: u64 = abi.notify_badge_bit;
/// End of the user (low) canonical half — user buffers must lie below it. /// End of the user (low) canonical half — user buffers must lie below it.
const user_half_end: u64 = 0x0000_8000_0000_0000; const user_half_end: u64 = 0x0000_8000_0000_0000;
@@ -124,8 +125,8 @@ fn copyAcross(source_as: u64, source_va: u64, destination_as: u64, destination_v
const s_left = page_size - ((source_va + off) & (page_size - 1)); const s_left = page_size - ((source_va + off) & (page_size - 1));
const d_left = page_size - ((destination_va + off) & (page_size - 1)); const d_left = page_size - ((destination_va + off) & (page_size - 1));
const n = @min(@min(s_left, d_left), len - off); const n = @min(@min(s_left, d_left), len - off);
const source: [*]const u8 = @ptrFromInt(system.physicalToVirtual(s)); const source: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(s));
const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(d)); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(d));
@memcpy(destination[0..n], source[0..n]); @memcpy(destination[0..n], source[0..n]);
off += n; off += n;
} }
@@ -147,7 +148,7 @@ pub fn copyFromUser(user_as: u64, user_va: u64, destination: []u8) bool {
const s = architecture.translate(user_as, user_va + off) orelse return false; const s = architecture.translate(user_as, user_va + off) orelse return false;
const s_left = page_size - ((user_va + off) & (page_size - 1)); const s_left = page_size - ((user_va + off) & (page_size - 1));
const n = @min(s_left, destination.len - off); const n = @min(s_left, destination.len - off);
const source: [*]const u8 = @ptrFromInt(system.physicalToVirtual(s)); const source: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(s));
@memcpy(destination[off..][0..n], source[0..n]); @memcpy(destination[off..][0..n], source[0..n]);
off += n; off += n;
} }
+13 -12
View File
@@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const parameters = @import("parameters"); const parameters = @import("parameters");
const architecture = @import("architecture"); const architecture = @import("architecture");
const console = @import("console.zig"); const console = @import("console.zig");
@@ -14,14 +15,14 @@ const initial_ramdisk = @import("initial-ramdisk");
const platform = @import("platform"); const platform = @import("platform");
const tests = @import("tests.zig"); const tests = @import("tests.zig");
const build_options = @import("build_options"); const build_options = @import("build_options");
const BootInformation = system.BootInformation; const BootInformation = boot_handoff.BootInformation;
/// The calling convention used to enter the kernel. Pinned to SystemV explicitly: /// The calling convention used to enter the kernel. Pinned to SystemV explicitly:
/// the bootloader is built for the UEFI target, whose C convention is Microsoft /// the bootloader is built for the UEFI target, whose C convention is Microsoft
/// x64 (first argument in RCX), while the kernel is SystemV (first argument in /// x64 (first argument in RCX), while the kernel is SystemV (first argument in
/// RDI). Both sides reference this so the `boot_information` pointer lands in the /// RDI). Both sides reference this so the `boot_information` pointer lands in the
/// register the other expects. `system.kernel_abi` re-exports it to the loader. /// register the other expects. `boot_handoff.kernel_abi` re-exports it to the loader.
pub const kernel_abi = system.kernel_abi; pub const kernel_abi = boot_handoff.kernel_abi;
// POST/checkpoint codes emitted to I/O port 0x80 at boot milestones — the // POST/checkpoint codes emitted to I/O port 0x80 at boot milestones — the
// last-resort progress signal on a machine with no text output at all. // last-resort progress signal on a machine with no text output at all.
@@ -50,7 +51,7 @@ var ap_trampoline_page: u64 = 0;
/// half. `boot_information` (also low) is reached through the physmap — its base is the /// half. `boot_information` (also low) is reached through the physmap — its base is the
/// same under the loader's bootstrap tables and the kernel's own. /// same under the loader's bootstrap tables and the kernel's own.
export fn kmainEntry(boot_information: *const BootInformation) callconv(kernel_abi) noreturn { export fn kmainEntry(boot_information: *const BootInformation) callconv(kernel_abi) noreturn {
kmain(@ptrFromInt(system.physicalToVirtual(@intFromPtr(boot_information)))); kmain(@ptrFromInt(boot_handoff.physicalToVirtual(@intFromPtr(boot_information))));
} }
fn kmain(boot_information: *const BootInformation) noreturn { fn kmain(boot_information: *const BootInformation) noreturn {
@@ -91,7 +92,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Summarise the physical memory the loader handed us. The array is danos's // Summarise the physical memory the loader handed us. The array is danos's
// own MemoryRegion, so this is a plain slice — no firmware layout in sight. // own MemoryRegion, so this is a plain slice — no firmware layout in sight.
const regions = @as([*]const system.MemoryRegion, @ptrFromInt(system.physicalToVirtual(boot_information.memory_map.regions)))[0..boot_information.memory_map.len]; const regions = @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.memory_map.regions)))[0..boot_information.memory_map.len];
var usable_pages: u64 = 0; var usable_pages: u64 = 0;
var reserved_pages: u64 = 0; // reserved RAM only — MMIO is device space, not RAM var reserved_pages: u64 = 0; // reserved RAM only — MMIO is device space, not RAM
for (regions) |r| { for (regions) |r| {
@@ -102,7 +103,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
} }
} }
const total_pages = usable_pages + reserved_pages; const total_pages = usable_pages + reserved_pages;
const total_bytes = total_pages * system.page_size; const total_bytes = total_pages * abi.page_size;
const gib = 1 << 30; const gib = 1 << 30;
log.write("\ndanos: physical memory\n"); log.write("\ndanos: physical memory\n");
@@ -277,7 +278,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// borrowing. This boot context then becomes the BSP's idle loop. // borrowing. This boot context then becomes the BSP's idle loop.
if (boot_information.init_len != 0) { if (boot_information.init_len != 0) {
status("starting /sbin/init...\n"); status("starting /sbin/init...\n");
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.spawnProcess(image, 4) catch |err| { process.spawnProcess(image, 4) catch |err| {
statusPrint("/sbin/init failed to load: {s}\n", .{@errorName(err)}); statusPrint("/sbin/init failed to load: {s}\n", .{@errorName(err)});
}; };
@@ -300,9 +301,9 @@ fn kmain(boot_information: *const BootInformation) noreturn {
/// Spawn every program bundled in the initial_ramdisk as its own ring-3 process. A bad /// Spawn every program bundled in the initial_ramdisk as its own ring-3 process. A bad
/// image or a program that fails to load is logged and skipped — the rest of the /// image or a program that fails to load is logged and skipped — the rest of the
/// system still runs. /// system still runs.
fn startInitialRamdiskBinaries(boot_information: *const system.BootInformation) void { fn startInitialRamdiskBinaries(boot_information: *const boot_handoff.BootInformation) void {
if (boot_information.initial_ramdisk_len == 0) return; if (boot_information.initial_ramdisk_len == 0) return;
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
status("initial_ramdisk: bad image, skipping\n"); status("initial_ramdisk: bad image, skipping\n");
return; return;
@@ -386,11 +387,11 @@ fn statusPrint(comptime fmt: []const u8, args: anytype) void {
/// Frames (4 KiB pages) to whole MiB. /// Frames (4 KiB pages) to whole MiB.
fn mib(pages: u64) u64 { fn mib(pages: u64) u64 {
return pages * system.page_size / (1024 * 1024); return pages * abi.page_size / (1024 * 1024);
} }
fn kib(frames: u64) u64 { fn kib(frames: u64) u64 {
return frames * system.page_size / (1024); return frames * abi.page_size / (1024);
} }
/// Report a CPU exception and halt **this core**. There's no fault recovery yet, so /// Report a CPU exception and halt **this core**. There's no fault recovery yet, so
+8 -7
View File
@@ -2,14 +2,15 @@
//! 4 KiB physical frames — the primitive every later memory feature (page //! 4 KiB physical frames — the primitive every later memory feature (page
//! tables, the heap) is built on top of. //! tables, the heap) is built on top of.
//! //!
//! This is generic kernel code: it works on the neutral `system.MemoryRegion` //! This is generic kernel code: it works on the neutral `boot_handoff.MemoryRegion`
//! array the loader hands over (see docs/memory-map.md), so it carries no UEFI //! array the loader hands over (see docs/memory-map.md), so it carries no UEFI
//! and nothing architecture-specific beyond the 4 KiB page. //! and nothing architecture-specific beyond the 4 KiB page.
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const page_size = system.page_size; const page_size = abi.page_size;
/// One bit per frame, covering physical RAM from 0 up to the highest usable /// One bit per frame, covering physical RAM from 0 up to the highest usable
/// address: 1 = used/unavailable, 0 = free. The bitmap itself lives in a frame /// address: 1 = used/unavailable, 0 = free. The bitmap itself lives in a frame
@@ -48,8 +49,8 @@ inline fn setFree(frame: usize) void {
bitmap[frame >> 3] &= ~(@as(u8, 1) << bit(frame)); bitmap[frame >> 3] &= ~(@as(u8, 1) << bit(frame));
} }
fn regions(map: system.MemoryMap) []const system.MemoryRegion { fn regions(map: boot_handoff.MemoryMap) []const boot_handoff.MemoryRegion {
return @as([*]const system.MemoryRegion, @ptrFromInt(system.physicalToVirtual(map.regions)))[0..map.len]; return @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(map.regions)))[0..map.len];
} }
/// Build the allocator from the loader's memory map. Reaches physical memory /// Build the allocator from the loader's memory map. Reaches physical memory
@@ -59,7 +60,7 @@ fn regions(map: system.MemoryMap) []const system.MemoryRegion {
/// region (lowest address), which must sit under the bootstrap physmap's reach /// region (lowest address), which must sit under the bootstrap physmap's reach
/// (4 GiB); it always does, as both this and the page-table allocator scan from /// (4 GiB); it always does, as both this and the page-table allocator scan from
/// low addresses up. /// low addresses up.
pub fn init(map: system.MemoryMap) void { pub fn init(map: boot_handoff.MemoryMap) void {
const regs = regions(map); const regs = regions(map);
// 1. Size the bitmap to cover every frame up to the highest RAM address — // 1. Size the bitmap to cover every frame up to the highest RAM address —
@@ -91,7 +92,7 @@ pub fn init(map: system.MemoryMap) void {
} }
} }
const bitmap_base = storage orelse @panic("pmm: no region large enough for the frame bitmap"); const bitmap_base = storage orelse @panic("pmm: no region large enough for the frame bitmap");
bitmap = @as([*]u8, @ptrFromInt(system.physicalToVirtual(bitmap_base)))[0..bitmap_bytes]; bitmap = @as([*]u8, @ptrFromInt(boot_handoff.physicalToVirtual(bitmap_base)))[0..bitmap_bytes];
// 3. Start with everything marked used, then free the usable regions. Doing // 3. Start with everything marked used, then free the usable regions. Doing
// it this way means every gap, reserved span and MMIO hole is unallocatable // it this way means every gap, reserved span and MMIO hole is unallocatable
+14 -12
View File
@@ -21,7 +21,9 @@
const std = @import("std"); const std = @import("std");
const elf = std.elf; const elf = std.elf;
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const device_abi = @import("device-abi");
const architecture = @import("architecture"); const architecture = @import("architecture");
const pmm = @import("pmm.zig"); const pmm = @import("pmm.zig");
const scheduler = @import("scheduler.zig"); const scheduler = @import("scheduler.zig");
@@ -31,8 +33,8 @@ const devices_broker = @import("devices-broker.zig");
const irq = @import("irq.zig"); const irq = @import("irq.zig");
const log = @import("log.zig"); const log = @import("log.zig");
const page_size = system.page_size; const page_size = abi.page_size;
const SystemCall = system.SystemCall; const SystemCall = abi.SystemCall;
/// User virtual addresses. PML4 index 224 — a user-exclusive region, far from /// User virtual addresses. PML4 index 224 — a user-exclusive region, far from
/// the identity map (low indices) and the vmm test address (index 128), so /// the identity map (low indices) and the vmm test address (index 128), so
@@ -79,7 +81,7 @@ pub var write_from_user: bool = false;
pub var write_count: u64 = 0; // total write syscalls served (for the heartbeat tests) pub var write_count: u64 = 0; // total write syscalls served (for the heartbeat tests)
pub var exit_code: u64 = 0; pub var exit_code: u64 = 0;
/// The system_call surface, dispatched on the saved system_call number (`system.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
/// written back into the trap frame, since the entry paths restore user registers /// written back into the trap frame, since the entry paths restore user registers
@@ -203,9 +205,9 @@ fn systemDeviceEnumerate(state: *architecture.CpuState) void {
const maximum = architecture.systemCallArg(state, 1); const maximum = architecture.systemCallArg(state, 1);
const t = scheduler.current(); const t = scheduler.current();
if (t.aspace == 0 or buffer_ptr >= user_half_end) return fail(state); if (t.aspace == 0 or buffer_ptr >= user_half_end) return fail(state);
const sz = @sizeOf(system.DeviceDescriptor); const sz = @sizeOf(device_abi.DeviceDescriptor);
const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half const cap = @min(maximum, (user_half_end - buffer_ptr) / sz); // clamp to the user half
const out: [*]system.DeviceDescriptor = @ptrFromInt(buffer_ptr); const out: [*]device_abi.DeviceDescriptor = @ptrFromInt(buffer_ptr);
architecture.setSystemCallResult(state, devices_broker.enumerate(out[0..@intCast(cap)])); architecture.setSystemCallResult(state, devices_broker.enumerate(out[0..@intCast(cap)]));
} }
@@ -228,7 +230,7 @@ fn systemMmioMap(state: *architecture.CpuState) void {
const owner = devices_broker.ownerOf(device_id) orelse return fail(state); const owner = devices_broker.ownerOf(device_id) orelse return fail(state);
if (owner != t.id) return fail(state); // not claimed by this process if (owner != t.id) return fail(state); // not claimed by this process
const r = devices_broker.resourceOf(device_id, resource_index) orelse return fail(state); const r = devices_broker.resourceOf(device_id, resource_index) orelse return fail(state);
if (r.kind != @intFromEnum(system.ResourceKind.memory)) return fail(state); if (r.kind != @intFromEnum(device_abi.ResourceKind.memory)) return fail(state);
if (t.device_map_next == 0) t.device_map_next = device_arena_base; if (t.device_map_next == 0) t.device_map_next = device_arena_base;
const first = r.start & ~@as(u64, page_size - 1); const first = r.start & ~@as(u64, page_size - 1);
@@ -260,7 +262,7 @@ fn systemDeviceRegister(state: *architecture.CpuState) void {
const t = scheduler.current(); const t = scheduler.current();
if (t.aspace == 0) return fail(state); if (t.aspace == 0) return fail(state);
var descriptor: system.DeviceDescriptor = undefined; var descriptor: device_abi.DeviceDescriptor = undefined;
if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state); if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state);
const id = devices_broker.register(parent_id, t.id, &descriptor) catch return fail(state); const id = devices_broker.register(parent_id, t.id, &descriptor) catch return fail(state);
@@ -284,7 +286,7 @@ fn ownedGsi(t: *scheduler.Task, device_id: u64, resource_index: u64) ?u32 {
const owner = devices_broker.ownerOf(device_id) orelse return null; const owner = devices_broker.ownerOf(device_id) orelse return null;
if (owner != t.id) return null; if (owner != t.id) return null;
const r = devices_broker.resourceOf(device_id, resource_index) orelse return null; const r = devices_broker.resourceOf(device_id, resource_index) orelse return null;
if (r.kind != @intFromEnum(system.ResourceKind.irq)) return null; if (r.kind != @intFromEnum(device_abi.ResourceKind.irq)) return null;
if (r.start >= irq.maximum_gsi) return null; if (r.start >= irq.maximum_gsi) return null;
return @intCast(r.start); return @intCast(r.start);
} }
@@ -373,7 +375,7 @@ fn systemMmap(state: *architecture.CpuState) void {
} }
for (frames[0..pages], 0..) |frame, i| { for (frames[0..pages], 0..) |frame, i| {
const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(frame)); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(frame));
@memset(destination[0..page_size], 0); // hand out zeroed memory @memset(destination[0..page_size], 0); // hand out zeroed memory
architecture.mapUserPageInto(t.aspace, base + i * page_size, frame, true, false); // RW + NX architecture.mapUserPageInto(t.aspace, base + i * page_size, frame, true, false); // RW + NX
} }
@@ -428,7 +430,7 @@ pub fn run(blob: []const u8) RunError!void {
// Fill the code frame through the physmap (supervisor RW): the user-facing // Fill the code frame through the physmap (supervisor RW): the user-facing
// mapping is read-only, and this also sidesteps CR0.WP/SMAP. The tail is // mapping is read-only, and this also sidesteps CR0.WP/SMAP. The tail is
// padded with int3 so a stray jump traps instead of sliding. // padded with int3 so a stray jump traps instead of sliding.
const code: [*]u8 = @ptrFromInt(system.physicalToVirtual(code_frame)); const code: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(code_frame));
@memcpy(code[0..blob.len], blob); @memcpy(code[0..blob.len], blob);
@memset(code[blob.len..page_size], 0xCC); @memset(code[blob.len..page_size], 0xCC);
@@ -542,7 +544,7 @@ fn parseSegments(image: []const u8, segs: *[maximum_segments]Segment) InitError!
/// frame mapped into it — so no per-page rollback list is needed here. /// frame mapped into it — so no per-page rollback list is needed here.
fn loadPageInto(aspace: u64, image: []const u8, seg: Segment, page_index: u64) InitError!void { fn loadPageInto(aspace: u64, image: []const u8, seg: Segment, page_index: u64) InitError!void {
const frame = pmm.alloc() orelse return error.OutOfMemory; const frame = pmm.alloc() orelse return error.OutOfMemory;
const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(frame)); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(frame));
@memset(destination[0..page_size], 0); @memset(destination[0..page_size], 0);
const page_off = page_index * page_size; const page_off = page_index * page_size;
if (page_off < seg.filesz) { if (page_off < seg.filesz) {
+30 -28
View File
@@ -10,7 +10,9 @@
//! exception report the handler prints (which also reaches serial). //! exception report the handler prints (which also reaches serial).
const std = @import("std"); const std = @import("std");
const system = @import("system"); const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const device_abi = @import("device-abi");
const architecture = @import("architecture"); const architecture = @import("architecture");
const devices_broker = @import("devices-broker.zig"); const devices_broker = @import("devices-broker.zig");
const platform = @import("platform"); const platform = @import("platform");
@@ -155,7 +157,7 @@ fn powerTest(comptime action: enum { off, reboot }) void {
result(); result();
} }
const BootInformation = system.BootInformation; const BootInformation = boot_handoff.BootInformation;
fn eql(a: []const u8, b: []const u8) bool { fn eql(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b); return std.mem.eql(u8, a, b);
@@ -167,7 +169,7 @@ fn smoke(boot_information: *const BootInformation) void {
// The memory map has some usable RAM. // The memory map has some usable RAM.
const mm = boot_information.memory_map; const mm = boot_information.memory_map;
const regions = @as([*]const system.MemoryRegion, @ptrFromInt(system.physicalToVirtual(mm.regions)))[0..mm.len]; const regions = @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(mm.regions)))[0..mm.len];
var usable: u64 = 0; var usable: u64 = 0;
for (regions) |r| { for (regions) |r| {
if (r.kind == .usable) usable += r.pages; if (r.kind == .usable) usable += r.pages;
@@ -179,7 +181,7 @@ fn smoke(boot_information: *const BootInformation) void {
const b = pmm.alloc(); const b = pmm.alloc();
check("alloc returns a frame", a != null); check("alloc returns a frame", a != null);
check("alloc returns distinct frames", a != null and b != null and a.? != b.?); check("alloc returns distinct frames", a != null and b != null and a.? != b.?);
check("frames are page-aligned", (a orelse 1) % system.page_size == 0); check("frames are page-aligned", (a orelse 1) % abi.page_size == 0);
// Freeing restores the count. // Freeing restores the count.
const before = pmm.stats().free_frames; const before = pmm.stats().free_frames;
@@ -189,7 +191,7 @@ fn smoke(boot_information: *const BootInformation) void {
// Paging is active on our own tables (the root is non-zero and page-aligned). // Paging is active on our own tables (the root is non-zero and page-aligned).
const root = architecture.activePageTable(); const root = architecture.activePageTable();
check("paging active (page-table root set)", root != 0 and root % system.page_size == 0); check("paging active (page-table root set)", root != 0 and root % abi.page_size == 0);
result(); result();
} }
@@ -575,7 +577,7 @@ fn smpTest() void {
const tramp = architecture.trampolinePage(); const tramp = architecture.trampolinePage();
check("trampoline frame reserved", tramp != 0); check("trampoline frame reserved", tramp != 0);
if (tramp != 0) { if (tramp != 0) {
const bytes: [*]const u8 = @ptrFromInt(system.physicalToVirtual(tramp)); const bytes: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(tramp));
var zeroed = true; var zeroed = true;
for (0..4096) |b| { for (0..4096) |b| {
if (bytes[b] != 0) zeroed = false; if (bytes[b] != 0) zeroed = false;
@@ -759,7 +761,7 @@ fn userMemTest() void {
var mapped: usize = 0; var mapped: usize = 0;
while (mapped < npages) : (mapped += 1) { while (mapped < npages) : (mapped += 1) {
frames[mapped] = pmm.alloc() orelse break; frames[mapped] = pmm.alloc() orelse break;
architecture.mapUserPageInto(aspace, arena + mapped * system.page_size, frames[mapped], true, false); architecture.mapUserPageInto(aspace, arena + mapped * abi.page_size, frames[mapped], true, false);
} }
check("granted three user pages", mapped == npages); check("granted three user pages", mapped == npages);
@@ -767,13 +769,13 @@ fn userMemTest() void {
var translate_ok = true; var translate_ok = true;
var rw_ok = true; var rw_ok = true;
for (0..npages) |i| { for (0..npages) |i| {
const va = arena + i * system.page_size; const va = arena + i * abi.page_size;
const physical = architecture.translate(aspace, va) orelse { const physical = architecture.translate(aspace, va) orelse {
translate_ok = false; translate_ok = false;
continue; continue;
}; };
if (physical != frames[i]) translate_ok = false; if (physical != frames[i]) translate_ok = false;
const p: [*]u8 = @ptrFromInt(system.physicalToVirtual(physical)); const p: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(physical));
p[0] = 0xA5; p[0] = 0xA5;
if (p[0] != 0xA5) rw_ok = false; if (p[0] != 0xA5) rw_ok = false;
} }
@@ -782,7 +784,7 @@ fn userMemTest() void {
// Release them the way munmap does, then tear down the address space. // Release them the way munmap does, then tear down the address space.
for (0..npages) |i| { for (0..npages) |i| {
const va = arena + i * system.page_size; const va = arena + i * abi.page_size;
if (architecture.translate(aspace, va)) |physical| { if (architecture.translate(aspace, va)) |physical| {
architecture.unmapUserPageInto(aspace, va); architecture.unmapUserPageInto(aspace, va);
pmm.free(physical); pmm.free(physical);
@@ -879,7 +881,7 @@ fn processTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.write_count = 0; process.write_count = 0;
process.write_from_user = false; process.write_from_user = false;
@@ -930,7 +932,7 @@ fn initTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.write_count = 0; process.write_count = 0;
const spawned = if (process.spawnProcess(image, 4)) true else |err| blk: { const spawned = if (process.spawnProcess(image, 4)) true else |err| blk: {
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)}); log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
@@ -964,7 +966,7 @@ fn initialRamdiskTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
@@ -1008,7 +1010,7 @@ fn vfsTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
@@ -1071,7 +1073,7 @@ fn hpetTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
@@ -1115,14 +1117,14 @@ fn hpetRouteOk() bool {
/// The GSI discovery recorded for the HPET, from the same device table the driver saw. /// The GSI discovery recorded for the HPET, from the same device table the driver saw.
fn hpetGsi() ?u32 { fn hpetGsi() ?u32 {
var buffer: [16]system.DeviceDescriptor = undefined; var buffer: [16]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len); const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| { for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(system.DeviceClass.timer)) continue; if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
if (d.parent != system.no_parent) continue; // the block, not a comparator child if (d.parent != device_abi.no_parent) continue; // the block, not a comparator child
for (0..d.resource_count) |j| { for (0..d.resource_count) |j| {
const r = d.resources[j]; const r = d.resources[j];
if (r.kind == @intFromEnum(system.ResourceKind.irq)) return @intCast(r.start); if (r.kind == @intFromEnum(device_abi.ResourceKind.irq)) return @intCast(r.start);
} }
} }
return null; return null;
@@ -1148,7 +1150,7 @@ fn busTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
@@ -1187,7 +1189,7 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(system.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; 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 { const rd = initial_ramdisk.Reader.init(image) orelse {
check("initial_ramdisk image is valid", false); check("initial_ramdisk image is valid", false);
result(); result();
@@ -1221,7 +1223,7 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
/// trusted and doesn't obey containment: a PCI function's BAR is not inside its host /// trusted and doesn't obey containment: a PCI function's BAR is not inside its host
/// bridge's `bus_range`, because a bus-number range isn't an address window. /// bridge's `bus_range`, because a bus-number range isn't an address window.
fn childrenContained() bool { fn childrenContained() bool {
var buffer: [64]system.DeviceDescriptor = undefined; var buffer: [64]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len); const n = @min(devices_broker.enumerate(&buffer), buffer.len);
const bus_id = hpetDeviceId() orelse return false; const bus_id = hpetDeviceId() orelse return false;
@@ -1237,7 +1239,7 @@ fn childrenContained() bool {
for (0..p.resource_count) |j| { for (0..p.resource_count) |j| {
const pr = p.resources[j]; const pr = p.resources[j];
if (pr.kind != r.kind) continue; if (pr.kind != r.kind) continue;
if (r.kind == @intFromEnum(system.ResourceKind.irq)) { if (r.kind == @intFromEnum(device_abi.ResourceKind.irq)) {
if (pr.start == r.start) ok = true; if (pr.start == r.start) ok = true;
} else if (r.len != 0 and r.start >= pr.start and } else if (r.len != 0 and r.start >= pr.start and
r.start + r.len <= pr.start + pr.len) ok = true; r.start + r.len <= pr.start + pr.len) ok = true;
@@ -1250,13 +1252,13 @@ fn childrenContained() bool {
/// Device id of the HPET (the bus bus claims), from the same table drivers see. /// Device id of the HPET (the bus bus claims), from the same table drivers see.
fn hpetDeviceId() ?u64 { fn hpetDeviceId() ?u64 {
var buffer: [64]system.DeviceDescriptor = undefined; var buffer: [64]device_abi.DeviceDescriptor = undefined;
const n = @min(devices_broker.enumerate(&buffer), buffer.len); const n = @min(devices_broker.enumerate(&buffer), buffer.len);
for (buffer[0..n]) |d| { for (buffer[0..n]) |d| {
if (d.class != @intFromEnum(system.DeviceClass.timer)) continue; if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
if (d.parent != system.no_parent) continue; // a comparator child, not the block if (d.parent != device_abi.no_parent) continue; // a comparator child, not the block
for (0..d.resource_count) |j| { for (0..d.resource_count) |j| {
if (d.resources[j].kind == @intFromEnum(system.ResourceKind.memory)) return d.id; if (d.resources[j].kind == @intFromEnum(device_abi.ResourceKind.memory)) return d.id;
} }
} }
return null; return null;
@@ -1350,7 +1352,7 @@ fn ioPassTest() void {
return; return;
}; };
// Map it the way mmio_map does (device grant), then tear the space down. // Map it the way mmio_map does (device grant), then tear the space down.
architecture.mapUserDeviceInto(aspace, process.device_arena_base, frame, system.page_size); architecture.mapUserDeviceInto(aspace, process.device_arena_base, frame, abi.page_size);
architecture.destroyAddressSpace(aspace); architecture.destroyAddressSpace(aspace);
// The page tables were reclaimed; the device-granted frame must not have been. // The page tables were reclaimed; the device-granted frame must not have been.