Rename the shared contract module danos -> system; QEMU logs to /var/log/system

The shared kernel<->user ABI contract (BootInformation, the SystemCall numbers,
DeviceDescriptor, page_size, ...) is now the `system` module at
system/system.zig, following the convention that a directory's root file takes
the directory's name.

One overlap to note: the runtime's syscall wrappers are already `runtime.system`,
so the single file that uses both the contract and those wrappers
(library/runtime/heap.zig) aliases the wrappers locally as `system_calls`. The
two are distinct (top-level `system` vs `runtime.system`); everywhere else the
contract is just `system`.

Also: the QEMU run's serial capture now lands in the FHS log location,
zig-out/var/log/system/serial0-<timestamp>.log — a stand-in for the kernel's own
logging system, which will eventually write there itself.

Suite 35/35 plus host tests green.
This commit is contained in:
Daniel Samson
2026-07-10 14:09:38 +01:00
parent 3d1de37d0e
commit d19a0ae38d
38 changed files with 202 additions and 198 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/danos.zig`). and a microkernel (`system/kernel/`), sharing a neutral handoff contract (`system/system.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 danos = @import("danos"); const system = @import("system");
const BootInformation = danos.BootInformation; const BootInformation = system.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 danos.Framebuffer{ .framebuffer = queryFramebuffer(bs) catch system.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) !danos.Framebuffer { fn queryFramebuffer(bs: *uefi.tables.BootServices) !system.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) !danos.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) !danos.PixelFormat { fn pixelFormat(fmt: GraphicsOutput.PixelFormat) !system.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 danos.physmap_base (the kernel's permanent way to // staged move), a physmap at system.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, danos.physicalToVirtual(address), address); // physmap try pool.map2M(pml4, system.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, danos.physicalToVirtual(p), p); try pool.map2M(pml4, system.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 < danos.kernel_virt_base) continue; if (seg.virtual < system.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) !danos.MemoryMap { fn exitBootServices(bs: *uefi.tables.BootServices) !system.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(danos.MemoryRegion)); const regions_buffer = try bs.allocatePool(.loader_data, cap * @sizeOf(system.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) !danos.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) danos.MemoryMap { fn convertMemoryMap(map: MemoryMapSlice, out: []u8) system.MemoryMap {
const regions: [*]danos.MemoryRegion = @ptrCast(@alignCast(out.ptr)); const regions: [*]system.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) danos.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 * danos.page_size; const region_end = d.physical_start + d.number_of_pages * system.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 * danos.page_size == d.physical_start) previous.base + previous.pages * system.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) danos.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) danos.MemoryKind { fn classify(d: *const uefi.tables.MemoryDescriptor) system.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,
+18 -14
View File
@@ -98,8 +98,8 @@ pub fn build(b: *std.Build) void {
// Shared handoff definitions (BootInformation, Framebuffer, ...). No target is set, // Shared handoff definitions (BootInformation, Framebuffer, ...). No target is set,
// so the module inherits the target of whichever binary imports it — the // so the module inherits the target of whichever binary imports it — the
// freestanding kernel or the UEFI bootloader. // freestanding kernel or the UEFI bootloader.
const danos_module = b.addModule("danos", .{ const system_module = b.addModule("system", .{
.root_source_file = b.path("system/danos.zig"), .root_source_file = b.path("system/system.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 +115,7 @@ 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 = "danos", .module = danos_module }, // paging uses the shared BootInformation/memory-map types .{ .name = "system", .module = system_module }, // paging uses the shared BootInformation/memory-map types
.{ .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 +134,7 @@ 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 = "danos", .module = danos_module }, // BootInformation (carries the ACPI RSDP) .{ .name = "system", .module = system_module }, // BootInformation (carries the ACPI RSDP)
.{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool) .{ .name = "parameters", .module = parameters_module }, // maximum_cpus (the discovery pool)
}, },
}); });
@@ -158,7 +158,7 @@ pub fn build(b: *std.Build) void {
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 = "danos", .module = danos_module }, .{ .name = "system", .module = system_module },
.{ .name = "vfs-protocol", .module = vfs_protocol_module }, .{ .name = "vfs-protocol", .module = vfs_protocol_module },
}, },
}); });
@@ -172,7 +172,7 @@ 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 = "danos", .module = danos_module }, .{ .name = "system", .module = system_module },
}, },
}); });
@@ -212,7 +212,7 @@ 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 = "danos", .module = danos_module }, .{ .name = "system", .module = system_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 },
@@ -301,7 +301,7 @@ pub fn build(b: *std.Build) void {
}), }),
.optimize = optimize, .optimize = optimize,
.imports = &.{ .imports = &.{
.{ .name = "danos", .module = danos_module }, .{ .name = "system", .module = system_module },
}, },
}), }),
}); });
@@ -372,14 +372,18 @@ pub fn build(b: *std.Build) void {
"-device", "-device",
"VGA,edid=on,xres=1280,yres=720", "VGA,edid=on,xres=1280,yres=720",
}); });
// Always capture the guest's serial0 (the kernel's machine-readable log) to a // Capture the guest's serial0 (danos's machine-readable log) to the FHS log
// timestamped file under zig-out, so each run leaves its own log behind. // location, /var/log/system — a stand-in for the kernel's own logging system,
const serial_log = b.fmt("{s}/run-x86-64-serial0-{s}.log", .{ b.install_path, timestamp(b) }); // which will one day write here itself. One timestamped file per run.
const log_dir = b.fmt("{s}/var/log/system", .{b.install_path});
const make_log_dir = b.addSystemCommand(&.{ "mkdir", "-p", log_dir });
const serial_log = b.fmt("{s}/serial0-{s}.log", .{ log_dir, timestamp(b) });
run_efi.addArgs(&.{ "-serial", b.fmt("file:{s}", .{serial_log}) }); run_efi.addArgs(&.{ "-serial", b.fmt("file:{s}", .{serial_log}) });
// The whole FHS zig-out must be installed before we mount it. // The whole FHS zig-out must be installed (and the log dir created) before we mount it.
run_efi.step.dependOn(b.getInstallStep()); run_efi.step.dependOn(b.getInstallStep());
run_efi.step.dependOn(&make_log_dir.step);
const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/run-x86-64-serial0-<timestamp>.log"); const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/var/log/system/serial0-<timestamp>.log");
run_efi_step.dependOn(&run_efi.step); run_efi_step.dependOn(&run_efi.step);
// const run_cmd = b.addRunArtifact(exe); // const run_cmd = b.addRunArtifact(exe);
@@ -396,7 +400,7 @@ pub fn build(b: *std.Build) void {
// 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(.{ const mod_tests = b.addTest(.{
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.root_source_file = b.path("system/danos.zig"), .root_source_file = b.path("system/system.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}), }),
+2 -2
View File
@@ -146,7 +146,7 @@ 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)
danos.zig the kernel↔user ABI contract (the `danos` module) system.zig the kernel↔user ABI contract (the `system` 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)
@@ -177,7 +177,7 @@ 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/danos.zig` | | Shared loader↔kernel contract (`BootInfo`, `Framebuffer`, `MemoryMap`, `Syscall`, ABI) | `system/system.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 `danos` module) system/danos.zig BootInfo.acpi_rsdp (u64, in the shared `system` module) system/system.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 `danos` module). So instead of a call, it module at all** (it imports only the shared `system` module). So instead of a call, it
deposits a value in the handoff struct: deposits a value in the handoff struct:
```zig ```zig
+1 -1
View File
@@ -83,7 +83,7 @@ There are really two independent questions, and it's worth not conflating them:
The kernel entry point `_start` currently still lives in the generic `main.zig` as The kernel entry point `_start` currently still lives in the generic `main.zig` as
a thin trampoline into `kmain`. It's arch-adjacent (its calling convention is a thin trampoline into `kmain`. It's arch-adjacent (its calling convention is
x86_64 [SysV](sysv.md), via the shared `danos.kernel_abi`), but it's three lines x86_64 [SysV](sysv.md), via the shared `system.kernel_abi`), but it's three lines
and mostly generic, so it stays put for now. When AArch64 arrives — where entry means setting and mostly generic, so it stays put for now. When AArch64 arrives — where entry means setting
up a stack and reading a device-tree pointer from a register — the entry work will up a stack and reading a device-tree pointer from a register — the entry work will
be substantial and per-arch, and *that* is when we extract an entry interface into be substantial and per-arch, and *that* is when we extract an entry interface into
+1 -1
View File
@@ -22,7 +22,7 @@ say.*
## The capability: claim before touch ## The capability: claim before touch
The five driver syscalls (`system/danos.zig`, dispatched in `system/kernel/process.zig`): The five driver syscalls (`system/system.zig`, dispatched in `system/kernel/process.zig`):
| # | Call | Meaning | | # | Call | Meaning |
|---|------|---------| |---|------|---------|
+7 -7
View File
@@ -86,9 +86,9 @@ All of this *must* happen now, because after exit there's no GOP to ask. (See
- Use the **LoadedImage** protocol to discover which device we booted from, then - Use the **LoadedImage** protocol to discover which device we booted from, then
**SimpleFileSystem** to open that volume. **SimpleFileSystem** to open that volume.
- Open the file named `danos`, seek to the end to learn its size, rewind, and read - Open the kernel ELF at its FHS path (`system\kernel`), seek to the end to learn its
the whole ELF into a firmware-allocated pool buffer. (`read` may return short, so size, rewind, and read the whole ELF into a firmware-allocated pool buffer. (`read`
we loop.) may return short, so we loop.)
- Parse the ELF: validate the `\x7fELF` magic and the `x86_64` machine type, then - Parse the ELF: validate the `\x7fELF` magic and the `x86_64` machine type, then
walk the program headers. For every `PT_LOAD` segment we: walk the program headers. For every `PT_LOAD` segment we:
- reserve the exact physical pages it's linked at (`p_paddr`) via - reserve the exact physical pages it's linked at (`p_paddr`) via
@@ -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(danos.kernel_abi) noreturn = const kernel: *const fn (*const BootInfo) callconv(system.kernel_abi) noreturn =
@ptrFromInt(entry); @ptrFromInt(entry);
kernel(&boot_info); kernel(&boot_info);
``` ```
@@ -142,17 +142,17 @@ 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
`danos.kernel_abi` (defined in `system/danos.zig`). The loader's function-pointer type `system.kernel_abi` (defined in `system/system.zig`). The loader's function-pointer type
and the kernel's `_start` both reference it, so the pointer lands in the register and the kernel's `_start` both reference it, so the pointer lands in the register
the kernel expects. This is the whole reason `kernel_abi` lives in the shared the kernel expects. This is the whole reason `kernel_abi` lives in the shared
`danos` module: it's a contract both binaries must agree on. See `system` 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/danos.zig` provides — imported by both as the `danos` module: what `system/system.zig` provides — imported by both as the `system` module:
- `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
@@ -8,7 +8,7 @@ natural unit because that's the granularity the CPU's paging hardware maps — a
it is the primitive everything above it stands on: page tables, the kernel heap, it is the primitive everything above it stands on: page tables, the kernel heap,
per-process memory all ultimately ask the frame allocator for pages. per-process memory all ultimately ask the frame allocator for pages.
It's **generic kernel code**: it operates on the neutral `danos.MemoryRegion` It's **generic kernel code**: it operates on the neutral `system.MemoryRegion`
array, so there's no UEFI in it and nothing architecture-specific beyond the 4 KiB array, so there's no UEFI in it and nothing architecture-specific beyond the 4 KiB
page. (Contrast [arch.md](arch.md), which is where CPU-specific code lives.) page. (Contrast [arch.md](arch.md), which is where CPU-specific code lives.)
+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/danos.zig`) is the four facts you need to Our `Framebuffer` struct (`system/system.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/danos.zig`) gates every on-screen path. A headless, `Framebuffer.present()` (in `system/system.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)
+2 -2
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/danos.zig`, the shared loader↔kernel contract: Defined in `system/system.zig`, the shared loader↔kernel contract:
```zig ```zig
pub const MemoryKind = enum(u32) { pub const MemoryKind = enum(u32) {
@@ -121,7 +121,7 @@ The kernel receives a plain array and reads it with zero UEFI knowledge:
```zig ```zig
const mm = boot_info.memory_map; const mm = boot_info.memory_map;
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(mm.regions))[0..mm.len]; const regions = @as([*]const system.MemoryRegion, @ptrFromInt(mm.regions))[0..mm.len];
for (regions) |r| { for (regions) |r| {
if (r.kind == .usable) usable_pages += r.pages; if (r.kind == .usable) usable_pages += r.pages;
} }
+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:
`danos.physToVirt(phys)`. The layout constants live in `system/danos.zig`: `system.physToVirt(phys)`. The layout constants live in `system/system.zig`:
| region | virtual base | PML4 slot | | region | virtual base | PML4 slot |
|--------|--------------|-----------| |--------|--------------|-----------|
+1 -1
View File
@@ -203,7 +203,7 @@ next lands.
[scheduling.md](scheduling.md#affinity-pinning-a-task-to-a-core)). The `affinity` [scheduling.md](scheduling.md#affinity-pinning-a-task-to-a-core)). The `affinity`
test confirms a pinned task never migrates. This is the mechanism the fault-on-AP test confirms a pinned task never migrates. This is the mechanism the fault-on-AP
test rides on, and the *explicit-affinity* real-time-predictable model. test rides on, and the *explicit-affinity* real-time-predictable model.
- **Right-sized footprint** — the per-CPU ceiling (`danos.max_cpus`, one constant - **Right-sized footprint** — the per-CPU ceiling (`system.max_cpus`, one constant
shared by discovery, the scheduler, and the per-core GDT/TSS) is generous (128), but shared by discovery, the scheduler, and the per-core GDT/TSS) is generous (128), but
the *large* per-core resources — the kernel and IST (double-fault) stacks — are the *large* per-core resources — the kernel and IST (double-fault) stacks — are
**heap-allocated at bring-up**, only for cores that actually come online. Only the **heap-allocated at bring-up**, only for cores that actually come online. Only the
+3 -3
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/danos.zig`: Several places in danos say "the kernel is SysV" — most visibly `system/system.zig`:
```zig ```zig
pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} }; pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} };
@@ -59,9 +59,9 @@ danos's two binaries default to different conventions:
When the loader jumps to the kernel passing the `BootInfo` pointer, both sides have When the loader jumps to the kernel passing the `BootInfo` pointer, both sides have
to agree *which register that pointer lands in*. Left to their defaults, the loader to agree *which register that pointer lands in*. Left to their defaults, the loader
would place it in RCX while the kernel looked in RDI — and the kernel would read would place it in RCX while the kernel looked in RDI — and the kernel would read
garbage. So both sides reference the same `danos.kernel_abi` (SysV): the loader's garbage. So both sides reference the same `system.kernel_abi` (SysV): the loader's
function-pointer type and the kernel's `_start` both carry function-pointer type and the kernel's `_start` both carry
`callconv(danos.kernel_abi)`, and the pointer reliably arrives in RDI. That is the `callconv(system.kernel_abi)`, and the pointer reliably arrives in RDI. That is the
whole reason `kernel_abi` lives in the shared contract — see [efi.md](efi.md) for whole reason `kernel_abi` lives in the shared contract — see [efi.md](efi.md) for
the handoff it governs. the handoff it governs.
+1 -1
View File
@@ -8,7 +8,7 @@ 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 `danos` module (the handoff layout in `system/danos.zig`). These compile the shared `system` module (the handoff layout in `system/system.zig`). These compile
for the host and run natively. 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 -1
View File
@@ -6,7 +6,7 @@
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 danos = @import("danos"); 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 danos = @import("danos"); const system = @import("system");
const sc = @import("system-call.zig"); const sc = @import("system-call.zig");
pub const DeviceDescriptor = danos.DeviceDescriptor; pub const DeviceDescriptor = system.DeviceDescriptor;
pub const ResourceDescriptor = danos.ResourceDescriptor; pub const ResourceDescriptor = system.ResourceDescriptor;
pub const DeviceClass = danos.DeviceClass; pub const DeviceClass = system.DeviceClass;
pub const ResourceKind = danos.ResourceKind; pub const ResourceKind = system.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 = danos.no_parent; pub const no_parent = system.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
+5 -5
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 danos = @import("danos"); const system = @import("system");
const system = @import("system.zig"); const system_calls = @import("system.zig");
const page_size = danos.page_size; const page_size = system.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`.
@@ -46,8 +46,8 @@ fn payloadOf(block: *Block) [*]u8 {
/// grants usually are adjacent). Returns false if the kernel is out of memory. /// grants usually are adjacent). Returns false if the kernel is out of memory.
fn grow(minimum_bytes: usize) bool { fn grow(minimum_bytes: usize) bool {
const bytes = alignUp(@max(minimum_bytes, chunk), page_size); const bytes = alignUp(@max(minimum_bytes, chunk), page_size);
const ret = system.mmap(bytes, system.PROT_READ | system.PROT_WRITE); const ret = system_calls.mmap(bytes, system_calls.PROT_READ | system_calls.PROT_WRITE);
if (system.mmapFailed(ret)) return false; if (system_calls.mmapFailed(ret)) return false;
const block: *Block = @ptrFromInt(ret); const block: *Block = @ptrFromInt(ret);
block.size = bytes; block.size = bytes;
+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 danos = @import("danos"); const system = @import("system");
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: danos.ServiceId, h: Handle) bool { pub fn register(id: system.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: danos.ServiceId) ?Handle { pub fn lookup(id: system.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 = danos.notify_badge_bit; pub const notify_badge_bit: u64 = system.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(danos.SystemCall.ipc_reply_wait)), : [n] "{rax}" (@intFromEnum(system.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 danos = @import("danos"); const system = @import("system");
const SystemCall = danos.SystemCall; const SystemCall = system.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 `danos.SystemCall`, the single //! stubs, one per kernel call. Numbers come from `system.SystemCall`, the single
//! source of truth shared with the kernel dispatcher. //! source of truth shared with the kernel dispatcher.
const danos = @import("danos"); const system = @import("system");
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 = danos.prot_read; pub const PROT_READ: usize = system.prot_read;
pub const PROT_WRITE: usize = danos.prot_write; pub const PROT_WRITE: usize = system.prot_write;
pub const PROT_EXEC: usize = danos.prot_exec; pub const PROT_EXEC: usize = system.prot_exec;
/// Give up the rest of this quantum. /// Give up the rest of this quantum.
pub fn yield() void { pub fn yield() void {
+12 -12
View File
@@ -15,7 +15,7 @@
//! 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 danos = @import("danos"); const system = @import("system");
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 +147,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(danos.physicalToVirtual(sdt_physical)); const h: *const SystemDescriptorTableHeader = @ptrFromInt(system.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 +376,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(danos.physicalToVirtual(rsdp_physical)); const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(system.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(danos.physicalToVirtual(rsdp_physical)), 20)) return error.BadRsdpChecksum; if (!checksumOk(@ptrFromInt(system.physicalToVirtual(rsdp_physical)), 20)) return error.BadRsdpChecksum;
if (rsdp.revision >= 2) { if (rsdp.revision >= 2) {
const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(danos.physicalToVirtual(rsdp_physical)); const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(system.physicalToVirtual(rsdp_physical));
if (!checksumOk(@ptrFromInt(danos.physicalToVirtual(rsdp_physical)), xsdp.length)) return error.BadXsdpChecksum; if (!checksumOk(@ptrFromInt(system.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 +393,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(danos.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]]; blocks[i] = @as([*]const u8, @ptrFromInt(system.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 +412,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(danos.physicalToVirtual(root_physical)); const header: *const SystemDescriptorTableHeader = @ptrFromInt(system.physicalToVirtual(root_physical));
if (!checksumOk(@ptrFromInt(danos.physicalToVirtual(root_physical)), header.length)) return error.BadRootChecksum; if (!checksumOk(@ptrFromInt(system.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(danos.physicalToVirtual(root_physical)); const base: [*]const u8 = @ptrFromInt(system.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 +427,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(danos.physicalToVirtual(sdt_physical)); const header: *const SystemDescriptorTableHeader = @ptrFromInt(system.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 +1120,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, danos.page_size, true)); return @ptrFromInt(hal.mapMmio(physical, system.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.
+2 -2
View File
@@ -9,7 +9,7 @@
//! compile-time choice. //! compile-time choice.
const std = @import("std"); const std = @import("std");
const danos = @import("danos"); const system = @import("system");
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 danos.BootInformation, boot_information: *const system.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 danos = @import("danos"); const system = @import("system");
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(danos.physicalToVirtual(msr & 0xFFFFF000)); // physical base is bits 12+ base = @intCast(system.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 danos = @import("danos"); const system = @import("system");
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 danos.BootInformation) void { pub fn enablePaging(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const system.BootInformation) void {
paging.init(allocFrame, freeFrame, boot_information); paging.init(allocFrame, freeFrame, boot_information);
} }
+10 -10
View File
@@ -10,10 +10,10 @@
//! 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 danos = @import("danos"); const system = @import("system");
const io = @import("io.zig"); const io = @import("io.zig");
const page_size = danos.page_size; const page_size = system.page_size;
// Page-table entry bits. // Page-table entry bits.
const present: u64 = 1 << 0; const present: u64 = 1 << 0;
@@ -58,7 +58,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(danos.physicalToVirtual(physical)); return @ptrFromInt(system.physicalToVirtual(physical));
} }
fn allocTable() u64 { fn allocTable() u64 {
@@ -102,12 +102,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, danos.physicalToVirtual(address), address, flags); mapPage(pml4, system.physicalToVirtual(address), address, flags);
} }
} }
fn regions(mm: danos.MemoryMap) []const danos.MemoryRegion { fn regions(mm: system.MemoryMap) []const system.MemoryRegion {
return @as([*]const danos.MemoryRegion, @ptrFromInt(danos.physicalToVirtual(mm.regions)))[0..mm.len]; return @as([*]const system.MemoryRegion, @ptrFromInt(system.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 +118,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 danos.BootInformation) void { pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const system.BootInformation) void {
alloc_frame = allocFrame; alloc_frame = allocFrame;
free_frame = freeFrame; free_frame = freeFrame;
enableNx(); enableNx();
@@ -136,7 +136,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, danos.physicalToVirtual(0xFEE00000), 0xFEE00000, present | writable | no_execute); mapPage(pml4, system.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 +206,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 = danos.physicalToVirtual(address); const virtual = system.physicalToVirtual(address);
mapPage(kernel_pml4, virtual, address, flags); mapPage(kernel_pml4, virtual, address, flags);
invalidate(virtual); invalidate(virtual);
} }
return danos.physicalToVirtual(physical); return system.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 danos = @import("danos"); const system = @import("system");
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(danos.physicalToVirtual(tramp_physical)); const destination: [*]u8 = @ptrFromInt(system.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(danos.physicalToVirtual(tramp_physical)); const destination: [*]u8 = @ptrFromInt(system.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(danos.physicalToVirtual(tramp_physical + (sym - start))); return @ptrFromInt(system.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 danos = @import("danos"); const system = @import("system");
/// 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: danos.Framebuffer) void { pub fn init(fb: system.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: danos.Framebuffer, fb: system.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: danos.Framebuffer) Console { pub fn init(fb: system.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 = danos.physicalToVirtual(fb.base); if (fb.base != 0) mapped.base = system.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 danos = @import("danos"); const system = @import("system");
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]danos.DeviceDescriptor = undefined; var devices: [maximum_devices]system.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, danos.no_parent); walk(device_tree.root, system.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) danos.no_parent else record(node, parent_id); const id = if (node.class == .root) system.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 danos.no_parent; // children of a dropped node become roots, not orphans return system.no_parent; // children of a dropped node become roots, not orphans
} }
var d = std.mem.zeroes(danos.DeviceDescriptor); var d = std.mem.zeroes(system.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, danos.maximum_device_resources); const rc = @min(node.resource_count, system.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: []danos.DeviceDescriptor) usize { pub fn enumerate(out: []system.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) ?danos.ResourceDescriptor { pub fn resourceOf(id: u64, index: u64) ?system.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) ?danos.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: danos.ResourceDescriptor, child: danos.ResourceDescriptor) bool { fn contains(parent: system.ResourceDescriptor, child: system.ResourceDescriptor) bool {
if (parent.kind != child.kind) return false; if (parent.kind != child.kind) return false;
if (child.kind == @intFromEnum(danos.ResourceKind.irq)) return parent.start == child.start; if (child.kind == @intFromEnum(system.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 danos.DeviceDescriptor) RegisterError!u64 { pub fn register(parent_id: u64, owner: u32, descriptor: *const system.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 > danos.maximum_device_resources) return error.TooManyResources; if (descriptor.resource_count > system.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 danos.DeviceDescr
if (!ok) return error.NotContained; if (!ok) return error.NotContained;
} }
var d = std.mem.zeroes(danos.DeviceDescriptor); var d = std.mem.zeroes(system.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 danos = @import("danos"); const system = @import("system");
const architecture = @import("architecture"); const architecture = @import("architecture");
const pmm = @import("pmm.zig"); const pmm = @import("pmm.zig");
const page_size = danos.page_size; const page_size = system.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
+7 -7
View File
@@ -22,13 +22,13 @@
//! 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 danos = @import("danos"); const system = @import("system");
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 = danos.page_size; const page_size = system.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 +50,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/danos.zig), because ring 3 has to test the same bit. /// shared contract (system/system.zig), because ring 3 has to test the same bit.
pub const notify_badge_bit: u64 = danos.notify_badge_bit; pub const notify_badge_bit: u64 = system.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 +124,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(danos.physicalToVirtual(s)); const source: [*]const u8 = @ptrFromInt(system.physicalToVirtual(s));
const destination: [*]u8 = @ptrFromInt(danos.physicalToVirtual(d)); const destination: [*]u8 = @ptrFromInt(system.physicalToVirtual(d));
@memcpy(destination[0..n], source[0..n]); @memcpy(destination[0..n], source[0..n]);
off += n; off += n;
} }
@@ -147,7 +147,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(danos.physicalToVirtual(s)); const source: [*]const u8 = @ptrFromInt(system.physicalToVirtual(s));
@memcpy(destination[off..][0..n], source[0..n]); @memcpy(destination[off..][0..n], source[0..n]);
off += n; off += n;
} }
+12 -12
View File
@@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const danos = @import("danos"); const system = @import("system");
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 +14,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 = danos.BootInformation; const BootInformation = system.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. `danos.kernel_abi` re-exports it to the loader. /// register the other expects. `system.kernel_abi` re-exports it to the loader.
pub const kernel_abi = danos.kernel_abi; pub const kernel_abi = system.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 +50,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(danos.physicalToVirtual(@intFromPtr(boot_information)))); kmain(@ptrFromInt(system.physicalToVirtual(@intFromPtr(boot_information))));
} }
fn kmain(boot_information: *const BootInformation) noreturn { fn kmain(boot_information: *const BootInformation) noreturn {
@@ -91,7 +91,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 danos.MemoryRegion, @ptrFromInt(danos.physicalToVirtual(boot_information.memory_map.regions)))[0..boot_information.memory_map.len]; const regions = @as([*]const system.MemoryRegion, @ptrFromInt(system.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 +102,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 * danos.page_size; const total_bytes = total_pages * system.page_size;
const gib = 1 << 30; const gib = 1 << 30;
log.write("\ndanos: physical memory\n"); log.write("\ndanos: physical memory\n");
@@ -277,7 +277,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(danos.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(system.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 +300,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 danos.BootInformation) void { fn startInitialRamdiskBinaries(boot_information: *const system.BootInformation) void {
if (boot_information.initial_ramdisk_len == 0) return; if (boot_information.initial_ramdisk_len == 0) return;
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const image = @as([*]const u8, @ptrFromInt(system.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 +386,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 * danos.page_size / (1024 * 1024); return pages * system.page_size / (1024 * 1024);
} }
fn kib(frames: u64) u64 { fn kib(frames: u64) u64 {
return frames * danos.page_size / (1024); return frames * system.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
+7 -7
View File
@@ -2,14 +2,14 @@
//! 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 `danos.MemoryRegion` //! This is generic kernel code: it works on the neutral `system.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 danos = @import("danos"); const system = @import("system");
const page_size = danos.page_size; const page_size = system.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 +48,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: danos.MemoryMap) []const danos.MemoryRegion { fn regions(map: system.MemoryMap) []const system.MemoryRegion {
return @as([*]const danos.MemoryRegion, @ptrFromInt(danos.physicalToVirtual(map.regions)))[0..map.len]; return @as([*]const system.MemoryRegion, @ptrFromInt(system.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 +59,7 @@ fn regions(map: danos.MemoryMap) []const danos.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: danos.MemoryMap) void { pub fn init(map: system.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 +91,7 @@ pub fn init(map: danos.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(danos.physicalToVirtual(bitmap_base)))[0..bitmap_bytes]; bitmap = @as([*]u8, @ptrFromInt(system.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
+12 -12
View File
@@ -21,7 +21,7 @@
const std = @import("std"); const std = @import("std");
const elf = std.elf; const elf = std.elf;
const danos = @import("danos"); const system = @import("system");
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 +31,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 = danos.page_size; const page_size = system.page_size;
const SystemCall = danos.SystemCall; const SystemCall = system.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 +79,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 (`danos.SystemCall`). /// The system_call surface, dispatched on the saved system_call number (`system.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 +203,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(danos.DeviceDescriptor); const sz = @sizeOf(system.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: [*]danos.DeviceDescriptor = @ptrFromInt(buffer_ptr); const out: [*]system.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 +228,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(danos.ResourceKind.memory)) return fail(state); if (r.kind != @intFromEnum(system.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 +260,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: danos.DeviceDescriptor = undefined; var descriptor: system.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 +284,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(danos.ResourceKind.irq)) return null; if (r.kind != @intFromEnum(system.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 +373,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(danos.physicalToVirtual(frame)); const destination: [*]u8 = @ptrFromInt(system.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 +428,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(danos.physicalToVirtual(code_frame)); const code: [*]u8 = @ptrFromInt(system.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 +542,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(danos.physicalToVirtual(frame)); const destination: [*]u8 = @ptrFromInt(system.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) {
+27 -27
View File
@@ -10,7 +10,7 @@
//! 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 danos = @import("danos"); const system = @import("system");
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");
@@ -153,7 +153,7 @@ fn powerTest(comptime action: enum { off, reboot }) void {
result(); result();
} }
const BootInformation = danos.BootInformation; const BootInformation = system.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);
@@ -165,7 +165,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 danos.MemoryRegion, @ptrFromInt(danos.physicalToVirtual(mm.regions)))[0..mm.len]; const regions = @as([*]const system.MemoryRegion, @ptrFromInt(system.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;
@@ -177,7 +177,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) % danos.page_size == 0); check("frames are page-aligned", (a orelse 1) % system.page_size == 0);
// Freeing restores the count. // Freeing restores the count.
const before = pmm.stats().free_frames; const before = pmm.stats().free_frames;
@@ -187,7 +187,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 % danos.page_size == 0); check("paging active (page-table root set)", root != 0 and root % system.page_size == 0);
result(); result();
} }
@@ -573,7 +573,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(danos.physicalToVirtual(tramp)); const bytes: [*]const u8 = @ptrFromInt(system.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;
@@ -757,7 +757,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 * danos.page_size, frames[mapped], true, false); architecture.mapUserPageInto(aspace, arena + mapped * system.page_size, frames[mapped], true, false);
} }
check("granted three user pages", mapped == npages); check("granted three user pages", mapped == npages);
@@ -765,13 +765,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 * danos.page_size; const va = arena + i * system.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(danos.physicalToVirtual(physical)); const p: [*]u8 = @ptrFromInt(system.physicalToVirtual(physical));
p[0] = 0xA5; p[0] = 0xA5;
if (p[0] != 0xA5) rw_ok = false; if (p[0] != 0xA5) rw_ok = false;
} }
@@ -780,7 +780,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 * danos.page_size; const va = arena + i * system.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);
@@ -877,7 +877,7 @@ fn processTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(system.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;
@@ -928,7 +928,7 @@ fn initTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(system.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)});
@@ -962,7 +962,7 @@ fn initialRamdiskTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const image = @as([*]const u8, @ptrFromInt(system.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();
@@ -1006,7 +1006,7 @@ fn vfsTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const image = @as([*]const u8, @ptrFromInt(system.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();
@@ -1069,7 +1069,7 @@ fn hpetTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const image = @as([*]const u8, @ptrFromInt(system.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();
@@ -1113,14 +1113,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]danos.DeviceDescriptor = undefined; var buffer: [16]system.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(danos.DeviceClass.timer)) continue; if (d.class != @intFromEnum(system.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // the block, not a comparator child if (d.parent != system.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(danos.ResourceKind.irq)) return @intCast(r.start); if (r.kind == @intFromEnum(system.ResourceKind.irq)) return @intCast(r.start);
} }
} }
return null; return null;
@@ -1146,7 +1146,7 @@ fn busTest(boot_information: *const BootInformation) void {
result(); result();
return; return;
} }
const image = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len]; const image = @as([*]const u8, @ptrFromInt(system.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();
@@ -1181,7 +1181,7 @@ fn busTest(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]danos.DeviceDescriptor = undefined; var buffer: [64]system.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;
@@ -1197,7 +1197,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(danos.ResourceKind.irq)) { if (r.kind == @intFromEnum(system.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;
@@ -1210,13 +1210,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]danos.DeviceDescriptor = undefined; var buffer: [64]system.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(danos.DeviceClass.timer)) continue; if (d.class != @intFromEnum(system.DeviceClass.timer)) continue;
if (d.parent != danos.no_parent) continue; // a comparator child, not the block if (d.parent != system.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(danos.ResourceKind.memory)) return d.id; if (d.resources[j].kind == @intFromEnum(system.ResourceKind.memory)) return d.id;
} }
} }
return null; return null;
@@ -1310,7 +1310,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, danos.page_size); architecture.mapUserDeviceInto(aspace, process.device_arena_base, frame, system.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.
+1 -1
View File
@@ -1,4 +1,4 @@
//! /sbin/vfs — the user-space VFS server. Shipped in the initial_ramdisk, spawned as a //! system/services/vfs — the user-space VFS server. Shipped in the initial_ramdisk, spawned as a
//! ring-3 process, and reached by every other process through IPC (the `runtime` //! ring-3 process, and reached by every other process through IPC (the `runtime`
//! file API marshals open/read/write/stat/close into calls to this server's //! file API marshals open/read/write/stat/close into calls to this server's
//! endpoint, published under the well-known `vfs` service id). //! endpoint, published under the well-known `vfs` service id).