Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e60daed6a | ||
|
|
dd044fb115 | ||
|
|
3ec14509a0 | ||
|
|
d71a5f25d3 | ||
|
|
2a0f17ae86 | ||
|
|
9ef61a0844 | ||
|
|
688b9101e8 | ||
|
|
1d7ba814dc | ||
|
|
8aba86b4ce | ||
|
|
a0c83f4b3f | ||
|
|
1ea48ed5d6 | ||
|
|
77a3ccd33d | ||
|
|
07da27dc39 | ||
|
|
d89657d0a4 | ||
|
|
849b4b62d4 | ||
|
|
8589bf713b |
+6
-6
@@ -29,7 +29,7 @@ pub fn main() uefi.Status {
|
||||
// report the reason (boot services are still up) and park the machine so the
|
||||
// message stays on screen.
|
||||
boot() catch |err| {
|
||||
log("\r\ndanos: boot failed: ");
|
||||
log("\r\nEFI: boot failed: ");
|
||||
logBytes(@errorName(err));
|
||||
log("\r\n");
|
||||
while (true) asm volatile ("hlt");
|
||||
@@ -65,14 +65,14 @@ fn boot() !noreturn {
|
||||
|
||||
// Best effort: a volume without /system/services/init still boots (kernel-only).
|
||||
loadInit(bs, &boot_information) catch |err| {
|
||||
log("danos: no /system/services/init (");
|
||||
log("EFI: no /system/services/init (");
|
||||
logBytes(@errorName(err));
|
||||
log(") - booting without user space\r\n");
|
||||
};
|
||||
|
||||
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
|
||||
loadInitialRamdisk(bs, &boot_information) catch |err| {
|
||||
log("danos: no initial_ramdisk (");
|
||||
log("EFI: no initial_ramdisk (");
|
||||
logBytes(@errorName(err));
|
||||
log(")\r\n");
|
||||
};
|
||||
@@ -84,7 +84,7 @@ fn boot() !noreturn {
|
||||
// the map and exiting would invalidate the map key.
|
||||
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);
|
||||
|
||||
// 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);
|
||||
boot_information.init_base = @intFromPtr(image.ptr);
|
||||
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.
|
||||
@@ -403,7 +403,7 @@ fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInfo
|
||||
const image = try loadFile(bs, initial_ramdisk_file_name);
|
||||
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
|
||||
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
|
||||
|
||||
@@ -226,12 +226,6 @@ pub fn build(b: *std.Build) void {
|
||||
});
|
||||
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
|
||||
// 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.
|
||||
@@ -583,7 +577,6 @@ pub fn build(b: *std.Build) void {
|
||||
"system/devices/device-abi.zig",
|
||||
"system/devices/pci-class.zig", // class/subclass/prog-IF 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-ids.zig", // class/subclass/protocol code assignments
|
||||
"library/mmio/mmio.zig", // barriers assemble + registers round-trip
|
||||
|
||||
+20
-4
@@ -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
|
||||
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
|
||||
dispatch, and orderly shutdown (init's stop cascade into a ring-3 S5 write).
|
||||
See that plan for the phase record.
|
||||
The acpi service grows the event side (settled direction 2026-07-13; detailed
|
||||
phases when M20 lands):
|
||||
|
||||
- **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
@@ -70,37 +70,33 @@ auto-merge to main when the branch is green; keep the branch; push everything.
|
||||
|
||||
## Status
|
||||
|
||||
- [x] **M21.0** — baseline (dead-code sweep confirmed landed on main — no
|
||||
acpi.zig conflict; `feat/power-events` cut; QMP channel in the harness:
|
||||
always-on unix socket, client with the capabilities handshake, per-case
|
||||
`qmp_after` hook, and a hook-must-deliver pass gate that the smoke case
|
||||
now proves with a harmless query-status; suite 58/58).
|
||||
- [x] **M21.1** — SCI + the power button (kernel appends the FADT as an
|
||||
acpi-tables memory resource, tagged by its "FACP" header; `power-protocol`
|
||||
module + `ServiceId.power = 5`; the acpi service converted to
|
||||
`runtime.service.run`, registers `.power`, reads PM1 event/control + GPE
|
||||
ports from its FADT copy, enables ACPI mode if SCI_EN is clear, binds the
|
||||
SCI (the len-1 irq), sets PWRBTN_EN; the SCI handler clears PM1_STS,
|
||||
logs `power: button pressed`, publishes `power_button`, acks. Scenario
|
||||
`power-button` injects a real `system_powerdown` via QMP; initial-ramdisk
|
||||
timeout 30→60s for the service's added boot work; suite 59/59).
|
||||
- [x] **M21.2** — Notify + GPE dispatch (interpreter handles `notify_opcode`
|
||||
into a bounded queue, cleared per-evaluate, drained via
|
||||
`takeNotifications`; the service walks GPE status/enable bytes, evaluates
|
||||
`\_GPE._Lxx`/`_Exx` per active bit, maps notified nodes to events
|
||||
(battery/ac/lid/generic), clears GPE_STS write-1, acks. EC `_Qxx` out.
|
||||
Host unit test with hand-encoded AML proves the queue; aml.zig joined the
|
||||
`zig build test` loop. QEMU raises no GPEs — suite is regression net,
|
||||
59/59).
|
||||
- [x] **M21.3** — orderly shutdown (init supervises its children on one
|
||||
endpoint that also carries signals, power events, and a re-arming
|
||||
heartbeat timer; on `power_button` or a `terminate` signal it logs
|
||||
`init: shutting down`, runs `stop(child, 2000, endpoint)` in reverse
|
||||
order, then requests `.power` shutdown; the acpi service honors shutdown
|
||||
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).
|
||||
- [ ] **M21.0** — baseline: rebase over anything newly merged (the dead-code
|
||||
sweep touches acpi.zig); cut `feat/power-events`; add the QMP channel to
|
||||
the harness (`-qmp unix:.../qmp.sock,server,nowait`, a small client with
|
||||
the `qmp_capabilities` handshake, a per-case `qmp_after` hook that sends
|
||||
a command N seconds after boot); existing suite stays green.
|
||||
- [ ] **M21.1** — SCI + the power button: kernel appends the FADT as an
|
||||
acpi-tables memory resource; new `power-protocol` module +
|
||||
`ServiceId.power`; the acpi service converts to the harness, registers
|
||||
`.power`, parses the event/GPE blocks from its FADT copy, enables ACPI
|
||||
mode if needed (SMI dance, spin on SCI_EN), binds the SCI, sets
|
||||
PWRBTN_EN; on SCI reads/clears PM1_STS and publishes `power_button`
|
||||
(log: `power: button pressed`), always irqAck. Scenario `power-button`:
|
||||
`qmp_after system_powerdown` → expect the log line.
|
||||
- [ ] **M21.2** — Notify + GPE dispatch: interpreter handles `notify_opcode`
|
||||
into a bounded queue drained after evaluate(); on GPE status bits the
|
||||
service evaluates `\_GPE._Lxx`/`_Exx`, maps notified nodes to events
|
||||
(PNP0C0A→battery, ACPI0003→ac, PNP0C0D→lid, else generic), clears
|
||||
GPE_STS, acks. EC `_Qxx` explicitly out (hardware track). Host unit
|
||||
tests for Notify in aml.zig; aml.zig joins the `zig build test` loop.
|
||||
- [ ] **M21.3** — orderly shutdown: init keeps child ids (spawnSupervised +
|
||||
exit endpoint), binds signals, subscribes to `.power`; on `power_button`
|
||||
logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in
|
||||
reverse spawn order, then sends `shutdown` to `.power`; the acpi service
|
||||
(sender PID 1 only) logs `power: entering S5` and writes SLP_TYP|SLP_EN
|
||||
from ring 3. Scenario `orderly-shutdown`: boot via init, `qmp_after
|
||||
system_powerdown`, ordered regex button→shutting-down→entering-S5, pass
|
||||
on QEMU exit. Docs + memory updated.
|
||||
- [ ] **merge** `feat/power-events` → main, push, keep the branch — **loop
|
||||
ends here**.
|
||||
|
||||
|
||||
@@ -20,9 +20,6 @@ pub const vfs_protocol = @import("vfs-protocol");
|
||||
|
||||
/// The device-manager protocol: hello + tree reports (docs/device-manager.md).
|
||||
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
|
||||
/// service. See library/runtime/input.zig and system/services/input/.
|
||||
pub const input = @import("input.zig");
|
||||
|
||||
@@ -177,7 +177,6 @@ pub const ServiceId = enum(u32) {
|
||||
input = 2,
|
||||
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)
|
||||
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)
|
||||
_,
|
||||
};
|
||||
|
||||
|
||||
@@ -157,13 +157,6 @@ pub var namespace: ?aml.Namespace = null;
|
||||
/// Physical address of the DSDT the FADT points at, or 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
|
||||
// address + length of each table's post-header bytecode. Scanned after the walk
|
||||
// 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.
|
||||
power_information = .{};
|
||||
fadt_physical = 0;
|
||||
fadt_length = 0;
|
||||
platform_information = .{};
|
||||
aml_stats = .{};
|
||||
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.
|
||||
if (power_information.sci_interrupt != 0) _ = node.addResource(.irq, power_information.sci_interrupt, 1);
|
||||
_ = 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.
|
||||
@@ -494,8 +482,6 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
|
||||
} else if (std.mem.eql(u8, &sig, &HPET)) {
|
||||
try parseHpet(device_tree, hal, header);
|
||||
} else if (std.mem.eql(u8, &sig, &FACP)) {
|
||||
fadt_physical = sdt_physical;
|
||||
fadt_length = header.length;
|
||||
parseFadt(header);
|
||||
} else if (std.mem.eql(u8, &sig, &SPCR)) {
|
||||
parseSpcr(header);
|
||||
|
||||
@@ -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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -141,9 +141,6 @@ const Frame = struct {
|
||||
/// A CreateField binding: a name that indexes into a buffer object.
|
||||
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 {
|
||||
namespace: *Namespace,
|
||||
hal: Hal,
|
||||
@@ -152,11 +149,6 @@ pub const Interpreter = struct {
|
||||
dynamic_overrides: std.AutoHashMapUnmanaged(*Node, Object) = .{},
|
||||
/// CreateField bindings active for the current evaluation.
|
||||
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 {
|
||||
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
|
||||
/// Field. Resets per-evaluation runtime state first.
|
||||
pub fn evaluate(self: *Interpreter, node: *Node, args: []const Object) Error!Object {
|
||||
self.notify_count = 0;
|
||||
self.dynamic_overrides.clearRetainingCapacity();
|
||||
self.fields.clearRetainingCapacity();
|
||||
return self.invoke(node, args);
|
||||
@@ -276,8 +267,6 @@ pub const Interpreter = struct {
|
||||
},
|
||||
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),
|
||||
|
||||
// CreateXField: source, index, name (bit widths differ by op)
|
||||
@@ -553,36 +542,6 @@ pub const Interpreter = struct {
|
||||
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 {
|
||||
const lead = current.peek() orelse return error.Truncated;
|
||||
if (isNameStart(lead)) {
|
||||
|
||||
@@ -93,21 +93,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
|
||||
pub fn main() void {
|
||||
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
if (!device.claim(hpet.device_id)) {
|
||||
_ = runtime.system.write("hpet: claim failed\n");
|
||||
_ = runtime.system.write("system/drivers/hpet: claim failed\n");
|
||||
return;
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -116,7 +116,7 @@ pub fn main() void {
|
||||
const gsi = hpet.gsi;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ pub fn main() void {
|
||||
// Counter period, so we can arm the comparator a fixed wall-clock distance out.
|
||||
const femtos_per_tick = rd(base, register_general_cap) >> 32;
|
||||
if (femtos_per_tick == 0) {
|
||||
_ = runtime.system.write("hpet: bad HPET period\n");
|
||||
_ = runtime.system.write("system/drivers/hpet: bad HPET period\n");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
_ = 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 -----------------------------------------------------
|
||||
// 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);
|
||||
}
|
||||
|
||||
_ = runtime.system.write("hpet: irq\n");
|
||||
_ = runtime.system.write("system/drivers/hpet: irq\n");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
_ = runtime.system.write("hpet: ok\n");
|
||||
_ = runtime.system.write("system/drivers/hpet: ok\n");
|
||||
while (true) runtime.system.sleep(1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0;
|
||||
//! the bus range and the MMIO apertures follow it), walk every
|
||||
//! 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
|
||||
//! 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);
|
||||
var line: [200]u8 = undefined;
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -74,37 +74,37 @@ fn configWrite16(bus: u64, dev: u64, function: u64, offset: u64, value: u16) voi
|
||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
_ = endpoint;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
};
|
||||
const total = device.enumerate(buffer);
|
||||
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||
if (d.id == bridge_id) break d;
|
||||
} 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;
|
||||
};
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| {
|
||||
if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource;
|
||||
} 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;
|
||||
};
|
||||
start_bus = bus_range.start;
|
||||
bus_count = bus_range.len;
|
||||
ecam_physical = descriptor.resources[0].start;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -116,17 +116,17 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
if (manager == null) runtime.system.sleep(20);
|
||||
}
|
||||
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;
|
||||
};
|
||||
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = bridge_id };
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
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;
|
||||
};
|
||||
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;
|
||||
}
|
||||
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
|
||||
@@ -229,7 +229,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
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;
|
||||
_ = 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 {
|
||||
const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent
|
||||
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;
|
||||
};
|
||||
runtime.service.run(protocol.message_maximum, .{
|
||||
|
||||
@@ -33,20 +33,20 @@ var controller_id: u64 = protocol.no_device;
|
||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
_ = endpoint;
|
||||
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;
|
||||
}
|
||||
|
||||
// Fetch our own descriptor back for the controller's resources.
|
||||
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;
|
||||
};
|
||||
const total = device.enumerate(buffer);
|
||||
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||
if (d.id == controller_id) break d;
|
||||
} 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;
|
||||
};
|
||||
|
||||
@@ -59,16 +59,16 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
break resource;
|
||||
}
|
||||
} 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;
|
||||
};
|
||||
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,
|
||||
register_window.start,
|
||||
register_window.len,
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -81,20 +81,20 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
if (manager == null) runtime.system.sleep(20);
|
||||
}
|
||||
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;
|
||||
};
|
||||
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id };
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
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;
|
||||
};
|
||||
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;
|
||||
}
|
||||
_ = runtime.system.write("usb-xhci-bus: hello acknowledged\n");
|
||||
_ = runtime.system.write("/system/drivers/usb-xhci-bus: hello acknowledged\n");
|
||||
|
||||
scanPorts(h);
|
||||
return true;
|
||||
@@ -135,7 +135,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
const capability_length = readRegister(0) & 0xFF;
|
||||
const structural = readRegister(0x04);
|
||||
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).
|
||||
var port: u32 = 1;
|
||||
@@ -145,7 +145,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
||||
connected += 1;
|
||||
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{
|
||||
.parent = controller_id,
|
||||
@@ -154,11 +154,11 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
||||
};
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
_ = 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;
|
||||
};
|
||||
}
|
||||
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.
|
||||
@@ -172,11 +172,11 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
|
||||
|
||||
pub fn main(init: runtime.process.Init) void {
|
||||
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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
runtime.service.run(protocol.message_maximum, .{
|
||||
|
||||
+26
-26
@@ -77,12 +77,12 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
architecture.setFaultHandler(onException);
|
||||
architecture.init();
|
||||
|
||||
status("danos: initialising kernel...\n");
|
||||
status("/system/kernel: initialising kernel...\n");
|
||||
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
|
||||
"danos: no framebuffer (headless) -> logging to serial/debugcon only\n");
|
||||
log.write("danos: cpu tables online (GDT, IDT, TSS)\n");
|
||||
"/system/kernel: no framebuffer (headless) -> logging to serial/debugcon only\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(" pitch : {d} bytes\n", .{fb.pitch});
|
||||
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 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(" 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)});
|
||||
@@ -119,7 +119,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
// until SMP bring-up; 0 means none was available (we stay uniprocessor).
|
||||
ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0;
|
||||
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) });
|
||||
const f0 = 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).
|
||||
architecture.enablePaging(pmm.alloc, pmm.free, boot_information);
|
||||
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(" 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.
|
||||
heap.init();
|
||||
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
|
||||
const s2 = pmm.stats();
|
||||
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| {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
if (devices_broker.dropped > 0) {
|
||||
// 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
|
||||
@@ -173,7 +173,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
|
||||
// Power register map extracted from the FADT + AML, for confidence it parsed.
|
||||
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 });
|
||||
if (pw.s5) |s| {
|
||||
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);
|
||||
|
||||
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(" lapic base : 0x{x}\n", .{pinfo.lapic_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)
|
||||
log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()});
|
||||
} 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);
|
||||
|
||||
@@ -248,14 +248,14 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
// Register the current context as the first task before enabling preemption.
|
||||
scheduler.init(4);
|
||||
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
|
||||
// the timer preempts among tasks.
|
||||
architecture.startTimer();
|
||||
architecture.enableInterrupts();
|
||||
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
|
||||
// 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);
|
||||
status("kernel initialised.\n");
|
||||
status("/system/kernel: initialised.\n");
|
||||
|
||||
// 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
|
||||
@@ -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
|
||||
// address space, preemptively — this boot context becomes the BSP's idle loop.
|
||||
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];
|
||||
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 {
|
||||
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
|
||||
// interrupt. The timer keeps preempting into init and any other work.
|
||||
scheduler.setPriority(0);
|
||||
status("\nkernel idle; user space is running.\n");
|
||||
status("\n/system/kernel: kernel idle; user space is running.\n");
|
||||
architecture.halt();
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ fn bringUpSecondaries() void {
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
architecture.setTrampolinePage(ap_trampoline_page);
|
||||
@@ -333,7 +333,7 @@ fn bringUpSecondaries() void {
|
||||
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
|
||||
for (cores[1..], 1..) |core, index| {
|
||||
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
|
||||
// real, rather than reserved statically for every possible core.
|
||||
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;
|
||||
};
|
||||
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) {
|
||||
if (architecture.startSecondary(core.apic_id, stack_top, @intFromPtr(pc), index)) {
|
||||
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;
|
||||
}
|
||||
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
|
||||
@@ -427,7 +427,7 @@ fn exitReasonForVector(vector: u64) abi.ExitReason {
|
||||
|
||||
fn onException(state: *const architecture.CpuState) noreturn {
|
||||
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(" IP : 0x{x:0>16}\n", .{architecture.instructionPointer(state)});
|
||||
if (architecture.faultAddress(state)) |address| statusPrint(" fault addr : 0x{x:0>16}\n", .{address});
|
||||
|
||||
+33
-53
@@ -152,10 +152,6 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
acpiReportTest(boot_information);
|
||||
} else if (eql(case, "acpi-ps2")) {
|
||||
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")) {
|
||||
initialRamdiskTest(boot_information);
|
||||
} 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);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn smoke(boot_information: *const BootInformation) void {
|
||||
log("DANOS-TEST-BEGIN: smoke\n", .{});
|
||||
@@ -1380,7 +1383,7 @@ fn initTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(4);
|
||||
|
||||
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("heartbeat text arrived intact", beat_ok);
|
||||
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);
|
||||
var deadline = architecture.millis() + 10000;
|
||||
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.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);
|
||||
var badge: u64 = 0;
|
||||
@@ -1659,11 +1662,11 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
deadline = architecture.millis() + 10000;
|
||||
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.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();
|
||||
}
|
||||
|
||||
@@ -1705,8 +1708,8 @@ fn signalsTest(boot_information: *const BootInformation) void {
|
||||
var saw_pass = false;
|
||||
var saw_fail = false;
|
||||
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 (process.write_len >= fail_marker.len and eql(process.write_buffer[0..fail_marker.len], fail_marker)) saw_fail = true;
|
||||
if (bufferHas(pass_marker)) saw_pass = true;
|
||||
if (bufferHas(fail_marker)) saw_fail = true;
|
||||
scheduler.yield();
|
||||
}
|
||||
scheduler.setPriority(4);
|
||||
@@ -1868,13 +1871,11 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
var deadline = architecture.millis() + 15000;
|
||||
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 digits_end = std.mem.indexOf(u8, line, count_suffix) orelse {
|
||||
scheduler.yield();
|
||||
continue;
|
||||
};
|
||||
reported = std.fmt.parseInt(u32, line[count_prefix.len..digits_end], 10) catch 0;
|
||||
const line = process.write_buffer[0..process.write_len];
|
||||
if (std.mem.indexOf(u8, line, count_prefix)) |start| {
|
||||
if (std.mem.indexOf(u8, line, count_suffix)) |digits_end| {
|
||||
reported = std.fmt.parseInt(u32, line[start + count_prefix.len .. digits_end], 10) catch 0;
|
||||
}
|
||||
}
|
||||
scheduler.yield();
|
||||
}
|
||||
@@ -1898,7 +1899,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
||||
deadline = architecture.millis() + 15000;
|
||||
var restarted = false;
|
||||
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.setPriority(4);
|
||||
@@ -1910,7 +1911,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
||||
deadline = architecture.millis() + 15000;
|
||||
var seen = false;
|
||||
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.setPriority(4);
|
||||
@@ -1928,27 +1929,6 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
||||
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
|
||||
/// (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 —
|
||||
@@ -2060,12 +2040,12 @@ fn supervisionTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
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.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]});
|
||||
check("the supervisor completed every step (spawn/list/kill/notify)", ok);
|
||||
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);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
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.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("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);
|
||||
@@ -2190,12 +2170,12 @@ fn inputTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 12000;
|
||||
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.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("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);
|
||||
@@ -2287,12 +2267,12 @@ fn hpetTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
|
||||
if (bufferHas(prefix) and process.write_count >= 2) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
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("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());
|
||||
@@ -2393,12 +2373,12 @@ fn busTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break;
|
||||
if (bufferHas(prefix)) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
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("driver syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||
check("every registered child is contained in its parent", childrenContained());
|
||||
@@ -2442,12 +2422,12 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
|
||||
scheduler.setPriority(1);
|
||||
const deadline = architecture.millis() + 10000;
|
||||
while (architecture.millis() < deadline) {
|
||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break;
|
||||
if (bufferHas(prefix)) break;
|
||||
scheduler.yield();
|
||||
}
|
||||
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("its syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||
result();
|
||||
|
||||
+42
-333
@@ -4,11 +4,13 @@
|
||||
//! grant, a broad irq window, the SCI), and runs the **shared AML module** in
|
||||
//! ring 3 — the same parser and interpreter the kernel uses.
|
||||
//!
|
||||
//! It also owns the **event side** (M21): it registers the domain-named `.power`
|
||||
//! service, binds the SCI (System Control Interrupt), and on a power-button
|
||||
//! fixed event publishes `power_button` to subscribers — and on init's request
|
||||
//! writes S5 to power the machine off. The device discovery (M20) and the event
|
||||
//! handling both run in one `runtime.service.run` loop.
|
||||
//! M20.2 (this increment): after parsing, walk the namespace and, for each
|
||||
//! present Device with a hardware id (`_HID`), evaluate its current resource
|
||||
//! settings (`_CRS`) through a ring-3 `Hal` (port I/O over the claimed node),
|
||||
//! register it under the acpi-tables node (its I/O ports and IRQs contained by
|
||||
//! 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 runtime = @import("runtime");
|
||||
@@ -16,7 +18,6 @@ const aml = @import("aml");
|
||||
const acpi_ids = @import("acpi-ids");
|
||||
const device = runtime.device;
|
||||
const protocol = runtime.device_manager_protocol;
|
||||
const power = runtime.power_protocol;
|
||||
/// 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).
|
||||
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.
|
||||
var node_id: 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.
|
||||
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 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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
node_id = 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;
|
||||
}
|
||||
|
||||
// Map the node's resources: the AML blobs (bytecode), the FADT (intact
|
||||
// "FACP" header — decision 3), the io_port grant, and the SCI irq.
|
||||
// Map each memory resource (an AML blob) and note the io_port resource.
|
||||
var blocks: [8][]const u8 = undefined;
|
||||
var block_count: usize = 0;
|
||||
var found_io = false;
|
||||
var fadt: ?[]const u8 = null;
|
||||
for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| {
|
||||
if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and !found_io) {
|
||||
io_resource_index = index;
|
||||
found_io = true;
|
||||
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;
|
||||
const base = device.mmioMap(node_id, index) orelse continue;
|
||||
const pointer: [*]const u8 = @ptrFromInt(base);
|
||||
const bytes = 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;
|
||||
blocks[block_count] = pointer[0..@intCast(resource.len)];
|
||||
block_count += 1;
|
||||
if (block_count == blocks.len) break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
var namespace = result.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 (devices == want) {
|
||||
_ = 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);
|
||||
}
|
||||
|
||||
// Register + report the present _HID devices (M20), then set up the power
|
||||
// event side (M21), then serve — all in one harness loop. The interpreter
|
||||
// and namespace outlive this frame (static), so the harness callbacks can
|
||||
// reach them.
|
||||
interpreter_arena = std.heap.ArenaAllocator.init(runtime.allocator());
|
||||
persistent_namespace = namespace;
|
||||
global_interpreter = aml.Interpreter.init(&persistent_namespace, .{
|
||||
// Register + report the present _HID devices (M20.2).
|
||||
var arena = std.heap.ArenaAllocator.init(runtime.allocator());
|
||||
var interpreter = aml.Interpreter.init(&namespace, .{
|
||||
.mapMmio = halMapMmio,
|
||||
.pioRead = halPioRead,
|
||||
.pioWrite = halPioWrite,
|
||||
}, interpreter_arena.allocator());
|
||||
}, arena.allocator());
|
||||
|
||||
readFadt(fadt);
|
||||
s5_valid = readSleepS5(&persistent_namespace);
|
||||
|
||||
runtime.service.run(power.message_maximum, .{
|
||||
.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 {
|
||||
// Pass 1: register every present _HID device under acpi-tables, remembering
|
||||
// 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
|
||||
// already sees the whole set (no keyboard-before-mouse race for ps2-bus).
|
||||
registered_count = 0;
|
||||
walkDevices(persistent_namespace.root, &global_interpreter);
|
||||
walkDevices(namespace.root, &interpreter);
|
||||
|
||||
const manager = runtime.ipc.lookup(.device_manager);
|
||||
var i: usize = 0;
|
||||
while (i < registered_count) : (i += 1) {
|
||||
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 desc = acpi_ids.description(hid);
|
||||
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
|
||||
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| {
|
||||
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]);
|
||||
var reply: [protocol.message_maximum]u8 = undefined;
|
||||
_ = 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);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 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,
|
||||
}
|
||||
// Stay resident: the claim holds, and the service is here to grow into the
|
||||
// supervised discoverer (M20.3, then the M21 event side on the SCI).
|
||||
while (true) runtime.system.sleep(1000);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
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;
|
||||
};
|
||||
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 {
|
||||
for (&children) |*child| {
|
||||
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;
|
||||
const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
|
||||
publishEvent(std.mem.asBytes(&event));
|
||||
@@ -238,7 +238,7 @@ fn addDriver(name: []const u8, device_id: u64, speaks_protocol: bool) void {
|
||||
spawnDriver(driver);
|
||||
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
|
||||
@@ -253,7 +253,7 @@ fn spawnDriver(driver: *Driver) void {
|
||||
argument_count = 1;
|
||||
}
|
||||
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;
|
||||
return;
|
||||
};
|
||||
@@ -267,9 +267,9 @@ fn spawnDriver(driver: *Driver) void {
|
||||
driver.state = .running;
|
||||
}
|
||||
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 {
|
||||
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;
|
||||
if (reason == .exited) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
if (driver.restarts >= crash_loop_cap) {
|
||||
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;
|
||||
}
|
||||
const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1);
|
||||
driver.state = .restarting;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ fn onDriverExit(driver: *Driver) void {
|
||||
fn sweepDeadlines() void {
|
||||
const now = system.clock();
|
||||
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);
|
||||
test_kill_pid = 0;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ fn sweepDeadlines() void {
|
||||
if (!driver.used) continue;
|
||||
switch (driver.state) {
|
||||
.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);
|
||||
// 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).
|
||||
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;
|
||||
};
|
||||
const total = device.enumerate(buffer);
|
||||
@@ -372,9 +372,9 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
}
|
||||
|
||||
if (matched == 0) {
|
||||
_ = runtime.system.write("device-manager: no matchable devices\n");
|
||||
_ = runtime.system.write("/system/services/device-manager: no matchable devices\n");
|
||||
} else {
|
||||
_ = runtime.system.write("device-manager: ok\n");
|
||||
_ = runtime.system.write("/system/services/device-manager: ok\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -395,13 +395,13 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
|
||||
var status: i32 = 0;
|
||||
if (hello.version != protocol.version) {
|
||||
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| {
|
||||
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 {
|
||||
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 };
|
||||
@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;
|
||||
if (driverByProcess(sender)) |driver| {
|
||||
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]);
|
||||
// Matching from reports (M19.3): a registered child whose identity
|
||||
// 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;
|
||||
for (&children) |*child| {
|
||||
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;
|
||||
status = 0;
|
||||
}
|
||||
|
||||
+16
-103
@@ -1,139 +1,52 @@
|
||||
//! /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,
|
||||
//! /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/system/services/init:,
|
||||
//! 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
|
||||
//! 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
|
||||
//! **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
|
||||
//! of user space. Drivers are *not* its job: the device manager discovers the
|
||||
//! hardware and spawns those. This is the service half of the 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.
|
||||
//! (the VFS server, the device manager), and settles into a heartbeat so it stays
|
||||
//! alive as the root of user space. Drivers are *not* its job: the device manager
|
||||
//! discovers the hardware and spawns those. This is the service half of the
|
||||
//! service/driver spawn split (docs/driver-model.md).
|
||||
|
||||
const std = @import("std");
|
||||
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
|
||||
/// 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.)
|
||||
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 {
|
||||
// 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
|
||||
// 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
|
||||
// the extern malloc/free symbols; Zig code uses this allocator.)
|
||||
const gpa = runtime.allocator();
|
||||
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);
|
||||
_ = runtime.system.write(buffer[0..message.len]);
|
||||
gpa.free(buffer);
|
||||
} else |_| {}
|
||||
|
||||
// One endpoint carries everything init waits on: children's exit
|
||||
// notifications (they are spawned supervised against it), init's own
|
||||
// signals, and power events it subscribes to. All arrive in the loop below.
|
||||
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.
|
||||
// Bring up the boot services. Best-effort and silent: each service announces its
|
||||
// own readiness (`vfs: ready`, ...), and in an isolation test that runs system/services/init: with
|
||||
// no system/services/init:ial-ramdisk the spawns simply no-op rather than deranging the heartbeat.
|
||||
for (boot_services) |service| {
|
||||
if (runtime.system.spawnSupervised(service, &.{}, supervision_endpoint)) |id| {
|
||||
children[child_count] = id;
|
||||
child_count += 1;
|
||||
}
|
||||
_ = runtime.system.spawn(service);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const got = runtime.ipc.replyWait(supervision_endpoint, &.{}, &receive, null);
|
||||
if (runtime.process.signalsFrom(got.badge)) |signals| {
|
||||
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;
|
||||
_ = runtime.system.write("/system/services/init: heartbeat\n");
|
||||
runtime.system.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
comptime {
|
||||
_ = &runtime.start._start; // pull the runtime entry shim into the image
|
||||
|
||||
@@ -115,14 +115,14 @@ fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
|
||||
|
||||
pub fn main() void {
|
||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||
_ = system.write("input: no endpoint\n");
|
||||
_ = system.write("/system/services/input: no endpoint\n");
|
||||
return;
|
||||
};
|
||||
if (!ipc.register(.input, endpoint)) {
|
||||
_ = system.write("input: register failed\n");
|
||||
_ = system.write("/system/services/input: register failed\n");
|
||||
return;
|
||||
}
|
||||
_ = system.write("input: ready\n");
|
||||
_ = system.write("/system/services/input: ready\n");
|
||||
|
||||
var reply_buffer: [protocol.reply_size]u8 = undefined;
|
||||
var reply_len: usize = 0;
|
||||
|
||||
@@ -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;
|
||||
@@ -87,7 +87,7 @@ fn releaseClientHandles(client: u32) void {
|
||||
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.
|
||||
@@ -144,9 +144,9 @@ fn handle(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.
|
||||
/// to release them (docs/process-lifecycle.md).
|
||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,9 @@ Usage:
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -82,10 +80,7 @@ ARCHES = {
|
||||
# `expect`: a regex that must appear in serial output => pass.
|
||||
# `fail`: optional regex whose appearance => immediate fail.
|
||||
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",
|
||||
"qmp_after": {"delay": 2, "command": "query-status"},
|
||||
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
{"name": "discovery",
|
||||
@@ -292,28 +287,6 @@ CASES = [
|
||||
r"device-manager: spawned ps2-bus[\s\S]*"
|
||||
r"ps2-bus: keyboard driver attached",
|
||||
"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 +
|
||||
# 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).
|
||||
@@ -364,7 +337,6 @@ CASES = [
|
||||
# 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).
|
||||
{"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",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# 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.")
|
||||
|
||||
|
||||
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):
|
||||
err = build(arch, case["name"])
|
||||
if err:
|
||||
@@ -488,27 +439,12 @@ def run_case(arch, case):
|
||||
cmd += ["-smp", str(case["smp"])]
|
||||
if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case
|
||||
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)
|
||||
try:
|
||||
timeout = case.get("timeout", TIMEOUT)
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
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 = ""
|
||||
if os.path.exists(serial):
|
||||
with open(serial, "r", errors="replace") as f:
|
||||
@@ -516,8 +452,6 @@ def run_case(arch, case):
|
||||
if fail and fail.search(text):
|
||||
return False, "hit failure marker"
|
||||
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"])
|
||||
if qemu.poll() is not None: # QEMU exited on its own
|
||||
if expect.search(text):
|
||||
|
||||
Reference in New Issue
Block a user