Device manager (increment 1): discover + match
The device manager is the ring-3 process that turns the device tree into a running system — the udev-analog. It is mechanism-vs-policy done right: the kernel enumerates the hardware and enforces the claim capability; this decides which driver serves which device, using no special privilege (the same device_enumerate any process could call). This first increment does the discovery + matching half: system/services/ device-manager enumerates /system/devices, matches each device to a driver by class (a small static policy table), and logs the decision — finding the HPET (a timer) and deciding `hpet` serves it. It does not spawn yet: spawning needs a `system_spawn` system call (the kernel spawns every initial-ramdisk binary in a loop today), which is the next increment. New `device-manager` test; suite 36/36 plus host tests.
This commit is contained in:
@@ -257,6 +257,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
const vfstest_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "vfs-test", "system/services/vfs/vfs-test.zig");
|
||||||
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "hpet", "system/drivers/hpet/hpet.zig");
|
const hpet_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "hpet", "system/drivers/hpet/hpet.zig");
|
||||||
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "bus", "system/drivers/bus/bus.zig");
|
const bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "bus", "system/drivers/bus/bus.zig");
|
||||||
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
|
|
||||||
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
|
// Pack the user binaries into the initial_ramdisk image with the host-side Python tool
|
||||||
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
// (the container format is trivial, and Python sidesteps std API churn). Args:
|
||||||
@@ -272,11 +273,14 @@ pub fn build(b: *std.Build) void {
|
|||||||
mk_run.addFileArg(hpet_exe.getEmittedBin());
|
mk_run.addFileArg(hpet_exe.getEmittedBin());
|
||||||
mk_run.addArg("bus");
|
mk_run.addArg("bus");
|
||||||
mk_run.addFileArg(bus_exe.getEmittedBin());
|
mk_run.addFileArg(bus_exe.getEmittedBin());
|
||||||
|
mk_run.addArg("device-manager");
|
||||||
|
mk_run.addFileArg(device_manager_exe.getEmittedBin());
|
||||||
|
|
||||||
// Also install the packed binaries to their FHS homes, so zig-out is a true image
|
// Also install the packed binaries to their FHS homes, so zig-out is a true image
|
||||||
// of the filesystem — even though at boot they arrive inside the initial-ramdisk.
|
// of the filesystem — even though at boot they arrive inside the initial-ramdisk.
|
||||||
for ([_]struct { *std.Build.Step.Compile, []const u8 }{
|
for ([_]struct { *std.Build.Step.Compile, []const u8 }{
|
||||||
.{ vfs_exe, "system/services" },
|
.{ vfs_exe, "system/services" },
|
||||||
|
.{ device_manager_exe, "system/services" },
|
||||||
.{ hpet_exe, "system/drivers" },
|
.{ hpet_exe, "system/drivers" },
|
||||||
.{ bus_exe, "system/drivers" },
|
.{ bus_exe, "system/drivers" },
|
||||||
}) |entry| {
|
}) |entry| {
|
||||||
|
|||||||
+2
-2
@@ -152,7 +152,7 @@ system/ → /system danos's own internals (the self-representation)
|
|||||||
architecture/x86_64/ the `architecture` module (never named by generic code)
|
architecture/x86_64/ the `architecture` module (never named by generic code)
|
||||||
devices/ the device model /system/devices reflects (+ aml/)
|
devices/ the device model /system/devices reflects (+ aml/)
|
||||||
drivers/ hpet/ bus/ one sub-project per driver → /system/drivers
|
drivers/ hpet/ bus/ one sub-project per driver → /system/drivers
|
||||||
services/ init/ vfs/ system servers → /system/services (vfs/ holds
|
services/ init/ vfs/ device-manager/ system servers → /system/services (vfs/ holds
|
||||||
vfs.zig, vfs-test.zig, protocol.zig)
|
vfs.zig, vfs-test.zig, protocol.zig)
|
||||||
library/ → /lib libraries, one sub-directory each
|
library/ → /lib libraries, one sub-directory each
|
||||||
runtime/ the danos-native runtime — the stable application ABI
|
runtime/ the danos-native runtime — the stable application ABI
|
||||||
@@ -193,7 +193,7 @@ appears in the private-ABI path.
|
|||||||
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
|
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |
|
||||||
| danos-native runtime (`runtime`): syscall wrappers, heap, IPC, device access — the stable application ABI | `library/runtime/` |
|
| danos-native runtime (`runtime`): syscall wrappers, heap, IPC, device access — the stable application ABI | `library/runtime/` |
|
||||||
| POSIX/C compatibility (`posix`): unistd, stdio — the one place POSIX names are allowed | `library/posix/` |
|
| POSIX/C compatibility (`posix`): unistd, stdio — the one place POSIX names are allowed | `library/posix/` |
|
||||||
| System services (init, the VFS server + its `protocol` module) | `system/services/` |
|
| System services (init, the VFS server + `protocol`, the device-manager) | `system/services/` |
|
||||||
| Device drivers, one sub-project each (`hpet` leaf driver, `bus` bus driver) | `system/drivers/` |
|
| Device drivers, one sub-project each (`hpet` leaf driver, `bus` bus driver) | `system/drivers/` |
|
||||||
| Build + `run-x86-64` (QEMU/OVMF) | `build.zig` |
|
| Build + `run-x86-64` (QEMU/OVMF) | `build.zig` |
|
||||||
| QEMU integration test harness | `test/qemu_test.py` |
|
| QEMU integration test harness | `test/qemu_test.py` |
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
|||||||
irqFreeTest();
|
irqFreeTest();
|
||||||
} else if (eql(case, "bus")) {
|
} else if (eql(case, "bus")) {
|
||||||
busTest(boot_information);
|
busTest(boot_information);
|
||||||
|
} else if (eql(case, "device-manager")) {
|
||||||
|
deviceManagerTest(boot_information);
|
||||||
} else if (eql(case, "poweroff")) {
|
} else if (eql(case, "poweroff")) {
|
||||||
powerTest(.off);
|
powerTest(.off);
|
||||||
} else if (eql(case, "reboot")) {
|
} else if (eql(case, "reboot")) {
|
||||||
@@ -1173,6 +1175,44 @@ fn busTest(boot_information: *const BootInformation) void {
|
|||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The device manager (a ring-3 service) enumerates /system/devices, matches each
|
||||||
|
/// device to a driver, and — eventually — spawns it. This increment only checks the
|
||||||
|
/// discovery+matching half: it must find the HPET (a timer) and decide `hpet` serves
|
||||||
|
/// it, printing "device-manager: ok". It uses no special privilege — the same
|
||||||
|
/// `device_enumerate` any process could call. (Spawning is the next increment.)
|
||||||
|
fn deviceManagerTest(boot_information: *const BootInformation) void {
|
||||||
|
log("DANOS-TEST-BEGIN: device-manager\n", .{});
|
||||||
|
if (boot_information.initial_ramdisk_len == 0) {
|
||||||
|
check("bootloader handed over an initial_ramdisk", false);
|
||||||
|
result();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
check("initial_ramdisk image is valid", false);
|
||||||
|
result();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.write_count = 0;
|
||||||
|
process.write_from_user = false;
|
||||||
|
check("device-manager spawned from the initial_ramdisk", spawnNamed(rd, "device-manager"));
|
||||||
|
|
||||||
|
const prefix = "device-manager: ok";
|
||||||
|
scheduler.setPriority(1);
|
||||||
|
const deadline = architecture.millis() + 10000;
|
||||||
|
while (architecture.millis() < deadline) {
|
||||||
|
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break;
|
||||||
|
scheduler.yield();
|
||||||
|
}
|
||||||
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
|
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
||||||
|
check("device manager enumerated the tree and matched a driver to a device", ok);
|
||||||
|
check("its syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
|
||||||
/// Every child `bus` registered must have each of its resources inside a parent
|
/// Every child `bus` registered must have each of its resources inside a parent
|
||||||
/// resource of the same kind — the invariant `device_register` exists to maintain,
|
/// resource of the same kind — the invariant `device_register` exists to maintain,
|
||||||
/// checked from the kernel's own table rather than the driver's word for it.
|
/// checked from the kernel's own table rather than the driver's word for it.
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//! /system/services/device-manager — the ring-3 process that turns the device
|
||||||
|
//! tree into a running system. The kernel enumerates the hardware and enforces the
|
||||||
|
//! claim capability (mechanism); this decides *which driver serves which device*
|
||||||
|
//! and, eventually, spawns it (policy). Keeping that split in user space is the
|
||||||
|
//! whole point of the microkernel: the manager is an ordinary, restartable process
|
||||||
|
//! with no special privilege — it uses the same `device_*` system calls any process
|
||||||
|
//! could ([drivers.md](../../../docs/drivers.md), [driver-model.md]).
|
||||||
|
//!
|
||||||
|
//! Increment 1 (this file): enumerate /system/devices and *match* each device to a
|
||||||
|
//! driver, logging the decision. It does not spawn anything yet — spawning needs a
|
||||||
|
//! `system_spawn` system call (the kernel spawns every initial-ramdisk binary in a
|
||||||
|
//! loop today; see system/kernel/kernel.zig). Increment 2 adds that call and turns
|
||||||
|
//! these decisions into actual spawns.
|
||||||
|
|
||||||
|
const runtime = @import("runtime");
|
||||||
|
const device = runtime.device;
|
||||||
|
|
||||||
|
/// The driver that serves each device class — the policy table. In a fuller system
|
||||||
|
/// this comes from the drivers describing what they bind (or a manifest under
|
||||||
|
/// /system/drivers); for now it is a small static map, which is enough to prove the
|
||||||
|
/// manager reads the tree and decides. `null` = no driver for this class yet.
|
||||||
|
fn driverFor(class: u64) ?[]const u8 {
|
||||||
|
if (class == @intFromEnum(device.DeviceClass.timer)) return "hpet"; // the HPET
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||||
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
|
_ = runtime.system.write("device-manager: out of memory\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
const total = device.enumerate(buffer);
|
||||||
|
const count = @min(total, buffer.len);
|
||||||
|
|
||||||
|
var matched: usize = 0;
|
||||||
|
for (buffer[0..count]) |descriptor| {
|
||||||
|
const driver_name = driverFor(descriptor.class) orelse continue;
|
||||||
|
// Increment 2 will `system_spawn(driver_name)` here; for now, record the
|
||||||
|
// decision so the policy is observable and testable.
|
||||||
|
_ = runtime.system.write("device-manager: match ");
|
||||||
|
_ = runtime.system.write(driver_name);
|
||||||
|
_ = runtime.system.write(" -> would spawn it\n");
|
||||||
|
matched += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched == 0) {
|
||||||
|
_ = runtime.system.write("device-manager: no matchable devices\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ = runtime.system.write("device-manager: ok\n");
|
||||||
|
while (true) runtime.system.sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const panic = runtime.panic;
|
||||||
|
comptime {
|
||||||
|
_ = &runtime.start._start; // pull the runtime entry shim into the image
|
||||||
|
}
|
||||||
@@ -197,6 +197,12 @@ CASES = [
|
|||||||
{"name": "hpet",
|
{"name": "hpet",
|
||||||
"expect": r"DANOS-TEST-RESULT: PASS",
|
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
|
# Device manager: a ring-3 service enumerates /system/devices and matches each
|
||||||
|
# device to a driver (discovery + policy in user space). This increment logs the
|
||||||
|
# decision; spawning follows.
|
||||||
|
{"name": "device-manager",
|
||||||
|
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||||
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# Bus driver: a user process claims a device, enumerates its children from the
|
# Bus driver: a user process claims a device, enumerates its children from the
|
||||||
# hardware, and publishes each with dev_register — and the kernel refuses a child
|
# hardware, and publishes each with dev_register — and the kernel refuses a child
|
||||||
# whose window escapes the parent's (else dev_register maps arbitrary memory).
|
# whose window escapes the parent's (else dev_register maps arbitrary memory).
|
||||||
|
|||||||
Reference in New Issue
Block a user