Author SHA1 Message Date
Daniel Samson 6e60daed6a Fix the drviers typo and make tests robust to source-path debug prefixes
The debug-message refactor prefixed each service/driver line with its
source path (system/drivers/hpet:, ...) for readability, but two things
left main red: a 'drviers' typo in hpet.zig and pci-bus.zig, and five
kernel tests (init, hpet, pci-scan, device-manager, vfs-client-death)
that starts-with-matched the old short markers, which no longer sit at
the front of the prefixed lines.

Fix the typo, and convert the fragile starts-with matchers to substring
matching via a bufferHas helper — 'hpet: ok' now matches inside
'system/drivers/hpet: ok' regardless of prefix. Future-proof against
further prefix changes and harmless for the tests that already passed.
Suite 58/58.
2026-07-13 06:23:21 +01:00
Daniel Samson dd044fb115 fix / debug 2026-07-13 05:41:57 +01:00
Daniel Samson 3ec14509a0 fix / debug 2026-07-13 05:41:05 +01:00
Daniel Samson d71a5f25d3 fix kernel: debug 2026-07-13 05:37:35 +01:00
Daniel Samson 2a0f17ae86 fix kernel: debug 2026-07-13 05:35:29 +01:00
Daniel Samson 9ef61a0844 fix kernel: debug prefix 2026-07-13 05:32:26 +01:00
Daniel Samson 688b9101e8 fix kernel: debug prefix 2026-07-13 05:31:36 +01:00
Daniel Samson 1d7ba814dc fix efi: debug prefix 2026-07-13 05:30:56 +01:00
Daniel Samson 8aba86b4ce fix vfs: debug prefix 2026-07-13 05:24:47 +01:00
Daniel Samson a0c83f4b3f fix input: debug prefix 2026-07-13 05:24:16 +01:00
Daniel Samson 1ea48ed5d6 fix init: debug prefix 2026-07-13 05:23:07 +01:00
Daniel Samson 77a3ccd33d fix hpet: debug prefix 2026-07-13 05:22:27 +01:00
Daniel Samson 07da27dc39 fix pci-bus: debug prefix 2026-07-13 05:21:47 +01:00
Daniel Samson d89657d0a4 fix device-manager: debug prefix 2026-07-13 05:21:08 +01:00
Daniel Samson 849b4b62d4 fix acpi: debug prefix 2026-07-13 05:20:31 +01:00
Daniel Samson 8589bf713b fix usb-xhci-bus debug prefix 2026-07-13 05:18:49 +01:00
21 changed files with 237 additions and 851 deletions
+6 -6
View File
@@ -29,7 +29,7 @@ pub fn main() uefi.Status {
// report the reason (boot services are still up) and park the machine so the // report the reason (boot services are still up) and park the machine so the
// message stays on screen. // message stays on screen.
boot() catch |err| { boot() catch |err| {
log("\r\ndanos: boot failed: "); log("\r\nEFI: boot failed: ");
logBytes(@errorName(err)); logBytes(@errorName(err));
log("\r\n"); log("\r\n");
while (true) asm volatile ("hlt"); while (true) asm volatile ("hlt");
@@ -65,14 +65,14 @@ fn boot() !noreturn {
// Best effort: a volume without /system/services/init still boots (kernel-only). // Best effort: a volume without /system/services/init still boots (kernel-only).
loadInit(bs, &boot_information) catch |err| { loadInit(bs, &boot_information) catch |err| {
log("danos: no /system/services/init ("); log("EFI: no /system/services/init (");
logBytes(@errorName(err)); logBytes(@errorName(err));
log(") - booting without user space\r\n"); log(") - booting without user space\r\n");
}; };
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too. // Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
loadInitialRamdisk(bs, &boot_information) catch |err| { loadInitialRamdisk(bs, &boot_information) catch |err| {
log("danos: no initial_ramdisk ("); log("EFI: no initial_ramdisk (");
logBytes(@errorName(err)); logBytes(@errorName(err));
log(")\r\n"); log(")\r\n");
}; };
@@ -84,7 +84,7 @@ fn boot() !noreturn {
// the map and exiting would invalidate the map key. // the map and exiting would invalidate the map key.
const cr3 = try buildBootstrapTables(bs, &boot_information); const cr3 = try buildBootstrapTables(bs, &boot_information);
log("danos: kernel loaded, exiting boot services\r\n"); log("EFI: kernel loaded, exiting boot services\r\n");
boot_information.memory_map = try exitBootServices(bs); boot_information.memory_map = try exitBootServices(bs);
// Switch onto our tables and jump to the kernel in one uninterruptible step. // Switch onto our tables and jump to the kernel in one uninterruptible step.
@@ -395,7 +395,7 @@ fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !
const image = try loadFile(bs, init_file_name); const image = try loadFile(bs, init_file_name);
boot_information.init_base = @intFromPtr(image.ptr); boot_information.init_base = @intFromPtr(image.ptr);
boot_information.init_len = image.len; boot_information.init_len = image.len;
log("danos: /system/services/init loaded\r\n"); log("EFI: /system/services/init loaded\r\n");
} }
/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init. /// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
@@ -403,7 +403,7 @@ fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInfo
const image = try loadFile(bs, initial_ramdisk_file_name); const image = try loadFile(bs, initial_ramdisk_file_name);
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr); boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
boot_information.initial_ramdisk_len = image.len; boot_information.initial_ramdisk_len = image.len;
log("danos: initial_ramdisk loaded\r\n"); log("EFI: initial_ramdisk loaded\r\n");
} }
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and /// Validate the ELF, copy every PT_LOAD segment to its physical address, and
-7
View File
@@ -226,12 +226,6 @@ pub fn build(b: *std.Build) void {
}); });
runtime_module.addImport("device-manager-protocol", device_manager_protocol_module); runtime_module.addImport("device-manager-protocol", device_manager_protocol_module);
// The power protocol: system power's domain-named surface (docs/m21-plan.md).
const power_protocol_module = b.addModule("power-protocol", .{
.root_source_file = b.path("system/services/power/protocol.zig"),
});
runtime_module.addImport("power-protocol", power_protocol_module);
// Typed volatile MMIO register access + memory-ordering barriers, for drivers on // Typed volatile MMIO register access + memory-ordering barriers, for drivers on
// top of an mmio_map grant. Depends only on `builtin` (arch-conditional barriers); // top of an mmio_map grant. Depends only on `builtin` (arch-conditional barriers);
// no target set, so it inherits each driver's. See library/mmio/mmio.zig. // no target set, so it inherits each driver's. See library/mmio/mmio.zig.
@@ -583,7 +577,6 @@ pub fn build(b: *std.Build) void {
"system/devices/device-abi.zig", "system/devices/device-abi.zig",
"system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding
"system/devices/acpi-ids.zig", // _HID name decoding "system/devices/acpi-ids.zig", // _HID name decoding
"system/devices/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
"system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings "system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
"system/devices/usb-ids.zig", // class/subclass/protocol code assignments "system/devices/usb-ids.zig", // class/subclass/protocol code assignments
"library/mmio/mmio.zig", // barriers assemble + registers round-trip "library/mmio/mmio.zig", // barriers assemble + registers round-trip
+20 -4
View File
@@ -189,8 +189,24 @@ suspend/resume — a future *lifecycle-vocabulary* extension, since "suspend"
has the shape of a signal every driver must answer, and it has no consumer has the shape of a signal every driver must answer, and it has no consumer
until laptop sleep); CPU P/C-states. until laptop sleep); CPU P/C-states.
## M21 — ACPI events + system power — DONE ## M21 preview — ACPI events + system power (planned next, not in this loop)
Built and merged (docs/m21-plan.md, 2026-07-13): the SCI + power button, Notify/GPE The acpi service grows the event side (settled direction 2026-07-13; detailed
dispatch, and orderly shutdown (init's stop cascade into a ring-3 S5 write). phases when M20 lands):
See that plan for the phase record.
- **21.1 SCI + fixed events**: irq_bind the SCI (the resource M20.1 already
records), read/clear PM1 status, publish the power-button event to
subscribers (the same pub/sub shape the manager uses).
- **21.2 GPE + Notify**: Notify dispatch in the shared AML interpreter, GPE
block handling, `Notify(device, code)` published per reported node. The
acpi service is a **bus** here: battery (PNP0C0A), AC (ACPI0003), and lid
(PNP0C0D) nodes are reported children; small class drivers bind them and
speak an evaluate/subscribe protocol to the service — the xHCI split,
repeated. The embedded controller (`_Qxx` queries) rides this phase;
QEMU emulates no battery/EC, so those paths are interface-complete and
validated on real hardware (the laptop is the win condition), while the
plumbing is proven by the power button.
- **21.3 the capstone**: QEMU `system_powerdown` → acpi service event → init
runs the M17 stop sequence over its children → kernel `\_S5` — orderly
shutdown as the scenario that proves lifecycle + events compose. (The
harness grows a QMP poke to inject the event.)
+27 -31
View File
@@ -70,37 +70,33 @@ auto-merge to main when the branch is green; keep the branch; push everything.
## Status ## Status
- [x] **M21.0** — baseline (dead-code sweep confirmed landed on main — no - [ ] **M21.0** — baseline: rebase over anything newly merged (the dead-code
acpi.zig conflict; `feat/power-events` cut; QMP channel in the harness: sweep touches acpi.zig); cut `feat/power-events`; add the QMP channel to
always-on unix socket, client with the capabilities handshake, per-case the harness (`-qmp unix:.../qmp.sock,server,nowait`, a small client with
`qmp_after` hook, and a hook-must-deliver pass gate that the smoke case the `qmp_capabilities` handshake, a per-case `qmp_after` hook that sends
now proves with a harmless query-status; suite 58/58). a command N seconds after boot); existing suite stays green.
- [x] **M21.1** — SCI + the power button (kernel appends the FADT as an - [ ] **M21.1** — SCI + the power button: kernel appends the FADT as an
acpi-tables memory resource, tagged by its "FACP" header; `power-protocol` acpi-tables memory resource; new `power-protocol` module +
module + `ServiceId.power = 5`; the acpi service converted to `ServiceId.power`; the acpi service converts to the harness, registers
`runtime.service.run`, registers `.power`, reads PM1 event/control + GPE `.power`, parses the event/GPE blocks from its FADT copy, enables ACPI
ports from its FADT copy, enables ACPI mode if SCI_EN is clear, binds the mode if needed (SMI dance, spin on SCI_EN), binds the SCI, sets
SCI (the len-1 irq), sets PWRBTN_EN; the SCI handler clears PM1_STS, PWRBTN_EN; on SCI reads/clears PM1_STS and publishes `power_button`
logs `power: button pressed`, publishes `power_button`, acks. Scenario (log: `power: button pressed`), always irqAck. Scenario `power-button`:
`power-button` injects a real `system_powerdown` via QMP; initial-ramdisk `qmp_after system_powerdown` → expect the log line.
timeout 30→60s for the service's added boot work; suite 59/59). - [ ] **M21.2** — Notify + GPE dispatch: interpreter handles `notify_opcode`
- [x] **M21.2** — Notify + GPE dispatch (interpreter handles `notify_opcode` into a bounded queue drained after evaluate(); on GPE status bits the
into a bounded queue, cleared per-evaluate, drained via service evaluates `\_GPE._Lxx`/`_Exx`, maps notified nodes to events
`takeNotifications`; the service walks GPE status/enable bytes, evaluates (PNP0C0A→battery, ACPI0003→ac, PNP0C0D→lid, else generic), clears
`\_GPE._Lxx`/`_Exx` per active bit, maps notified nodes to events GPE_STS, acks. EC `_Qxx` explicitly out (hardware track). Host unit
(battery/ac/lid/generic), clears GPE_STS write-1, acks. EC `_Qxx` out. tests for Notify in aml.zig; aml.zig joins the `zig build test` loop.
Host unit test with hand-encoded AML proves the queue; aml.zig joined the - [ ] **M21.3** — orderly shutdown: init keeps child ids (spawnSupervised +
`zig build test` loop. QEMU raises no GPEs — suite is regression net, exit endpoint), binds signals, subscribes to `.power`; on `power_button`
59/59). logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in
- [x] **M21.3** — orderly shutdown (init supervises its children on one reverse spawn order, then sends `shutdown` to `.power`; the acpi service
endpoint that also carries signals, power events, and a re-arming (sender PID 1 only) logs `power: entering S5` and writes SLP_TYP|SLP_EN
heartbeat timer; on `power_button` or a `terminate` signal it logs from ring 3. Scenario `orderly-shutdown`: boot via init, `qmp_after
`init: shutting down`, runs `stop(child, 2000, endpoint)` in reverse system_powerdown`, ordered regex button→shutting-down→entering-S5, pass
order, then requests `.power` shutdown; the acpi service honors shutdown on QEMU exit. Docs + memory updated.
from a subscriber — init is the one subscriber, a soft gate that survives
testing where PID 1 isn't init — and writes SLP_TYP|SLP_EN from ring 3.
`orderly-shutdown` scenario proves button → shutting-down → S5 → QEMU
exit; suite 60/60).
- [ ] **merge** `feat/power-events` → main, push, keep the branch — **loop - [ ] **merge** `feat/power-events` → main, push, keep the branch — **loop
ends here**. ends here**.
-3
View File
@@ -20,9 +20,6 @@ pub const vfs_protocol = @import("vfs-protocol");
/// The device-manager protocol: hello + tree reports (docs/device-manager.md). /// The device-manager protocol: hello + tree reports (docs/device-manager.md).
pub const device_manager_protocol = @import("device-manager-protocol"); pub const device_manager_protocol = @import("device-manager-protocol");
/// The power protocol: events (button, lid, battery) + shutdown (docs/m21-plan.md).
pub const power_protocol = @import("power-protocol");
/// Keyboard-event listening (subscribe/next) and broadcasting (publish), over the input /// Keyboard-event listening (subscribe/next) and broadcasting (publish), over the input
/// service. See library/runtime/input.zig and system/services/input/. /// service. See library/runtime/input.zig and system/services/input/.
pub const input = @import("input.zig"); pub const input = @import("input.zig");
-1
View File
@@ -177,7 +177,6 @@ pub const ServiceId = enum(u32) {
input = 2, input = 2,
ps2_bus = 3, // the 8042 owner; child device drivers attach here for raw bytes ps2_bus = 3, // the 8042 owner; child device drivers attach here for raw bytes
device_manager = 4, // the tree, the matcher, the supervisor (docs/device-manager.md) device_manager = 4, // the tree, the matcher, the supervisor (docs/device-manager.md)
power = 5, // system power: events (button, lid, battery) + shutdown (docs/m21-plan.md; domain-named per decision 7 — the acpi service registers it on x86, a PSCI service will on ARM)
_, _,
}; };
-14
View File
@@ -157,13 +157,6 @@ pub var namespace: ?aml.Namespace = null;
/// Physical address of the DSDT the FADT points at, or 0. /// Physical address of the DSDT the FADT points at, or 0.
pub var dsdt_physical: u64 = 0; pub var dsdt_physical: u64 = 0;
/// The FADT itself (physical + length), published on the acpi-tables node so
/// the ring-3 acpi service can read the PM1 event and GPE blocks it needs for
/// the event side (docs/m21-plan.md decision 3). Distinguished from the AML
/// blob resources by its intact "FACP" header — the blobs are header-stripped.
var fadt_physical: u64 = 0;
var fadt_length: u64 = 0;
// AML blocks (DSDT + any SSDTs) collected during the table walk, as physical // AML blocks (DSDT + any SSDTs) collected during the table walk, as physical
// address + length of each table's post-header bytecode. Scanned after the walk // address + length of each table's post-header bytecode. Scanned after the walk
// for the sleep-state (`_Sx`) packages. // for the sleep-state (`_Sx`) packages.
@@ -380,8 +373,6 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
// Start clean so a re-run doesn't accumulate stale state. // Start clean so a re-run doesn't accumulate stale state.
power_information = .{}; power_information = .{};
fadt_physical = 0;
fadt_length = 0;
platform_information = .{}; platform_information = .{};
aml_stats = .{}; aml_stats = .{};
namespace = null; namespace = null;
@@ -456,9 +447,6 @@ fn publishAcpiTablesNode(device_tree: *DeviceTree) !void {
// SCI (recorded first, len 1) stays distinct so M21 can pick it out. // SCI (recorded first, len 1) stays distinct so M21 can pick it out.
if (power_information.sci_interrupt != 0) _ = node.addResource(.irq, power_information.sci_interrupt, 1); if (power_information.sci_interrupt != 0) _ = node.addResource(.irq, power_information.sci_interrupt, 1);
_ = node.addResource(.irq, 0, 256); _ = node.addResource(.irq, 0, 256);
// The FADT rides along (M21): the service reads the PM1 event / GPE blocks
// from its own copy, telling it apart from the AML blobs by signature.
if (fadt_physical != 0) _ = node.addResource(.memory, fadt_physical, fadt_length);
} }
/// The number of Device objects in the namespace built during discovery, or 0. /// The number of Device objects in the namespace built during discovery, or 0.
@@ -494,8 +482,6 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
} else if (std.mem.eql(u8, &sig, &HPET)) { } else if (std.mem.eql(u8, &sig, &HPET)) {
try parseHpet(device_tree, hal, header); try parseHpet(device_tree, hal, header);
} else if (std.mem.eql(u8, &sig, &FACP)) { } else if (std.mem.eql(u8, &sig, &FACP)) {
fadt_physical = sdt_physical;
fadt_length = header.length;
parseFadt(header); parseFadt(header);
} else if (std.mem.eql(u8, &sig, &SPCR)) { } else if (std.mem.eql(u8, &sig, &SPCR)) {
parseSpcr(header); parseSpcr(header);
-28
View File
@@ -230,31 +230,3 @@ test "interpreter runs a method with args, arithmetic, and control flow" {
const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0 const lo = try interpreter.evaluate(tst, &.{.{ .integer = 2 }}); // 2+5=7 !> 10 -> 0
try std.testing.expectEqual(@as(u64, 0), try lo.asInteger()); try std.testing.expectEqual(@as(u64, 0), try lo.asInteger());
} }
test "interpreter records Notify(device, code)" {
// Device(DEV_) { Name(_HID, 0x030AD041) } // PNP0A03-ish placeholder
// Method(TST_, 0) { Notify(DEV_, 0x80); Return(Zero) }
// Encoded: a Device holding a Name, then a Method issuing Notify on it.
const blob = [_]u8{
0x5B, 0x82, 0x0F, 0x44, 0x45, 0x56, 0x5F, // Device(DEV_) len=0x0F (pkglen + DEV_ + Name)
0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x03, // Name(_HID, DWord 0x030AD041)
0x14, 0x0F, 0x54, 0x53, 0x54, 0x5F, 0x00, // Method(TST_, 0) len=0x0F (pkglen + TST_ + flags + body)
0x86, 0x44, 0x45, 0x56, 0x5F, 0x0A, 0x80, // Notify(DEV_, 0x80)
0xA4, 0x00, // Return(Zero)
};
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var result = try parse(arena.allocator(), &.{&blob});
const namespace = &result.namespace;
const tst = namespace.resolve(namespace.root, false, 0, &.{.{ 'T', 'S', 'T', '_' }}) orelse return error.NoMethod;
const dev = namespace.resolve(namespace.root, false, 0, &.{.{ 'D', 'E', 'V', '_' }}) orelse return error.NoDevice;
var interpreter = Interpreter.init(namespace, .{ .mapMmio = noMap, .pioRead = noRead, .pioWrite = noWrite }, arena.allocator());
_ = try interpreter.evaluate(tst, &.{});
const events = interpreter.takeNotifications();
try std.testing.expectEqual(@as(usize, 1), events.len);
try std.testing.expectEqual(dev, events[0].node);
try std.testing.expectEqual(@as(u64, 0x80), events[0].code);
}
-41
View File
@@ -141,9 +141,6 @@ const Frame = struct {
/// A CreateField binding: a name that indexes into a buffer object. /// A CreateField binding: a name that indexes into a buffer object.
const BufferField = struct { buffer: *Node, byte_off: usize, bit_width: u32 }; const BufferField = struct { buffer: *Node, byte_off: usize, bit_width: u32 };
/// One Notify(device, code) the interpreter executed.
pub const NotifyEvent = struct { node: *Node, code: u64 };
pub const Interpreter = struct { pub const Interpreter = struct {
namespace: *Namespace, namespace: *Namespace,
hal: Hal, hal: Hal,
@@ -152,11 +149,6 @@ pub const Interpreter = struct {
dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{}, dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{},
/// CreateField bindings active for the current evaluation. /// CreateField bindings active for the current evaluation.
fields: std.AutoHashMapUnmanaged(*Node, BufferField) = .{}, fields: std.AutoHashMapUnmanaged(*Node, BufferField) = .{},
/// Notify(device, code) operations the last evaluation executed — a GPE or
/// EC handler tells the OS "look at this device" this way. Bounded; the
/// caller drains it with `takeNotifications` after `evaluate` (M21).
notify_queue: [16]NotifyEvent = undefined,
notify_count: usize = 0,
pub fn init(namespace: *Namespace, hal: Hal, arena: std.mem.Allocator) Interpreter { pub fn init(namespace: *Namespace, hal: Hal, arena: std.mem.Allocator) Interpreter {
return .{ .namespace = namespace, .hal = hal, .arena = arena }; return .{ .namespace = namespace, .hal = hal, .arena = arena };
@@ -165,7 +157,6 @@ pub const Interpreter = struct {
/// Evaluate a namespace object: invoke a Method, read a Name's value, or read a /// Evaluate a namespace object: invoke a Method, read a Name's value, or read a
/// Field. Resets per-evaluation runtime state first. /// Field. Resets per-evaluation runtime state first.
pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object { pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object {
self.notify_count = 0;
self.dynamic_overrides.clearRetainingCapacity(); self.dynamic_overrides.clearRetainingCapacity();
self.fields.clearRetainingCapacity(); self.fields.clearRetainingCapacity();
return self.invoke(node, args); return self.invoke(node, args);
@@ -276,8 +267,6 @@ pub const Interpreter = struct {
}, },
opcode.to_buffer_opcode => try self.passThroughUnary(current, frame), opcode.to_buffer_opcode => try self.passThroughUnary(current, frame),
opcode.notify_opcode => try self.notify(current, frame),
opcode.extended_opcode_prefix => try self.ext(current, frame), opcode.extended_opcode_prefix => try self.ext(current, frame),
// CreateXField: source, index, name (bit widths differ by op) // CreateXField: source, index, name (bit widths differ by op)
@@ -553,36 +542,6 @@ pub const Interpreter = struct {
try self.storeInto(current, frame, value); try self.storeInto(current, frame, value);
} }
/// Notify(SuperName, NotifyValue): resolve the named device, evaluate the
/// code, and record the pair for the caller to dispatch. AML control flow
/// continues (Notify returns nothing).
fn notify(self: *Interpreter, current: *Cursor, frame: *Frame) Error!Object {
const lead = current.peek() orelse return error.Truncated;
var target: ?*Node = null;
if (isNameStart(lead)) {
const name_path = try current.nameString();
target = self.namespace.resolve(frame.scope, name_path.rooted, name_path.parents, name_path.slice());
} else {
// A non-name SuperName (Local/Arg holding a reference).
const obj = try self.term(current, frame);
if (obj == .reference) target = obj.reference;
}
const code = try self.evaluateInteger(current, frame);
if (target) |node| {
if (self.notify_count < self.notify_queue.len) {
self.notify_queue[self.notify_count] = .{ .node = node, .code = code };
self.notify_count += 1;
}
}
return .uninitialized;
}
/// The Notify events the last `evaluate` produced. Valid until the next
/// `evaluate` clears the queue.
pub fn takeNotifications(self: *Interpreter) []const NotifyEvent {
return self.notify_queue[0..self.notify_count];
}
fn storeInto(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void { fn storeInto(self: *Interpreter, current: *Cursor, frame: *Frame, value: Object) Error!void {
const lead = current.peek() orelse return error.Truncated; const lead = current.peek() orelse return error.Truncated;
if (isNameStart(lead)) { if (isNameStart(lead)) {
+11 -11
View File
@@ -93,21 +93,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
pub fn main() void { pub fn main() void {
// Enumerate into a heap buffer (too big for the one-page user stack). // Enumerate into a heap buffer (too big for the one-page user stack).
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch {
_ = runtime.system.write("hpet: out of memory\n"); _ = runtime.system.write("system/drivers/hpet: out of memory\n");
return; return;
}; };
const hpet = findHpet(buffer) orelse { const hpet = findHpet(buffer) orelse {
_ = runtime.system.write("hpet: no HPET with an IRQ\n"); _ = runtime.system.write("system/drivers/hpet: no HPET with an IRQ\n");
return; return;
}; };
if (!device.claim(hpet.device_id)) { if (!device.claim(hpet.device_id)) {
_ = runtime.system.write("hpet: claim failed\n"); _ = runtime.system.write("system/drivers/hpet: claim failed\n");
return; return;
} }
const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse { const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse {
_ = runtime.system.write("hpet: mmio_map failed\n"); _ = runtime.system.write("system/drivers/hpet: mmio_map failed\n");
return; return;
}; };
@@ -116,7 +116,7 @@ pub fn main() void {
const gsi = hpet.gsi; const gsi = hpet.gsi;
const endpoint = ipc.createIpcEndpoint() orelse { const endpoint = ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("hpet: create_ipc_endpoint failed\n"); _ = runtime.system.write("system/drivers/hpet: create_ipc_endpoint failed\n");
return; return;
}; };
@@ -124,7 +124,7 @@ pub fn main() void {
// Counter period, so we can arm the comparator a fixed wall-clock distance out. // Counter period, so we can arm the comparator a fixed wall-clock distance out.
const femtos_per_tick = rd(base, register_general_cap) >> 32; const femtos_per_tick = rd(base, register_general_cap) >> 32;
if (femtos_per_tick == 0) { if (femtos_per_tick == 0) {
_ = runtime.system.write("hpet: bad HPET period\n"); _ = runtime.system.write("system/drivers/hpet: bad HPET period\n");
return; return;
} }
const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick; const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick;
@@ -147,10 +147,10 @@ pub fn main() void {
wr(base, register_general_configuration, rd(base, register_general_configuration) | configuration_enable); wr(base, register_general_configuration, rd(base, register_general_configuration) | configuration_enable);
if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) { if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) {
_ = runtime.system.write("hpet: irq_bind failed\n"); _ = runtime.system.write("system/drivers/hpet: irq_bind failed\n");
return; return;
} }
_ = runtime.system.write("hpet: bound, sleeping until the hardware speaks\n"); _ = runtime.system.write("system/drivers/hpet: bound, sleeping until the hardware speaks\n");
// --- the driver loop ----------------------------------------------------- // --- the driver loop -----------------------------------------------------
// Blocked in replyWait. No polling, no spinning: the next line of this function // Blocked in replyWait. No polling, no spinning: the next line of this function
@@ -178,14 +178,14 @@ pub fn main() void {
wr(base, register_timer0_configuration, rd(base, register_timer0_configuration) & ~tn_int_enb); wr(base, register_timer0_configuration, rd(base, register_timer0_configuration) & ~tn_int_enb);
} }
_ = runtime.system.write("hpet: irq\n"); _ = runtime.system.write("system/drivers/hpet: irq\n");
if (!device.irqAck(hpet.device_id, hpet.irq)) { if (!device.irqAck(hpet.device_id, hpet.irq)) {
_ = runtime.system.write("hpet: irq_ack failed\n"); _ = runtime.system.write("system/drivers/hpet: irq_ack failed\n");
return; return;
} }
} }
_ = runtime.system.write("hpet: ok\n"); _ = runtime.system.write("system/drivers/hpet: ok\n");
while (true) runtime.system.sleep(1000); while (true) runtime.system.sleep(1000);
} }
+16 -16
View File
@@ -6,7 +6,7 @@
//! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0; //! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0;
//! the bus range and the MMIO apertures follow it), walk every //! the bus range and the MMIO apertures follow it), walk every
//! bus/device/function config header, and log what the walk finds — ending //! bus/device/function config header, and log what the walk finds — ending
//! with "pci-bus: N functions found", which the `pci-scan` scenario compares //! with "/system/drivers/pci-bus: N functions found", which the `pci-scan` scenario compares
//! against the kernel's own enumeration. Registration and reports (M19.2), and //! against the kernel's own enumeration. Registration and reports (M19.2), and
//! the kernel walk's retirement (M19.3), build on this proven-equivalent scan. //! the kernel walk's retirement (M19.3), build on this proven-equivalent scan.
@@ -31,9 +31,9 @@ fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32) void {
const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if); const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if);
var line: [200]u8 = undefined; var line: [200]u8 = undefined;
const text = if (pif.len != 0) const text = if (pif.len != 0)
std.fmt.bufPrint(&line, "pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if, pif }) catch return std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2} ({s})\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if, pif }) catch return
else else
std.fmt.bufPrint(&line, "pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) catch return; std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} class 0x{x:0>2} ({s}) subclass 0x{x:0>2} ({s}) progif 0x{x:0>2}\n", .{ bus, dev, function, cc.base, pci_class.className(cc.base), cc.subclass, pci_class.subclassName(cc.base, cc.subclass), cc.prog_if }) catch return;
_ = runtime.system.write(text); _ = runtime.system.write(text);
} }
@@ -74,37 +74,37 @@ fn configWrite16(bus: u64, dev: u64, function: u64, offset: u64, value: u16) voi
fn initialise(endpoint: runtime.ipc.Handle) bool { fn initialise(endpoint: runtime.ipc.Handle) bool {
_ = endpoint; _ = endpoint;
if (!device.claim(bridge_id)) { if (!device.claim(bridge_id)) {
writeLine("pci-bus: unable to claim bridge device {d}\n", .{bridge_id}); writeLine("/system/drivers/pci-bus: unable to claim bridge device {d}\n", .{bridge_id});
return false; return false;
} }
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("pci-bus: out of memory\n"); _ = runtime.system.write("/system/drivers/pci-bus: out of memory\n");
return false; return false;
}; };
const total = device.enumerate(buffer); const total = device.enumerate(buffer);
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| { const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
if (d.id == bridge_id) break d; if (d.id == bridge_id) break d;
} else { } else {
writeLine("pci-bus: device {d} not in the device tree\n", .{bridge_id}); writeLine("/system/drivers/pci-bus: device {d} not in the device tree\n", .{bridge_id});
return false; return false;
}; };
// Resource 0 is the ECAM window (1 MiB of config space per bus); the bus // Resource 0 is the ECAM window (1 MiB of config space per bus); the bus
// range rides beside it. The MMIO apertures (M19.0) come after both. // range rides beside it. The MMIO apertures (M19.0) come after both.
if (descriptor.resource_count < 2 or descriptor.resources[0].kind != @intFromEnum(device.ResourceKind.memory)) { if (descriptor.resource_count < 2 or descriptor.resources[0].kind != @intFromEnum(device.ResourceKind.memory)) {
_ = runtime.system.write("pci-bus: bridge has no ECAM window\n"); _ = runtime.system.write("/system/drivers/pci-bus: bridge has no ECAM window\n");
return false; return false;
} }
const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| { const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| {
if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource; if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource;
} else { } else {
_ = runtime.system.write("pci-bus: bridge has no bus range\n"); _ = runtime.system.write("/system/drivers/pci-bus: bridge has no bus range\n");
return false; return false;
}; };
start_bus = bus_range.start; start_bus = bus_range.start;
bus_count = bus_range.len; bus_count = bus_range.len;
ecam_physical = descriptor.resources[0].start; ecam_physical = descriptor.resources[0].start;
ecam_base = device.mmioMap(bridge_id, 0) orelse { ecam_base = device.mmioMap(bridge_id, 0) orelse {
_ = runtime.system.write("pci-bus: ECAM mmio_map failed\n"); _ = runtime.system.write("/system/drivers/pci-bus: ECAM mmio_map failed\n");
return false; return false;
}; };
@@ -116,17 +116,17 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
if (manager == null) runtime.system.sleep(20); if (manager == null) runtime.system.sleep(20);
} }
const h = manager orelse { const h = manager orelse {
_ = runtime.system.write("pci-bus: no device manager to hello\n"); _ = runtime.system.write("/system/drivers/pci-bus: no device manager to hello\n");
return false; return false;
}; };
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = bridge_id }; const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = bridge_id };
var reply: [protocol.message_maximum]u8 = undefined; var reply: [protocol.message_maximum]u8 = undefined;
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch { const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
_ = runtime.system.write("pci-bus: hello call failed\n"); _ = runtime.system.write("/system/drivers/pci-bus: hello call failed\n");
return false; return false;
}; };
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) { if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
_ = runtime.system.write("pci-bus: hello refused\n"); _ = runtime.system.write("/system/drivers/pci-bus: hello refused\n");
return false; return false;
} }
manager_handle = h; manager_handle = h;
@@ -159,7 +159,7 @@ fn scan() void {
} }
} }
} }
writeLine("pci-bus: {d} functions found\n", .{found}); writeLine("/system/drivers/pci-bus: {d} functions found\n", .{found});
} }
/// Register one function under the bridge and report it to the manager. The /// Register one function under the bridge and report it to the manager. The
@@ -229,7 +229,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
} }
const registered = device.register(bridge_id, &descriptor) orelse { const registered = device.register(bridge_id, &descriptor) orelse {
writeLine("pci-bus: register refused for {d}:{d}.{d}\n", .{ bus, dev, function }); writeLine("/system/drivers/pci-bus: register refused for {d}:{d}.{d}\n", .{ bus, dev, function });
return; return;
}; };
const report = protocol.ChildAdded{ const report = protocol.ChildAdded{
@@ -240,7 +240,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
}; };
var reply: [protocol.message_maximum]u8 = undefined; var reply: [protocol.message_maximum]u8 = undefined;
_ = runtime.ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch { _ = runtime.ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch {
writeLine("pci-bus: child report for {d}:{d}.{d} failed\n", .{ bus, dev, function }); writeLine("/system/drivers/pci-bus: child report for {d}:{d}.{d} failed\n", .{ bus, dev, function });
}; };
} }
@@ -255,7 +255,7 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
pub fn main(init: runtime.process.Init) void { pub fn main(init: runtime.process.Init) void {
const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent
bridge_id = std.fmt.parseInt(u64, argument, 10) catch { bridge_id = std.fmt.parseInt(u64, argument, 10) catch {
writeLine("pci-bus: malformed bridge device id '{s}'\n", .{argument}); writeLine("/system/drivers/pci-bus: malformed bridge device id '{s}'\n", .{argument});
return; return;
}; };
runtime.service.run(protocol.message_maximum, .{ runtime.service.run(protocol.message_maximum, .{
+16 -16
View File
@@ -33,20 +33,20 @@ var controller_id: u64 = protocol.no_device;
fn initialise(endpoint: runtime.ipc.Handle) bool { fn initialise(endpoint: runtime.ipc.Handle) bool {
_ = endpoint; _ = endpoint;
if (!device.claim(controller_id)) { if (!device.claim(controller_id)) {
writeLine("usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id}); writeLine("/system/drivers/usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id});
return false; return false;
} }
// Fetch our own descriptor back for the controller's resources. // Fetch our own descriptor back for the controller's resources.
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("usb-xhci-bus: out of memory\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: out of memory\n");
return false; return false;
}; };
const total = device.enumerate(buffer); const total = device.enumerate(buffer);
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| { const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
if (d.id == controller_id) break d; if (d.id == controller_id) break d;
} else { } else {
writeLine("usb-xhci-bus: device {d} not in the device tree\n", .{controller_id}); writeLine("/system/drivers/usb-xhci-bus: device {d} not in the device tree\n", .{controller_id});
return false; return false;
}; };
@@ -59,16 +59,16 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
break resource; break resource;
} }
} else { } else {
writeLine("usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id}); writeLine("/system/drivers/usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id});
return false; return false;
}; };
writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{ writeLine("/system/drivers/usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{
controller_id, controller_id,
register_window.start, register_window.start,
register_window.len, register_window.len,
}); });
register_base = device.mmioMap(controller_id, register_index) orelse { register_base = device.mmioMap(controller_id, register_index) orelse {
_ = runtime.system.write("usb-xhci-bus: mmio_map failed\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: mmio_map failed\n");
return false; return false;
}; };
@@ -81,20 +81,20 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
if (manager == null) runtime.system.sleep(20); if (manager == null) runtime.system.sleep(20);
} }
const h = manager orelse { const h = manager orelse {
_ = runtime.system.write("usb-xhci-bus: no device manager to hello\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: no device manager to hello\n");
return false; return false;
}; };
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id }; const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id };
var reply: [protocol.message_maximum]u8 = undefined; var reply: [protocol.message_maximum]u8 = undefined;
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch { const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
_ = runtime.system.write("usb-xhci-bus: hello call failed\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: hello call failed\n");
return false; return false;
}; };
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) { if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
_ = runtime.system.write("usb-xhci-bus: hello refused\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: hello refused\n");
return false; return false;
} }
_ = runtime.system.write("usb-xhci-bus: hello acknowledged\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: hello acknowledged\n");
scanPorts(h); scanPorts(h);
return true; return true;
@@ -135,7 +135,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
const capability_length = readRegister(0) & 0xFF; const capability_length = readRegister(0) & 0xFF;
const structural = readRegister(0x04); const structural = readRegister(0x04);
const maximum_ports: u32 = structural >> 24; const maximum_ports: u32 = structural >> 24;
writeLine("usb-xhci-bus: {d} root-hub ports\n", .{maximum_ports}); writeLine("/system/drivers/usb-xhci-bus: {d} root-hub ports\n", .{maximum_ports});
// PORTSC registers: operational base + 0x400 + 0x10 per port (1-based). // PORTSC registers: operational base + 0x400 + 0x10 per port (1-based).
var port: u32 = 1; var port: u32 = 1;
@@ -145,7 +145,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
if (port_status & 1 == 0) continue; // CCS: nothing connected if (port_status & 1 == 0) continue; // CCS: nothing connected
connected += 1; connected += 1;
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
writeLine("usb-xhci-bus: port {d} connected — {s} (speed class {d})\n", .{ port, speedName(speed), speed }); writeLine("/system/drivers/usb-xhci-bus: port {d} connected — {s} (speed class {d})\n", .{ port, speedName(speed), speed });
const report = protocol.ChildAdded{ const report = protocol.ChildAdded{
.parent = controller_id, .parent = controller_id,
@@ -154,11 +154,11 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
}; };
var reply: [protocol.message_maximum]u8 = undefined; var reply: [protocol.message_maximum]u8 = undefined;
_ = runtime.ipc.call(manager, std.mem.asBytes(&report), &reply) catch { _ = runtime.ipc.call(manager, std.mem.asBytes(&report), &reply) catch {
writeLine("usb-xhci-bus: child report for port {d} failed\n", .{port}); writeLine("/system/drivers/usb-xhci-bus: child report for port {d} failed\n", .{port});
continue; continue;
}; };
} }
if (connected == 0) _ = runtime.system.write("usb-xhci-bus: no devices connected\n"); if (connected == 0) _ = runtime.system.write("/system/drivers/usb-xhci-bus: no devices connected\n");
} }
/// No bus protocol to serve yet — transfer requests arrive with the USB track. /// No bus protocol to serve yet — transfer requests arrive with the USB track.
@@ -172,11 +172,11 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
pub fn main(init: runtime.process.Init) void { pub fn main(init: runtime.process.Init) void {
const argument = init.arguments.get(1) orelse { const argument = init.arguments.get(1) orelse {
_ = runtime.system.write("usb-xhci-bus: missing controller device id (argv[1])\n"); _ = runtime.system.write("/system/drivers/usb-xhci-bus: missing controller device id (argv[1])\n");
return; return;
}; };
controller_id = std.fmt.parseInt(u64, argument, 10) catch { controller_id = std.fmt.parseInt(u64, argument, 10) catch {
writeLine("usb-xhci-bus: malformed controller device id '{s}'\n", .{argument}); writeLine("/system/drivers/usb-xhci-bus: malformed controller device id '{s}'\n", .{argument});
return; return;
}; };
runtime.service.run(protocol.message_maximum, .{ runtime.service.run(protocol.message_maximum, .{
+26 -26
View File
@@ -77,12 +77,12 @@ fn kmain(boot_information: *const BootInformation) noreturn {
architecture.setFaultHandler(onException); architecture.setFaultHandler(onException);
architecture.init(); architecture.init();
status("danos: initialising kernel...\n"); status("/system/kernel: initialising kernel...\n");
log.write(if (console.present()) log.write(if (console.present())
"danos: framebuffer console online (bootstrap; graphics driver later)\n" "/system/kernel: framebuffer console online (bootstrap; graphics driver later)\n"
else else
"danos: no framebuffer (headless) -> logging to serial/debugcon only\n"); "/system/kernel: no framebuffer (headless) -> logging to serial/debugcon only\n");
log.write("danos: cpu tables online (GDT, IDT, TSS)\n"); log.write("/system/kernel: cpu tables online (GDT, IDT, TSS)\n");
log.print(" resolution : {d}x{d}\n", .{ fb.width, fb.height }); log.print(" resolution : {d}x{d}\n", .{ fb.width, fb.height });
log.print(" pitch : {d} bytes\n", .{fb.pitch}); log.print(" pitch : {d} bytes\n", .{fb.pitch});
log.print(" format : {s}\n", .{@tagName(fb.format)}); log.print(" format : {s}\n", .{@tagName(fb.format)});
@@ -105,7 +105,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
const total_bytes = total_pages * abi.page_size; const total_bytes = total_pages * abi.page_size;
const gib = 1 << 30; const gib = 1 << 30;
log.write("\ndanos: physical memory\n"); log.write("\n/system/kernel: physical memory\n");
log.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) }); log.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
log.print(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)}); log.print(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)});
log.print(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)}); log.print(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)});
@@ -119,7 +119,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// until SMP bring-up; 0 means none was available (we stay uniprocessor). // until SMP bring-up; 0 means none was available (we stay uniprocessor).
ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0; ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0;
const s1 = pmm.stats(); const s1 = pmm.stats();
log.print("\ndanos: frame allocator online\n", .{}); log.print("\n/system/kernel: frame allocator online\n", .{});
log.print(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) }); log.print(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) });
const f0 = pmm.alloc(); const f0 = pmm.alloc();
const f1 = pmm.alloc(); const f1 = pmm.alloc();
@@ -133,14 +133,14 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Switch off the firmware's page tables onto our own (with real permissions). // Switch off the firmware's page tables onto our own (with real permissions).
architecture.enablePaging(pmm.alloc, pmm.free, boot_information); architecture.enablePaging(pmm.alloc, pmm.free, boot_information);
log.checkpoint(cp_paging); log.checkpoint(cp_paging);
log.print("\ndanos: paging enabled\n", .{}); log.print("\n/system/kernel: paging enabled\n", .{});
log.print(" page tables: root = 0x{x:0>16}\n", .{architecture.activePageTable()}); log.print(" page tables: root = 0x{x:0>16}\n", .{architecture.activePageTable()});
log.print(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_information.kernel_segment_count}); log.print(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_information.kernel_segment_count});
// Bring up the kernel heap (dynamic allocation), built on the VMM. // Bring up the kernel heap (dynamic allocation), built on the VMM.
heap.init(); heap.init();
log.checkpoint(cp_heap); log.checkpoint(cp_heap);
log.write("\ndanos: kernel heap online\n"); log.write("\n/system/kernel: kernel heap online\n");
// Measure the amount of resources the kernel is actually using // Measure the amount of resources the kernel is actually using
const s2 = pmm.stats(); const s2 = pmm.stats();
log.print(" Kernel footprint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)}); log.print(" Kernel footprint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)});
@@ -156,7 +156,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
}; };
if (platform.discover(boot_information, heap.allocator(), hal)) |devtree| { if (platform.discover(boot_information, heap.allocator(), hal)) |devtree| {
var device_tree = devtree; var device_tree = devtree;
log.write("\ndanos: device discovery online\n"); log.write("\n/system/kernel: device discovery online\n");
device_tree.dump(log.write); device_tree.dump(log.write);
// Snapshot the device tree for user-space drivers (device_enumerate/claim/ // Snapshot the device tree for user-space drivers (device_enumerate/claim/
@@ -164,7 +164,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
devices_broker.init(&device_tree); devices_broker.init(&device_tree);
if (devices_broker.dropped > 0) { if (devices_broker.dropped > 0) {
// Otherwise entirely silent: drivers would just never see that hardware. // Otherwise entirely silent: drivers would just never see that hardware.
log.print("danos: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped}); log.print("/system/kernel: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped});
} }
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to // Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
@@ -173,7 +173,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Power register map extracted from the FADT + AML, for confidence it parsed. // Power register map extracted from the FADT + AML, for confidence it parsed.
const pw = platform.powerInformation(); const pw = platform.powerInformation();
log.write("danos: power\n"); log.write("/system/kernel: power\n");
log.print(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width }); log.print(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width });
if (pw.s5) |s| { if (pw.s5) |s| {
log.print(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b }); log.print(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b });
@@ -221,7 +221,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
}); });
if (pinfo.spcr_uart) |u| architecture.serialReconfigure(u.mmio, u.address); if (pinfo.spcr_uart) |u| architecture.serialReconfigure(u.mmio, u.address);
log.write("danos: platform\n"); log.write("/system/kernel: platform\n");
log.print(" 8259 PIC : {s}\n", .{if (pinfo.pic_present) "present" else "absent"}); log.print(" 8259 PIC : {s}\n", .{if (pinfo.pic_present) "present" else "absent"});
log.print(" lapic base : 0x{x}\n", .{pinfo.lapic_base}); log.print(" lapic base : 0x{x}\n", .{pinfo.lapic_base});
log.print(" hpet base : 0x{x}\n", .{hpet_base}); log.print(" hpet base : 0x{x}\n", .{hpet_base});
@@ -237,7 +237,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
if (platform.cpusDropped() > 0) if (platform.cpusDropped() > 0)
log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()}); log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()});
} else |err| { } else |err| {
log.print("\ndanos: device discovery failed: {s}\n", .{@errorName(err)}); log.print("\n/system/kernel: device discovery failed: {s}\n", .{@errorName(err)});
} }
log.checkpoint(cp_discovery); log.checkpoint(cp_discovery);
@@ -248,14 +248,14 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Register the current context as the first task before enabling preemption. // Register the current context as the first task before enabling preemption.
scheduler.init(4); scheduler.init(4);
log.checkpoint(cp_scheduler); log.checkpoint(cp_scheduler);
log.write("\ndanos: scheduler online\n"); log.write("\n/system/kernel: scheduler online\n");
// Start the timer and unmask interrupts — the kernel now has a heartbeat, and // Start the timer and unmask interrupts — the kernel now has a heartbeat, and
// the timer preempts among tasks. // the timer preempts among tasks.
architecture.startTimer(); architecture.startTimer();
architecture.enableInterrupts(); architecture.enableInterrupts();
log.checkpoint(cp_timer); log.checkpoint(cp_timer);
log.print("danos: timer online ({d} Hz tick; timer clock {d} MHz, clock {d} MHz; calibrated via {s})\n", .{ architecture.timer_hz, architecture.timerClockHz() / 1_000_000, architecture.clockHz() / 1_000_000, architecture.timerCalibrationSource() }); log.print("/system/kernel: timer online ({d} Hz tick; timer clock {d} MHz, clock {d} MHz; calibrated via {s})\n", .{ architecture.timer_hz, architecture.timerClockHz() / 1_000_000, architecture.clockHz() / 1_000_000, architecture.timerCalibrationSource() });
// Wake the other cores (application processors). A no-op on a single-core // Wake the other cores (application processors). A no-op on a single-core
// machine; on SMP each AP climbs to long mode and reports in (docs/smp.md). // machine; on SMP each AP climbs to long mode and reports in (docs/smp.md).
@@ -269,7 +269,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
} }
log.checkpoint(cp_running); log.checkpoint(cp_running);
status("kernel initialised.\n"); status("/system/kernel: initialised.\n");
// Publish the initial-ramdisk so user space can `system_spawn` its bundled // Publish the initial-ramdisk so user space can `system_spawn` its bundled
// binaries by name. The kernel no longer launches them itself: init is the // binaries by name. The kernel no longer launches them itself: init is the
@@ -282,10 +282,10 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// manager then discovers the hardware and spawns each driver. init runs on its own // manager then discovers the hardware and spawns each driver. init runs on its own
// address space, preemptively — this boot context becomes the BSP's idle loop. // address space, preemptively — this boot context becomes the BSP's idle loop.
if (boot_information.init_len != 0) { if (boot_information.init_len != 0) {
status("starting /system/services/init...\n"); status("/system/kernel: starting /system/services/init...\n");
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len]; const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| { process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| {
statusPrint("/system/services/init failed to load: {s}\n", .{@errorName(err)}); statusPrint("/system/kernel: /system/services/init failed to load: {s}\n", .{@errorName(err)});
}; };
} else { } else {
status("no /system/services/init on the boot volume.\n"); status("no /system/services/init on the boot volume.\n");
@@ -294,7 +294,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
// Become the idle task: drop below every real task and halt until an // Become the idle task: drop below every real task and halt until an
// interrupt. The timer keeps preempting into init and any other work. // interrupt. The timer keeps preempting into init and any other work.
scheduler.setPriority(0); scheduler.setPriority(0);
status("\nkernel idle; user space is running.\n"); status("\n/system/kernel: kernel idle; user space is running.\n");
architecture.halt(); architecture.halt();
} }
@@ -321,7 +321,7 @@ fn bringUpSecondaries() void {
// vector addresses it). It's kept for the system's life — armed only during a // vector addresses it). It's kept for the system's life — armed only during a
// wake, inert (zeroed, non-executable) otherwise — so cores can be re-woken later. // wake, inert (zeroed, non-executable) otherwise — so cores can be re-woken later.
if (ap_trampoline_page == 0) { if (ap_trampoline_page == 0) {
log.write("danos: smp: no low page for the AP trampoline; staying uniprocessor\n"); log.write("/system/kernel: smp: no low page for the AP trampoline; staying uniprocessor\n");
return; return;
} }
architecture.setTrampolinePage(ap_trampoline_page); architecture.setTrampolinePage(ap_trampoline_page);
@@ -333,7 +333,7 @@ fn bringUpSecondaries() void {
if (std.mem.eql(u8, tc, "smp-retry")) architecture.testFailNextWakes(1); if (std.mem.eql(u8, tc, "smp-retry")) architecture.testFailNextWakes(1);
} }
log.print("\ndanos: bringing up {d} application processor(s)\n", .{cores.len - 1}); log.print("\n/system/kernel: bringing up {d} application processor(s)\n", .{cores.len - 1});
const maximum_wake_attempts = 3; // a core that misses the first INIT-SIPI-SIPI gets retried const maximum_wake_attempts = 3; // a core that misses the first INIT-SIPI-SIPI gets retried
for (cores[1..], 1..) |core, index| { for (cores[1..], 1..) |core, index| {
const stack = heap.allocator().alloc(u8, parameters.kernel_stack_size) catch { const stack = heap.allocator().alloc(u8, parameters.kernel_stack_size) catch {
@@ -344,7 +344,7 @@ fn bringUpSecondaries() void {
// This core's dedicated fault stack — allocated only now that the core is // This core's dedicated fault stack — allocated only now that the core is
// real, rather than reserved statically for every possible core. // real, rather than reserved statically for every possible core.
const fault_stack = heap.allocator().alloc(u8, architecture.fault_stack_size) catch { const fault_stack = heap.allocator().alloc(u8, architecture.fault_stack_size) catch {
log.print(" cpu apic_id {d}: no fault stack; skipped\n", .{core.apic_id}); log.print("/system/kernel: cpu apic_id {d}: no fault stack; skipped\n", .{core.apic_id});
continue; continue;
}; };
architecture.setFaultStack(index, (@intFromPtr(fault_stack.ptr) + fault_stack.len) & ~@as(usize, 15)); architecture.setFaultStack(index, (@intFromPtr(fault_stack.ptr) + fault_stack.len) & ~@as(usize, 15));
@@ -353,14 +353,14 @@ fn bringUpSecondaries() void {
while (attempt <= maximum_wake_attempts) : (attempt += 1) { while (attempt <= maximum_wake_attempts) : (attempt += 1) {
if (architecture.startSecondary(core.apic_id, stack_top, @intFromPtr(pc), index)) { if (architecture.startSecondary(core.apic_id, stack_top, @intFromPtr(pc), index)) {
pc.online = true; pc.online = true;
log.print(" cpu apic_id {d}: online (attempt {d})\n", .{ core.apic_id, attempt }); log.print("/system/kernel: cpu apic_id {d}: online (attempt {d})\n", .{ core.apic_id, attempt });
break; break;
} }
if (attempt == maximum_wake_attempts) if (attempt == maximum_wake_attempts)
log.print(" cpu apic_id {d}: no response after {d} attempts (parked)\n", .{ core.apic_id, maximum_wake_attempts }); log.print("/system/kernel: cpu apic_id {d}: no response after {d} attempts (parked)\n", .{ core.apic_id, maximum_wake_attempts });
} }
} }
log.print("danos: {d}/{d} cores online\n", .{ scheduler.onlineCount(), cores.len }); log.print("/system/kernel: {d}/{d} cores online\n", .{ scheduler.onlineCount(), cores.len });
} }
/// A user-facing status line: to the diagnostic `log` *and* the on-screen console /// A user-facing status line: to the diagnostic `log` *and* the on-screen console
@@ -427,7 +427,7 @@ fn exitReasonForVector(vector: u64) abi.ExitReason {
fn onException(state: *const architecture.CpuState) noreturn { fn onException(state: *const architecture.CpuState) noreturn {
if (architecture.fromUser(state) and scheduler.currentIsUserProcess() and recoverableFault(state.vector)) { if (architecture.fromUser(state) and scheduler.currentIsUserProcess() and recoverableFault(state.vector)) {
statusPrint("\ndanos: process {d} ({s}) killed by {s} (vector {d}) on core {d}\n", .{ scheduler.currentId(), scheduler.current().name(), architecture.exceptionName(state.vector), state.vector, scheduler.currentCpuIndex() }); statusPrint("\n/system/kernel: process {d} ({s}) killed by {s} (vector {d}) on core {d}\n", .{ scheduler.currentId(), scheduler.current().name(), architecture.exceptionName(state.vector), state.vector, scheduler.currentCpuIndex() });
statusPrint(" error code : 0x{x}\n", .{state.error_code}); statusPrint(" error code : 0x{x}\n", .{state.error_code});
statusPrint(" IP : 0x{x:0>16}\n", .{architecture.instructionPointer(state)}); statusPrint(" IP : 0x{x:0>16}\n", .{architecture.instructionPointer(state)});
if (architecture.faultAddress(state)) |address| statusPrint(" fault addr : 0x{x:0>16}\n", .{address}); if (architecture.faultAddress(state)) |address| statusPrint(" fault addr : 0x{x:0>16}\n", .{address});
+33 -53
View File
@@ -152,10 +152,6 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
acpiReportTest(boot_information); acpiReportTest(boot_information);
} else if (eql(case, "acpi-ps2")) { } else if (eql(case, "acpi-ps2")) {
acpiReportTest(boot_information); // same spawn; the harness regex differs acpiReportTest(boot_information); // same spawn; the harness regex differs
} else if (eql(case, "power-button")) {
acpiReportTest(boot_information); // boot the manager (spawns the acpi service); harness injects the button
} else if (eql(case, "orderly-shutdown")) {
orderlyShutdownTest(boot_information);
} else if (eql(case, "initial-ramdisk")) { } else if (eql(case, "initial-ramdisk")) {
initialRamdiskTest(boot_information); initialRamdiskTest(boot_information);
} else if (eql(case, "vfs")) { } else if (eql(case, "vfs")) {
@@ -211,6 +207,13 @@ fn eql(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b); return std.mem.eql(u8, a, b);
} }
/// Whether the captured last-write buffer *contains* `needle`. Markers are
/// matched as substrings, not prefixes, so a service's source-path debug prefix
/// (`system/drivers/hpet: ok`) still satisfies a marker like `hpet: ok`.
fn bufferHas(needle: []const u8) bool {
return std.mem.indexOf(u8, process.write_buffer[0..process.write_len], needle) != null;
}
/// Non-destructive checks of the memory map and frame allocator. /// Non-destructive checks of the memory map and frame allocator.
fn smoke(boot_information: *const BootInformation) void { fn smoke(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: smoke\n", .{}); log("DANOS-TEST-BEGIN: smoke\n", .{});
@@ -1380,7 +1383,7 @@ fn initTest(boot_information: *const BootInformation) void {
scheduler.setPriority(4); scheduler.setPriority(4);
const prefix = "init: heartbeat"; const prefix = "init: heartbeat";
const beat_ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const beat_ok = bufferHas(prefix);
check("init produced repeated heartbeats (>=2)", process.write_count >= 2); check("init produced repeated heartbeats (>=2)", process.write_count >= 2);
check("heartbeat text arrived intact", beat_ok); check("heartbeat text arrived intact", beat_ok);
check("heartbeats came from user mode (CPL 3)", process.write_from_user); check("heartbeats came from user mode (CPL 3)", process.write_from_user);
@@ -1642,11 +1645,11 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
var deadline = architecture.millis() + 10000; var deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= parked.len and eql(process.write_buffer[0..parked.len], parked)) break; if (bufferHas(parked)) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
check("client parked holding an open handle", process.write_len >= parked.len and eql(process.write_buffer[0..parked.len], parked)); check("client parked holding an open handle", bufferHas(parked));
check("the kill is accepted", process.killProcess(me, client) == 0); check("the kill is accepted", process.killProcess(me, client) == 0);
var badge: u64 = 0; var badge: u64 = 0;
@@ -1659,11 +1662,11 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
deadline = architecture.millis() + 10000; deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= released.len and eql(process.write_buffer[0..released.len], released)) break; if (bufferHas(released)) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
check("the VFS released the dead client's handle", process.write_len >= released.len and eql(process.write_buffer[0..released.len], released)); check("the VFS released the dead client's handle", bufferHas(released));
result(); result();
} }
@@ -1705,8 +1708,8 @@ fn signalsTest(boot_information: *const BootInformation) void {
var saw_pass = false; var saw_pass = false;
var saw_fail = false; var saw_fail = false;
while (architecture.millis() < deadline and !saw_pass and !saw_fail) { while (architecture.millis() < deadline and !saw_pass and !saw_fail) {
if (process.write_len >= pass_marker.len and eql(process.write_buffer[0..pass_marker.len], pass_marker)) saw_pass = true; if (bufferHas(pass_marker)) saw_pass = true;
if (process.write_len >= fail_marker.len and eql(process.write_buffer[0..fail_marker.len], fail_marker)) saw_fail = true; if (bufferHas(fail_marker)) saw_fail = true;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
@@ -1868,13 +1871,11 @@ fn pciScanTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
var deadline = architecture.millis() + 15000; var deadline = architecture.millis() + 15000;
while (architecture.millis() < deadline and reported == 0) { while (architecture.millis() < deadline and reported == 0) {
if (process.write_len > count_prefix.len + count_suffix.len and eql(process.write_buffer[0..count_prefix.len], count_prefix)) { const line = process.write_buffer[0..process.write_len];
const line = process.write_buffer[0..process.write_len]; if (std.mem.indexOf(u8, line, count_prefix)) |start| {
const digits_end = std.mem.indexOf(u8, line, count_suffix) orelse { if (std.mem.indexOf(u8, line, count_suffix)) |digits_end| {
scheduler.yield(); reported = std.fmt.parseInt(u32, line[start + count_prefix.len .. digits_end], 10) catch 0;
continue; }
};
reported = std.fmt.parseInt(u32, line[count_prefix.len..digits_end], 10) catch 0;
} }
scheduler.yield(); scheduler.yield();
} }
@@ -1898,7 +1899,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
deadline = architecture.millis() + 15000; deadline = architecture.millis() + 15000;
var restarted = false; var restarted = false;
while (architecture.millis() < deadline and !restarted) { while (architecture.millis() < deadline and !restarted) {
if (process.write_len >= restart_marker.len and eql(process.write_buffer[0..restart_marker.len], restart_marker)) restarted = true; if (bufferHas(restart_marker)) restarted = true;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
@@ -1910,7 +1911,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
deadline = architecture.millis() + 15000; deadline = architecture.millis() + 15000;
var seen = false; var seen = false;
while (architecture.millis() < deadline and !seen) { while (architecture.millis() < deadline and !seen) {
if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) seen = true; if (bufferHas(marker)) seen = true;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
@@ -1928,27 +1929,6 @@ fn pciScanTest(boot_information: *const BootInformation) void {
result(); result();
} }
/// M21.3 capstone: orderly shutdown. Boot init with the initial-ramdisk
/// published, so init spawns the full service tree (vfs, input, device-manager
/// -> discovery/acpi); the harness injects a real power-button event via QMP;
/// the acpi service publishes it; init runs the stop sequence over its children
/// and asks the power service for S5; the machine powers off (QEMU exits). The
/// kernel test only spawns init — the ordered chain is the harness assertion.
fn orderlyShutdownTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: orderly-shutdown\n", .{});
if (boot_information.init_len == 0 or boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over init and the initial_ramdisk", false);
result();
return;
}
const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(ramdisk);
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
check("init spawned as PID root of user space", spawned);
result();
}
/// M20.2: the acpi service registers + reports its _HID devices. Boot normally /// M20.2: the acpi service registers + reports its _HID devices. Boot normally
/// (the manager spawns discovery); the harness's expect regex requires the two /// (the manager spawns discovery); the harness's expect regex requires the two
/// PS/2 nodes among the service's report lines, each with its _CRS resources — /// PS/2 nodes among the service's report lines, each with its _CRS resources —
@@ -2060,12 +2040,12 @@ fn supervisionTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) break; if (bufferHas(marker)) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker); const ok = bufferHas(marker);
if (!ok and process.write_len > 0) log("DANOS-SUPERVISION: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]}); if (!ok and process.write_len > 0) log("DANOS-SUPERVISION: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
check("the supervisor completed every step (spawn/list/kill/notify)", ok); check("the supervisor completed every step (spawn/list/kill/notify)", ok);
check("it ran in user mode (CPL 3)", process.write_from_user); check("it ran in user mode (CPL 3)", process.write_from_user);
@@ -2145,12 +2125,12 @@ fn vfsTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break; if (bufferHas(prefix) and process.write_count >= 2) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const ok = bufferHas(prefix);
check("client completed the VFS round trip (open/write/read matched)", ok); check("client completed the VFS round trip (open/write/read matched)", ok);
check("the round trip ran repeatedly (server stays up)", process.write_count >= 2); check("the round trip ran repeatedly (server stays up)", process.write_count >= 2);
check("client syscalls came from user mode (CPL 3)", process.write_from_user); check("client syscalls came from user mode (CPL 3)", process.write_from_user);
@@ -2190,12 +2170,12 @@ fn inputTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 12000; const deadline = architecture.millis() + 12000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break; if (bufferHas(prefix) and process.write_count >= 2) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const ok = bufferHas(prefix);
check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok); check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
check("events kept flowing (service + async send stay up)", process.write_count >= 2); check("events kept flowing (service + async send stay up)", process.write_count >= 2);
check("client syscalls came from user mode (CPL 3)", process.write_from_user); check("client syscalls came from user mode (CPL 3)", process.write_from_user);
@@ -2287,12 +2267,12 @@ fn hpetTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break; if (bufferHas(prefix) and process.write_count >= 2) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const ok = bufferHas(prefix);
check("user driver mapped HPET MMIO and was woken by its interrupt", ok); check("user driver mapped HPET MMIO and was woken by its interrupt", ok);
check("driver syscalls came from user mode (CPL 3)", process.write_from_user); check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
check("kernel routed and re-armed the HPET's line at the I/O APIC", hpetRouteOk()); check("kernel routed and re-armed the HPET's line at the I/O APIC", hpetRouteOk());
@@ -2393,12 +2373,12 @@ fn busTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break; if (bufferHas(prefix)) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const ok = bufferHas(prefix);
check("bus driver published children and the kernel refused an out-of-window one", ok); check("bus driver published children and the kernel refused an out-of-window one", ok);
check("driver syscalls came from user mode (CPL 3)", process.write_from_user); check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
check("every registered child is contained in its parent", childrenContained()); check("every registered child is contained in its parent", childrenContained());
@@ -2442,12 +2422,12 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
scheduler.setPriority(1); scheduler.setPriority(1);
const deadline = architecture.millis() + 10000; const deadline = architecture.millis() + 10000;
while (architecture.millis() < deadline) { while (architecture.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break; if (bufferHas(prefix)) break;
scheduler.yield(); scheduler.yield();
} }
scheduler.setPriority(4); scheduler.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix); const ok = bufferHas(prefix);
check("device manager matched the timer and system_spawn'd hpet, which came up", ok); check("device manager matched the timer and system_spawn'd hpet, which came up", ok);
check("its syscalls came from user mode (CPL 3)", process.write_from_user); check("its syscalls came from user mode (CPL 3)", process.write_from_user);
result(); result();
+42 -333
View File
@@ -4,11 +4,13 @@
//! grant, a broad irq window, the SCI), and runs the **shared AML module** in //! grant, a broad irq window, the SCI), and runs the **shared AML module** in
//! ring 3 — the same parser and interpreter the kernel uses. //! ring 3 — the same parser and interpreter the kernel uses.
//! //!
//! It also owns the **event side** (M21): it registers the domain-named `.power` //! M20.2 (this increment): after parsing, walk the namespace and, for each
//! service, binds the SCI (System Control Interrupt), and on a power-button //! present Device with a hardware id (`_HID`), evaluate its current resource
//! fixed event publishes `power_button` to subscribers — and on init's request //! settings (`_CRS`) through a ring-3 `Hal` (port I/O over the claimed node),
//! writes S5 to power the machine off. The device discovery (M20) and the event //! register it under the acpi-tables node (its I/O ports and IRQs contained by
//! handling both run in one `runtime.service.run` loop. //! the node's broad grants), and report it to the device manager with its
//! EISA-decoded hid as identity. Matching those reports to drivers (ps2-bus)
//! and retiring the kernel's own device build follow in M20.3.
const std = @import("std"); const std = @import("std");
const runtime = @import("runtime"); const runtime = @import("runtime");
@@ -16,7 +18,6 @@ const aml = @import("aml");
const acpi_ids = @import("acpi-ids"); const acpi_ids = @import("acpi-ids");
const device = runtime.device; const device = runtime.device;
const protocol = runtime.device_manager_protocol; const protocol = runtime.device_manager_protocol;
const power = runtime.power_protocol;
/// AML opcode/prefix bytes by name (`zero_opcode`, `byte_prefix`, …) — so the `_HID` /// AML opcode/prefix bytes by name (`zero_opcode`, `byte_prefix`, …) — so the `_HID`
/// integer decode names the opcodes instead of bare 0x0A/0x0B/… (docs/coding-standards.md). /// integer decode names the opcodes instead of bare 0x0A/0x0B/… (docs/coding-standards.md).
const opcodes = aml.opcodes; const opcodes = aml.opcodes;
@@ -30,43 +31,6 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
// window — the Hal routes every port access through this one claim. // window — the Hal routes every port access through this one claim.
var node_id: u64 = 0; var node_id: u64 = 0;
var io_resource_index: u64 = 0; var io_resource_index: u64 = 0;
// The SCI's irq resource index on the node (the len-1 irq, distinct from the
// broad [0,256) window), for irqBind / irqAck.
var sci_resource_index: u64 = 0;
var has_sci = false;
// PM1 event/control and GPE register ports, read from the FADT copy the kernel
// publishes on the node (M21). Port 0 means absent.
var pm1a_evt: u16 = 0;
var pm1b_evt: u16 = 0;
var pm1_evt_len: u8 = 0;
var pm1a_cnt: u16 = 0;
var pm1b_cnt: u16 = 0;
var gpe0_blk: u16 = 0;
var gpe0_len: u8 = 0;
var gpe1_blk: u16 = 0;
var gpe1_len: u8 = 0;
var smi_cmd: u16 = 0;
var acpi_enable_value: u8 = 0;
var s5_slp_typ_a: u8 = 0;
var s5_slp_typ_b: u8 = 0;
var s5_valid = false;
// PM1 event-register bits (ACPI): PWRBTN in the status/enable word is bit 8;
// the control word's SCI_EN is bit 0; SLP_EN is bit 13.
const pwrbtn_bit: u16 = 1 << 8;
const sci_en_bit: u32 = 1 << 0;
const slp_en: u32 = 1 << 13;
// The `.power` subscribers: endpoints handed over as capabilities, each
// receiving events as buffered messages. Dropped on a failed send. The
// subscriber's task id is kept too — a shutdown request is honored only from a
// subscriber (init subscribes; a stray process does not), the soft gate that
// stands in for "only the system supervisor may power off" without hardcoding
// a pid the kernel's idle tasks would have taken.
const maximum_subscribers = 8;
var subscribers: [maximum_subscribers]?runtime.ipc.Handle = .{null} ** maximum_subscribers;
var subscriber_tasks: [maximum_subscribers]u32 = .{0} ** maximum_subscribers;
// Pass-1 registration record (see main): what pass 2 reports. // Pass-1 registration record (see main): what pass 2 reports.
const Registered = struct { hid: [8]u8 = .{0} ** 8, hid_len: usize = 0, device_id: u64 = 0, resource_count: u64 = 0 }; const Registered = struct { hid: [8]u8 = .{0} ** 8, hid_len: usize = 0, device_id: u64 = 0, resource_count: u64 = 0 };
@@ -108,60 +72,48 @@ pub fn main(init: runtime.process.Init) void {
const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null; const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null;
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("acpi: out of memory\n"); _ = runtime.system.write("/system/services/acpi: out of memory\n");
return; return;
}; };
const node = findTablesNode(buffer) orelse { const node = findTablesNode(buffer) orelse {
_ = runtime.system.write("acpi: no acpi-tables node to claim\n"); _ = runtime.system.write("/system/services/acpi: no acpi-tables node to claim\n");
return; return;
}; };
node_id = node.id; node_id = node.id;
if (!device.claim(node_id)) { if (!device.claim(node_id)) {
_ = runtime.system.write("acpi: unable to claim acpi-tables\n"); _ = runtime.system.write("/system/services/acpi: unable to claim acpi-tables\n");
return; return;
} }
// Map the node's resources: the AML blobs (bytecode), the FADT (intact // Map each memory resource (an AML blob) and note the io_port resource.
// "FACP" header — decision 3), the io_port grant, and the SCI irq.
var blocks: [8][]const u8 = undefined; var blocks: [8][]const u8 = undefined;
var block_count: usize = 0; var block_count: usize = 0;
var found_io = false; var found_io = false;
var fadt: ?[]const u8 = null;
for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| { for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| {
if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and !found_io) { if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and !found_io) {
io_resource_index = index; io_resource_index = index;
found_io = true; found_io = true;
continue; continue;
} }
if (resource.kind == @intFromEnum(device.ResourceKind.irq) and resource.len == 1) {
sci_resource_index = index;
has_sci = true;
continue;
}
if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue; if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue;
const base = device.mmioMap(node_id, index) orelse continue; const base = device.mmioMap(node_id, index) orelse continue;
const pointer: [*]const u8 = @ptrFromInt(base); const pointer: [*]const u8 = @ptrFromInt(base);
const bytes = pointer[0..@intCast(resource.len)]; blocks[block_count] = pointer[0..@intCast(resource.len)];
if (bytes.len >= 4 and std.mem.eql(u8, bytes[0..4], "FACP")) {
fadt = bytes;
continue;
}
if (block_count == blocks.len) continue;
blocks[block_count] = bytes;
block_count += 1; block_count += 1;
if (block_count == blocks.len) break;
} }
if (block_count == 0) { if (block_count == 0) {
_ = runtime.system.write("acpi: no AML blobs on the node\n"); _ = runtime.system.write("/system/services/acpi: no AML blobs on the node\n");
return; return;
} }
const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch { const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch {
_ = runtime.system.write("acpi: AML parse failed\n"); _ = runtime.system.write("/system/services/acpi: AML parse failed\n");
return; return;
}; };
var namespace = result.namespace; var namespace = result.namespace;
const devices = aml.deviceCount(&namespace); const devices = aml.deviceCount(&namespace);
writeLine("acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices }); writeLine("/system/services/acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
if (expected) |want| { if (expected) |want| {
if (devices == want) { if (devices == want) {
_ = runtime.system.write("acpi-parse: ok\n"); _ = runtime.system.write("acpi-parse: ok\n");
@@ -172,295 +124,52 @@ pub fn main(init: runtime.process.Init) void {
while (true) runtime.system.sleep(1000); while (true) runtime.system.sleep(1000);
} }
// Register + report the present _HID devices (M20), then set up the power // Register + report the present _HID devices (M20.2).
// event side (M21), then serve — all in one harness loop. The interpreter var arena = std.heap.ArenaAllocator.init(runtime.allocator());
// and namespace outlive this frame (static), so the harness callbacks can var interpreter = aml.Interpreter.init(&namespace, .{
// reach them.
interpreter_arena = std.heap.ArenaAllocator.init(runtime.allocator());
persistent_namespace = namespace;
global_interpreter = aml.Interpreter.init(&persistent_namespace, .{
.mapMmio = halMapMmio, .mapMmio = halMapMmio,
.pioRead = halPioRead, .pioRead = halPioRead,
.pioWrite = halPioWrite, .pioWrite = halPioWrite,
}, interpreter_arena.allocator()); }, arena.allocator());
readFadt(fadt); // Pass 1: register every present _HID device under acpi-tables, remembering
s5_valid = readSleepS5(&persistent_namespace); // each (hid, device id). Pass 2: report them all. Registering before any
// report reaches the manager means a driver it spawns on the first report
runtime.service.run(power.message_maximum, .{ // already sees the whole set (no keyboard-before-mouse race for ps2-bus).
.service = .power,
.init = onInit,
.on_message = onMessage,
.on_notification = onNotification,
});
}
// Static so the harness callbacks (which run after main's stack frame is gone)
// can reach the namespace and interpreter.
var persistent_namespace: aml.Namespace = undefined;
var global_interpreter: aml.Interpreter = undefined;
var interpreter_arena: std.heap.ArenaAllocator = undefined;
/// Startup under the harness: register + report the discovered devices to the
/// manager (M20), then enable ACPI mode and arm the power button (M21).
fn onInit(endpoint: runtime.ipc.Handle) bool {
registered_count = 0; registered_count = 0;
walkDevices(persistent_namespace.root, &global_interpreter); walkDevices(namespace.root, &interpreter);
const manager = runtime.ipc.lookup(.device_manager); const manager = runtime.ipc.lookup(.device_manager);
var i: usize = 0; var i: usize = 0;
while (i < registered_count) : (i += 1) { while (i < registered_count) : (i += 1) {
const entry = registered[i]; const entry = registered[i];
// Append the _HID's human-readable name when it is a known standard PnP/ACPI
// id (e.g. PNP0303 -> "PS/2 Keyboard"), so the boot log says what each
// reported device actually is. The description trails the existing fields so
// the acpi-report/acpi-ps2 matchers still see "<hid> (device N, M resources)".
const hid = entry.hid[0..entry.hid_len]; const hid = entry.hid[0..entry.hid_len];
const desc = acpi_ids.description(hid); const desc = acpi_ids.description(hid);
if (desc.len != 0) if (desc.len != 0)
writeLine("acpi: reported {s} (device {d}, {d} resources) — {s}\n", .{ hid, entry.device_id, entry.resource_count, desc }) writeLine("/system/services/acpi: reported {s} (device {d}, {d} resources) — {s}\n", .{ hid, entry.device_id, entry.resource_count, desc })
else else
writeLine("acpi: reported {s} (device {d}, {d} resources)\n", .{ hid, entry.device_id, entry.resource_count }); writeLine("/system/services/acpi: reported {s} (device {d}, {d} resources)\n", .{ hid, entry.device_id, entry.resource_count });
if (manager) |h| { if (manager) |h| {
var report = protocol.ChildAdded{ .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id }; var report = protocol.ChildAdded{
.parent = node_id,
.bus_address = entry.device_id,
.identity = 0,
.device_id = entry.device_id,
};
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]); @memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
var reply: [protocol.message_maximum]u8 = undefined; var reply: [protocol.message_maximum]u8 = undefined;
_ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {}; _ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
} }
} }
writeLine("acpi: reported {d} device(s) to the manager\n", .{registered_count}); writeLine("/system/services/acpi: reported {d} device(s) to the manager\n", .{registered_count});
armPowerButton(endpoint); // Stay resident: the claim holds, and the service is here to grow into the
return true; // supervised discoverer (M20.3, then the M21 event side on the SCI).
} while (true) runtime.system.sleep(1000);
// --- power event side (M21) ---------------------------------------------------
/// Read the PM1 event/control and GPE register ports plus the SMI enable pair
/// from the FADT copy on the node. Offsets are from the FADT table start (the
/// SDT header is the first 36 bytes). Prefers the 32-bit port fields; QEMU's
/// FADT populates them.
fn readFadt(fadt: ?[]const u8) void {
const f = fadt orelse {
_ = runtime.system.write("acpi: no FADT on the node — power events off\n");
return;
};
smi_cmd = @truncate(rd32(f, 48));
acpi_enable_value = f[52];
pm1a_evt = @truncate(rd32(f, 56));
pm1b_evt = @truncate(rd32(f, 60));
pm1a_cnt = @truncate(rd32(f, 64));
pm1b_cnt = @truncate(rd32(f, 68));
gpe0_blk = @truncate(rd32(f, 80));
gpe1_blk = @truncate(rd32(f, 84));
pm1_evt_len = if (f.len > 88) f[88] else 4;
gpe0_len = if (f.len > 92) f[92] else 0;
gpe1_len = if (f.len > 93) f[93] else 0;
}
fn readSleepS5(ns: *aml.Namespace) bool {
const st = aml.sleepState(ns, 5) orelse return false;
s5_slp_typ_a = st.slp_typ_a;
s5_slp_typ_b = st.slp_typ_b;
return true;
}
/// Enable ACPI mode if the firmware isn't already in it, then bind the SCI and
/// set PWRBTN_EN so the power button raises an interrupt we can see.
fn armPowerButton(endpoint: runtime.ipc.Handle) void {
if (pm1a_cnt != 0 and (halPioRead(2, pm1a_cnt) & sci_en_bit) == 0 and smi_cmd != 0) {
// Switch to ACPI mode: write ACPI_ENABLE to the SMI command port, then
// spin (bounded) until SCI_EN latches.
halPioWrite(1, smi_cmd, acpi_enable_value);
var tries: u32 = 0;
while (tries < 1000 and (halPioRead(2, pm1a_cnt) & sci_en_bit) == 0) : (tries += 1) {
runtime.system.sleep(1);
}
}
if (!has_sci) {
_ = runtime.system.write("acpi: no SCI resource — power button unavailable\n");
return;
}
if (!device.irqBind(node_id, sci_resource_index, endpoint)) {
_ = runtime.system.write("acpi: SCI irq_bind failed\n");
return;
}
// PWRBTN_EN lives in the PM1 enable register at evt_blk + evt_len/2.
if (pm1a_evt != 0) {
const en_port = pm1a_evt + pm1_evt_len / 2;
halPioWrite(2, en_port, @as(u16, @truncate(halPioRead(2, en_port))) | pwrbtn_bit);
}
if (pm1b_evt != 0) {
const en_port = pm1b_evt + pm1_evt_len / 2;
halPioWrite(2, en_port, @as(u16, @truncate(halPioRead(2, en_port))) | pwrbtn_bit);
}
_ = runtime.system.write("acpi: power button armed\n");
}
/// The SCI fired. Read PM1 status; a set PWRBTN_STS is the power button — clear
/// it (write-1), publish, log. Any other set status is cleared and logged
/// (GPE/Notify dispatch is M21.2). Always re-arm the line.
fn onSci() void {
var handled = false;
inline for (.{ pm1a_evt, pm1b_evt }) |evt_port| {
if (evt_port != 0) {
const sts: u16 = @truncate(halPioRead(2, evt_port));
if (sts & pwrbtn_bit != 0) {
halPioWrite(2, evt_port, pwrbtn_bit); // write-1-to-clear
handled = true;
} else if (sts != 0) {
halPioWrite(2, evt_port, sts); // clear whatever else latched
}
}
}
if (handled) {
_ = runtime.system.write("power: button pressed\n");
publishButton();
}
handleGpe();
_ = device.irqAck(node_id, sci_resource_index);
}
/// General-purpose events: for each set+enabled GPE bit, evaluate its `\_GPE`
/// handler method (`_Lxx` level / `_Exx` edge), drain the Notify queue the
/// method produced, and publish an event per notified device. Then clear the
/// status bit. QEMU raises no GPEs on this config, so this path is exercised by
/// host unit tests (docs/m21-plan.md decision 5); on real hardware it carries
/// battery/AC/lid. The embedded controller's `_Qxx` queries are out of scope.
fn handleGpe() void {
handleGpeBlock(gpe0_blk, gpe0_len, 0);
handleGpeBlock(gpe1_blk, gpe1_len, gpe0_len * 4);
}
fn handleGpeBlock(blk: u16, len: u8, gpe_base: u32) void {
if (blk == 0 or len == 0) return;
const status_bytes = len / 2; // status half, then enable half
var byte_index: u8 = 0;
while (byte_index < status_bytes) : (byte_index += 1) {
const sts: u8 = @truncate(halPioRead(1, blk + byte_index));
const en: u8 = @truncate(halPioRead(1, blk + status_bytes + byte_index));
const active = sts & en;
if (active == 0) continue;
var bit: u3 = 0;
while (true) : (bit += 1) {
if (active & (@as(u8, 1) << bit) != 0) {
dispatchGpe(gpe_base + @as(u32, byte_index) * 8 + bit);
}
if (bit == 7) break;
}
halPioWrite(1, blk + byte_index, active); // write-1-to-clear the serviced bits
}
}
/// Evaluate the `\_GPE._L%02X` or `_E%02X` handler for GPE number `n`, then
/// publish an event for each device it notified.
fn dispatchGpe(n: u32) void {
const gpe_scope = aml.Namespace.resolve(&persistent_namespace, persistent_namespace.root, true, 0, &.{seg4("_GPE")}) orelse return;
var name: [4]u8 = .{ '_', 'L', 0, 0 };
writeHex2(name[2..4], n);
var method = aml.Namespace.childOf(gpe_scope, name);
if (method == null) {
name[1] = 'E';
method = aml.Namespace.childOf(gpe_scope, name);
}
const m = method orelse return; // no handler — the status bit was already cleared
_ = global_interpreter.evaluate(m, &.{}) catch return;
for (global_interpreter.takeNotifications()) |event| publishNotify(event.node, event.code);
}
fn publishNotify(node: *aml.Node, code: u64) void {
// Map the notified device's _HID to a domain event where we recognize it.
var hid: [8]u8 = .{0} ** 8;
if (readHid(node, &global_interpreter)) |h| hid = h;
const which: power.Event = if (std.mem.eql(u8, hid[0..7], "PNP0C0A")) .battery else if (std.mem.eql(u8, hid[0..7], "ACPI0003")) .ac else if (std.mem.eql(u8, hid[0..7], "PNP0C0D")) .lid else .notify;
var event = power.EventMessage{ .event = @intFromEnum(which), .code = @truncate(code) };
event.hid = hid;
writeLine("power: notify {s} code {d}\n", .{ hid[0..7], code });
publishEvent(std.mem.asBytes(&event));
}
/// Two lowercase hex digits of `n` into `out[0..2]`.
fn writeHex2(out: []u8, n: u32) void {
const digits = "0123456789ABCDEF";
out[0] = digits[(n >> 4) & 0xF];
out[1] = digits[n & 0xF];
}
fn publishButton() void {
const event = power.EventMessage{ .event = @intFromEnum(power.Event.power_button) };
publishEvent(std.mem.asBytes(&event));
}
fn publishEvent(bytes: []const u8) void {
for (&subscribers) |*slot| {
if (slot.*) |handle| {
if (!runtime.ipc.send(handle, bytes)) slot.* = null;
}
}
}
fn isSubscriber(task: u32) bool {
for (&subscribers, 0..) |*slot, si| {
if (slot.* != null and subscriber_tasks[si] == task) return true;
}
return false;
}
/// Enter S5 (soft off): write SLP_TYP|SLP_EN to the PM1 control register(s).
/// Mirrors the kernel's power.zig sleepValue. Only reached from a PID-1
/// shutdown request (M21.3).
fn enterS5() void {
if (!s5_valid or pm1a_cnt == 0) {
_ = runtime.system.write("power: S5 unavailable\n");
return;
}
_ = runtime.system.write("power: entering S5\n");
halPioWrite(2, pm1a_cnt, (@as(u32, s5_slp_typ_a & 0x7) << 10) | slp_en);
if (pm1b_cnt != 0) halPioWrite(2, pm1b_cnt, (@as(u32, s5_slp_typ_b & 0x7) << 10) | slp_en);
// If control returns, the write did not take — say so instead of hanging.
runtime.system.sleep(500);
_ = runtime.system.write("power: S5 write did not take\n");
}
// --- harness callbacks --------------------------------------------------------
fn onNotification(badge: u64) void {
// The only notification the service binds is the SCI (an IRQ badge).
_ = badge;
onSci();
}
/// The `.power` protocol: subscribe (endpoint as the call's capability),
/// shutdown (PID 1 only). Device discovery uses a different endpoint (the
/// device manager's), so nothing here handles ChildAdded.
fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime.ipc.Handle) usize {
if (message.len < 1) return 0;
switch (message[0]) {
@intFromEnum(power.Operation.subscribe) => {
var status: i32 = -1;
if (capability) |handle| {
for (&subscribers, 0..) |*slot, si| {
if (slot.* == null) {
slot.* = handle;
subscriber_tasks[si] = sender;
status = 0;
break;
}
}
}
const r = power.Reply{ .status = status };
@memcpy(reply[0..@sizeOf(power.Reply)], std.mem.asBytes(&r));
return @sizeOf(power.Reply);
},
@intFromEnum(power.Operation.shutdown) => {
// Honored only from a power subscriber — init, which has already run
// the stop sequence over everything else. The power service is
// mechanism (write S5); deciding *when* to shut down and stopping
// the rest of the system first is init's policy.
const allowed = isSubscriber(sender);
const r = power.Reply{ .status = if (allowed) 0 else -1 };
@memcpy(reply[0..@sizeOf(power.Reply)], std.mem.asBytes(&r));
if (allowed) enterS5();
return @sizeOf(power.Reply);
},
else => return 0,
}
} }
/// Depth-first walk: register + report each present device with a _HID, then /// Depth-first walk: register + report each present device with a _HID, then
@@ -498,7 +207,7 @@ fn registerDevice(node: *aml.Node, hid: [8]u8, interpreter: *aml.Interpreter) vo
applyCrs(&descriptor, node, interpreter); applyCrs(&descriptor, node, interpreter);
const id = device.register(node_id, &descriptor) orelse { const id = device.register(node_id, &descriptor) orelse {
writeLine("acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]}); writeLine("/system/services/acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]});
return; return;
}; };
registered[registered_count] = .{ .hid = hid, .hid_len = @intCast(hid_len), .device_id = id, .resource_count = descriptor.resource_count }; registered[registered_count] = .{ .hid = hid, .hid_len = @intCast(hid_len), .device_id = id, .resource_count = descriptor.resource_count };
@@ -191,7 +191,7 @@ fn addChild(parent: u64, bus_address: u64, identity: u64, device_id: u64, report
fn pruneChildrenOf(reporter: u32) void { fn pruneChildrenOf(reporter: u32) void {
for (&children) |*child| { for (&children) |*child| {
if (child.used and child.reporter == reporter) { if (child.used and child.reporter == reporter) {
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address }); writeLine("/system/services/device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
child.used = false; child.used = false;
const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address }; const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
publishEvent(std.mem.asBytes(&event)); publishEvent(std.mem.asBytes(&event));
@@ -238,7 +238,7 @@ fn addDriver(name: []const u8, device_id: u64, speaks_protocol: bool) void {
spawnDriver(driver); spawnDriver(driver);
return; return;
} }
writeLine("device-manager: driver table full; cannot supervise {s}\n", .{name}); writeLine("/system/services/device-manager: driver table full; cannot supervise {s}\n", .{name});
} }
/// (Re)spawn a driver instance: supervised on the manager's own endpoint, the /// (Re)spawn a driver instance: supervised on the manager's own endpoint, the
@@ -253,7 +253,7 @@ fn spawnDriver(driver: *Driver) void {
argument_count = 1; argument_count = 1;
} }
const child = system.spawnSupervised(driver.name(), arguments[0..argument_count], manager_endpoint) orelse { const child = system.spawnSupervised(driver.name(), arguments[0..argument_count], manager_endpoint) orelse {
writeLine("device-manager: failed to spawn {s}\n", .{driver.name()}); writeLine("/system/services/device-manager: failed to spawn {s}\n", .{driver.name()});
driver.state = .failed; driver.state = .failed;
return; return;
}; };
@@ -267,9 +267,9 @@ fn spawnDriver(driver: *Driver) void {
driver.state = .running; driver.state = .running;
} }
if (driver.device_id != protocol.no_device) { if (driver.device_id != protocol.no_device) {
writeLine("device-manager: spawned {s} for device {d}\n", .{ driver.name(), driver.device_id }); writeLine("/system/services/device-manager: spawned {s} for device {d}\n", .{ driver.name(), driver.device_id });
} else { } else {
writeLine("device-manager: spawned {s}\n", .{driver.name()}); writeLine("/system/services/device-manager: spawned {s}\n", .{driver.name()});
} }
} }
@@ -281,7 +281,7 @@ fn onDriverExit(driver: *Driver) void {
const reason = runtime.process.exitReason(driver.process_id) orelse .fault; const reason = runtime.process.exitReason(driver.process_id) orelse .fault;
if (reason == .exited) { if (reason == .exited) {
driver.state = .stopped; driver.state = .stopped;
writeLine("device-manager: {s} exited cleanly; not restarting\n", .{driver.name()}); writeLine("/system/services/device-manager: {s} exited cleanly; not restarting\n", .{driver.name()});
return; return;
} }
const now = system.clock(); const now = system.clock();
@@ -289,13 +289,13 @@ fn onDriverExit(driver: *Driver) void {
driver.restarts = if (alive_ns < fast_death_ns) driver.restarts + 1 else 1; driver.restarts = if (alive_ns < fast_death_ns) driver.restarts + 1 else 1;
if (driver.restarts >= crash_loop_cap) { if (driver.restarts >= crash_loop_cap) {
driver.state = .failed; driver.state = .failed;
writeLine("device-manager: {s} is failing repeatedly (crash loop); giving up\n", .{driver.name()}); writeLine("/system/services/device-manager: {s} is failing repeatedly (crash loop); giving up\n", .{driver.name()});
return; return;
} }
const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1); const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1);
driver.state = .restarting; driver.state = .restarting;
driver.restart_due_ns = now + delay_ms * 1_000_000; driver.restart_due_ns = now + delay_ms * 1_000_000;
writeLine("device-manager: restarting {s} in {d} ms (died: {s})\n", .{ driver.name(), delay_ms, @tagName(reason) }); writeLine("/system/services/device-manager: restarting {s} in {d} ms (died: {s})\n", .{ driver.name(), delay_ms, @tagName(reason) });
_ = system.timerOnce(manager_endpoint, delay_ms + 50); _ = system.timerOnce(manager_endpoint, delay_ms + 50);
} }
@@ -306,7 +306,7 @@ fn onDriverExit(driver: *Driver) void {
fn sweepDeadlines() void { fn sweepDeadlines() void {
const now = system.clock(); const now = system.clock();
if (test_kill_pid != 0 and now >= test_kill_due_ns) { if (test_kill_pid != 0 and now >= test_kill_due_ns) {
writeLine("device-manager: test mode: killing the reporter\n", .{}); writeLine("/system/services/device-manager: test mode: killing the reporter\n", .{});
_ = system.kill(test_kill_pid); _ = system.kill(test_kill_pid);
test_kill_pid = 0; test_kill_pid = 0;
} }
@@ -314,7 +314,7 @@ fn sweepDeadlines() void {
if (!driver.used) continue; if (!driver.used) continue;
switch (driver.state) { switch (driver.state) {
.awaiting_hello => if (now >= driver.hello_deadline_ns) { .awaiting_hello => if (now >= driver.hello_deadline_ns) {
writeLine("device-manager: {s} missed its hello deadline\n", .{driver.name()}); writeLine("/system/services/device-manager: {s} missed its hello deadline\n", .{driver.name()});
_ = system.kill(driver.process_id); _ = system.kill(driver.process_id);
// The exit notification finishes the job via onDriverExit. // The exit notification finishes the job via onDriverExit.
}, },
@@ -331,7 +331,7 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
// Enumerate into a heap buffer (too big for the one-page user stack). // Enumerate into a heap buffer (too big for the one-page user stack).
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
_ = runtime.system.write("device-manager: out of memory\n"); _ = runtime.system.write("/system/services/device-manager: out of memory\n");
return false; return false;
}; };
const total = device.enumerate(buffer); const total = device.enumerate(buffer);
@@ -372,9 +372,9 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
} }
if (matched == 0) { if (matched == 0) {
_ = runtime.system.write("device-manager: no matchable devices\n"); _ = runtime.system.write("/system/services/device-manager: no matchable devices\n");
} else { } else {
_ = runtime.system.write("device-manager: ok\n"); _ = runtime.system.write("/system/services/device-manager: ok\n");
} }
return true; return true;
} }
@@ -395,13 +395,13 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
var status: i32 = 0; var status: i32 = 0;
if (hello.version != protocol.version) { if (hello.version != protocol.version) {
status = -1; status = -1;
writeLine("device-manager: refused hello (version {d}) from process {d}\n", .{ hello.version, sender }); writeLine("/system/services/device-manager: refused hello (version {d}) from process {d}\n", .{ hello.version, sender });
} else if (driverByProcess(sender)) |driver| { } else if (driverByProcess(sender)) |driver| {
driver.state = .running; driver.state = .running;
writeLine("device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id }); writeLine("/system/services/device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id });
} else { } else {
status = -1; status = -1;
writeLine("device-manager: hello from unknown process {d}\n", .{sender}); writeLine("/system/services/device-manager: hello from unknown process {d}\n", .{sender});
} }
const hello_reply = protocol.HelloReply{ .status = status }; const hello_reply = protocol.HelloReply{ .status = status };
@memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply)); @memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply));
@@ -417,7 +417,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
var status: i32 = 0; var status: i32 = 0;
if (driverByProcess(sender)) |driver| { if (driverByProcess(sender)) |driver| {
if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1; if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
writeLine("device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ report.parent, report.bus_address, report.identity, driver.name() }); writeLine("/system/services/device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ report.parent, report.bus_address, report.identity, driver.name() });
if (status == 0) publishEvent(message[0..protocol.child_added_size]); if (status == 0) publishEvent(message[0..protocol.child_added_size]);
// Matching from reports (M19.3): a registered child whose identity // Matching from reports (M19.3): a registered child whose identity
// names a driver gets one, once — re-reports after a bus restart // names a driver gets one, once — re-reports after a bus restart
@@ -478,7 +478,7 @@ fn onChildRemoved(message: []const u8, reply: []u8, sender: u32) usize {
var status: i32 = -1; var status: i32 = -1;
for (&children) |*child| { for (&children) |*child| {
if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == sender) { if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == sender) {
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address }); writeLine("/system/services/device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
child.used = false; child.used = false;
status = 0; status = 0;
} }
+16 -103
View File
@@ -1,139 +1,52 @@
//! /system/services/init — the first user-space program, PID 1. Built as its own //! /system/services/system/services/init: — the first user-space program, PID 1. Built as its own
//! freestanding binary (see build.zig), shipped on the boot volume at /system/services/init, //! freestanding binary (see build.zig), shipped on the boot volume at /system/services/system/services/init:,
//! loaded by the bootloader, and started in ring 3 as a scheduled process by the //! loaded by the bootloader, and started in ring 3 as a scheduled process by the
//! kernel (system/kernel/process.zig). It links against the shared user runtime //! kernel (system/kernel/process.zig). It links against the shared user runtime
//! library `runtime` and talks to the kernel only through `runtime`'s system_call wrappers. //! library `runtime` and talks to the kernel only through `runtime`'s system_call wrappers.
//! //!
//! It proves the C-convention heap works, then — as PID 1 — acts as the system's //! It proves the C-convention heap works, then — as PID 1 — acts as the system's
//! **service supervisor**: it spawns the user-space services danos brings up at boot //! **service supervisor**: it spawns the user-space services danos brings up at boot
//! (the VFS server, the device manager), and settles into an event loop as the root //! (the VFS server, the device manager), and settles into a heartbeat so it stays
//! of user space. Drivers are *not* its job: the device manager discovers the //! alive as the root of user space. Drivers are *not* its job: the device manager
//! hardware and spawns those. This is the service half of the service/driver spawn //! discovers the hardware and spawns those. This is the service half of the
//! split (docs/driver-model.md). //! service/driver spawn split (docs/driver-model.md).
//!
//! M21: init also owns **orderly shutdown**. It supervises its children (keeping
//! their ids and an exit endpoint), subscribes to the power service, and on a
//! power-button event runs the stop sequence over its children in reverse order
//! before asking the power service to enter S5 — lifecycle (M17) and events (M21)
//! composing into a clean poweroff.
const std = @import("std");
const runtime = @import("runtime"); const runtime = @import("runtime");
const power = runtime.power_protocol;
/// The system services init brings up at boot, in order. This is init's policy — the /// The system services system/services/init: brings up at boot, in order. This is system/services/init:'s policy — the
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent /// microkernel keeps such choices in user space, not the kernel. Drivers are absent
/// on purpose: the device manager owns those. (A future init reads this from a /// on purpose: the device manager owns those. (A future system/services/init: reads this from a
/// manifest under /system/services instead of a hardcoded list.) /// manifest under /system/services instead of a hardcoded list.)
const boot_services = [_][]const u8{ "vfs", "input", "device-manager" }; const boot_services = [_][]const u8{ "vfs", "input", "device-manager" };
var children: [boot_services.len]u32 = .{0} ** boot_services.len;
var child_count: usize = 0;
var supervision_endpoint: runtime.ipc.Handle = 0;
pub fn main() void { pub fn main() void {
// Prove the heap end to end: allocate through the runtime allocator (which // Prove the heap end to end: allocate through the runtime allocator (which
// mmaps pages from the kernel and carves them with the free list), write into // mmaps pages from the kernel and carves them with the free list), write into
// that heap buffer (exercising the widened debug_write bounds check), and // that heap buffer (exercising the widened debug_write bounds check), and
// free it. A fault here would kill init before it heartbeats — so the init // free it. A fault here would kill system/services/init: before it heartbeats — so the system/services/init:
// test doubles as the heap regression test. (C code links the same heap via // test doubles as the heap regression test. (C code links the same heap via
// the extern malloc/free symbols; Zig code uses this allocator.) // the extern malloc/free symbols; Zig code uses this allocator.)
const gpa = runtime.allocator(); const gpa = runtime.allocator();
if (gpa.alloc(u8, 64)) |buffer| { if (gpa.alloc(u8, 64)) |buffer| {
const message = "init: heap ok\n"; const message = "/system/services/init: heap ok\n";
@memcpy(buffer[0..message.len], message); @memcpy(buffer[0..message.len], message);
_ = runtime.system.write(buffer[0..message.len]); _ = runtime.system.write(buffer[0..message.len]);
gpa.free(buffer); gpa.free(buffer);
} else |_| {} } else |_| {}
// One endpoint carries everything init waits on: children's exit // Bring up the boot services. Best-effort and silent: each service announces its
// notifications (they are spawned supervised against it), init's own // own readiness (`vfs: ready`, ...), and in an isolation test that runs system/services/init: with
// signals, and power events it subscribes to. All arrive in the loop below. // no system/services/init:ial-ramdisk the spawns simply no-op rather than deranging the heartbeat.
supervision_endpoint = runtime.ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("init: no endpoint\n");
return;
};
_ = runtime.process.bindSignals(supervision_endpoint);
// Bring up the boot services, supervised so init can stop them cleanly.
// Best-effort and silent: each service announces its own readiness, and in
// an isolation test with no initial-ramdisk the spawns simply no-op.
for (boot_services) |service| { for (boot_services) |service| {
if (runtime.system.spawnSupervised(service, &.{}, supervision_endpoint)) |id| { _ = runtime.system.spawn(service);
children[child_count] = id;
child_count += 1;
}
} }
// Subscribe to power events (retry: the power service registers well after
// init starts). Best-effort — without it, a `terminate` signal still
// triggers the same shutdown path.
subscribePower();
// A re-arming timer drives the liveness heartbeat: proof PID 1 is alive
// (the init test's marker) while the loop stays free to receive signals,
// power events, and children's exit notifications.
_ = runtime.system.timerOnce(supervision_endpoint, 1000);
var receive: [power.message_maximum]u8 = undefined;
while (true) { while (true) {
const got = runtime.ipc.replyWait(supervision_endpoint, &.{}, &receive, null); _ = runtime.system.write("/system/services/init: heartbeat\n");
if (runtime.process.signalsFrom(got.badge)) |signals| { runtime.system.sleep(1000);
if (signals.has(.terminate)) shutDown();
continue;
}
if (got.isTimer()) {
_ = runtime.system.write("init: heartbeat\n");
_ = runtime.system.timerOnce(supervision_endpoint, 1000);
continue;
}
if (got.isMessage() and got.len >= 2 and receive[0] == @intFromEnum(power.Operation.event)) {
// A power event (the only buffered messages init receives).
if (receive[1] == @intFromEnum(power.Event.power_button)) shutDown();
continue;
}
// Child-exit notifications and anything else: keep waiting.
if (got.isNotification()) continue;
} }
} }
/// Look up the power service and subscribe our endpoint (handed over as the
/// call's capability) so events arrive as buffered messages here.
fn subscribePower() void {
var handle: ?runtime.ipc.Handle = null;
var tries: u32 = 0;
while (handle == null and tries < 200) : (tries += 1) {
handle = runtime.ipc.lookup(.power);
if (handle == null) runtime.system.sleep(20);
}
// A missing power service is not fatal — init proceeds to its heartbeat and
// a `terminate` signal still drives shutdown. Silent so the no-ramdisk init
// test's heartbeat marker is the next line written.
const h = handle orelse return;
const request = power.Subscribe{};
var reply: [power.message_maximum]u8 = undefined;
_ = runtime.ipc.callCap(h, std.mem.asBytes(&request), &reply, supervision_endpoint) catch {};
}
/// The stop sequence: terminate each child in reverse spawn order (vfs last —
/// other services may flush through it), waiting up to a deadline for each to
/// exit before killing it, then ask the power service to enter S5.
fn shutDown() void {
_ = runtime.system.write("init: shutting down\n");
var i = child_count;
while (i > 0) {
i -= 1;
if (children[i] != 0) runtime.process.stop(children[i], 2000, supervision_endpoint);
}
if (runtime.ipc.lookup(.power)) |h| {
const request = power.Shutdown{};
var reply: [power.message_maximum]u8 = undefined;
_ = runtime.ipc.call(h, std.mem.asBytes(&request), &reply) catch {};
}
// If S5 did not take, init has nothing left to do but idle.
while (true) runtime.system.sleep(1000);
}
pub const panic = runtime.panic; pub const panic = runtime.panic;
comptime { comptime {
_ = &runtime.start._start; // pull the runtime entry shim into the image _ = &runtime.start._start; // pull the runtime entry shim into the image
+3 -3
View File
@@ -115,14 +115,14 @@ fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
pub fn main() void { pub fn main() void {
const endpoint = ipc.createIpcEndpoint() orelse { const endpoint = ipc.createIpcEndpoint() orelse {
_ = system.write("input: no endpoint\n"); _ = system.write("/system/services/input: no endpoint\n");
return; return;
}; };
if (!ipc.register(.input, endpoint)) { if (!ipc.register(.input, endpoint)) {
_ = system.write("input: register failed\n"); _ = system.write("/system/services/input: register failed\n");
return; return;
} }
_ = system.write("input: ready\n"); _ = system.write("/system/services/input: ready\n");
var reply_buffer: [protocol.reply_size]u8 = undefined; var reply_buffer: [protocol.reply_size]u8 = undefined;
var reply_len: usize = 0; var reply_len: usize = 0;
-68
View File
@@ -1,68 +0,0 @@
//! The power protocol (docs/m21-plan.md): system power's domain-named surface,
//! registered under `ServiceId.power`. On x86 the acpi service serves it; on
//! ARM a PSCI/mailbox service will register the same id — subscribers never
//! learn which firmware they are on (m19-m20-plan.md decision 7). The
//! vfs-protocol pattern: extern-struct messages, a version, reserved fields.
/// The protocol version a client states nowhere yet — reserved for the day a
/// handshake needs it; requests carry it so a mismatch can be refused loudly.
pub const version: u16 = 1;
pub const Operation = enum(u8) {
/// Subscribe to power events: the subscriber's endpoint rides as the
/// call's capability (the input/device-manager pattern); events arrive on
/// it as buffered messages carrying an `EventMessage`.
subscribe = 1,
/// Orderly shutdown's last step: enter S5. Accepted only from PID 1
/// (init) — the process that has already run the stop sequence over
/// everything else.
shutdown = 2,
/// The published event payload (never sent *to* the service).
event = 3,
};
/// What happened. The vocabulary is hardware-neutral: a lid is a lid whether
/// ACPI or a PSCI mailbox reported it.
pub const Event = enum(u8) {
power_button = 1,
lid = 2,
ac = 3,
battery = 4,
/// A device notification that maps to none of the named events — the
/// `code` and `hid` fields say which device and what code.
notify = 5,
};
pub const Subscribe = extern struct {
operation: u8 = @intFromEnum(Operation.subscribe),
reserved0: u8 = 0,
version: u16 = version,
reserved1: u32 = 0,
};
pub const Shutdown = extern struct {
operation: u8 = @intFromEnum(Operation.shutdown),
reserved0: u8 = 0,
version: u16 = version,
reserved1: u32 = 0,
};
/// A published event, as the buffered-message payload subscribers receive.
pub const EventMessage = extern struct {
operation: u8 = @intFromEnum(Operation.event),
/// An Event value.
event: u8,
reserved0: u16 = 0,
/// The device notification code (Notify's second argument), or 0.
code: u32 = 0,
/// The notifying device's hardware id (EISA-decoded), or all zero.
hid: [8]u8 = .{0} ** 8,
};
pub const Reply = extern struct {
status: i32,
reserved: u32 = 0,
};
/// Upper bound on any message in this protocol — sizes endpoint buffers.
pub const message_maximum = 64;
+3 -3
View File
@@ -87,7 +87,7 @@ fn releaseClientHandles(client: u32) void {
released += 1; released += 1;
} }
} }
if (released != 0) writeLine("vfs: released {d} handle(s) for dead client {d}\n", .{ released, client }); if (released != 0) writeLine("/system/services/vfs: released {d} handle(s) for dead client {d}\n", .{ released, client });
} }
/// Handle one request from `sender`; write the reply into `out`, return its length. /// Handle one request from `sender`; write the reply into `out`, return its length.
@@ -144,9 +144,9 @@ fn handle(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.
/// to release them (docs/process-lifecycle.md). /// to release them (docs/process-lifecycle.md).
fn initialise(endpoint: runtime.ipc.Handle) bool { fn initialise(endpoint: runtime.ipc.Handle) bool {
if (!runtime.process.subscribeExits(endpoint)) { if (!runtime.process.subscribeExits(endpoint)) {
_ = runtime.system.write("vfs: exit subscription failed\n"); _ = runtime.system.write("/system/services/vfs: exit subscription failed\n");
} }
_ = runtime.system.write("vfs: ready\n"); _ = runtime.system.write("/system/services/vfs: ready\n");
return true; return true;
} }
-66
View File
@@ -18,11 +18,9 @@ Usage:
""" """
import argparse import argparse
import json
import os import os
import re import re
import shutil import shutil
import socket
import subprocess import subprocess
import sys import sys
import time import time
@@ -82,10 +80,7 @@ ARCHES = {
# `expect`: a regex that must appear in serial output => pass. # `expect`: a regex that must appear in serial output => pass.
# `fail`: optional regex whose appearance => immediate fail. # `fail`: optional regex whose appearance => immediate fail.
CASES = [ CASES = [
# smoke also proves the QMP channel: the harmless query must be delivered
# (handshake + command) before the case may pass — see run_case.
{"name": "smoke", {"name": "smoke",
"qmp_after": {"delay": 2, "command": "query-status"},
"expect": r"DANOS-TEST-RESULT: PASS", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
{"name": "discovery", {"name": "discovery",
@@ -292,28 +287,6 @@ CASES = [
r"device-manager: spawned ps2-bus[\s\S]*" r"device-manager: spawned ps2-bus[\s\S]*"
r"ps2-bus: keyboard driver attached", r"ps2-bus: keyboard driver attached",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# M21.1: the SCI + power button. Boot the manager (which spawns the acpi
# service); ~4s in, QMP system_powerdown raises the ACPI power-button fixed
# event; the service's SCI handler must log the press (docs/m21-plan.md).
{"name": "power-button",
"smp": 4,
"timeout": 60,
"qmp_after": {"delay": 4, "command": "system_powerdown"},
"expect": r"power: button pressed",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# M21.3 capstone: orderly shutdown. Boot init (the full tree comes up);
# ~5s in, QMP system_powerdown raises the power button; the acpi service
# publishes it, init stops its children then requests S5, and QEMU exits.
# The ordered regex proves button -> shutting-down -> entering-S5; the case
# passes on QEMU's self-exit through S5 (docs/m21-plan.md).
{"name": "orderly-shutdown",
"smp": 4,
"timeout": 90,
"qmp_after": {"delay": 5, "command": "system_powerdown"},
"expect": r"power: button pressed[\s\S]*"
r"init: shutting down[\s\S]*"
r"power: entering S5",
"fail": r"power: S5 write did not take|DANOS-TEST-RESULT: FAIL"},
# M20.2: the acpi service evaluates _CRS/_STA in ring 3 and registers + # M20.2: the acpi service evaluates _CRS/_STA in ring 3 and registers +
# reports its _HID devices — the two PS/2 nodes must appear with resources # reports its _HID devices — the two PS/2 nodes must appear with resources
# (keyboard: io 0x60/0x64 + IRQ = 3; mouse: IRQ = 1) (docs/m19-m20-plan.md). # (keyboard: io 0x60/0x64 + IRQ = 3; mouse: IRQ = 1) (docs/m19-m20-plan.md).
@@ -364,7 +337,6 @@ CASES = [
# The initial_ramdisk: the loader ferries a bundle of user binaries; the kernel parses # The initial_ramdisk: the loader ferries a bundle of user binaries; the kernel parses
# it and spawns each as a ring-3 process (here the VFS-server stub heartbeats). # it and spawns each as a ring-3 process (here the VFS-server stub heartbeats).
{"name": "initial-ramdisk", {"name": "initial-ramdisk",
"timeout": 60, # the acpi service's boot-time SCI setup can push the marker past 30s under load
"expect": r"DANOS-TEST-RESULT: PASS", "expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"}, "fail": r"DANOS-TEST-RESULT: FAIL"},
# The user-space VFS: a client opens/writes/reads a file through the rt file # The user-space VFS: a client opens/writes/reads a file through the rt file
@@ -446,27 +418,6 @@ def resolve_firmware(arch):
+ "\nInstall OVMF (edk2-ovmf / ovmf) or add its path above.") + "\nInstall OVMF (edk2-ovmf / ovmf) or add its path above.")
def qmp_send(path, command):
"""One QMP command: connect, capabilities handshake, execute. Raises on any
failure — the caller retries until the guest's socket is ready. This is how
a case injects a host-side event (system_powerdown = the ACPI power button)
into the running guest (docs/m21-plan.md)."""
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(5)
try:
sock.connect(path)
stream = sock.makefile("rw")
stream.readline() # the QMP greeting
stream.write(json.dumps({"execute": "qmp_capabilities"}) + "\n")
stream.flush()
stream.readline() # {"return": {}}
stream.write(json.dumps({"execute": command}) + "\n")
stream.flush()
stream.readline()
finally:
sock.close()
def run_case(arch, case): def run_case(arch, case):
err = build(arch, case["name"]) err = build(arch, case["name"])
if err: if err:
@@ -488,27 +439,12 @@ def run_case(arch, case):
cmd += ["-smp", str(case["smp"])] cmd += ["-smp", str(case["smp"])]
if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case
cmd += case["qemu_extra"] cmd += case["qemu_extra"]
# A QMP control socket, always present (additive): how a case's `qmp_after`
# hook injects host-side events into the guest mid-run.
qmp_path = os.path.join(WORK, "qmp.sock")
if os.path.exists(qmp_path):
os.remove(qmp_path)
cmd += ["-qmp", f"unix:{qmp_path},server,nowait"]
qmp_after = case.get("qmp_after") # {"delay": seconds, "command": "..."}
qmp_sent = False
started = time.monotonic()
qemu = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) qemu = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try: try:
timeout = case.get("timeout", TIMEOUT) timeout = case.get("timeout", TIMEOUT)
deadline = time.monotonic() + timeout deadline = time.monotonic() + timeout
while time.monotonic() < deadline: while time.monotonic() < deadline:
time.sleep(0.2) time.sleep(0.2)
if qmp_after and not qmp_sent and time.monotonic() - started >= qmp_after["delay"]:
try:
qmp_send(qmp_path, qmp_after["command"])
qmp_sent = True
except OSError:
pass # socket not up yet; retry next tick
text = "" text = ""
if os.path.exists(serial): if os.path.exists(serial):
with open(serial, "r", errors="replace") as f: with open(serial, "r", errors="replace") as f:
@@ -516,8 +452,6 @@ def run_case(arch, case):
if fail and fail.search(text): if fail and fail.search(text):
return False, "hit failure marker" return False, "hit failure marker"
if expect.search(text): if expect.search(text):
if qmp_after and not qmp_sent:
continue # the hook must deliver before the case may pass
return True, "matched " + repr(case["expect"]) return True, "matched " + repr(case["expect"])
if qemu.poll() is not None: # QEMU exited on its own if qemu.poll() is not None: # QEMU exited on its own
if expect.search(text): if expect.search(text):