Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e60daed6a | ||
|
|
dd044fb115 | ||
|
|
3ec14509a0 | ||
|
|
d71a5f25d3 | ||
|
|
2a0f17ae86 | ||
|
|
9ef61a0844 | ||
|
|
688b9101e8 | ||
|
|
1d7ba814dc | ||
|
|
8aba86b4ce | ||
|
|
a0c83f4b3f | ||
|
|
1ea48ed5d6 | ||
|
|
77a3ccd33d | ||
|
|
07da27dc39 | ||
|
|
d89657d0a4 | ||
|
|
849b4b62d4 | ||
|
|
8589bf713b | ||
|
|
738f6aa697 | ||
|
|
01e56e3f36 | ||
|
|
d5d15cefcb | ||
|
|
fd96a35eb9 | ||
|
|
e3fe3f3f45 | ||
|
|
60da667b42 | ||
|
|
36145e623b | ||
|
|
565415327d | ||
|
|
bf6bdb389d |
+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
|
// report the reason (boot services are still up) and park the machine so the
|
||||||
// message stays on screen.
|
// message stays on screen.
|
||||||
boot() catch |err| {
|
boot() catch |err| {
|
||||||
log("\r\ndanos: boot failed: ");
|
log("\r\nEFI: boot failed: ");
|
||||||
logBytes(@errorName(err));
|
logBytes(@errorName(err));
|
||||||
log("\r\n");
|
log("\r\n");
|
||||||
while (true) asm volatile ("hlt");
|
while (true) asm volatile ("hlt");
|
||||||
@@ -65,14 +65,14 @@ fn boot() !noreturn {
|
|||||||
|
|
||||||
// Best effort: a volume without /system/services/init still boots (kernel-only).
|
// Best effort: a volume without /system/services/init still boots (kernel-only).
|
||||||
loadInit(bs, &boot_information) catch |err| {
|
loadInit(bs, &boot_information) catch |err| {
|
||||||
log("danos: no /system/services/init (");
|
log("EFI: no /system/services/init (");
|
||||||
logBytes(@errorName(err));
|
logBytes(@errorName(err));
|
||||||
log(") - booting without user space\r\n");
|
log(") - booting without user space\r\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
|
// Best effort: the initial_ramdisk (VFS server + drivers) is optional too.
|
||||||
loadInitialRamdisk(bs, &boot_information) catch |err| {
|
loadInitialRamdisk(bs, &boot_information) catch |err| {
|
||||||
log("danos: no initial_ramdisk (");
|
log("EFI: no initial_ramdisk (");
|
||||||
logBytes(@errorName(err));
|
logBytes(@errorName(err));
|
||||||
log(")\r\n");
|
log(")\r\n");
|
||||||
};
|
};
|
||||||
@@ -84,7 +84,7 @@ fn boot() !noreturn {
|
|||||||
// the map and exiting would invalidate the map key.
|
// the map and exiting would invalidate the map key.
|
||||||
const cr3 = try buildBootstrapTables(bs, &boot_information);
|
const cr3 = try buildBootstrapTables(bs, &boot_information);
|
||||||
|
|
||||||
log("danos: kernel loaded, exiting boot services\r\n");
|
log("EFI: kernel loaded, exiting boot services\r\n");
|
||||||
boot_information.memory_map = try exitBootServices(bs);
|
boot_information.memory_map = try exitBootServices(bs);
|
||||||
|
|
||||||
// Switch onto our tables and jump to the kernel in one uninterruptible step.
|
// Switch onto our tables and jump to the kernel in one uninterruptible step.
|
||||||
@@ -395,7 +395,7 @@ fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !
|
|||||||
const image = try loadFile(bs, init_file_name);
|
const image = try loadFile(bs, init_file_name);
|
||||||
boot_information.init_base = @intFromPtr(image.ptr);
|
boot_information.init_base = @intFromPtr(image.ptr);
|
||||||
boot_information.init_len = image.len;
|
boot_information.init_len = image.len;
|
||||||
log("danos: /system/services/init loaded\r\n");
|
log("EFI: /system/services/init loaded\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
|
/// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init.
|
||||||
@@ -403,7 +403,7 @@ fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInfo
|
|||||||
const image = try loadFile(bs, initial_ramdisk_file_name);
|
const image = try loadFile(bs, initial_ramdisk_file_name);
|
||||||
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
|
boot_information.initial_ramdisk_base = @intFromPtr(image.ptr);
|
||||||
boot_information.initial_ramdisk_len = image.len;
|
boot_information.initial_ramdisk_len = image.len;
|
||||||
log("danos: initial_ramdisk loaded\r\n");
|
log("EFI: initial_ramdisk loaded\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and
|
/// Validate the ELF, copy every PT_LOAD segment to its physical address, and
|
||||||
|
|||||||
@@ -347,6 +347,9 @@ pub fn build(b: *std.Build) void {
|
|||||||
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig");
|
||||||
const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig");
|
const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig");
|
||||||
const pci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "pci-bus", "system/drivers/pci-bus/pci-bus.zig");
|
const pci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "pci-bus", "system/drivers/pci-bus/pci-bus.zig");
|
||||||
|
// The PCI bus driver decodes each function's class triple to human names in its
|
||||||
|
// boot log (class/subclass/prog-IF), so pull in the shared pci-class reference.
|
||||||
|
pci_bus_exe.root_module.addImport("pci-class", pci_class_module);
|
||||||
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
// A test fixture, not a real driver: hellos to the device manager, then faults —
|
||||||
// what the driver-restart scenario drives the crash-loop cap with.
|
// what the driver-restart scenario drives the crash-loop cap with.
|
||||||
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
const crash_test_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "crash-test", "system/services/crash-test/crash-test.zig");
|
||||||
@@ -367,6 +370,8 @@ pub fn build(b: *std.Build) void {
|
|||||||
const discovery_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "discovery", discovery_source);
|
const discovery_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "discovery", discovery_source);
|
||||||
if (discovery == .acpi) discovery_exe.root_module.addImport("aml", aml_module);
|
if (discovery == .acpi) discovery_exe.root_module.addImport("aml", aml_module);
|
||||||
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
|
// Names the xHCI PCI class triple from the shared taxonomy instead of a bare 0x0C0330.
|
||||||
|
device_manager_exe.root_module.addImport("pci-class", pci_class_module);
|
||||||
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
||||||
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
||||||
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input", "system/services/input/input.zig");
|
const input_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "input", "system/services/input/input.zig");
|
||||||
|
|||||||
@@ -142,6 +142,32 @@ conventions above — `snake_case` — because it's an identifier, not a filenam
|
|||||||
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
|
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
|
||||||
resolving away. See the repository-layout section of [README.md](README.md).
|
resolving away. See the repository-layout section of [README.md](README.md).
|
||||||
|
|
||||||
|
## Named values, not magic numbers
|
||||||
|
|
||||||
|
The naming rule has a twin: **a value with meaning gets a name, too.** The same
|
||||||
|
principle drives both — a reader should never have to leave the code to understand it.
|
||||||
|
An abbreviated *name* forces a reader to guess; a bare *number* forces them worse, out
|
||||||
|
to a spec or a header or a comment three files away, to learn what the value even *is*.
|
||||||
|
If `0x0C` is the PCI serial-bus class, the code says `BaseClass.serial_bus`, not `0x0C`;
|
||||||
|
if `0x04` is the ACPI IRQ resource descriptor, it says `SmallResourceType.irq`, not
|
||||||
|
`0x04`. The number is an implementation detail of the name — recorded once, where the
|
||||||
|
name is defined, and never spelled again at a use site.
|
||||||
|
|
||||||
|
**Prefer an `enum`** when the values form a set (device classes, AML opcodes, resource
|
||||||
|
descriptor types, states): the type then also says *which* set a value belongs to, and
|
||||||
|
the compiler rejects a value from the wrong one. A lone `pub const` with a descriptive
|
||||||
|
name suffices for a one-off (`const large_descriptor_bit = 0x80`). Reach for the enum
|
||||||
|
the moment code elsewhere compares against, packs, or produces the value — a packed PCI
|
||||||
|
class triple is written from named parts (`.serial_bus`, `.usb`, `.xhci`), never as
|
||||||
|
`0x0C_03_30` under a comment that decodes the bytes.
|
||||||
|
|
||||||
|
The exceptions are the numbers that carry no hidden meaning: `0` and `1` as plain zero
|
||||||
|
and one, an index step, a field width, a bit shift. `x + 1`, `buffer[0]`, and `<< 8`
|
||||||
|
need no christening — there is nothing to look up. The test is exactly the naming test:
|
||||||
|
*would a reader have to look this up to know what it means?* If yes, name it. This is
|
||||||
|
what `opcodes.zig`'s `*_opcode` constants, `acpi-ids`'s `HardwareId`, and `pci-class`'s
|
||||||
|
class enums already are — reference data defined once and named everywhere it is used.
|
||||||
|
|
||||||
## Why acronyms are the line
|
## Why acronyms are the line
|
||||||
|
|
||||||
Because an acronym has no letters to restore. `MMIO` doesn't become "memory mapped
|
Because an acronym has no letters to restore. `MMIO` doesn't become "memory mapped
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ branch is green; keep branches; push everything.
|
|||||||
race). The `acpi-ps2` scenario proves report → spawn → ps2-bus attaches
|
race). The `acpi-ps2` scenario proves report → spawn → ps2-bus attaches
|
||||||
its keyboard; `ioport` retargeted to the acpi-tables I/O window (the
|
its keyboard; `ioport` retargeted to the acpi-tables I/O window (the
|
||||||
kernel-built PS/2 node is gone). Suite 58/58.
|
kernel-built PS/2 node is gone). Suite 58/58.
|
||||||
- [ ] **merge** `feat/acpi-service` → main, push — **loop ends here**.
|
- [x] **merge** `feat/acpi-service` → main, push (merged 2026-07-13) — **discovery migration complete**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# M21 execution plan: ACPI events + system power
|
||||||
|
|
||||||
|
The operational plan for the event side of the acpi service and orderly
|
||||||
|
shutdown — the capstone [m19-m20-plan.md](m19-m20-plan.md) previewed. Same
|
||||||
|
rules as its predecessors: one phase at a time, each green before the next;
|
||||||
|
this file is the build order and the checklist.
|
||||||
|
|
||||||
|
**Definition of green, every phase:** `zig build` clean, `zig build test`
|
||||||
|
clean, `python3 test/qemu_test.py` passes (existing scenarios plus the
|
||||||
|
phase's new one), and the relevant design doc updated. Commit per green phase
|
||||||
|
(no co-author trailers). Failing cases preserve their serial logs
|
||||||
|
(`<case>-failed-serial.log`).
|
||||||
|
|
||||||
|
**Workflow:** dedicated worktree; branch `feat/power-events` off `main`;
|
||||||
|
auto-merge to main when the branch is green; keep the branch; push everything.
|
||||||
|
|
||||||
|
## Settled decisions (2026-07-13, approved)
|
||||||
|
|
||||||
|
1. **S5 is executed by the acpi service from ring 3.** No new syscall: the
|
||||||
|
broad port grant (M20 decision 5) already made this physically possible —
|
||||||
|
the service holds the PM1 control ports in its io grant and derives `_S5`
|
||||||
|
from its own namespace (`aml.sleepState`). Formalizing it adds no
|
||||||
|
authority. The kernel keeps `power.zig` for its own test paths and
|
||||||
|
panic-time use.
|
||||||
|
2. **The power surface is domain-named** (decision 7 of the last plan): a
|
||||||
|
`power-protocol` module + `ServiceId.power = 5`, registered by the acpi
|
||||||
|
service — on ARM, a PSCI/mailbox service registers the same id and
|
||||||
|
subscribers never know the difference. Messages: `subscribe` (endpoint as
|
||||||
|
the call's capability, the input/manager pattern), `shutdown` (accepted
|
||||||
|
only from PID 1 — init), and events published as buffered messages:
|
||||||
|
`power_button`, `lid`, `ac`, `battery`, generic `notify` with a code.
|
||||||
|
3. **The service learns event ports from its own FADT copy**: the kernel adds
|
||||||
|
the FADT as one more memory resource on the acpi-tables node; the service
|
||||||
|
tells it apart from the AML blobs by signature ("FACP" header — the blob
|
||||||
|
resources are header-stripped bytecode and start with no signature). The
|
||||||
|
kernel's own FADT parse is untouched.
|
||||||
|
4. **The acpi service converts to the harness** (`runtime.service.run`):
|
||||||
|
protocol messages (subscribe/shutdown), the SCI notification, and the
|
||||||
|
existing report flow fold into one loop — the shape it was always meant
|
||||||
|
to have.
|
||||||
|
5. **GPE/Notify correctness is proven by host unit tests** (synthetic AML
|
||||||
|
with a Notify inside a method body; aml.zig joins the `zig build test`
|
||||||
|
loop). The QEMU scenario proves the power button — a *fixed* event,
|
||||||
|
deterministically injectable via QMP `system_powerdown` — because QEMU
|
||||||
|
cannot raise GPEs deterministically on this config. Battery/AC/lid and the
|
||||||
|
embedded controller (`_Qxx`) are interface-complete here and validated on
|
||||||
|
real hardware (the laptop) later.
|
||||||
|
|
||||||
|
## Ground truth the phases build on (verified 2026-07-13)
|
||||||
|
|
||||||
|
- `system/devices/power.zig` `shutdown()` is the kernel's S5 write
|
||||||
|
(SLP_TYP|SLP_EN to PM1a/PM1b control); there is no power syscall.
|
||||||
|
- init (`system/services/init/init.zig`) spawns vfs/input/device-manager
|
||||||
|
fire-and-forget — no child ids kept, no signals, no event loop. The whole
|
||||||
|
stop toolkit exists in `runtime.process` (stop/sendSignal/bindSignals).
|
||||||
|
- `test/qemu_test.py` has no QMP channel (serial is a one-way file).
|
||||||
|
- The kernel parses PM1 *control* blocks and SCI_INT from the FADT; the PM1
|
||||||
|
**event** blocks (offsets 56/60, len at 88) and **GPE0/GPE1** blocks
|
||||||
|
(offsets 80/84, lens 92/93) are unparsed — the service reads them from its
|
||||||
|
FADT copy (decision 3).
|
||||||
|
- The acpi-tables node carries the SCI as its only `len == 1` irq resource
|
||||||
|
(the broad window is len 256) — that is how the service finds it to
|
||||||
|
`irqBind`.
|
||||||
|
- `notify_opcode = 0x86` exists in `system/devices/aml/opcodes.zig` but the
|
||||||
|
interpreter never handles it — a GPE `_Lxx` body containing Notify fails
|
||||||
|
evaluation today. Everything else a GPE handler needs (field access,
|
||||||
|
control flow, method calls) is proven by the ring-3 `_STA`/`_CRS` work.
|
||||||
|
- The dead-code sweep (spawned task) also edits `system/devices/acpi.zig`;
|
||||||
|
M21.0 checks whether it landed and rebases before touching that file.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- [ ] **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**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase notes
|
||||||
|
|
||||||
|
**M21.0 QMP:** open the unix socket after Popen, complete the
|
||||||
|
`qmp_capabilities` handshake, then send the hook's command (for these
|
||||||
|
scenarios: `{"execute": "system_powerdown"}`). The socket is additive — no
|
||||||
|
existing case may notice it. Note e3fe3f3 recently reworked how the harness
|
||||||
|
boots; adapt to its current shape rather than the pre-rework description.
|
||||||
|
|
||||||
|
**M21.1 SCI details:** PM1_STS is at the event block base (write-1-to-clear);
|
||||||
|
PM1_EN at base + block_len/2; PWRBTN bit is 8 in both. If PM1b exists, mirror
|
||||||
|
reads/writes to both blocks. Enable ACPI mode only when SCI_EN (PM1 control
|
||||||
|
bit 0) is clear — OVMF boots may already have it set. The publish path reuses
|
||||||
|
the manager's subscriber table pattern (bounded, drop-on-failed-send).
|
||||||
|
|
||||||
|
**M21.2 GPE walk:** GPE0_STS bytes live at the GPE0 block base, GPE0_EN in
|
||||||
|
the block's upper half; for a set+enabled bit n, the handler method is
|
||||||
|
`_L%02X` (level) or `_E%02X` (edge) under `\_GPE`. Evaluate, drain the notify
|
||||||
|
queue, clear the status bit, ack. A missing handler method is clear-and-log,
|
||||||
|
not an error.
|
||||||
|
|
||||||
|
**M21.3 ordering:** init subscribes with retries — the acpi service registers
|
||||||
|
`.power` well after init starts. The stop sequence runs vfs last (other
|
||||||
|
services may flush through it). The S5 write mirrors `power.zig`'s
|
||||||
|
`sleepValue` (SLP_TYP bits [12:10], SLP_EN bit 13); if the write returns, log
|
||||||
|
`power: S5 write did not take` so the scenario fails loudly instead of
|
||||||
|
hanging.
|
||||||
|
|
||||||
|
**Explicitly out of scope:** the embedded controller and `_Qxx` queries,
|
||||||
|
battery `_BST`/`_BIF` evaluation beyond the interface stubs, lid/AC on QEMU
|
||||||
|
(no emulation), reboot over the power protocol, S3 sleep, per-device D-states
|
||||||
|
(a future lifecycle-vocabulary extension), thermal zones.
|
||||||
+1
-373
@@ -17,7 +17,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const boot_handoff = @import("boot-handoff");
|
const boot_handoff = @import("boot-handoff");
|
||||||
const abi = @import("abi");
|
const abi = @import("abi");
|
||||||
const acpi_ids = @import("acpi-ids");
|
|
||||||
const parameters = @import("parameters");
|
const parameters = @import("parameters");
|
||||||
const device_model = @import("device-model.zig");
|
const device_model = @import("device-model.zig");
|
||||||
const aml = @import("aml/aml.zig");
|
const aml = @import("aml/aml.zig");
|
||||||
@@ -409,9 +408,7 @@ pub fn discover(rsdp_physical: u64, memory_regions: []const boot_handoff.MemoryR
|
|||||||
// tree (M20.3): the ring-3 acpi service claims the acpi-tables node
|
// tree (M20.3): the ring-3 acpi service claims the acpi-tables node
|
||||||
// (published below), re-parses the same blobs, and registers + reports
|
// (published below), re-parses the same blobs, and registers + reports
|
||||||
// the _HID devices itself. The kernel keeps the namespace only for the
|
// the _HID devices itself. The kernel keeps the namespace only for the
|
||||||
// \_S5 sleep type above. The device-building helpers below
|
// \_S5 sleep type above.
|
||||||
// (wireAcpiDevices and friends) are retained but unreferenced — a
|
|
||||||
// focused dead-code sweep follows the migration.
|
|
||||||
} else |_| {
|
} else |_| {
|
||||||
// AML parse failed (e.g. out of memory); power stays best-effort with
|
// AML parse failed (e.g. out of memory); power stays best-effort with
|
||||||
// whatever the FADT alone provided.
|
// whatever the FADT alone provided.
|
||||||
@@ -816,342 +813,6 @@ fn parseDmar(hal: Hal, header: *const SystemDescriptorTableHeader) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- AML namespace -> generic device tree -----------------------------------
|
|
||||||
|
|
||||||
/// The PCI bus context while descending the ACPI namespace: the generic host
|
|
||||||
/// bridge whose children ACPI address (`_ADR`) devices resolve against, and the bus number.
|
|
||||||
const PciContext = struct { bridge: *device_model.Device, bus: u8 };
|
|
||||||
|
|
||||||
/// Mirror the ACPI namespace's Device objects into the generic tree, *merging*
|
|
||||||
/// them with the PCI-enumerated nodes: a PCI root bridge (`PNP0A03`/`PNP0A08`)
|
|
||||||
/// folds onto the existing `pci_host_bridge`, and each addressed (`_ADR`) device folds onto
|
|
||||||
/// the matching PCI function (annotating it with the ACPI hardware ID (`_HID`) and nesting the
|
|
||||||
/// ACPI-only children — keyboard, RTC, … — beneath it). Namespace devices with no
|
|
||||||
/// PCI match land under a synthetic `acpi` node.
|
|
||||||
fn wireAcpiDevices(device_tree: *DeviceTree, aml_namespace: *aml.Namespace, hal: Hal) !void {
|
|
||||||
var arena = std.heap.ArenaAllocator.init(device_tree.allocator);
|
|
||||||
defer arena.deinit();
|
|
||||||
var interpreter = aml.Interpreter.init(aml_namespace, .{
|
|
||||||
.mapMmio = hal.mapMmio,
|
|
||||||
.pioRead = hal.pioRead,
|
|
||||||
.pioWrite = hal.pioWrite,
|
|
||||||
}, arena.allocator());
|
|
||||||
|
|
||||||
const acpi_root = try device_tree.addChild(device_tree.root, .unknown, "acpi");
|
|
||||||
try mirrorDevices(device_tree, aml_namespace.root, acpi_root, null, &interpreter);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mirrorDevices(device_tree: *DeviceTree, node: *aml.Node, parent_device: *device_model.Device, context: ?PciContext, interpreter: *aml.Interpreter) (error{OutOfMemory})!void {
|
|
||||||
var child = node.first_child;
|
|
||||||
while (child) |c| : (child = c.next_sibling) {
|
|
||||||
if (c.kind != .device) {
|
|
||||||
// A scope — the System Bus (\_SB), General Purpose Events (\_GPE), … —
|
|
||||||
// descend without adding a node.
|
|
||||||
try mirrorDevices(device_tree, c, parent_device, context, interpreter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip devices the firmware reports as not present (via a device-status (`_STA`) method),
|
|
||||||
// along with their whole subtree — per the ACPI rules.
|
|
||||||
if (!devicePresent(interpreter, c)) continue;
|
|
||||||
|
|
||||||
var mirrored_device: *device_model.Device = undefined;
|
|
||||||
var child_context = context;
|
|
||||||
|
|
||||||
if (isPciRootNode(c)) {
|
|
||||||
// The PCI root bridge folds onto the generic host bridge.
|
|
||||||
mirrored_device = matchHostBridge(device_tree) orelse
|
|
||||||
try device_tree.addChild(parent_device, .acpi_device, &c.segment);
|
|
||||||
child_context = .{ .bridge = mirrored_device, .bus = 0 };
|
|
||||||
} else {
|
|
||||||
// An addressed device folds onto its matching PCI function; anything
|
|
||||||
// else becomes a fresh node under the current parent.
|
|
||||||
mirrored_device = pick: {
|
|
||||||
if (context) |pc| {
|
|
||||||
if (readAdr(c)) |adr| {
|
|
||||||
if (findPciNode(pc.bridge, pc.bus, adr)) |pnode| break :pick pnode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :pick try device_tree.addChild(parent_device, .acpi_device, &c.segment);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
applyHid(mirrored_device, c, interpreter);
|
|
||||||
applyCrs(mirrored_device, c, interpreter);
|
|
||||||
try mirrorDevices(device_tree, c, mirrored_device, child_context, interpreter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate a device's status (`_STA`) to decide if it is present. An absent status
|
|
||||||
/// (`_STA`) means present by default; an evaluation failure is treated as present too (we'd
|
|
||||||
/// rather over-report than hide a device we couldn't introspect).
|
|
||||||
fn devicePresent(interpreter: *aml.Interpreter, node: *aml.Node) bool {
|
|
||||||
const sta = aml.Namespace.childOf(node, seg4("_STA")) orelse return true;
|
|
||||||
const obj = interpreter.evaluate(sta, &.{}) catch return true;
|
|
||||||
const status = obj.asInteger() catch return true;
|
|
||||||
return (status & 0x01) != 0; // bit 0 = present
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The first PCI host bridge in the generic tree (segment 0).
|
|
||||||
fn matchHostBridge(device_tree: *DeviceTree) ?*device_model.Device {
|
|
||||||
var c = device_tree.root.first_child;
|
|
||||||
while (c) |ch| : (c = ch.next_sibling) {
|
|
||||||
if (ch.class == .pci_host_bridge) return ch;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The PCI function node under `bridge` at the address the device's address object
|
|
||||||
/// (`_ADR`) names (device/function on
|
|
||||||
/// `bus`), or null.
|
|
||||||
fn findPciNode(bridge: *device_model.Device, bus: u8, adr: u32) ?*device_model.Device {
|
|
||||||
const device: u16 = @truncate((adr >> 16) & 0x1F);
|
|
||||||
const function: u16 = @truncate(adr & 0x7);
|
|
||||||
const target: u16 = (@as(u16, bus) << 8) | (device << 3) | function;
|
|
||||||
var c = bridge.first_child;
|
|
||||||
while (c) |ch| : (c = ch.next_sibling) {
|
|
||||||
if (ch.ids.pci_bdf) |bdf| {
|
|
||||||
if (bdf == target) return ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A device's address (`_ADR`) — a static integer Name — or null.
|
|
||||||
fn readAdr(node: *aml.Node) ?u32 {
|
|
||||||
const n = aml.Namespace.childOf(node, seg4("_ADR")) orelse return null;
|
|
||||||
if (n.kind != .name) return null;
|
|
||||||
var p: usize = 0;
|
|
||||||
return @truncate(readIntObj(n.value, &p) orelse return null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a `_HID` string names a PCI(e) host bridge.
|
|
||||||
fn isPciRootHid(hid: []const u8) bool {
|
|
||||||
const id = acpi_ids.HardwareId.fromHid(hid) orelse return false;
|
|
||||||
return id == .pci_bus or id == .pci_express_root_bridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a namespace device is a PCI(e) host bridge. A packed EISA id is decoded
|
|
||||||
/// to its string form first, so both encodings answer through the one registry.
|
|
||||||
fn isPciRootNode(node: *aml.Node) bool {
|
|
||||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return false;
|
|
||||||
if (hid.kind != .name or hid.value.len == 0) return false;
|
|
||||||
switch (hid.value[0]) {
|
|
||||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
|
||||||
var p: usize = 0;
|
|
||||||
const n = readIntObj(hid.value, &p) orelse return false;
|
|
||||||
var buffer: [8]u8 = undefined;
|
|
||||||
return isPciRootHid(eisaIdToStr(@truncate(n), &buffer));
|
|
||||||
},
|
|
||||||
0x0D => return isPciRootHid(cstr(hid.value[1..])),
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a device's hardware ID (`_HID`) into the generic device: an integer decodes as an EISA
|
|
||||||
/// id ("PNP0A03"), a string is taken verbatim. Handles both the common static
|
|
||||||
/// Name form and a Method form (evaluated).
|
|
||||||
fn applyHid(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
|
||||||
const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return;
|
|
||||||
if (hid.kind == .method) {
|
|
||||||
const obj = interpreter.evaluate(hid, &.{}) catch return;
|
|
||||||
switch (obj) {
|
|
||||||
.integer => |n| setEisaHid(device, @truncate(n)),
|
|
||||||
.string => |s| device.setHid(s),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hid.kind != .name or hid.value.len == 0) return;
|
|
||||||
const v = hid.value;
|
|
||||||
switch (v[0]) {
|
|
||||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
|
||||||
var p: usize = 0;
|
|
||||||
const n = readIntObj(v, &p) orelse return;
|
|
||||||
setEisaHid(device, @truncate(n));
|
|
||||||
},
|
|
||||||
0x0D => device.setHid(cstr(v[1..])), // StringPrefix
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setEisaHid(device: *device_model.Device, id: u32) void {
|
|
||||||
device.ids.acpi_hid = id;
|
|
||||||
var buffer: [8]u8 = undefined;
|
|
||||||
device.setHid(eisaIdToStr(id, &buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a device's current resource settings (`_CRS`). The evaluator handles both the static
|
|
||||||
/// `Buffer` form (a `Name`) and the method form uniformly, yielding the
|
|
||||||
/// ResourceTemplate bytes we then decode.
|
|
||||||
fn applyCrs(device: *device_model.Device, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
|
||||||
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
|
||||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
|
||||||
const buffer = switch (obj) {
|
|
||||||
.buffer => |b| b,
|
|
||||||
else => return,
|
|
||||||
};
|
|
||||||
parseResourceTemplate(device, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk a ResourceTemplate byte list, adding recognised descriptors as resources.
|
|
||||||
fn parseResourceTemplate(device: *device_model.Device, bytes: []const u8) void {
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < bytes.len) {
|
|
||||||
const tag = bytes[i];
|
|
||||||
if (tag & 0x80 == 0) {
|
|
||||||
// Small descriptor: length in low 3 bits, type in bits [6:3].
|
|
||||||
const len: usize = tag & 0x07;
|
|
||||||
const body = i + 1;
|
|
||||||
if (body + len > bytes.len) break;
|
|
||||||
switch ((tag >> 3) & 0x0F) {
|
|
||||||
0x04 => if (len >= 2) { // IRQ: a 16-bit mask, one resource per set bit
|
|
||||||
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
|
||||||
var b: usize = 0;
|
|
||||||
while (b < 16) : (b += 1) {
|
|
||||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) _ = device.addResource(.irq, b, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
0x08 => if (len >= 7) { // IO port: minimum at +1, length at +6
|
|
||||||
_ = device.addResource(.io_port, rd16(bytes, body + 1), bytes[body + 6]);
|
|
||||||
},
|
|
||||||
0x09 => if (len >= 3) { // Fixed IO: base at +0, length at +2
|
|
||||||
_ = device.addResource(.io_port, rd16(bytes, body), bytes[body + 2]);
|
|
||||||
},
|
|
||||||
0x0F => break, // EndTag
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
i = body + len;
|
|
||||||
} else {
|
|
||||||
// Large descriptor: 16-bit length follows the tag.
|
|
||||||
if (i + 3 > bytes.len) break;
|
|
||||||
const len: usize = @intCast(rd16(bytes, i + 1));
|
|
||||||
const body = i + 3;
|
|
||||||
if (body + len > bytes.len) break;
|
|
||||||
switch (tag) {
|
|
||||||
0x85 => if (len >= 17) { // Memory32: minimum at +1, length at +13
|
|
||||||
_ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 13));
|
|
||||||
},
|
|
||||||
0x86 => if (len >= 9) { // Memory32Fixed: base at +1, length at +5
|
|
||||||
_ = device.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 5));
|
|
||||||
},
|
|
||||||
0x89 => if (len >= 2) { // Extended IRQ: count at +1, then count u32s
|
|
||||||
const count = bytes[body + 1];
|
|
||||||
var k: usize = 0;
|
|
||||||
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
|
||||||
_ = device.addResource(.irq, rd32(bytes, body + 2 + k * 4), 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
0x87, 0x88, 0x8A => parseAddressSpace(device, tag, bytes[body .. body + len]),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
i = body + len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Word/DWord/QWord address-space descriptors: resource type at [0], then
|
|
||||||
/// granularity/minimum/maximum/translation/length, each of width `w`.
|
|
||||||
fn parseAddressSpace(device: *device_model.Device, tag: u8, body: []const u8) void {
|
|
||||||
const w: usize = switch (tag) {
|
|
||||||
0x88 => 2, // Word
|
|
||||||
0x87 => 4, // DWord
|
|
||||||
else => 8, // QWord (0x8A)
|
|
||||||
};
|
|
||||||
if (body.len < 3 + 5 * w) return;
|
|
||||||
const minimum = readN(body, 3 + w, w);
|
|
||||||
const length = readN(body, 3 + 4 * w, w);
|
|
||||||
const kind: device_model.ResourceKind = switch (body[0]) {
|
|
||||||
0 => .memory,
|
|
||||||
1 => .io_port,
|
|
||||||
else => .bus_range,
|
|
||||||
};
|
|
||||||
_ = device.addResource(kind, minimum, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode a packed EISA id into its 7-char string (e.g. 0x030AD041 -> "PNP0A03").
|
|
||||||
fn eisaIdToStr(id: u32, buffer: *[8]u8) []const u8 {
|
|
||||||
const b0: u16 = @intCast(id & 0xFF);
|
|
||||||
const b1: u16 = @intCast((id >> 8) & 0xFF);
|
|
||||||
const b2: u8 = @truncate(id >> 16);
|
|
||||||
const b3: u8 = @truncate(id >> 24);
|
|
||||||
const mfg = (b0 << 8) | b1;
|
|
||||||
buffer[0] = '@' + @as(u8, @intCast((mfg >> 10) & 0x1F));
|
|
||||||
buffer[1] = '@' + @as(u8, @intCast((mfg >> 5) & 0x1F));
|
|
||||||
buffer[2] = '@' + @as(u8, @intCast(mfg & 0x1F));
|
|
||||||
buffer[3] = hexDigit((b2 >> 4) & 0xF);
|
|
||||||
buffer[4] = hexDigit(b2 & 0xF);
|
|
||||||
buffer[5] = hexDigit((b3 >> 4) & 0xF);
|
|
||||||
buffer[6] = hexDigit(b3 & 0xF);
|
|
||||||
return buffer[0..7];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hexDigit(n: u8) u8 {
|
|
||||||
return if (n < 10) '0' + n else 'A' + (n - 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seg4(comptime s: *const [4:0]u8) [4]u8 {
|
|
||||||
return s[0..4].*;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cstr(bytes: []const u8) []const u8 {
|
|
||||||
const index = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
|
|
||||||
return bytes[0..index];
|
|
||||||
}
|
|
||||||
|
|
||||||
const PkgLen = struct { value: usize, size: usize };
|
|
||||||
|
|
||||||
fn packageLength(bytes: []const u8, p: usize) ?PkgLen {
|
|
||||||
if (p >= bytes.len) return null;
|
|
||||||
const lead = bytes[p];
|
|
||||||
const follow: usize = lead >> 6;
|
|
||||||
if (p + 1 + follow > bytes.len) return null;
|
|
||||||
if (follow == 0) return .{ .value = lead & 0x3F, .size = 1 };
|
|
||||||
var value: usize = lead & 0x0F;
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < follow) : (i += 1) value |= @as(usize, bytes[p + 1 + i]) << @intCast(4 + i * 8);
|
|
||||||
return .{ .value = value, .size = 1 + follow };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read an AML integer object at `p`, advancing `p` past it.
|
|
||||||
fn readIntObj(bytes: []const u8, p: *usize) ?u64 {
|
|
||||||
if (p.* >= bytes.len) return null;
|
|
||||||
const opcode = bytes[p.*];
|
|
||||||
p.* += 1;
|
|
||||||
return switch (opcode) {
|
|
||||||
0x00 => 0,
|
|
||||||
0x01 => 1,
|
|
||||||
0xFF => 0xFF,
|
|
||||||
0x0A => readLE(bytes, p, 1),
|
|
||||||
0x0B => readLE(bytes, p, 2),
|
|
||||||
0x0C => readLE(bytes, p, 4),
|
|
||||||
0x0E => readLE(bytes, p, 8),
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readLE(bytes: []const u8, p: *usize, n: usize) ?u64 {
|
|
||||||
if (p.* + n > bytes.len) return null;
|
|
||||||
const v = readN(bytes, p.*, n);
|
|
||||||
p.* += n;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readN(bytes: []const u8, off: usize, n: usize) u64 {
|
|
||||||
var v: u64 = 0;
|
|
||||||
var k: usize = 0;
|
|
||||||
while (k < n and off + k < bytes.len) : (k += 1) v |= @as(u64, bytes[off + k]) << @intCast(k * 8);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rd16(bytes: []const u8, off: usize) u64 {
|
|
||||||
return readN(bytes, off, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rd32(bytes: []const u8, off: usize) u64 {
|
|
||||||
return readN(bytes, off, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helpers ----------------------------------------------------------------
|
// --- helpers ----------------------------------------------------------------
|
||||||
|
|
||||||
/// Sum `len` bytes; an ACPI table/pointer is valid when the low 8 bits are zero.
|
/// Sum `len` bytes; an ACPI table/pointer is valid when the low 8 bits are zero.
|
||||||
@@ -1192,42 +853,9 @@ fn readCntRegister(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_o
|
|||||||
return .{ .mmio = false, .address = port, .width = width };
|
return .{ .mmio = false, .address = port, .width = width };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The mapped configuration space of one PCI function (its 4 KiB ECAM page). Mapped
|
|
||||||
/// writable so BAR sizing can probe it; reads and writes both go through here.
|
|
||||||
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
|
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
|
||||||
/// x86 is little-endian and native, so an unaligned load suffices.
|
/// x86 is little-endian and native, so an unaligned load suffices.
|
||||||
fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T {
|
fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T {
|
||||||
const p: *align(1) const T = @ptrCast(bytes + off);
|
const p: *align(1) const T = @ptrCast(bytes + off);
|
||||||
return p.*;
|
return p.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- tests ------------------------------------------------------------------
|
|
||||||
|
|
||||||
test "eisaIdToStr decodes a packed EISA id" {
|
|
||||||
var buffer: [8]u8 = undefined;
|
|
||||||
// 0x030AD041 is the well-known encoding of "PNP0A03" (PCI root bridge).
|
|
||||||
try std.testing.expectEqualStrings("PNP0A03", eisaIdToStr(0x030AD041, &buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "parseResourceTemplate extracts IO, IRQ, and fixed memory" {
|
|
||||||
// ResourceTemplate { IO(minimum 0x60, len 8), IRQ(4), Memory32Fixed(0xFED00000, 0x1000) }
|
|
||||||
const runtime = [_]u8{
|
|
||||||
0x47, 0x01, 0x60, 0x00, 0x60, 0x00, 0x01, 0x08, // small IO descriptor
|
|
||||||
0x22, 0x10, 0x00, // small IRQ descriptor (mask bit 4 -> IRQ 4)
|
|
||||||
0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0xD0, 0xFE, 0x00, 0x10, 0x00, 0x00, // Memory32Fixed
|
|
||||||
0x79, 0x00, // EndTag
|
|
||||||
};
|
|
||||||
var device = device_model.Device{};
|
|
||||||
parseResourceTemplate(&device, &runtime);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(@as(u8, 3), device.resource_count);
|
|
||||||
const rs = device.resources[0..device.resource_count];
|
|
||||||
try std.testing.expectEqual(device_model.ResourceKind.io_port, rs[0].kind);
|
|
||||||
try std.testing.expectEqual(@as(u64, 0x60), rs[0].start);
|
|
||||||
try std.testing.expectEqual(@as(u64, 8), rs[0].len);
|
|
||||||
try std.testing.expectEqual(device_model.ResourceKind.irq, rs[1].kind);
|
|
||||||
try std.testing.expectEqual(@as(u64, 4), rs[1].start);
|
|
||||||
try std.testing.expectEqual(device_model.ResourceKind.memory, rs[2].kind);
|
|
||||||
try std.testing.expectEqual(@as(u64, 0xFED00000), rs[2].start);
|
|
||||||
try std.testing.expectEqual(@as(u64, 0x1000), rs[2].len);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ const std = @import("std");
|
|||||||
const opcode = @import("opcodes.zig");
|
const opcode = @import("opcodes.zig");
|
||||||
const parser = @import("parser.zig");
|
const parser = @import("parser.zig");
|
||||||
|
|
||||||
|
/// The named AML opcode/prefix bytes (`zero_opcode`, `byte_prefix`, …). Re-exported so
|
||||||
|
/// callers that decode raw AML bytes — e.g. the acpi service reading a `_HID` integer —
|
||||||
|
/// name the opcodes instead of writing bare 0x0A/0x0B/… literals (docs/coding-standards.md).
|
||||||
|
pub const opcodes = @import("opcodes.zig");
|
||||||
|
|
||||||
pub const Namespace = @import("namespace.zig").Namespace;
|
pub const Namespace = @import("namespace.zig").Namespace;
|
||||||
pub const Node = @import("namespace.zig").Node;
|
pub const Node = @import("namespace.zig").Node;
|
||||||
pub const NodeKind = @import("namespace.zig").NodeKind;
|
pub const NodeKind = @import("namespace.zig").NodeKind;
|
||||||
|
|||||||
+496
-189
@@ -7,6 +7,16 @@
|
|||||||
//! apart. Pure reference data (from the PCI spec; see https://wiki.osdev.org/PCI) — no
|
//! apart. Pure reference data (from the PCI spec; see https://wiki.osdev.org/PCI) — no
|
||||||
//! hardware access — so it is shared by kernel discovery (the device-tree dump) and any
|
//! hardware access — so it is shared by kernel discovery (the device-tree dump) and any
|
||||||
//! user-space tool (a future lspci, driver matching).
|
//! user-space tool (a future lspci, driver matching).
|
||||||
|
//!
|
||||||
|
//! The taxonomy is named, not numbered (docs/coding-standards.md, "Named values"): the
|
||||||
|
//! base class is a `BaseClass` enum, and each class with defined subclasses gets a
|
||||||
|
//! namespace holding its `SubClass` enum (and, where the spec defines them, per-subclass
|
||||||
|
//! `ProgIf` enums) — the same shape as `usb-ids.zig`. Code that *means* a specific class
|
||||||
|
//! names it (`BaseClass.serial_bus`, `serial_bus.usb.ProgIf.xhci`) rather than writing a
|
||||||
|
//! bare 0x0C/0x03/0x30. The `className`/`subclassName`/`progIfName` functions still take
|
||||||
|
//! the raw bytes a function reports in its header, because that is what hardware hands us.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
/// The three bytes of a PCI class code, unpacked from the `0xCCSSPP` value discovery
|
/// The three bytes of a PCI class code, unpacked from the `0xCCSSPP` value discovery
|
||||||
/// records in `Device.ids.pci_class` (CC = base class, SS = subclass, PP = prog-IF).
|
/// records in `Device.ids.pci_class` (CC = base class, SS = subclass, PP = prog-IF).
|
||||||
@@ -22,148 +32,465 @@ pub const ClassCode = struct {
|
|||||||
.prog_if = @intCast(packed_code & 0xFF),
|
.prog_if = @intCast(packed_code & 0xFF),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Re-pack the triple into the `0xCCSSPP` form. Lets code name a whole class code
|
||||||
|
/// from its parts — `pack(.{ .base = @intFromEnum(BaseClass.serial_bus), … })` —
|
||||||
|
/// instead of writing the literal 0x0C0330.
|
||||||
|
pub fn pack(self: ClassCode) u24 {
|
||||||
|
return (@as(u24, self.base) << 16) | (@as(u24, self.subclass) << 8) | self.prog_if;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Base class (config byte 0x0B). Non-exhaustive: an unlisted code is a real but
|
||||||
|
/// unnamed class, decoded as "Unknown" rather than rejected.
|
||||||
|
pub const BaseClass = enum(u8) {
|
||||||
|
unclassified = 0x00,
|
||||||
|
mass_storage = 0x01,
|
||||||
|
network = 0x02,
|
||||||
|
display = 0x03,
|
||||||
|
multimedia = 0x04,
|
||||||
|
memory = 0x05,
|
||||||
|
bridge = 0x06,
|
||||||
|
simple_communication = 0x07,
|
||||||
|
base_system_peripheral = 0x08,
|
||||||
|
input_device = 0x09,
|
||||||
|
docking_station = 0x0A,
|
||||||
|
processor = 0x0B,
|
||||||
|
serial_bus = 0x0C,
|
||||||
|
wireless = 0x0D,
|
||||||
|
intelligent = 0x0E,
|
||||||
|
satellite_communication = 0x0F,
|
||||||
|
encryption = 0x10,
|
||||||
|
signal_processing = 0x11,
|
||||||
|
processing_accelerator = 0x12,
|
||||||
|
non_essential_instrumentation = 0x13,
|
||||||
|
co_processor = 0x40,
|
||||||
|
unassigned = 0xFF,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub fn name(self: BaseClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.unclassified => "Unclassified",
|
||||||
|
.mass_storage => "Mass Storage Controller",
|
||||||
|
.network => "Network Controller",
|
||||||
|
.display => "Display Controller",
|
||||||
|
.multimedia => "Multimedia Controller",
|
||||||
|
.memory => "Memory Controller",
|
||||||
|
.bridge => "Bridge",
|
||||||
|
.simple_communication => "Simple Communication Controller",
|
||||||
|
.base_system_peripheral => "Base System Peripheral",
|
||||||
|
.input_device => "Input Device Controller",
|
||||||
|
.docking_station => "Docking Station",
|
||||||
|
.processor => "Processor",
|
||||||
|
.serial_bus => "Serial Bus Controller",
|
||||||
|
.wireless => "Wireless Controller",
|
||||||
|
.intelligent => "Intelligent Controller",
|
||||||
|
.satellite_communication => "Satellite Communication Controller",
|
||||||
|
.encryption => "Encryption Controller",
|
||||||
|
.signal_processing => "Signal Processing Controller",
|
||||||
|
.processing_accelerator => "Processing Accelerator",
|
||||||
|
.non_essential_instrumentation => "Non-Essential Instrumentation",
|
||||||
|
.co_processor => "Co-Processor",
|
||||||
|
.unassigned => "Unassigned Class (Vendor specific)",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Per-class subclass (and prog-IF) taxonomies --------------------------------------
|
||||||
|
// One namespace per base class that has defined subclasses, named after the class. Each
|
||||||
|
// holds an exhaustive `SubClass` enum (so an unlisted code decodes to the class default,
|
||||||
|
// not a wrong name), and, where the spec assigns them, per-subclass `ProgIf` enums.
|
||||||
|
|
||||||
|
pub const mass_storage = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
scsi_bus = 0x00,
|
||||||
|
ide = 0x01,
|
||||||
|
floppy = 0x02,
|
||||||
|
ipi_bus = 0x03,
|
||||||
|
raid = 0x04,
|
||||||
|
ata = 0x05,
|
||||||
|
serial_ata = 0x06,
|
||||||
|
serial_attached_scsi = 0x07,
|
||||||
|
non_volatile_memory = 0x08,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.scsi_bus => "SCSI Bus Controller",
|
||||||
|
.ide => "IDE Controller",
|
||||||
|
.floppy => "Floppy Disk Controller",
|
||||||
|
.ipi_bus => "IPI Bus Controller",
|
||||||
|
.raid => "RAID Controller",
|
||||||
|
.ata => "ATA Controller",
|
||||||
|
.serial_ata => "Serial ATA Controller",
|
||||||
|
.serial_attached_scsi => "Serial Attached SCSI Controller",
|
||||||
|
.non_volatile_memory => "Non-Volatile Memory Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const serial_ata = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
vendor_specific = 0x00,
|
||||||
|
ahci = 0x01,
|
||||||
|
serial_storage_bus = 0x02,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.vendor_specific => "Vendor Specific Interface",
|
||||||
|
.ahci => "AHCI 1.0",
|
||||||
|
.serial_storage_bus => "Serial Storage Bus",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const non_volatile_memory = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
nvmhci = 0x01,
|
||||||
|
nvm_express = 0x02,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.nvmhci => "NVMHCI",
|
||||||
|
.nvm_express => "NVM Express",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const network = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
ethernet = 0x00,
|
||||||
|
token_ring = 0x01,
|
||||||
|
fddi = 0x02,
|
||||||
|
atm = 0x03,
|
||||||
|
isdn = 0x04,
|
||||||
|
picmg_multi_computing = 0x06,
|
||||||
|
infiniband = 0x07,
|
||||||
|
fabric = 0x08,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.ethernet => "Ethernet Controller",
|
||||||
|
.token_ring => "Token Ring Controller",
|
||||||
|
.fddi => "FDDI Controller",
|
||||||
|
.atm => "ATM Controller",
|
||||||
|
.isdn => "ISDN Controller",
|
||||||
|
.picmg_multi_computing => "PICMG 2.14 Multi Computing Controller",
|
||||||
|
.infiniband => "Infiniband Controller",
|
||||||
|
.fabric => "Fabric Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const display = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
vga_compatible = 0x00,
|
||||||
|
xga = 0x01,
|
||||||
|
three_dimensional = 0x02,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.vga_compatible => "VGA Compatible Controller",
|
||||||
|
.xga => "XGA Controller",
|
||||||
|
.three_dimensional => "3D Controller (Not VGA-Compatible)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const vga_compatible = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
vga = 0x00,
|
||||||
|
compatible_8514 = 0x01,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.vga => "VGA Controller",
|
||||||
|
.compatible_8514 => "8514-Compatible Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const multimedia = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
video = 0x00,
|
||||||
|
audio = 0x01,
|
||||||
|
telephony = 0x02,
|
||||||
|
audio_device = 0x03,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.video => "Multimedia Video Controller",
|
||||||
|
.audio => "Multimedia Audio Controller",
|
||||||
|
.telephony => "Computer Telephony Device",
|
||||||
|
.audio_device => "Audio Device",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const memory = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
ram = 0x00,
|
||||||
|
flash = 0x01,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.ram => "RAM Controller",
|
||||||
|
.flash => "Flash Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const bridge = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
host = 0x00,
|
||||||
|
isa = 0x01,
|
||||||
|
eisa = 0x02,
|
||||||
|
mca = 0x03,
|
||||||
|
pci_to_pci = 0x04,
|
||||||
|
pcmcia = 0x05,
|
||||||
|
nubus = 0x06,
|
||||||
|
cardbus = 0x07,
|
||||||
|
raceway = 0x08,
|
||||||
|
pci_to_pci_semi_transparent = 0x09,
|
||||||
|
infiniband_to_pci = 0x0A,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.host => "Host Bridge",
|
||||||
|
.isa => "ISA Bridge",
|
||||||
|
.eisa => "EISA Bridge",
|
||||||
|
.mca => "MCA Bridge",
|
||||||
|
.pci_to_pci => "PCI-to-PCI Bridge",
|
||||||
|
.pcmcia => "PCMCIA Bridge",
|
||||||
|
.nubus => "NuBus Bridge",
|
||||||
|
.cardbus => "CardBus Bridge",
|
||||||
|
.raceway => "RACEway Bridge",
|
||||||
|
.pci_to_pci_semi_transparent => "PCI-to-PCI Bridge (Semi-Transparent)",
|
||||||
|
.infiniband_to_pci => "InfiniBand-to-PCI Host Bridge",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const pci_to_pci = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
normal_decode = 0x00,
|
||||||
|
subtractive_decode = 0x01,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.normal_decode => "Normal Decode",
|
||||||
|
.subtractive_decode => "Subtractive Decode",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const simple_communication = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
serial = 0x00,
|
||||||
|
parallel = 0x01,
|
||||||
|
multiport_serial = 0x02,
|
||||||
|
modem = 0x03,
|
||||||
|
gpib = 0x04,
|
||||||
|
smart_card = 0x05,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.serial => "Serial Controller",
|
||||||
|
.parallel => "Parallel Controller",
|
||||||
|
.multiport_serial => "Multiport Serial Controller",
|
||||||
|
.modem => "Modem",
|
||||||
|
.gpib => "IEEE 488.1/2 (GPIB) Controller",
|
||||||
|
.smart_card => "Smart Card Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const serial = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
compatible_8250 = 0x00,
|
||||||
|
compatible_16450 = 0x01,
|
||||||
|
compatible_16550 = 0x02,
|
||||||
|
compatible_16650 = 0x03,
|
||||||
|
compatible_16750 = 0x04,
|
||||||
|
compatible_16850 = 0x05,
|
||||||
|
compatible_16950 = 0x06,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.compatible_8250 => "8250-Compatible (Generic XT)",
|
||||||
|
.compatible_16450 => "16450-Compatible",
|
||||||
|
.compatible_16550 => "16550-Compatible",
|
||||||
|
.compatible_16650 => "16650-Compatible",
|
||||||
|
.compatible_16750 => "16750-Compatible",
|
||||||
|
.compatible_16850 => "16850-Compatible",
|
||||||
|
.compatible_16950 => "16950-Compatible",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const base_system_peripheral = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
pic = 0x00,
|
||||||
|
dma = 0x01,
|
||||||
|
timer = 0x02,
|
||||||
|
rtc = 0x03,
|
||||||
|
pci_hot_plug = 0x04,
|
||||||
|
sd_host = 0x05,
|
||||||
|
iommu = 0x06,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.pic => "PIC",
|
||||||
|
.dma => "DMA Controller",
|
||||||
|
.timer => "Timer",
|
||||||
|
.rtc => "RTC Controller",
|
||||||
|
.pci_hot_plug => "PCI Hot-Plug Controller",
|
||||||
|
.sd_host => "SD Host Controller",
|
||||||
|
.iommu => "IOMMU",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const input_device = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
keyboard = 0x00,
|
||||||
|
digitizer_pen = 0x01,
|
||||||
|
mouse = 0x02,
|
||||||
|
scanner = 0x03,
|
||||||
|
gameport = 0x04,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.keyboard => "Keyboard Controller",
|
||||||
|
.digitizer_pen => "Digitizer Pen",
|
||||||
|
.mouse => "Mouse Controller",
|
||||||
|
.scanner => "Scanner Controller",
|
||||||
|
.gameport => "Gameport Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const serial_bus = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
firewire = 0x00,
|
||||||
|
access_bus = 0x01,
|
||||||
|
ssa = 0x02,
|
||||||
|
usb = 0x03,
|
||||||
|
fibre_channel = 0x04,
|
||||||
|
smbus = 0x05,
|
||||||
|
infiniband = 0x06,
|
||||||
|
ipmi = 0x07,
|
||||||
|
sercos = 0x08,
|
||||||
|
canbus = 0x09,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.firewire => "FireWire (IEEE 1394) Controller",
|
||||||
|
.access_bus => "ACCESS Bus Controller",
|
||||||
|
.ssa => "SSA",
|
||||||
|
.usb => "USB Controller",
|
||||||
|
.fibre_channel => "Fibre Channel",
|
||||||
|
.smbus => "SMBus Controller",
|
||||||
|
.infiniband => "InfiniBand Controller",
|
||||||
|
.ipmi => "IPMI Interface",
|
||||||
|
.sercos => "SERCOS Interface (IEC 61491)",
|
||||||
|
.canbus => "CANbus Controller",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const usb = struct {
|
||||||
|
pub const ProgIf = enum(u8) {
|
||||||
|
uhci = 0x00,
|
||||||
|
ohci = 0x10,
|
||||||
|
ehci = 0x20,
|
||||||
|
xhci = 0x30,
|
||||||
|
unspecified = 0x80,
|
||||||
|
device = 0xFE,
|
||||||
|
|
||||||
|
pub fn name(self: ProgIf) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.uhci => "UHCI Controller",
|
||||||
|
.ohci => "OHCI Controller",
|
||||||
|
.ehci => "EHCI (USB2) Controller",
|
||||||
|
.xhci => "XHCI (USB3) Controller",
|
||||||
|
.unspecified => "Unspecified",
|
||||||
|
.device => "USB Device (not a host controller)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const wireless = struct {
|
||||||
|
pub const SubClass = enum(u8) {
|
||||||
|
irda = 0x00,
|
||||||
|
consumer_ir = 0x01,
|
||||||
|
rf = 0x10,
|
||||||
|
bluetooth = 0x11,
|
||||||
|
broadband = 0x12,
|
||||||
|
ethernet_802_1a = 0x20,
|
||||||
|
ethernet_802_1b = 0x21,
|
||||||
|
|
||||||
|
pub fn name(self: SubClass) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.irda => "iRDA Compatible Controller",
|
||||||
|
.consumer_ir => "Consumer IR Controller",
|
||||||
|
.rf => "RF Controller",
|
||||||
|
.bluetooth => "Bluetooth Controller",
|
||||||
|
.broadband => "Broadband Controller",
|
||||||
|
.ethernet_802_1a => "Ethernet Controller (802.1a)",
|
||||||
|
.ethernet_802_1b => "Ethernet Controller (802.1b)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Raw-byte decoding (what a function reports in its header) -------------------------
|
||||||
|
|
||||||
|
/// The name of an exhaustive class-code enum member, or null if `value` is not one — the
|
||||||
|
/// bridge from a raw config byte to a named taxonomy above.
|
||||||
|
fn enumName(comptime Enum: type, value: u8) ?[]const u8 {
|
||||||
|
return (std.enums.fromInt(Enum, value) orelse return null).name();
|
||||||
|
}
|
||||||
|
|
||||||
/// Name of the base class (byte 0x0B), e.g. `0x06` -> "Bridge".
|
/// Name of the base class (byte 0x0B), e.g. `0x06` -> "Bridge".
|
||||||
pub fn className(base: u8) []const u8 {
|
pub fn className(base: u8) []const u8 {
|
||||||
return switch (base) {
|
return @as(BaseClass, @enumFromInt(base)).name();
|
||||||
0x00 => "Unclassified",
|
|
||||||
0x01 => "Mass Storage Controller",
|
|
||||||
0x02 => "Network Controller",
|
|
||||||
0x03 => "Display Controller",
|
|
||||||
0x04 => "Multimedia Controller",
|
|
||||||
0x05 => "Memory Controller",
|
|
||||||
0x06 => "Bridge",
|
|
||||||
0x07 => "Simple Communication Controller",
|
|
||||||
0x08 => "Base System Peripheral",
|
|
||||||
0x09 => "Input Device Controller",
|
|
||||||
0x0A => "Docking Station",
|
|
||||||
0x0B => "Processor",
|
|
||||||
0x0C => "Serial Bus Controller",
|
|
||||||
0x0D => "Wireless Controller",
|
|
||||||
0x0E => "Intelligent Controller",
|
|
||||||
0x0F => "Satellite Communication Controller",
|
|
||||||
0x10 => "Encryption Controller",
|
|
||||||
0x11 => "Signal Processing Controller",
|
|
||||||
0x12 => "Processing Accelerator",
|
|
||||||
0x13 => "Non-Essential Instrumentation",
|
|
||||||
0x40 => "Co-Processor",
|
|
||||||
0xFF => "Unassigned Class (Vendor specific)",
|
|
||||||
else => "Unknown",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Name of the subclass within its base class, e.g. `(0x06, 0x01)` -> "ISA Bridge".
|
/// Name of the subclass within its base class, e.g. `(0x06, 0x01)` -> "ISA Bridge".
|
||||||
/// Subclass `0x80` is "Other" by PCI convention; anything unlisted is "Unknown".
|
/// Subclass `0x80` is "Other" by PCI convention; anything unlisted is "Unknown".
|
||||||
pub fn subclassName(base: u8, subclass: u8) []const u8 {
|
pub fn subclassName(base: u8, subclass: u8) []const u8 {
|
||||||
return switch (base) {
|
const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
|
||||||
0x01 => switch (subclass) {
|
.mass_storage => enumName(mass_storage.SubClass, subclass),
|
||||||
0x00 => "SCSI Bus Controller",
|
.network => enumName(network.SubClass, subclass),
|
||||||
0x01 => "IDE Controller",
|
.display => enumName(display.SubClass, subclass),
|
||||||
0x02 => "Floppy Disk Controller",
|
.multimedia => enumName(multimedia.SubClass, subclass),
|
||||||
0x03 => "IPI Bus Controller",
|
.memory => enumName(memory.SubClass, subclass),
|
||||||
0x04 => "RAID Controller",
|
.bridge => enumName(bridge.SubClass, subclass),
|
||||||
0x05 => "ATA Controller",
|
.simple_communication => enumName(simple_communication.SubClass, subclass),
|
||||||
0x06 => "Serial ATA Controller",
|
.base_system_peripheral => enumName(base_system_peripheral.SubClass, subclass),
|
||||||
0x07 => "Serial Attached SCSI Controller",
|
.input_device => enumName(input_device.SubClass, subclass),
|
||||||
0x08 => "Non-Volatile Memory Controller",
|
.serial_bus => enumName(serial_bus.SubClass, subclass),
|
||||||
else => defaultSubclass(subclass),
|
.wireless => enumName(wireless.SubClass, subclass),
|
||||||
},
|
else => null,
|
||||||
0x02 => switch (subclass) {
|
|
||||||
0x00 => "Ethernet Controller",
|
|
||||||
0x01 => "Token Ring Controller",
|
|
||||||
0x02 => "FDDI Controller",
|
|
||||||
0x03 => "ATM Controller",
|
|
||||||
0x04 => "ISDN Controller",
|
|
||||||
0x06 => "PICMG 2.14 Multi Computing Controller",
|
|
||||||
0x07 => "Infiniband Controller",
|
|
||||||
0x08 => "Fabric Controller",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x03 => switch (subclass) {
|
|
||||||
0x00 => "VGA Compatible Controller",
|
|
||||||
0x01 => "XGA Controller",
|
|
||||||
0x02 => "3D Controller (Not VGA-Compatible)",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x04 => switch (subclass) {
|
|
||||||
0x00 => "Multimedia Video Controller",
|
|
||||||
0x01 => "Multimedia Audio Controller",
|
|
||||||
0x02 => "Computer Telephony Device",
|
|
||||||
0x03 => "Audio Device",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x05 => switch (subclass) {
|
|
||||||
0x00 => "RAM Controller",
|
|
||||||
0x01 => "Flash Controller",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x06 => switch (subclass) {
|
|
||||||
0x00 => "Host Bridge",
|
|
||||||
0x01 => "ISA Bridge",
|
|
||||||
0x02 => "EISA Bridge",
|
|
||||||
0x03 => "MCA Bridge",
|
|
||||||
0x04 => "PCI-to-PCI Bridge",
|
|
||||||
0x05 => "PCMCIA Bridge",
|
|
||||||
0x06 => "NuBus Bridge",
|
|
||||||
0x07 => "CardBus Bridge",
|
|
||||||
0x08 => "RACEway Bridge",
|
|
||||||
0x09 => "PCI-to-PCI Bridge (Semi-Transparent)",
|
|
||||||
0x0A => "InfiniBand-to-PCI Host Bridge",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x07 => switch (subclass) {
|
|
||||||
0x00 => "Serial Controller",
|
|
||||||
0x01 => "Parallel Controller",
|
|
||||||
0x02 => "Multiport Serial Controller",
|
|
||||||
0x03 => "Modem",
|
|
||||||
0x04 => "IEEE 488.1/2 (GPIB) Controller",
|
|
||||||
0x05 => "Smart Card Controller",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x08 => switch (subclass) {
|
|
||||||
0x00 => "PIC",
|
|
||||||
0x01 => "DMA Controller",
|
|
||||||
0x02 => "Timer",
|
|
||||||
0x03 => "RTC Controller",
|
|
||||||
0x04 => "PCI Hot-Plug Controller",
|
|
||||||
0x05 => "SD Host Controller",
|
|
||||||
0x06 => "IOMMU",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x09 => switch (subclass) {
|
|
||||||
0x00 => "Keyboard Controller",
|
|
||||||
0x01 => "Digitizer Pen",
|
|
||||||
0x02 => "Mouse Controller",
|
|
||||||
0x03 => "Scanner Controller",
|
|
||||||
0x04 => "Gameport Controller",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x0C => switch (subclass) {
|
|
||||||
0x00 => "FireWire (IEEE 1394) Controller",
|
|
||||||
0x01 => "ACCESS Bus Controller",
|
|
||||||
0x02 => "SSA",
|
|
||||||
0x03 => "USB Controller",
|
|
||||||
0x04 => "Fibre Channel",
|
|
||||||
0x05 => "SMBus Controller",
|
|
||||||
0x06 => "InfiniBand Controller",
|
|
||||||
0x07 => "IPMI Interface",
|
|
||||||
0x08 => "SERCOS Interface (IEC 61491)",
|
|
||||||
0x09 => "CANbus Controller",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
0x0D => switch (subclass) {
|
|
||||||
0x00 => "iRDA Compatible Controller",
|
|
||||||
0x01 => "Consumer IR Controller",
|
|
||||||
0x10 => "RF Controller",
|
|
||||||
0x11 => "Bluetooth Controller",
|
|
||||||
0x12 => "Broadband Controller",
|
|
||||||
0x20 => "Ethernet Controller (802.1a)",
|
|
||||||
0x21 => "Ethernet Controller (802.1b)",
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
},
|
|
||||||
else => defaultSubclass(subclass),
|
|
||||||
};
|
};
|
||||||
|
return named orelse defaultSubclass(subclass);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn defaultSubclass(subclass: u8) []const u8 {
|
fn defaultSubclass(subclass: u8) []const u8 {
|
||||||
@@ -175,68 +502,34 @@ fn defaultSubclass(subclass: u8) []const u8 {
|
|||||||
/// Returns "" when the prog-IF carries no standard meaning for this class/subclass —
|
/// Returns "" when the prog-IF carries no standard meaning for this class/subclass —
|
||||||
/// callers just print the hex byte in that case.
|
/// callers just print the hex byte in that case.
|
||||||
pub fn progIfName(base: u8, subclass: u8, prog_if: u8) []const u8 {
|
pub fn progIfName(base: u8, subclass: u8, prog_if: u8) []const u8 {
|
||||||
return switch (base) {
|
const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
|
||||||
0x01 => switch (subclass) {
|
.mass_storage => switch (std.enums.fromInt(mass_storage.SubClass, subclass) orelse return "") {
|
||||||
0x06 => switch (prog_if) { // Serial ATA
|
.serial_ata => enumName(mass_storage.serial_ata.ProgIf, prog_if),
|
||||||
0x00 => "Vendor Specific Interface",
|
.non_volatile_memory => enumName(mass_storage.non_volatile_memory.ProgIf, prog_if),
|
||||||
0x01 => "AHCI 1.0",
|
else => null,
|
||||||
0x02 => "Serial Storage Bus",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
0x08 => switch (prog_if) { // Non-Volatile Memory
|
|
||||||
0x01 => "NVMHCI",
|
|
||||||
0x02 => "NVM Express",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
else => "",
|
|
||||||
},
|
},
|
||||||
0x03 => switch (subclass) {
|
.display => switch (std.enums.fromInt(display.SubClass, subclass) orelse return "") {
|
||||||
0x00 => switch (prog_if) { // VGA Compatible
|
.vga_compatible => enumName(display.vga_compatible.ProgIf, prog_if),
|
||||||
0x00 => "VGA Controller",
|
else => null,
|
||||||
0x01 => "8514-Compatible Controller",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
else => "",
|
|
||||||
},
|
},
|
||||||
0x06 => switch (subclass) {
|
.bridge => switch (std.enums.fromInt(bridge.SubClass, subclass) orelse return "") {
|
||||||
0x04 => switch (prog_if) { // PCI-to-PCI Bridge
|
.pci_to_pci => enumName(bridge.pci_to_pci.ProgIf, prog_if),
|
||||||
0x00 => "Normal Decode",
|
else => null,
|
||||||
0x01 => "Subtractive Decode",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
else => "",
|
|
||||||
},
|
},
|
||||||
0x07 => switch (subclass) {
|
.simple_communication => switch (std.enums.fromInt(simple_communication.SubClass, subclass) orelse return "") {
|
||||||
0x00 => switch (prog_if) { // Serial Controller
|
.serial => enumName(simple_communication.serial.ProgIf, prog_if),
|
||||||
0x00 => "8250-Compatible (Generic XT)",
|
else => null,
|
||||||
0x01 => "16450-Compatible",
|
|
||||||
0x02 => "16550-Compatible",
|
|
||||||
0x03 => "16650-Compatible",
|
|
||||||
0x04 => "16750-Compatible",
|
|
||||||
0x05 => "16850-Compatible",
|
|
||||||
0x06 => "16950-Compatible",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
else => "",
|
|
||||||
},
|
},
|
||||||
0x0C => switch (subclass) {
|
.serial_bus => switch (std.enums.fromInt(serial_bus.SubClass, subclass) orelse return "") {
|
||||||
0x03 => switch (prog_if) { // USB Controller
|
.usb => enumName(serial_bus.usb.ProgIf, prog_if),
|
||||||
0x00 => "UHCI Controller",
|
else => null,
|
||||||
0x10 => "OHCI Controller",
|
|
||||||
0x20 => "EHCI (USB2) Controller",
|
|
||||||
0x30 => "XHCI (USB3) Controller",
|
|
||||||
0x80 => "Unspecified",
|
|
||||||
0xFE => "USB Device (not a host controller)",
|
|
||||||
else => "",
|
|
||||||
},
|
|
||||||
else => "",
|
|
||||||
},
|
},
|
||||||
else => "",
|
else => null,
|
||||||
};
|
};
|
||||||
|
return named orelse "";
|
||||||
}
|
}
|
||||||
|
|
||||||
test "decodes the common class codes" {
|
test "decodes the common class codes" {
|
||||||
const std = @import("std");
|
|
||||||
const eq = std.testing.expectEqualStrings;
|
const eq = std.testing.expectEqualStrings;
|
||||||
|
|
||||||
const isa = ClassCode.unpack(0x06_01_00);
|
const isa = ClassCode.unpack(0x06_01_00);
|
||||||
@@ -251,11 +544,25 @@ test "decodes the common class codes" {
|
|||||||
try eq("AHCI 1.0", progIfName(ahci.base, ahci.subclass, ahci.prog_if));
|
try eq("AHCI 1.0", progIfName(ahci.base, ahci.subclass, ahci.prog_if));
|
||||||
|
|
||||||
const xhci = ClassCode.unpack(0x0C_03_30);
|
const xhci = ClassCode.unpack(0x0C_03_30);
|
||||||
|
try eq("Serial Bus Controller", className(xhci.base));
|
||||||
try eq("USB Controller", subclassName(xhci.base, xhci.subclass));
|
try eq("USB Controller", subclassName(xhci.base, xhci.subclass));
|
||||||
try eq("XHCI (USB3) Controller", progIfName(xhci.base, xhci.subclass, xhci.prog_if));
|
try eq("XHCI (USB3) Controller", progIfName(xhci.base, xhci.subclass, xhci.prog_if));
|
||||||
|
}
|
||||||
|
|
||||||
// Unknowns and the "Other" convention.
|
test "unlisted codes fall back without a wrong name" {
|
||||||
try eq("Other", subclassName(0x02, 0x80));
|
const eq = std.testing.expectEqualStrings;
|
||||||
try eq("Unknown", subclassName(0x06, 0x7E));
|
try eq("Unknown", className(0x77)); // no such base class
|
||||||
try eq("", progIfName(0x06, 0x00, 0x00)); // host bridge: prog-IF has no standard name
|
try eq("Other", subclassName(0x01, 0x80)); // 0x80 is the PCI "Other" convention
|
||||||
|
try eq("Unknown", subclassName(0x01, 0x7A)); // unlisted mass-storage subclass
|
||||||
|
try eq("", progIfName(0x01, 0x06, 0x7F)); // no standard SATA prog-IF for 0x7F
|
||||||
|
try eq("", progIfName(0x02, 0x00, 0x00)); // class with no prog-IF taxonomy at all
|
||||||
|
}
|
||||||
|
|
||||||
|
test "named parts pack to the raw triple" {
|
||||||
|
const xhci = ClassCode{
|
||||||
|
.base = @intFromEnum(BaseClass.serial_bus),
|
||||||
|
.subclass = @intFromEnum(serial_bus.SubClass.usb),
|
||||||
|
.prog_if = @intFromEnum(serial_bus.usb.ProgIf.xhci),
|
||||||
|
};
|
||||||
|
try std.testing.expectEqual(@as(u24, 0x0C_03_30), xhci.pack());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,21 +93,21 @@ fn findHpet(buffer: []device.DeviceDescriptor) ?Found {
|
|||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
// Enumerate into a heap buffer (too big for the one-page user stack).
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 32) catch {
|
||||||
_ = runtime.system.write("hpet: out of memory\n");
|
_ = runtime.system.write("system/drivers/hpet: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hpet = findHpet(buffer) orelse {
|
const hpet = findHpet(buffer) orelse {
|
||||||
_ = runtime.system.write("hpet: no HPET with an IRQ\n");
|
_ = runtime.system.write("system/drivers/hpet: no HPET with an IRQ\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!device.claim(hpet.device_id)) {
|
if (!device.claim(hpet.device_id)) {
|
||||||
_ = runtime.system.write("hpet: claim failed\n");
|
_ = runtime.system.write("system/drivers/hpet: claim failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse {
|
const base = device.mmioMap(hpet.device_id, hpet.mmio) orelse {
|
||||||
_ = runtime.system.write("hpet: mmio_map failed\n");
|
_ = runtime.system.write("system/drivers/hpet: mmio_map failed\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ pub fn main() void {
|
|||||||
const gsi = hpet.gsi;
|
const gsi = hpet.gsi;
|
||||||
|
|
||||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
_ = runtime.system.write("hpet: create_ipc_endpoint failed\n");
|
_ = runtime.system.write("system/drivers/hpet: create_ipc_endpoint failed\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ pub fn main() void {
|
|||||||
// Counter period, so we can arm the comparator a fixed wall-clock distance out.
|
// Counter period, so we can arm the comparator a fixed wall-clock distance out.
|
||||||
const femtos_per_tick = rd(base, register_general_cap) >> 32;
|
const femtos_per_tick = rd(base, register_general_cap) >> 32;
|
||||||
if (femtos_per_tick == 0) {
|
if (femtos_per_tick == 0) {
|
||||||
_ = runtime.system.write("hpet: bad HPET period\n");
|
_ = runtime.system.write("system/drivers/hpet: bad HPET period\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick;
|
const ticks_per_ms = 1_000_000_000_000 / femtos_per_tick;
|
||||||
@@ -147,10 +147,10 @@ pub fn main() void {
|
|||||||
wr(base, register_general_configuration, rd(base, register_general_configuration) | configuration_enable);
|
wr(base, register_general_configuration, rd(base, register_general_configuration) | configuration_enable);
|
||||||
|
|
||||||
if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) {
|
if (!device.irqBind(hpet.device_id, hpet.irq, endpoint)) {
|
||||||
_ = runtime.system.write("hpet: irq_bind failed\n");
|
_ = runtime.system.write("system/drivers/hpet: irq_bind failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ = runtime.system.write("hpet: bound, sleeping until the hardware speaks\n");
|
_ = runtime.system.write("system/drivers/hpet: bound, sleeping until the hardware speaks\n");
|
||||||
|
|
||||||
// --- the driver loop -----------------------------------------------------
|
// --- the driver loop -----------------------------------------------------
|
||||||
// Blocked in replyWait. No polling, no spinning: the next line of this function
|
// Blocked in replyWait. No polling, no spinning: the next line of this function
|
||||||
@@ -178,14 +178,14 @@ pub fn main() void {
|
|||||||
wr(base, register_timer0_configuration, rd(base, register_timer0_configuration) & ~tn_int_enb);
|
wr(base, register_timer0_configuration, rd(base, register_timer0_configuration) & ~tn_int_enb);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = runtime.system.write("hpet: irq\n");
|
_ = runtime.system.write("system/drivers/hpet: irq\n");
|
||||||
if (!device.irqAck(hpet.device_id, hpet.irq)) {
|
if (!device.irqAck(hpet.device_id, hpet.irq)) {
|
||||||
_ = runtime.system.write("hpet: irq_ack failed\n");
|
_ = runtime.system.write("system/drivers/hpet: irq_ack failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = runtime.system.write("hpet: ok\n");
|
_ = runtime.system.write("system/drivers/hpet: ok\n");
|
||||||
while (true) runtime.system.sleep(1000);
|
while (true) runtime.system.sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0;
|
//! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0;
|
||||||
//! the bus range and the MMIO apertures follow it), walk every
|
//! the bus range and the MMIO apertures follow it), walk every
|
||||||
//! bus/device/function config header, and log what the walk finds — ending
|
//! bus/device/function config header, and log what the walk finds — ending
|
||||||
//! with "pci-bus: N functions found", which the `pci-scan` scenario compares
|
//! with "/system/drivers/pci-bus: N functions found", which the `pci-scan` scenario compares
|
||||||
//! against the kernel's own enumeration. Registration and reports (M19.2), and
|
//! against the kernel's own enumeration. Registration and reports (M19.2), and
|
||||||
//! the kernel walk's retirement (M19.3), build on this proven-equivalent scan.
|
//! the kernel walk's retirement (M19.3), build on this proven-equivalent scan.
|
||||||
|
|
||||||
@@ -14,12 +14,29 @@ const std = @import("std");
|
|||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const protocol = runtime.device_manager_protocol;
|
const protocol = runtime.device_manager_protocol;
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
|
const pci_class = @import("pci-class");
|
||||||
|
|
||||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
var line: [128]u8 = undefined;
|
var line: [128]u8 = undefined;
|
||||||
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
_ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a discovered function with its (class / subclass / prog-IF) triple decoded
|
||||||
|
/// to human names — the boot-log breadcrumb that says *what* the hardware is, so
|
||||||
|
/// "class 0x01 (Mass Storage Controller) subclass 0x06 (Serial ATA Controller)
|
||||||
|
/// progif 0x01 (AHCI 1.0)" reads straight off the log when writing a new driver.
|
||||||
|
/// A dedicated wider buffer than `writeLine`'s, since the decoded names are long.
|
||||||
|
fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32) void {
|
||||||
|
const cc = pci_class.ClassCode.unpack(@truncate(class_triple));
|
||||||
|
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, "/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, "/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);
|
||||||
|
}
|
||||||
|
|
||||||
var bridge_id: u64 = protocol.no_device;
|
var bridge_id: u64 = protocol.no_device;
|
||||||
var ecam_base: usize = 0;
|
var ecam_base: usize = 0;
|
||||||
var ecam_physical: u64 = 0;
|
var ecam_physical: u64 = 0;
|
||||||
@@ -57,37 +74,37 @@ fn configWrite16(bus: u64, dev: u64, function: u64, offset: u64, value: u16) voi
|
|||||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||||
_ = endpoint;
|
_ = endpoint;
|
||||||
if (!device.claim(bridge_id)) {
|
if (!device.claim(bridge_id)) {
|
||||||
writeLine("pci-bus: unable to claim bridge device {d}\n", .{bridge_id});
|
writeLine("/system/drivers/pci-bus: unable to claim bridge device {d}\n", .{bridge_id});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("pci-bus: out of memory\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: out of memory\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const total = device.enumerate(buffer);
|
const total = device.enumerate(buffer);
|
||||||
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||||
if (d.id == bridge_id) break d;
|
if (d.id == bridge_id) break d;
|
||||||
} else {
|
} else {
|
||||||
writeLine("pci-bus: device {d} not in the device tree\n", .{bridge_id});
|
writeLine("/system/drivers/pci-bus: device {d} not in the device tree\n", .{bridge_id});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
// Resource 0 is the ECAM window (1 MiB of config space per bus); the bus
|
// Resource 0 is the ECAM window (1 MiB of config space per bus); the bus
|
||||||
// range rides beside it. The MMIO apertures (M19.0) come after both.
|
// range rides beside it. The MMIO apertures (M19.0) come after both.
|
||||||
if (descriptor.resource_count < 2 or descriptor.resources[0].kind != @intFromEnum(device.ResourceKind.memory)) {
|
if (descriptor.resource_count < 2 or descriptor.resources[0].kind != @intFromEnum(device.ResourceKind.memory)) {
|
||||||
_ = runtime.system.write("pci-bus: bridge has no ECAM window\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: bridge has no ECAM window\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| {
|
const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| {
|
||||||
if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource;
|
if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource;
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("pci-bus: bridge has no bus range\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: bridge has no bus range\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
start_bus = bus_range.start;
|
start_bus = bus_range.start;
|
||||||
bus_count = bus_range.len;
|
bus_count = bus_range.len;
|
||||||
ecam_physical = descriptor.resources[0].start;
|
ecam_physical = descriptor.resources[0].start;
|
||||||
ecam_base = device.mmioMap(bridge_id, 0) orelse {
|
ecam_base = device.mmioMap(bridge_id, 0) orelse {
|
||||||
_ = runtime.system.write("pci-bus: ECAM mmio_map failed\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: ECAM mmio_map failed\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,17 +116,17 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
if (manager == null) runtime.system.sleep(20);
|
if (manager == null) runtime.system.sleep(20);
|
||||||
}
|
}
|
||||||
const h = manager orelse {
|
const h = manager orelse {
|
||||||
_ = runtime.system.write("pci-bus: no device manager to hello\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: no device manager to hello\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = bridge_id };
|
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = bridge_id };
|
||||||
var reply: [protocol.message_maximum]u8 = undefined;
|
var reply: [protocol.message_maximum]u8 = undefined;
|
||||||
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
|
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
|
||||||
_ = runtime.system.write("pci-bus: hello call failed\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: hello call failed\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
|
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
|
||||||
_ = runtime.system.write("pci-bus: hello refused\n");
|
_ = runtime.system.write("/system/drivers/pci-bus: hello refused\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
manager_handle = h;
|
manager_handle = h;
|
||||||
@@ -137,12 +154,12 @@ fn scan() void {
|
|||||||
if (vendor_device & 0xFFFF == 0xFFFF) continue;
|
if (vendor_device & 0xFFFF == 0xFFFF) continue;
|
||||||
const class_revision = configRead(bus, dev, function, 0x08);
|
const class_revision = configRead(bus, dev, function, 0x08);
|
||||||
found += 1;
|
found += 1;
|
||||||
writeLine("pci-bus: {d}:{d}.{d} class 0x{x:0>6}\n", .{ bus, dev, function, class_revision >> 8 });
|
logFunction(bus, dev, function, class_revision >> 8);
|
||||||
registerAndReport(bus, dev, function, class_revision >> 8);
|
registerAndReport(bus, dev, function, class_revision >> 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeLine("pci-bus: {d} functions found\n", .{found});
|
writeLine("/system/drivers/pci-bus: {d} functions found\n", .{found});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register one function under the bridge and report it to the manager. The
|
/// Register one function under the bridge and report it to the manager. The
|
||||||
@@ -212,7 +229,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
|||||||
}
|
}
|
||||||
|
|
||||||
const registered = device.register(bridge_id, &descriptor) orelse {
|
const registered = device.register(bridge_id, &descriptor) orelse {
|
||||||
writeLine("pci-bus: register refused for {d}:{d}.{d}\n", .{ bus, dev, function });
|
writeLine("/system/drivers/pci-bus: register refused for {d}:{d}.{d}\n", .{ bus, dev, function });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const report = protocol.ChildAdded{
|
const report = protocol.ChildAdded{
|
||||||
@@ -223,7 +240,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
|||||||
};
|
};
|
||||||
var reply: [protocol.message_maximum]u8 = undefined;
|
var reply: [protocol.message_maximum]u8 = undefined;
|
||||||
_ = runtime.ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch {
|
_ = runtime.ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch {
|
||||||
writeLine("pci-bus: child report for {d}:{d}.{d} failed\n", .{ bus, dev, function });
|
writeLine("/system/drivers/pci-bus: child report for {d}:{d}.{d} failed\n", .{ bus, dev, function });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +255,7 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
|
|||||||
pub fn main(init: runtime.process.Init) void {
|
pub fn main(init: runtime.process.Init) void {
|
||||||
const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent
|
const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent
|
||||||
bridge_id = std.fmt.parseInt(u64, argument, 10) catch {
|
bridge_id = std.fmt.parseInt(u64, argument, 10) catch {
|
||||||
writeLine("pci-bus: malformed bridge device id '{s}'\n", .{argument});
|
writeLine("/system/drivers/pci-bus: malformed bridge device id '{s}'\n", .{argument});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
runtime.service.run(protocol.message_maximum, .{
|
runtime.service.run(protocol.message_maximum, .{
|
||||||
|
|||||||
@@ -72,17 +72,17 @@ fn modifierWord(modifiers: scancode.ModifierSnapshot) u32 {
|
|||||||
pub fn main(init: runtime.process.Init) void {
|
pub fn main(init: runtime.process.Init) void {
|
||||||
const hid = init.arguments.get(1).?;
|
const hid = init.arguments.get(1).?;
|
||||||
if (hid.len == 0) {
|
if (hid.len == 0) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no HID argument\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: no HID argument\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeLine("system/drivers/ps2-bus/keyboard: starting for hid {s}\n", .{hid});
|
writeLine("/system/drivers/ps2-bus/keyboard: starting for hid {s}\n", .{hid});
|
||||||
|
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: out of memory\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
||||||
writeLine("system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid});
|
writeLine("/system/drivers/ps2-bus/keyboard: no device for hid {s}\n", .{hid});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,38 +90,38 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
// absent (as today) it defaults to us.
|
// absent (as today) it defaults to us.
|
||||||
const layout_name = init.arguments.get(2) orelse "us";
|
const layout_name = init.arguments.get(2) orelse "us";
|
||||||
const layout = xkb.byName(layout_name) orelse xkb.us;
|
const layout = xkb.byName(layout_name) orelse xkb.us;
|
||||||
writeLine("system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name});
|
writeLine("/system/drivers/ps2-bus/keyboard: layout {s}\n", .{layout.name});
|
||||||
|
|
||||||
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
||||||
// keyboard sends (it owns the controller; we own the decoding).
|
// keyboard sends (it owns the controller; we own the decoding).
|
||||||
const bus = lookupBus() orelse {
|
const bus = lookupBus() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: ps2-bus service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: no endpoint\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: no endpoint\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) };
|
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.keyboard) };
|
||||||
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||||
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach call failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: attach call failed\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (attached.len < @sizeOf(ps2.AttachReply) or
|
if (attached.len < @sizeOf(ps2.AttachReply) or
|
||||||
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
||||||
{
|
{
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: attach refused\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: attach refused\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast keyboard events through the input service so programs can listen
|
// Broadcast keyboard events through the input service so programs can listen
|
||||||
// for them (docs/input.md).
|
// for them (docs/input.md).
|
||||||
var source = runtime.input.connectSource() orelse {
|
var source = runtime.input.connectSource() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: input service unavailable\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: input service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/keyboard: ok\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/keyboard: ok\n");
|
||||||
|
|
||||||
var decoder = scancode.Decoder{};
|
var decoder = scancode.Decoder{};
|
||||||
var state = scancode.KeyboardState{};
|
var state = scancode.KeyboardState{};
|
||||||
|
|||||||
@@ -51,50 +51,50 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
const hid = init.arguments.get(1).?;
|
const hid = init.arguments.get(1).?;
|
||||||
|
|
||||||
if (hid.len == 0) {
|
if (hid.len == 0) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no HID argument\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: no HID argument\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeLine("system/drivers/ps2-bus/mouse: starting for hid {s}\n", .{hid});
|
writeLine("/system/drivers/ps2-bus/mouse: starting for hid {s}\n", .{hid});
|
||||||
|
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: out of memory\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
if (device.findDeviceDescriptorByHid(buffer, hid) == null) {
|
||||||
writeLine("system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
|
writeLine("/system/drivers/ps2-bus/mouse: no device for hid {s}\n", .{hid});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
// Attach to the bus: hand it our endpoint, and it forwards every byte the
|
||||||
// mouse sends (it owns the controller; we own the decoding).
|
// mouse sends (it owns the controller; we own the decoding).
|
||||||
const bus = lookupBus() orelse {
|
const bus = lookupBus() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ps2-bus service unavailable\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: ps2-bus service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: no endpoint\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: no endpoint\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) };
|
var attach = ps2.AttachRequest{ .device_type = @intFromEnum(ps2.DeviceType.mouse) };
|
||||||
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
var attach_reply: [@sizeOf(ps2.AttachReply)]u8 = undefined;
|
||||||
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
const attached = ipc.callCap(bus, std.mem.asBytes(&attach), &attach_reply, endpoint) catch {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach call failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: attach call failed\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (attached.len < @sizeOf(ps2.AttachReply) or
|
if (attached.len < @sizeOf(ps2.AttachReply) or
|
||||||
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
std.mem.bytesToValue(ps2.AttachReply, attach_reply[0..@sizeOf(ps2.AttachReply)]).status != @intFromEnum(ps2.AttachStatus.ok))
|
||||||
{
|
{
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: attach refused\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: attach refused\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast mouse events through the input service so programs can listen
|
// Broadcast mouse events through the input service so programs can listen
|
||||||
// for them (docs/input.md).
|
// for them (docs/input.md).
|
||||||
var source = runtime.input.connectSource() orelse {
|
var source = runtime.input.connectSource() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: input service unavailable\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: input service unavailable\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus/mouse: ok\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus/mouse: ok\n");
|
||||||
|
|
||||||
var assembler = mouse_packet.Assembler{};
|
var assembler = mouse_packet.Assembler{};
|
||||||
var buttons: u32 = 0;
|
var buttons: u32 = 0;
|
||||||
|
|||||||
@@ -30,19 +30,19 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
|||||||
/// attaches, or null if nothing was spawned.
|
/// attaches, or null if nothing was spawned.
|
||||||
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType {
|
fn spawnIdentifiedDriver(controller: ps2.Controller, port: ps2.Port) ?ps2.DeviceType {
|
||||||
const device_type = controller.identifyDevice(port) orelse {
|
const device_type = controller.identifyDevice(port) orelse {
|
||||||
writeLine("system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
writeLine("/system/drivers/ps2-bus: identify timed out on port {s}\n", .{@tagName(port)});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
const driver_name = device_type.driverName() orelse {
|
const driver_name = device_type.driverName() orelse {
|
||||||
writeLine("system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
writeLine("/system/drivers/ps2-bus: unrecognized device on port {s}\n", .{@tagName(port)});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
const hid = device_type.hid() orelse "";
|
const hid = device_type.hid() orelse "";
|
||||||
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
|
if (runtime.system.spawnWithArguments(driver_name, &.{hid}) != null) {
|
||||||
writeLine("system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
|
writeLine("/system/drivers/ps2-bus: port {s} is a {s}, spawned {s}\n", .{ @tagName(port), hid, driver_name });
|
||||||
return device_type;
|
return device_type;
|
||||||
}
|
}
|
||||||
writeLine("system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
writeLine("/system/drivers/ps2-bus: failed to spawn {s}\n", .{driver_name});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
|
|||||||
const device_type = maybe_type orelse continue;
|
const device_type = maybe_type orelse continue;
|
||||||
if (@intFromEnum(device_type) != request.device_type) continue;
|
if (@intFromEnum(device_type) != request.device_type) continue;
|
||||||
port_endpoints[port_index] = endpoint;
|
port_endpoints[port_index] = endpoint;
|
||||||
writeLine("system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)});
|
writeLine("/system/drivers/ps2-bus: {s} driver attached\n", .{@tagName(device_type)});
|
||||||
return reply.write(out, .ok);
|
return reply.write(out, .ok);
|
||||||
}
|
}
|
||||||
return reply.write(out, .no_such_device);
|
return reply.write(out, .no_such_device);
|
||||||
@@ -91,7 +91,7 @@ fn handleAttach(message: []const u8, got: ipc.Received, out: []u8) usize {
|
|||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: out of memory\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,16 +103,16 @@ pub fn main() void {
|
|||||||
// is on which port is decided later by identify, not by this HID.
|
// is on which port is decided later by identify, not by this HID.
|
||||||
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_keyboard.hid());
|
const maybe_controller_device_descriptor = device.findDeviceDescriptorByHid(buffer, acpi_ids.HardwareId.ps2_keyboard.hid());
|
||||||
if (maybe_controller_device_descriptor) |controller_device_descriptor| {
|
if (maybe_controller_device_descriptor) |controller_device_descriptor| {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: found PS/2 controller\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: found PS/2 controller\n");
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: initializing controller\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: initializing controller\n");
|
||||||
|
|
||||||
if (!device.claim(controller_device_descriptor.id)) {
|
if (!device.claim(controller_device_descriptor.id)) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: unable to claim controller \n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: unable to claim controller \n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const controller = ps2.Controller.init(controller_device_descriptor) orelse {
|
const controller = ps2.Controller.init(controller_device_descriptor) orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IO ports\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller is missing its IO ports\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
maybe_controller = controller;
|
maybe_controller = controller;
|
||||||
@@ -123,7 +123,7 @@ pub fn main() void {
|
|||||||
controller.flushOutputBuffer();
|
controller.flushOutputBuffer();
|
||||||
|
|
||||||
const current = controller.readConfigurationByte() orelse {
|
const current = controller.readConfigurationByte() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,49 +132,49 @@ pub fn main() void {
|
|||||||
ps2.configuration_first_port_translation);
|
ps2.configuration_first_port_translation);
|
||||||
|
|
||||||
if (controller.writeConfigurationByte(update) == null) {
|
if (controller.writeConfigurationByte(update) == null) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller.performSelfTest()) |reply| {
|
if (controller.performSelfTest()) |reply| {
|
||||||
if (reply != ps2.response_controller_test_passed) {
|
if (reply != ps2.response_controller_test_passed) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: perform controller self test failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: perform controller self test failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller self test timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller self test timed out\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
has_two_channels = controller.hasTwoChannels() orelse {
|
has_two_channels = controller.hasTwoChannels() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller channels timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller channels timed out\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (has_two_channels) {
|
if (has_two_channels) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: has two channels\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: has two channels\n");
|
||||||
// keep the bus quiet until we have tested the ports and are ready to use them
|
// keep the bus quiet until we have tested the ports and are ready to use them
|
||||||
controller.disablePort(.two);
|
controller.disablePort(.two);
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: has one channel\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: has one channel\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface tests: always test port 1, test port 2 only if it exists
|
// interface tests: always test port 1, test port 2 only if it exists
|
||||||
const port_one_works = (controller.testPort(.one) orelse {
|
const port_one_works = (controller.testPort(.one) orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: port 1 test timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: port 1 test timed out\n");
|
||||||
return;
|
return;
|
||||||
}) == ps2.response_port_test_passed;
|
}) == ps2.response_port_test_passed;
|
||||||
|
|
||||||
var port_two_works = false;
|
var port_two_works = false;
|
||||||
if (has_two_channels) {
|
if (has_two_channels) {
|
||||||
port_two_works = (controller.testPort(.two) orelse {
|
port_two_works = (controller.testPort(.two) orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: port 2 test timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: port 2 test timed out\n");
|
||||||
return;
|
return;
|
||||||
}) == ps2.response_port_test_passed;
|
}) == ps2.response_port_test_passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!port_one_works and !port_two_works) {
|
if (!port_one_works and !port_two_works) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: no usable ports\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: no usable ports\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,16 +188,16 @@ pub fn main() void {
|
|||||||
// abort bring-up of the other one
|
// abort bring-up of the other one
|
||||||
if (port_one_works) {
|
if (port_one_works) {
|
||||||
if (controller.resetDevice(.one)) |passed| {
|
if (controller.resetDevice(.one)) |passed| {
|
||||||
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset failed\n");
|
if (!passed) _ = runtime.system.write("/system/drivers/ps2-bus: port 1 device reset failed\n");
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: port 1 device reset timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: port 1 device reset timed out\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (port_two_works) {
|
if (port_two_works) {
|
||||||
if (controller.resetDevice(.two)) |passed| {
|
if (controller.resetDevice(.two)) |passed| {
|
||||||
if (!passed) _ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset failed\n");
|
if (!passed) _ = runtime.system.write("/system/drivers/ps2-bus: port 2 device reset failed\n");
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: port 2 device reset timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: port 2 device reset timed out\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,13 +207,13 @@ pub fn main() void {
|
|||||||
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.one)] = spawnIdentifiedDriver(controller, .one);
|
if (port_one_works) port_device_types[@intFromEnum(ps2.Port.one)] = spawnIdentifiedDriver(controller, .one);
|
||||||
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two);
|
if (port_two_works) port_device_types[@intFromEnum(ps2.Port.two)] = spawnIdentifiedDriver(controller, .two);
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: no PS/2 controller found\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: no PS/2 controller found\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const controller = maybe_controller.?;
|
const controller = maybe_controller.?;
|
||||||
const interrupt_index = maybe_interrupt_index orelse {
|
const interrupt_index = maybe_interrupt_index orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller is missing its IRQ\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller is missing its IRQ\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,11 +221,11 @@ pub fn main() void {
|
|||||||
// well-known id so the children can find it, the way input subscribers find
|
// well-known id so the children can find it, the way input subscribers find
|
||||||
// the input service.
|
// the input service.
|
||||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: no endpoint\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: no endpoint\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (!ipc.register(.ps2_bus, endpoint)) {
|
if (!ipc.register(.ps2_bus, endpoint)) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: register failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: register failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ pub fn main() void {
|
|||||||
// let the controller raise them — an interrupt with nobody bound is lost.
|
// let the controller raise them — an interrupt with nobody bound is lost.
|
||||||
controller.drainOutputBuffer();
|
controller.drainOutputBuffer();
|
||||||
if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) {
|
if (!device.irqBind(controller.device_id, interrupt_index, endpoint)) {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: irq_bind failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: irq_bind failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,21 +253,21 @@ pub fn main() void {
|
|||||||
.gsi = descriptor.resources[auxiliary_index].start,
|
.gsi = descriptor.resources[auxiliary_index].start,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: auxiliary irq_bind failed\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: auxiliary irq_bind failed\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var configuration = controller.readConfigurationByte() orelse {
|
var configuration = controller.readConfigurationByte() orelse {
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: controller configuration timed out\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: controller configuration timed out\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit();
|
if (port_device_types[@intFromEnum(ps2.Port.one)] != null) configuration |= ps2.Port.one.interruptBit();
|
||||||
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit();
|
if (maybe_auxiliary_interrupt != null) configuration |= ps2.Port.two.interruptBit();
|
||||||
_ = controller.writeConfigurationByte(configuration);
|
_ = controller.writeConfigurationByte(configuration);
|
||||||
|
|
||||||
_ = runtime.system.write("system/drivers/ps2-bus: ok\n");
|
_ = runtime.system.write("/system/drivers/ps2-bus: ok\n");
|
||||||
|
|
||||||
// The forwarding loop: an IRQ1 notification drains the output buffer, routing
|
// The forwarding loop: an IRQ1 notification drains the output buffer, routing
|
||||||
// each byte to the attached driver of the port it came from; a client message
|
// each byte to the attached driver of the port it came from; a client message
|
||||||
|
|||||||
@@ -33,20 +33,20 @@ var controller_id: u64 = protocol.no_device;
|
|||||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||||
_ = endpoint;
|
_ = endpoint;
|
||||||
if (!device.claim(controller_id)) {
|
if (!device.claim(controller_id)) {
|
||||||
writeLine("usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id});
|
writeLine("/system/drivers/usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch our own descriptor back for the controller's resources.
|
// Fetch our own descriptor back for the controller's resources.
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("usb-xhci-bus: out of memory\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: out of memory\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const total = device.enumerate(buffer);
|
const total = device.enumerate(buffer);
|
||||||
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| {
|
||||||
if (d.id == controller_id) break d;
|
if (d.id == controller_id) break d;
|
||||||
} else {
|
} else {
|
||||||
writeLine("usb-xhci-bus: device {d} not in the device tree\n", .{controller_id});
|
writeLine("/system/drivers/usb-xhci-bus: device {d} not in the device tree\n", .{controller_id});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,16 +59,16 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
break resource;
|
break resource;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writeLine("usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id});
|
writeLine("/system/drivers/usb-xhci-bus: controller device {d} has no register BAR\n", .{controller_id});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{
|
writeLine("/system/drivers/usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{
|
||||||
controller_id,
|
controller_id,
|
||||||
register_window.start,
|
register_window.start,
|
||||||
register_window.len,
|
register_window.len,
|
||||||
});
|
});
|
||||||
register_base = device.mmioMap(controller_id, register_index) orelse {
|
register_base = device.mmioMap(controller_id, register_index) orelse {
|
||||||
_ = runtime.system.write("usb-xhci-bus: mmio_map failed\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: mmio_map failed\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,20 +81,20 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
if (manager == null) runtime.system.sleep(20);
|
if (manager == null) runtime.system.sleep(20);
|
||||||
}
|
}
|
||||||
const h = manager orelse {
|
const h = manager orelse {
|
||||||
_ = runtime.system.write("usb-xhci-bus: no device manager to hello\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: no device manager to hello\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id };
|
const hello = protocol.Hello{ .role = @intFromEnum(protocol.Role.bus), .device_id = controller_id };
|
||||||
var reply: [protocol.message_maximum]u8 = undefined;
|
var reply: [protocol.message_maximum]u8 = undefined;
|
||||||
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
|
const n = runtime.ipc.call(h, std.mem.asBytes(&hello), &reply) catch {
|
||||||
_ = runtime.system.write("usb-xhci-bus: hello call failed\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: hello call failed\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
|
if (n < protocol.reply_size or std.mem.bytesToValue(protocol.HelloReply, reply[0..protocol.reply_size]).status != 0) {
|
||||||
_ = runtime.system.write("usb-xhci-bus: hello refused\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: hello refused\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_ = runtime.system.write("usb-xhci-bus: hello acknowledged\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: hello acknowledged\n");
|
||||||
|
|
||||||
scanPorts(h);
|
scanPorts(h);
|
||||||
return true;
|
return true;
|
||||||
@@ -108,6 +108,22 @@ fn readRegister(offset: usize) u32 {
|
|||||||
return register.*;
|
return register.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The xHCI default Protocol Speed IDs (the PORTSC port-speed field, bits 13:10)
|
||||||
|
/// decoded to human names — the boot-log breadcrumb for what actually enumerated on
|
||||||
|
/// a port, the USB analog of the pci-bus class-code line. A controller may redefine
|
||||||
|
/// these through its Supported Protocol capability, but the defaults cover every
|
||||||
|
/// speed QEMU and real hardware report at this (pre-descriptor) stage.
|
||||||
|
fn speedName(speed: u32) []const u8 {
|
||||||
|
return switch (speed) {
|
||||||
|
1 => "Full-speed (USB 2.0, 12 Mb/s)",
|
||||||
|
2 => "Low-speed (USB 2.0, 1.5 Mb/s)",
|
||||||
|
3 => "High-speed (USB 2.0, 480 Mb/s)",
|
||||||
|
4 => "SuperSpeed (USB 3.0, 5 Gb/s)",
|
||||||
|
5 => "SuperSpeedPlus (USB 3.1, 10 Gb/s)",
|
||||||
|
else => "unknown speed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// The root-hub port scan: read the capability registers for the port count
|
/// The root-hub port scan: read the capability registers for the port count
|
||||||
/// and the operational-register offset, then one PORTSC per port. The connect
|
/// and the operational-register offset, then one PORTSC per port. The connect
|
||||||
/// bit (CCS) and the speed field reflect hardware state directly — no
|
/// bit (CCS) and the speed field reflect hardware state directly — no
|
||||||
@@ -119,7 +135,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
|||||||
const capability_length = readRegister(0) & 0xFF;
|
const capability_length = readRegister(0) & 0xFF;
|
||||||
const structural = readRegister(0x04);
|
const structural = readRegister(0x04);
|
||||||
const maximum_ports: u32 = structural >> 24;
|
const maximum_ports: u32 = structural >> 24;
|
||||||
writeLine("usb-xhci-bus: {d} root-hub ports\n", .{maximum_ports});
|
writeLine("/system/drivers/usb-xhci-bus: {d} root-hub ports\n", .{maximum_ports});
|
||||||
|
|
||||||
// PORTSC registers: operational base + 0x400 + 0x10 per port (1-based).
|
// PORTSC registers: operational base + 0x400 + 0x10 per port (1-based).
|
||||||
var port: u32 = 1;
|
var port: u32 = 1;
|
||||||
@@ -129,7 +145,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
|||||||
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
if (port_status & 1 == 0) continue; // CCS: nothing connected
|
||||||
connected += 1;
|
connected += 1;
|
||||||
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
|
const speed = (port_status >> 10) & 0xF; // the PORTSC port-speed class
|
||||||
writeLine("usb-xhci-bus: port {d} connected (speed class {d})\n", .{ port, speed });
|
writeLine("/system/drivers/usb-xhci-bus: port {d} connected — {s} (speed class {d})\n", .{ port, speedName(speed), speed });
|
||||||
|
|
||||||
const report = protocol.ChildAdded{
|
const report = protocol.ChildAdded{
|
||||||
.parent = controller_id,
|
.parent = controller_id,
|
||||||
@@ -138,11 +154,11 @@ fn scanPorts(manager: runtime.ipc.Handle) void {
|
|||||||
};
|
};
|
||||||
var reply: [protocol.message_maximum]u8 = undefined;
|
var reply: [protocol.message_maximum]u8 = undefined;
|
||||||
_ = runtime.ipc.call(manager, std.mem.asBytes(&report), &reply) catch {
|
_ = runtime.ipc.call(manager, std.mem.asBytes(&report), &reply) catch {
|
||||||
writeLine("usb-xhci-bus: child report for port {d} failed\n", .{port});
|
writeLine("/system/drivers/usb-xhci-bus: child report for port {d} failed\n", .{port});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (connected == 0) _ = runtime.system.write("usb-xhci-bus: no devices connected\n");
|
if (connected == 0) _ = runtime.system.write("/system/drivers/usb-xhci-bus: no devices connected\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// No bus protocol to serve yet — transfer requests arrive with the USB track.
|
/// No bus protocol to serve yet — transfer requests arrive with the USB track.
|
||||||
@@ -156,11 +172,11 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
|
|||||||
|
|
||||||
pub fn main(init: runtime.process.Init) void {
|
pub fn main(init: runtime.process.Init) void {
|
||||||
const argument = init.arguments.get(1) orelse {
|
const argument = init.arguments.get(1) orelse {
|
||||||
_ = runtime.system.write("usb-xhci-bus: missing controller device id (argv[1])\n");
|
_ = runtime.system.write("/system/drivers/usb-xhci-bus: missing controller device id (argv[1])\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
controller_id = std.fmt.parseInt(u64, argument, 10) catch {
|
controller_id = std.fmt.parseInt(u64, argument, 10) catch {
|
||||||
writeLine("usb-xhci-bus: malformed controller device id '{s}'\n", .{argument});
|
writeLine("/system/drivers/usb-xhci-bus: malformed controller device id '{s}'\n", .{argument});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
runtime.service.run(protocol.message_maximum, .{
|
runtime.service.run(protocol.message_maximum, .{
|
||||||
|
|||||||
+26
-26
@@ -77,12 +77,12 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
architecture.setFaultHandler(onException);
|
architecture.setFaultHandler(onException);
|
||||||
architecture.init();
|
architecture.init();
|
||||||
|
|
||||||
status("danos: initialising kernel...\n");
|
status("/system/kernel: initialising kernel...\n");
|
||||||
log.write(if (console.present())
|
log.write(if (console.present())
|
||||||
"danos: framebuffer console online (bootstrap; graphics driver later)\n"
|
"/system/kernel: framebuffer console online (bootstrap; graphics driver later)\n"
|
||||||
else
|
else
|
||||||
"danos: no framebuffer (headless) -> logging to serial/debugcon only\n");
|
"/system/kernel: no framebuffer (headless) -> logging to serial/debugcon only\n");
|
||||||
log.write("danos: cpu tables online (GDT, IDT, TSS)\n");
|
log.write("/system/kernel: cpu tables online (GDT, IDT, TSS)\n");
|
||||||
log.print(" resolution : {d}x{d}\n", .{ fb.width, fb.height });
|
log.print(" resolution : {d}x{d}\n", .{ fb.width, fb.height });
|
||||||
log.print(" pitch : {d} bytes\n", .{fb.pitch});
|
log.print(" pitch : {d} bytes\n", .{fb.pitch});
|
||||||
log.print(" format : {s}\n", .{@tagName(fb.format)});
|
log.print(" format : {s}\n", .{@tagName(fb.format)});
|
||||||
@@ -105,7 +105,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
const total_bytes = total_pages * abi.page_size;
|
const total_bytes = total_pages * abi.page_size;
|
||||||
const gib = 1 << 30;
|
const gib = 1 << 30;
|
||||||
|
|
||||||
log.write("\ndanos: physical memory\n");
|
log.write("\n/system/kernel: physical memory\n");
|
||||||
log.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
|
log.print(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
|
||||||
log.print(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)});
|
log.print(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)});
|
||||||
log.print(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)});
|
log.print(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)});
|
||||||
@@ -119,7 +119,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// until SMP bring-up; 0 means none was available (we stay uniprocessor).
|
// until SMP bring-up; 0 means none was available (we stay uniprocessor).
|
||||||
ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0;
|
ap_trampoline_page = pmm.allocBelow(0x100000) orelse 0;
|
||||||
const s1 = pmm.stats();
|
const s1 = pmm.stats();
|
||||||
log.print("\ndanos: frame allocator online\n", .{});
|
log.print("\n/system/kernel: frame allocator online\n", .{});
|
||||||
log.print(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) });
|
log.print(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) });
|
||||||
const f0 = pmm.alloc();
|
const f0 = pmm.alloc();
|
||||||
const f1 = pmm.alloc();
|
const f1 = pmm.alloc();
|
||||||
@@ -133,14 +133,14 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// Switch off the firmware's page tables onto our own (with real permissions).
|
// Switch off the firmware's page tables onto our own (with real permissions).
|
||||||
architecture.enablePaging(pmm.alloc, pmm.free, boot_information);
|
architecture.enablePaging(pmm.alloc, pmm.free, boot_information);
|
||||||
log.checkpoint(cp_paging);
|
log.checkpoint(cp_paging);
|
||||||
log.print("\ndanos: paging enabled\n", .{});
|
log.print("\n/system/kernel: paging enabled\n", .{});
|
||||||
log.print(" page tables: root = 0x{x:0>16}\n", .{architecture.activePageTable()});
|
log.print(" page tables: root = 0x{x:0>16}\n", .{architecture.activePageTable()});
|
||||||
log.print(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_information.kernel_segment_count});
|
log.print(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_information.kernel_segment_count});
|
||||||
|
|
||||||
// Bring up the kernel heap (dynamic allocation), built on the VMM.
|
// Bring up the kernel heap (dynamic allocation), built on the VMM.
|
||||||
heap.init();
|
heap.init();
|
||||||
log.checkpoint(cp_heap);
|
log.checkpoint(cp_heap);
|
||||||
log.write("\ndanos: kernel heap online\n");
|
log.write("\n/system/kernel: kernel heap online\n");
|
||||||
// Measure the amount of resources the kernel is actually using
|
// Measure the amount of resources the kernel is actually using
|
||||||
const s2 = pmm.stats();
|
const s2 = pmm.stats();
|
||||||
log.print(" Kernel footprint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)});
|
log.print(" Kernel footprint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)});
|
||||||
@@ -156,7 +156,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
};
|
};
|
||||||
if (platform.discover(boot_information, heap.allocator(), hal)) |devtree| {
|
if (platform.discover(boot_information, heap.allocator(), hal)) |devtree| {
|
||||||
var device_tree = devtree;
|
var device_tree = devtree;
|
||||||
log.write("\ndanos: device discovery online\n");
|
log.write("\n/system/kernel: device discovery online\n");
|
||||||
device_tree.dump(log.write);
|
device_tree.dump(log.write);
|
||||||
|
|
||||||
// Snapshot the device tree for user-space drivers (device_enumerate/claim/
|
// Snapshot the device tree for user-space drivers (device_enumerate/claim/
|
||||||
@@ -164,7 +164,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
devices_broker.init(&device_tree);
|
devices_broker.init(&device_tree);
|
||||||
if (devices_broker.dropped > 0) {
|
if (devices_broker.dropped > 0) {
|
||||||
// Otherwise entirely silent: drivers would just never see that hardware.
|
// Otherwise entirely silent: drivers would just never see that hardware.
|
||||||
log.print("danos: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped});
|
log.print("/system/kernel: WARNING {d} device(s) dropped — table full\n", .{devices_broker.dropped});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
|
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
|
||||||
@@ -173,7 +173,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
|
|
||||||
// Power register map extracted from the FADT + AML, for confidence it parsed.
|
// Power register map extracted from the FADT + AML, for confidence it parsed.
|
||||||
const pw = platform.powerInformation();
|
const pw = platform.powerInformation();
|
||||||
log.write("danos: power\n");
|
log.write("/system/kernel: power\n");
|
||||||
log.print(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width });
|
log.print(" pm1a_cnt : {s} 0x{x} (width {d})\n", .{ if (pw.pm1a_cnt.mmio) "mmio" else "io", pw.pm1a_cnt.address, pw.pm1a_cnt.width });
|
||||||
if (pw.s5) |s| {
|
if (pw.s5) |s| {
|
||||||
log.print(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b });
|
log.print(" S5 slp_typ : a={d} b={d}\n", .{ s.slp_typ_a, s.slp_typ_b });
|
||||||
@@ -221,7 +221,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
});
|
});
|
||||||
if (pinfo.spcr_uart) |u| architecture.serialReconfigure(u.mmio, u.address);
|
if (pinfo.spcr_uart) |u| architecture.serialReconfigure(u.mmio, u.address);
|
||||||
|
|
||||||
log.write("danos: platform\n");
|
log.write("/system/kernel: platform\n");
|
||||||
log.print(" 8259 PIC : {s}\n", .{if (pinfo.pic_present) "present" else "absent"});
|
log.print(" 8259 PIC : {s}\n", .{if (pinfo.pic_present) "present" else "absent"});
|
||||||
log.print(" lapic base : 0x{x}\n", .{pinfo.lapic_base});
|
log.print(" lapic base : 0x{x}\n", .{pinfo.lapic_base});
|
||||||
log.print(" hpet base : 0x{x}\n", .{hpet_base});
|
log.print(" hpet base : 0x{x}\n", .{hpet_base});
|
||||||
@@ -237,7 +237,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
if (platform.cpusDropped() > 0)
|
if (platform.cpusDropped() > 0)
|
||||||
log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()});
|
log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()});
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.print("\ndanos: device discovery failed: {s}\n", .{@errorName(err)});
|
log.print("\n/system/kernel: device discovery failed: {s}\n", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
log.checkpoint(cp_discovery);
|
log.checkpoint(cp_discovery);
|
||||||
|
|
||||||
@@ -248,14 +248,14 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// Register the current context as the first task before enabling preemption.
|
// Register the current context as the first task before enabling preemption.
|
||||||
scheduler.init(4);
|
scheduler.init(4);
|
||||||
log.checkpoint(cp_scheduler);
|
log.checkpoint(cp_scheduler);
|
||||||
log.write("\ndanos: scheduler online\n");
|
log.write("\n/system/kernel: scheduler online\n");
|
||||||
|
|
||||||
// Start the timer and unmask interrupts — the kernel now has a heartbeat, and
|
// Start the timer and unmask interrupts — the kernel now has a heartbeat, and
|
||||||
// the timer preempts among tasks.
|
// the timer preempts among tasks.
|
||||||
architecture.startTimer();
|
architecture.startTimer();
|
||||||
architecture.enableInterrupts();
|
architecture.enableInterrupts();
|
||||||
log.checkpoint(cp_timer);
|
log.checkpoint(cp_timer);
|
||||||
log.print("danos: timer online ({d} Hz tick; timer clock {d} MHz, clock {d} MHz; calibrated via {s})\n", .{ architecture.timer_hz, architecture.timerClockHz() / 1_000_000, architecture.clockHz() / 1_000_000, architecture.timerCalibrationSource() });
|
log.print("/system/kernel: timer online ({d} Hz tick; timer clock {d} MHz, clock {d} MHz; calibrated via {s})\n", .{ architecture.timer_hz, architecture.timerClockHz() / 1_000_000, architecture.clockHz() / 1_000_000, architecture.timerCalibrationSource() });
|
||||||
|
|
||||||
// Wake the other cores (application processors). A no-op on a single-core
|
// Wake the other cores (application processors). A no-op on a single-core
|
||||||
// machine; on SMP each AP climbs to long mode and reports in (docs/smp.md).
|
// machine; on SMP each AP climbs to long mode and reports in (docs/smp.md).
|
||||||
@@ -269,7 +269,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.checkpoint(cp_running);
|
log.checkpoint(cp_running);
|
||||||
status("kernel initialised.\n");
|
status("/system/kernel: initialised.\n");
|
||||||
|
|
||||||
// Publish the initial-ramdisk so user space can `system_spawn` its bundled
|
// Publish the initial-ramdisk so user space can `system_spawn` its bundled
|
||||||
// binaries by name. The kernel no longer launches them itself: init is the
|
// binaries by name. The kernel no longer launches them itself: init is the
|
||||||
@@ -282,10 +282,10 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// manager then discovers the hardware and spawns each driver. init runs on its own
|
// manager then discovers the hardware and spawns each driver. init runs on its own
|
||||||
// address space, preemptively — this boot context becomes the BSP's idle loop.
|
// address space, preemptively — this boot context becomes the BSP's idle loop.
|
||||||
if (boot_information.init_len != 0) {
|
if (boot_information.init_len != 0) {
|
||||||
status("starting /system/services/init...\n");
|
status("/system/kernel: starting /system/services/init...\n");
|
||||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
|
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.init_base)))[0..boot_information.init_len];
|
||||||
process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| {
|
process.spawnProcess(image, 4, &.{"/system/services/init"}) catch |err| {
|
||||||
statusPrint("/system/services/init failed to load: {s}\n", .{@errorName(err)});
|
statusPrint("/system/kernel: /system/services/init failed to load: {s}\n", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
status("no /system/services/init on the boot volume.\n");
|
status("no /system/services/init on the boot volume.\n");
|
||||||
@@ -294,7 +294,7 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
|||||||
// Become the idle task: drop below every real task and halt until an
|
// Become the idle task: drop below every real task and halt until an
|
||||||
// interrupt. The timer keeps preempting into init and any other work.
|
// interrupt. The timer keeps preempting into init and any other work.
|
||||||
scheduler.setPriority(0);
|
scheduler.setPriority(0);
|
||||||
status("\nkernel idle; user space is running.\n");
|
status("\n/system/kernel: kernel idle; user space is running.\n");
|
||||||
architecture.halt();
|
architecture.halt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ fn bringUpSecondaries() void {
|
|||||||
// vector addresses it). It's kept for the system's life — armed only during a
|
// vector addresses it). It's kept for the system's life — armed only during a
|
||||||
// wake, inert (zeroed, non-executable) otherwise — so cores can be re-woken later.
|
// wake, inert (zeroed, non-executable) otherwise — so cores can be re-woken later.
|
||||||
if (ap_trampoline_page == 0) {
|
if (ap_trampoline_page == 0) {
|
||||||
log.write("danos: smp: no low page for the AP trampoline; staying uniprocessor\n");
|
log.write("/system/kernel: smp: no low page for the AP trampoline; staying uniprocessor\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
architecture.setTrampolinePage(ap_trampoline_page);
|
architecture.setTrampolinePage(ap_trampoline_page);
|
||||||
@@ -333,7 +333,7 @@ fn bringUpSecondaries() void {
|
|||||||
if (std.mem.eql(u8, tc, "smp-retry")) architecture.testFailNextWakes(1);
|
if (std.mem.eql(u8, tc, "smp-retry")) architecture.testFailNextWakes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.print("\ndanos: bringing up {d} application processor(s)\n", .{cores.len - 1});
|
log.print("\n/system/kernel: bringing up {d} application processor(s)\n", .{cores.len - 1});
|
||||||
const maximum_wake_attempts = 3; // a core that misses the first INIT-SIPI-SIPI gets retried
|
const maximum_wake_attempts = 3; // a core that misses the first INIT-SIPI-SIPI gets retried
|
||||||
for (cores[1..], 1..) |core, index| {
|
for (cores[1..], 1..) |core, index| {
|
||||||
const stack = heap.allocator().alloc(u8, parameters.kernel_stack_size) catch {
|
const stack = heap.allocator().alloc(u8, parameters.kernel_stack_size) catch {
|
||||||
@@ -344,7 +344,7 @@ fn bringUpSecondaries() void {
|
|||||||
// This core's dedicated fault stack — allocated only now that the core is
|
// This core's dedicated fault stack — allocated only now that the core is
|
||||||
// real, rather than reserved statically for every possible core.
|
// real, rather than reserved statically for every possible core.
|
||||||
const fault_stack = heap.allocator().alloc(u8, architecture.fault_stack_size) catch {
|
const fault_stack = heap.allocator().alloc(u8, architecture.fault_stack_size) catch {
|
||||||
log.print(" cpu apic_id {d}: no fault stack; skipped\n", .{core.apic_id});
|
log.print("/system/kernel: cpu apic_id {d}: no fault stack; skipped\n", .{core.apic_id});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
architecture.setFaultStack(index, (@intFromPtr(fault_stack.ptr) + fault_stack.len) & ~@as(usize, 15));
|
architecture.setFaultStack(index, (@intFromPtr(fault_stack.ptr) + fault_stack.len) & ~@as(usize, 15));
|
||||||
@@ -353,14 +353,14 @@ fn bringUpSecondaries() void {
|
|||||||
while (attempt <= maximum_wake_attempts) : (attempt += 1) {
|
while (attempt <= maximum_wake_attempts) : (attempt += 1) {
|
||||||
if (architecture.startSecondary(core.apic_id, stack_top, @intFromPtr(pc), index)) {
|
if (architecture.startSecondary(core.apic_id, stack_top, @intFromPtr(pc), index)) {
|
||||||
pc.online = true;
|
pc.online = true;
|
||||||
log.print(" cpu apic_id {d}: online (attempt {d})\n", .{ core.apic_id, attempt });
|
log.print("/system/kernel: cpu apic_id {d}: online (attempt {d})\n", .{ core.apic_id, attempt });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (attempt == maximum_wake_attempts)
|
if (attempt == maximum_wake_attempts)
|
||||||
log.print(" cpu apic_id {d}: no response after {d} attempts (parked)\n", .{ core.apic_id, maximum_wake_attempts });
|
log.print("/system/kernel: cpu apic_id {d}: no response after {d} attempts (parked)\n", .{ core.apic_id, maximum_wake_attempts });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.print("danos: {d}/{d} cores online\n", .{ scheduler.onlineCount(), cores.len });
|
log.print("/system/kernel: {d}/{d} cores online\n", .{ scheduler.onlineCount(), cores.len });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A user-facing status line: to the diagnostic `log` *and* the on-screen console
|
/// A user-facing status line: to the diagnostic `log` *and* the on-screen console
|
||||||
@@ -427,7 +427,7 @@ fn exitReasonForVector(vector: u64) abi.ExitReason {
|
|||||||
|
|
||||||
fn onException(state: *const architecture.CpuState) noreturn {
|
fn onException(state: *const architecture.CpuState) noreturn {
|
||||||
if (architecture.fromUser(state) and scheduler.currentIsUserProcess() and recoverableFault(state.vector)) {
|
if (architecture.fromUser(state) and scheduler.currentIsUserProcess() and recoverableFault(state.vector)) {
|
||||||
statusPrint("\ndanos: process {d} ({s}) killed by {s} (vector {d}) on core {d}\n", .{ scheduler.currentId(), scheduler.current().name(), architecture.exceptionName(state.vector), state.vector, scheduler.currentCpuIndex() });
|
statusPrint("\n/system/kernel: process {d} ({s}) killed by {s} (vector {d}) on core {d}\n", .{ scheduler.currentId(), scheduler.current().name(), architecture.exceptionName(state.vector), state.vector, scheduler.currentCpuIndex() });
|
||||||
statusPrint(" error code : 0x{x}\n", .{state.error_code});
|
statusPrint(" error code : 0x{x}\n", .{state.error_code});
|
||||||
statusPrint(" IP : 0x{x:0>16}\n", .{architecture.instructionPointer(state)});
|
statusPrint(" IP : 0x{x:0>16}\n", .{architecture.instructionPointer(state)});
|
||||||
if (architecture.faultAddress(state)) |address| statusPrint(" fault addr : 0x{x:0>16}\n", .{address});
|
if (architecture.faultAddress(state)) |address| statusPrint(" fault addr : 0x{x:0>16}\n", .{address});
|
||||||
|
|||||||
+33
-28
@@ -207,6 +207,13 @@ fn eql(a: []const u8, b: []const u8) bool {
|
|||||||
return std.mem.eql(u8, a, b);
|
return std.mem.eql(u8, a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the captured last-write buffer *contains* `needle`. Markers are
|
||||||
|
/// matched as substrings, not prefixes, so a service's source-path debug prefix
|
||||||
|
/// (`system/drivers/hpet: ok`) still satisfies a marker like `hpet: ok`.
|
||||||
|
fn bufferHas(needle: []const u8) bool {
|
||||||
|
return std.mem.indexOf(u8, process.write_buffer[0..process.write_len], needle) != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Non-destructive checks of the memory map and frame allocator.
|
/// Non-destructive checks of the memory map and frame allocator.
|
||||||
fn smoke(boot_information: *const BootInformation) void {
|
fn smoke(boot_information: *const BootInformation) void {
|
||||||
log("DANOS-TEST-BEGIN: smoke\n", .{});
|
log("DANOS-TEST-BEGIN: smoke\n", .{});
|
||||||
@@ -1376,7 +1383,7 @@ fn initTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const prefix = "init: heartbeat";
|
const prefix = "init: heartbeat";
|
||||||
const beat_ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const beat_ok = bufferHas(prefix);
|
||||||
check("init produced repeated heartbeats (>=2)", process.write_count >= 2);
|
check("init produced repeated heartbeats (>=2)", process.write_count >= 2);
|
||||||
check("heartbeat text arrived intact", beat_ok);
|
check("heartbeat text arrived intact", beat_ok);
|
||||||
check("heartbeats came from user mode (CPL 3)", process.write_from_user);
|
check("heartbeats came from user mode (CPL 3)", process.write_from_user);
|
||||||
@@ -1638,11 +1645,11 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
var deadline = architecture.millis() + 10000;
|
var deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= parked.len and eql(process.write_buffer[0..parked.len], parked)) break;
|
if (bufferHas(parked)) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
check("client parked holding an open handle", process.write_len >= parked.len and eql(process.write_buffer[0..parked.len], parked));
|
check("client parked holding an open handle", bufferHas(parked));
|
||||||
|
|
||||||
check("the kill is accepted", process.killProcess(me, client) == 0);
|
check("the kill is accepted", process.killProcess(me, client) == 0);
|
||||||
var badge: u64 = 0;
|
var badge: u64 = 0;
|
||||||
@@ -1655,11 +1662,11 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
deadline = architecture.millis() + 10000;
|
deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= released.len and eql(process.write_buffer[0..released.len], released)) break;
|
if (bufferHas(released)) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
check("the VFS released the dead client's handle", process.write_len >= released.len and eql(process.write_buffer[0..released.len], released));
|
check("the VFS released the dead client's handle", bufferHas(released));
|
||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1701,8 +1708,8 @@ fn signalsTest(boot_information: *const BootInformation) void {
|
|||||||
var saw_pass = false;
|
var saw_pass = false;
|
||||||
var saw_fail = false;
|
var saw_fail = false;
|
||||||
while (architecture.millis() < deadline and !saw_pass and !saw_fail) {
|
while (architecture.millis() < deadline and !saw_pass and !saw_fail) {
|
||||||
if (process.write_len >= pass_marker.len and eql(process.write_buffer[0..pass_marker.len], pass_marker)) saw_pass = true;
|
if (bufferHas(pass_marker)) saw_pass = true;
|
||||||
if (process.write_len >= fail_marker.len and eql(process.write_buffer[0..fail_marker.len], fail_marker)) saw_fail = true;
|
if (bufferHas(fail_marker)) saw_fail = true;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
@@ -1864,13 +1871,11 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
var deadline = architecture.millis() + 15000;
|
var deadline = architecture.millis() + 15000;
|
||||||
while (architecture.millis() < deadline and reported == 0) {
|
while (architecture.millis() < deadline and reported == 0) {
|
||||||
if (process.write_len > count_prefix.len + count_suffix.len and eql(process.write_buffer[0..count_prefix.len], count_prefix)) {
|
const line = process.write_buffer[0..process.write_len];
|
||||||
const line = process.write_buffer[0..process.write_len];
|
if (std.mem.indexOf(u8, line, count_prefix)) |start| {
|
||||||
const digits_end = std.mem.indexOf(u8, line, count_suffix) orelse {
|
if (std.mem.indexOf(u8, line, count_suffix)) |digits_end| {
|
||||||
scheduler.yield();
|
reported = std.fmt.parseInt(u32, line[start + count_prefix.len .. digits_end], 10) catch 0;
|
||||||
continue;
|
}
|
||||||
};
|
|
||||||
reported = std.fmt.parseInt(u32, line[count_prefix.len..digits_end], 10) catch 0;
|
|
||||||
}
|
}
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
@@ -1894,7 +1899,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
|||||||
deadline = architecture.millis() + 15000;
|
deadline = architecture.millis() + 15000;
|
||||||
var restarted = false;
|
var restarted = false;
|
||||||
while (architecture.millis() < deadline and !restarted) {
|
while (architecture.millis() < deadline and !restarted) {
|
||||||
if (process.write_len >= restart_marker.len and eql(process.write_buffer[0..restart_marker.len], restart_marker)) restarted = true;
|
if (bufferHas(restart_marker)) restarted = true;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
@@ -1906,7 +1911,7 @@ fn pciScanTest(boot_information: *const BootInformation) void {
|
|||||||
deadline = architecture.millis() + 15000;
|
deadline = architecture.millis() + 15000;
|
||||||
var seen = false;
|
var seen = false;
|
||||||
while (architecture.millis() < deadline and !seen) {
|
while (architecture.millis() < deadline and !seen) {
|
||||||
if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) seen = true;
|
if (bufferHas(marker)) seen = true;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
@@ -2035,12 +2040,12 @@ fn supervisionTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 10000;
|
const deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) break;
|
if (bufferHas(marker)) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker);
|
const ok = bufferHas(marker);
|
||||||
if (!ok and process.write_len > 0) log("DANOS-SUPERVISION: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
|
if (!ok and process.write_len > 0) log("DANOS-SUPERVISION: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
|
||||||
check("the supervisor completed every step (spawn/list/kill/notify)", ok);
|
check("the supervisor completed every step (spawn/list/kill/notify)", ok);
|
||||||
check("it ran in user mode (CPL 3)", process.write_from_user);
|
check("it ran in user mode (CPL 3)", process.write_from_user);
|
||||||
@@ -2120,12 +2125,12 @@ fn vfsTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 10000;
|
const deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
|
if (bufferHas(prefix) and process.write_count >= 2) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const ok = bufferHas(prefix);
|
||||||
check("client completed the VFS round trip (open/write/read matched)", ok);
|
check("client completed the VFS round trip (open/write/read matched)", ok);
|
||||||
check("the round trip ran repeatedly (server stays up)", process.write_count >= 2);
|
check("the round trip ran repeatedly (server stays up)", process.write_count >= 2);
|
||||||
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
|
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
@@ -2165,12 +2170,12 @@ fn inputTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 12000;
|
const deadline = architecture.millis() + 12000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
|
if (bufferHas(prefix) and process.write_count >= 2) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const ok = bufferHas(prefix);
|
||||||
check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
|
check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
|
||||||
check("events kept flowing (service + async send stay up)", process.write_count >= 2);
|
check("events kept flowing (service + async send stay up)", process.write_count >= 2);
|
||||||
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
|
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
@@ -2262,12 +2267,12 @@ fn hpetTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 10000;
|
const deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix) and process.write_count >= 2) break;
|
if (bufferHas(prefix) and process.write_count >= 2) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const ok = bufferHas(prefix);
|
||||||
check("user driver mapped HPET MMIO and was woken by its interrupt", ok);
|
check("user driver mapped HPET MMIO and was woken by its interrupt", ok);
|
||||||
check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
|
check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
check("kernel routed and re-armed the HPET's line at the I/O APIC", hpetRouteOk());
|
check("kernel routed and re-armed the HPET's line at the I/O APIC", hpetRouteOk());
|
||||||
@@ -2368,12 +2373,12 @@ fn busTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 10000;
|
const deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break;
|
if (bufferHas(prefix)) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const ok = bufferHas(prefix);
|
||||||
check("bus driver published children and the kernel refused an out-of-window one", ok);
|
check("bus driver published children and the kernel refused an out-of-window one", ok);
|
||||||
check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
|
check("driver syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
check("every registered child is contained in its parent", childrenContained());
|
check("every registered child is contained in its parent", childrenContained());
|
||||||
@@ -2417,12 +2422,12 @@ fn deviceManagerTest(boot_information: *const BootInformation) void {
|
|||||||
scheduler.setPriority(1);
|
scheduler.setPriority(1);
|
||||||
const deadline = architecture.millis() + 10000;
|
const deadline = architecture.millis() + 10000;
|
||||||
while (architecture.millis() < deadline) {
|
while (architecture.millis() < deadline) {
|
||||||
if (process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix)) break;
|
if (bufferHas(prefix)) break;
|
||||||
scheduler.yield();
|
scheduler.yield();
|
||||||
}
|
}
|
||||||
scheduler.setPriority(4);
|
scheduler.setPriority(4);
|
||||||
|
|
||||||
const ok = process.write_len >= prefix.len and eql(process.write_buffer[0..prefix.len], prefix);
|
const ok = bufferHas(prefix);
|
||||||
check("device manager matched the timer and system_spawn'd hpet, which came up", ok);
|
check("device manager matched the timer and system_spawn'd hpet, which came up", ok);
|
||||||
check("its syscalls came from user mode (CPL 3)", process.write_from_user);
|
check("its syscalls came from user mode (CPL 3)", process.write_from_user);
|
||||||
result();
|
result();
|
||||||
|
|||||||
@@ -15,8 +15,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const aml = @import("aml");
|
const aml = @import("aml");
|
||||||
|
const acpi_ids = @import("acpi-ids");
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
const protocol = runtime.device_manager_protocol;
|
const protocol = runtime.device_manager_protocol;
|
||||||
|
/// 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;
|
||||||
|
|
||||||
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
var line: [128]u8 = undefined;
|
var line: [128]u8 = undefined;
|
||||||
@@ -68,16 +72,16 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null;
|
const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null;
|
||||||
|
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("acpi: out of memory\n");
|
_ = runtime.system.write("/system/services/acpi: out of memory\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const node = findTablesNode(buffer) orelse {
|
const node = findTablesNode(buffer) orelse {
|
||||||
_ = runtime.system.write("acpi: no acpi-tables node to claim\n");
|
_ = runtime.system.write("/system/services/acpi: no acpi-tables node to claim\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
node_id = node.id;
|
node_id = node.id;
|
||||||
if (!device.claim(node_id)) {
|
if (!device.claim(node_id)) {
|
||||||
_ = runtime.system.write("acpi: unable to claim acpi-tables\n");
|
_ = runtime.system.write("/system/services/acpi: unable to claim acpi-tables\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,17 +103,17 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
if (block_count == blocks.len) break;
|
if (block_count == blocks.len) break;
|
||||||
}
|
}
|
||||||
if (block_count == 0) {
|
if (block_count == 0) {
|
||||||
_ = runtime.system.write("acpi: no AML blobs on the node\n");
|
_ = runtime.system.write("/system/services/acpi: no AML blobs on the node\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch {
|
const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch {
|
||||||
_ = runtime.system.write("acpi: AML parse failed\n");
|
_ = runtime.system.write("/system/services/acpi: AML parse failed\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
var namespace = result.namespace;
|
var namespace = result.namespace;
|
||||||
const devices = aml.deviceCount(&namespace);
|
const devices = aml.deviceCount(&namespace);
|
||||||
writeLine("acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
|
writeLine("/system/services/acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices });
|
||||||
if (expected) |want| {
|
if (expected) |want| {
|
||||||
if (devices == want) {
|
if (devices == want) {
|
||||||
_ = runtime.system.write("acpi-parse: ok\n");
|
_ = runtime.system.write("acpi-parse: ok\n");
|
||||||
@@ -139,7 +143,16 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < registered_count) : (i += 1) {
|
while (i < registered_count) : (i += 1) {
|
||||||
const entry = registered[i];
|
const entry = registered[i];
|
||||||
writeLine("acpi: reported {s} (device {d}, {d} resources)\n", .{ entry.hid[0..entry.hid_len], entry.device_id, entry.resource_count });
|
// 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("/system/services/acpi: reported {s} (device {d}, {d} resources) — {s}\n", .{ hid, entry.device_id, entry.resource_count, desc })
|
||||||
|
else
|
||||||
|
writeLine("/system/services/acpi: reported {s} (device {d}, {d} resources)\n", .{ hid, entry.device_id, entry.resource_count });
|
||||||
if (manager) |h| {
|
if (manager) |h| {
|
||||||
var report = protocol.ChildAdded{
|
var report = protocol.ChildAdded{
|
||||||
.parent = node_id,
|
.parent = node_id,
|
||||||
@@ -152,7 +165,7 @@ pub fn main(init: runtime.process.Init) void {
|
|||||||
_ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
|
_ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeLine("acpi: reported {d} device(s) to the manager\n", .{registered_count});
|
writeLine("/system/services/acpi: reported {d} device(s) to the manager\n", .{registered_count});
|
||||||
|
|
||||||
// Stay resident: the claim holds, and the service is here to grow into the
|
// 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).
|
// supervised discoverer (M20.3, then the M21 event side on the SCI).
|
||||||
@@ -172,8 +185,10 @@ fn walkDevices(node: *aml.Node, interpreter: *aml.Interpreter) void {
|
|||||||
|
|
||||||
if (readHid(c, interpreter)) |hid| {
|
if (readHid(c, interpreter)) |hid| {
|
||||||
// Skip PCI roots — pci-bus already reports PCI functions; ACPI adds
|
// Skip PCI roots — pci-bus already reports PCI functions; ACPI adds
|
||||||
// only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2).
|
// only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2). The two
|
||||||
if (!std.mem.eql(u8, hid[0..7], "PNP0A03") and !std.mem.eql(u8, hid[0..7], "PNP0A08")) {
|
// roots are named through the shared registry, not bare _HID strings.
|
||||||
|
const id = acpi_ids.HardwareId.fromHid(hid[0..7]);
|
||||||
|
if (id != .pci_bus and id != .pci_express_root_bridge) {
|
||||||
registerDevice(c, hid, interpreter);
|
registerDevice(c, hid, interpreter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +207,7 @@ fn registerDevice(node: *aml.Node, hid: [8]u8, interpreter: *aml.Interpreter) vo
|
|||||||
applyCrs(&descriptor, node, interpreter);
|
applyCrs(&descriptor, node, interpreter);
|
||||||
|
|
||||||
const id = device.register(node_id, &descriptor) orelse {
|
const id = device.register(node_id, &descriptor) orelse {
|
||||||
writeLine("acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]});
|
writeLine("/system/services/acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
registered[registered_count] = .{ .hid = hid, .hid_len = @intCast(hid_len), .device_id = id, .resource_count = descriptor.resource_count };
|
registered[registered_count] = .{ .hid = hid, .hid_len = @intCast(hid_len), .device_id = id, .resource_count = descriptor.resource_count };
|
||||||
@@ -225,7 +240,9 @@ fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 {
|
|||||||
if (hid.kind != .name or hid.value.len == 0) return null;
|
if (hid.kind != .name or hid.value.len == 0) return null;
|
||||||
const v = hid.value;
|
const v = hid.value;
|
||||||
switch (v[0]) {
|
switch (v[0]) {
|
||||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
// A static _HID names an integer EISA id: Zero/One/Ones or a Byte/Word/DWord/
|
||||||
|
// QWord integer prefix. Anything else is not an integer we can EISA-decode.
|
||||||
|
opcodes.zero_opcode, opcodes.one_opcode, opcodes.ones_opcode, opcodes.byte_prefix, opcodes.word_prefix, opcodes.dword_prefix, opcodes.qword_prefix => {
|
||||||
var p: usize = 0;
|
var p: usize = 0;
|
||||||
const n = readIntObj(v, &p) orelse return null;
|
const n = readIntObj(v, &p) orelse return null;
|
||||||
_ = eisaIdToStr(@truncate(n), &buffer);
|
_ = eisaIdToStr(@truncate(n), &buffer);
|
||||||
@@ -237,6 +254,33 @@ fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 {
|
|||||||
|
|
||||||
// --- _CRS resource-template decode (ported from the kernel's acpi.zig) --------
|
// --- _CRS resource-template decode (ported from the kernel's acpi.zig) --------
|
||||||
|
|
||||||
|
/// A resource template is a byte list of descriptors. Each starts with a tag byte whose
|
||||||
|
/// high bit picks the encoding: a *small* descriptor carries its type in bits [6:3] and
|
||||||
|
/// its length in bits [2:0]; a *large* descriptor is the whole tag byte, followed by a
|
||||||
|
/// 16-bit length. These are the descriptor types danos decodes into resources — named so
|
||||||
|
/// the walk below reads by descriptor, not by 0x04/0x85/… (docs/coding-standards.md).
|
||||||
|
const large_descriptor_bit: u8 = 0x80; // set in a tag byte => large descriptor
|
||||||
|
const small_length_mask: u8 = 0x07; // low 3 bits of a small tag = body length
|
||||||
|
const small_type_shift: u3 = 3; // small type sits in bits [6:3]
|
||||||
|
|
||||||
|
/// Small resource descriptor types (tag bits [6:3]). Non-exhaustive: an unhandled type
|
||||||
|
/// is skipped by its length, not misread.
|
||||||
|
const SmallResourceType = enum(u8) {
|
||||||
|
irq = 0x04,
|
||||||
|
io_port = 0x08,
|
||||||
|
fixed_io_port = 0x09,
|
||||||
|
end_tag = 0x0F,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Large resource descriptor types (the whole tag byte). Non-exhaustive for the same reason.
|
||||||
|
const LargeResourceType = enum(u8) {
|
||||||
|
memory32 = 0x85,
|
||||||
|
memory32_fixed = 0x86,
|
||||||
|
extended_irq = 0x89,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter: *aml.Interpreter) void {
|
||||||
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
const crs = aml.Namespace.childOf(node, seg4("_CRS")) orelse return;
|
||||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
||||||
@@ -247,21 +291,21 @@ fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter:
|
|||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < bytes.len) {
|
while (i < bytes.len) {
|
||||||
const tag = bytes[i];
|
const tag = bytes[i];
|
||||||
if (tag & 0x80 == 0) {
|
if (tag & large_descriptor_bit == 0) {
|
||||||
const len: usize = tag & 0x07;
|
const len: usize = tag & small_length_mask;
|
||||||
const body = i + 1;
|
const body = i + 1;
|
||||||
if (body + len > bytes.len) break;
|
if (body + len > bytes.len) break;
|
||||||
switch ((tag >> 3) & 0x0F) {
|
switch (@as(SmallResourceType, @enumFromInt((tag >> small_type_shift) & 0x0F))) {
|
||||||
0x04 => if (len >= 2) { // IRQ mask
|
.irq => if (len >= 2) { // IRQ mask
|
||||||
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
const mask = @as(u16, bytes[body]) | (@as(u16, bytes[body + 1]) << 8);
|
||||||
var b: usize = 0;
|
var b: usize = 0;
|
||||||
while (b < 16) : (b += 1) {
|
while (b < 16) : (b += 1) {
|
||||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) addResource(descriptor, .irq, b, 1);
|
if (mask & (@as(u16, 1) << @intCast(b)) != 0) addResource(descriptor, .irq, b, 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0x08 => if (len >= 7) addResource(descriptor, .io_port, rd16(bytes, body + 1), bytes[body + 6]),
|
.io_port => if (len >= 7) addResource(descriptor, .io_port, rd16(bytes, body + 1), bytes[body + 6]),
|
||||||
0x09 => if (len >= 3) addResource(descriptor, .io_port, rd16(bytes, body), bytes[body + 2]),
|
.fixed_io_port => if (len >= 3) addResource(descriptor, .io_port, rd16(bytes, body), bytes[body + 2]),
|
||||||
0x0F => break,
|
.end_tag => break,
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
i = body + len;
|
i = body + len;
|
||||||
@@ -270,10 +314,10 @@ fn applyCrs(descriptor: *device.DeviceDescriptor, node: *aml.Node, interpreter:
|
|||||||
const len: usize = @intCast(rd16(bytes, i + 1));
|
const len: usize = @intCast(rd16(bytes, i + 1));
|
||||||
const body = i + 3;
|
const body = i + 3;
|
||||||
if (body + len > bytes.len) break;
|
if (body + len > bytes.len) break;
|
||||||
switch (tag) {
|
switch (@as(LargeResourceType, @enumFromInt(tag))) {
|
||||||
0x85 => if (len >= 17) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 13)),
|
.memory32 => if (len >= 17) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 13)),
|
||||||
0x86 => if (len >= 9) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 5)),
|
.memory32_fixed => if (len >= 9) addResource(descriptor, .memory, rd32(bytes, body + 1), rd32(bytes, body + 5)),
|
||||||
0x89 => if (len >= 2) {
|
.extended_irq => if (len >= 2) {
|
||||||
const count = bytes[body + 1];
|
const count = bytes[body + 1];
|
||||||
var k: usize = 0;
|
var k: usize = 0;
|
||||||
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) {
|
||||||
@@ -325,22 +369,22 @@ fn readIntObj(bytes: []const u8, p: *usize) ?u64 {
|
|||||||
const op = bytes[p.*];
|
const op = bytes[p.*];
|
||||||
p.* += 1;
|
p.* += 1;
|
||||||
switch (op) {
|
switch (op) {
|
||||||
0x00 => return 0,
|
opcodes.zero_opcode => return 0,
|
||||||
0x01 => return 1,
|
opcodes.one_opcode => return 1,
|
||||||
0xFF => return 1,
|
opcodes.ones_opcode => return 1,
|
||||||
0x0A => {
|
opcodes.byte_prefix => {
|
||||||
if (p.* >= bytes.len) return null;
|
if (p.* >= bytes.len) return null;
|
||||||
const v = bytes[p.*];
|
const v = bytes[p.*];
|
||||||
p.* += 1;
|
p.* += 1;
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
0x0B => {
|
opcodes.word_prefix => {
|
||||||
if (p.* + 2 > bytes.len) return null;
|
if (p.* + 2 > bytes.len) return null;
|
||||||
const v = rd16(bytes, p.*);
|
const v = rd16(bytes, p.*);
|
||||||
p.* += 2;
|
p.* += 2;
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
0x0C => {
|
opcodes.dword_prefix => {
|
||||||
if (p.* + 4 > bytes.len) return null;
|
if (p.* + 4 > bytes.len) return null;
|
||||||
const v = rd32(bytes, p.*);
|
const v = rd32(bytes, p.*);
|
||||||
p.* += 4;
|
p.* += 4;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
const acpi_ids = @import("acpi-ids");
|
const acpi_ids = @import("acpi-ids");
|
||||||
|
const pci_class = @import("pci-class");
|
||||||
const protocol = runtime.device_manager_protocol;
|
const protocol = runtime.device_manager_protocol;
|
||||||
const device = runtime.device;
|
const device = runtime.device;
|
||||||
const system = runtime.system;
|
const system = runtime.system;
|
||||||
@@ -41,10 +42,14 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller:
|
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller —
|
||||||
/// Serial Bus Controller (0x0C) / USB Controller (0x03) / XHCI (0x30) — the names
|
/// Serial Bus Controller / USB Controller / XHCI — named from pci-class.zig rather
|
||||||
/// pci-class.zig decodes.
|
/// than written as the bare 0x0C0330 (docs/coding-standards.md, "Named values").
|
||||||
const xhci_pci_class: u64 = 0x0C_03_30;
|
const xhci_pci_class: u64 = pci_class.ClassCode.pack(.{
|
||||||
|
.base = @intFromEnum(pci_class.BaseClass.serial_bus),
|
||||||
|
.subclass = @intFromEnum(pci_class.serial_bus.SubClass.usb),
|
||||||
|
.prog_if = @intFromEnum(pci_class.serial_bus.usb.ProgIf.xhci),
|
||||||
|
});
|
||||||
|
|
||||||
/// The driver that serves a *reported* PCI function (M19.3: matching moved
|
/// The driver that serves a *reported* PCI function (M19.3: matching moved
|
||||||
/// from the boot snapshot to the bus reports), or null. A machine can carry
|
/// from the boot snapshot to the bus reports), or null. A machine can carry
|
||||||
@@ -186,7 +191,7 @@ fn addChild(parent: u64, bus_address: u64, identity: u64, device_id: u64, report
|
|||||||
fn pruneChildrenOf(reporter: u32) void {
|
fn pruneChildrenOf(reporter: u32) void {
|
||||||
for (&children) |*child| {
|
for (&children) |*child| {
|
||||||
if (child.used and child.reporter == reporter) {
|
if (child.used and child.reporter == reporter) {
|
||||||
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
writeLine("/system/services/device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
||||||
child.used = false;
|
child.used = false;
|
||||||
const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
|
const event = protocol.ChildRemoved{ .parent = child.parent, .bus_address = child.bus_address };
|
||||||
publishEvent(std.mem.asBytes(&event));
|
publishEvent(std.mem.asBytes(&event));
|
||||||
@@ -233,7 +238,7 @@ fn addDriver(name: []const u8, device_id: u64, speaks_protocol: bool) void {
|
|||||||
spawnDriver(driver);
|
spawnDriver(driver);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeLine("device-manager: driver table full; cannot supervise {s}\n", .{name});
|
writeLine("/system/services/device-manager: driver table full; cannot supervise {s}\n", .{name});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// (Re)spawn a driver instance: supervised on the manager's own endpoint, the
|
/// (Re)spawn a driver instance: supervised on the manager's own endpoint, the
|
||||||
@@ -248,7 +253,7 @@ fn spawnDriver(driver: *Driver) void {
|
|||||||
argument_count = 1;
|
argument_count = 1;
|
||||||
}
|
}
|
||||||
const child = system.spawnSupervised(driver.name(), arguments[0..argument_count], manager_endpoint) orelse {
|
const child = system.spawnSupervised(driver.name(), arguments[0..argument_count], manager_endpoint) orelse {
|
||||||
writeLine("device-manager: failed to spawn {s}\n", .{driver.name()});
|
writeLine("/system/services/device-manager: failed to spawn {s}\n", .{driver.name()});
|
||||||
driver.state = .failed;
|
driver.state = .failed;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -262,9 +267,9 @@ fn spawnDriver(driver: *Driver) void {
|
|||||||
driver.state = .running;
|
driver.state = .running;
|
||||||
}
|
}
|
||||||
if (driver.device_id != protocol.no_device) {
|
if (driver.device_id != protocol.no_device) {
|
||||||
writeLine("device-manager: spawned {s} for device {d}\n", .{ driver.name(), driver.device_id });
|
writeLine("/system/services/device-manager: spawned {s} for device {d}\n", .{ driver.name(), driver.device_id });
|
||||||
} else {
|
} else {
|
||||||
writeLine("device-manager: spawned {s}\n", .{driver.name()});
|
writeLine("/system/services/device-manager: spawned {s}\n", .{driver.name()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +281,7 @@ fn onDriverExit(driver: *Driver) void {
|
|||||||
const reason = runtime.process.exitReason(driver.process_id) orelse .fault;
|
const reason = runtime.process.exitReason(driver.process_id) orelse .fault;
|
||||||
if (reason == .exited) {
|
if (reason == .exited) {
|
||||||
driver.state = .stopped;
|
driver.state = .stopped;
|
||||||
writeLine("device-manager: {s} exited cleanly; not restarting\n", .{driver.name()});
|
writeLine("/system/services/device-manager: {s} exited cleanly; not restarting\n", .{driver.name()});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const now = system.clock();
|
const now = system.clock();
|
||||||
@@ -284,13 +289,13 @@ fn onDriverExit(driver: *Driver) void {
|
|||||||
driver.restarts = if (alive_ns < fast_death_ns) driver.restarts + 1 else 1;
|
driver.restarts = if (alive_ns < fast_death_ns) driver.restarts + 1 else 1;
|
||||||
if (driver.restarts >= crash_loop_cap) {
|
if (driver.restarts >= crash_loop_cap) {
|
||||||
driver.state = .failed;
|
driver.state = .failed;
|
||||||
writeLine("device-manager: {s} is failing repeatedly (crash loop); giving up\n", .{driver.name()});
|
writeLine("/system/services/device-manager: {s} is failing repeatedly (crash loop); giving up\n", .{driver.name()});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1);
|
const delay_ms = backoff_base_ms << @intCast(driver.restarts - 1);
|
||||||
driver.state = .restarting;
|
driver.state = .restarting;
|
||||||
driver.restart_due_ns = now + delay_ms * 1_000_000;
|
driver.restart_due_ns = now + delay_ms * 1_000_000;
|
||||||
writeLine("device-manager: restarting {s} in {d} ms (died: {s})\n", .{ driver.name(), delay_ms, @tagName(reason) });
|
writeLine("/system/services/device-manager: restarting {s} in {d} ms (died: {s})\n", .{ driver.name(), delay_ms, @tagName(reason) });
|
||||||
_ = system.timerOnce(manager_endpoint, delay_ms + 50);
|
_ = system.timerOnce(manager_endpoint, delay_ms + 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +306,7 @@ fn onDriverExit(driver: *Driver) void {
|
|||||||
fn sweepDeadlines() void {
|
fn sweepDeadlines() void {
|
||||||
const now = system.clock();
|
const now = system.clock();
|
||||||
if (test_kill_pid != 0 and now >= test_kill_due_ns) {
|
if (test_kill_pid != 0 and now >= test_kill_due_ns) {
|
||||||
writeLine("device-manager: test mode: killing the reporter\n", .{});
|
writeLine("/system/services/device-manager: test mode: killing the reporter\n", .{});
|
||||||
_ = system.kill(test_kill_pid);
|
_ = system.kill(test_kill_pid);
|
||||||
test_kill_pid = 0;
|
test_kill_pid = 0;
|
||||||
}
|
}
|
||||||
@@ -309,7 +314,7 @@ fn sweepDeadlines() void {
|
|||||||
if (!driver.used) continue;
|
if (!driver.used) continue;
|
||||||
switch (driver.state) {
|
switch (driver.state) {
|
||||||
.awaiting_hello => if (now >= driver.hello_deadline_ns) {
|
.awaiting_hello => if (now >= driver.hello_deadline_ns) {
|
||||||
writeLine("device-manager: {s} missed its hello deadline\n", .{driver.name()});
|
writeLine("/system/services/device-manager: {s} missed its hello deadline\n", .{driver.name()});
|
||||||
_ = system.kill(driver.process_id);
|
_ = system.kill(driver.process_id);
|
||||||
// The exit notification finishes the job via onDriverExit.
|
// The exit notification finishes the job via onDriverExit.
|
||||||
},
|
},
|
||||||
@@ -326,7 +331,7 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
|
|
||||||
// Enumerate into a heap buffer (too big for the one-page user stack).
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||||
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = runtime.system.write("device-manager: out of memory\n");
|
_ = runtime.system.write("/system/services/device-manager: out of memory\n");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const total = device.enumerate(buffer);
|
const total = device.enumerate(buffer);
|
||||||
@@ -367,9 +372,9 @@ fn initialise(endpoint: runtime.ipc.Handle) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (matched == 0) {
|
if (matched == 0) {
|
||||||
_ = runtime.system.write("device-manager: no matchable devices\n");
|
_ = runtime.system.write("/system/services/device-manager: no matchable devices\n");
|
||||||
} else {
|
} else {
|
||||||
_ = runtime.system.write("device-manager: ok\n");
|
_ = runtime.system.write("/system/services/device-manager: ok\n");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -390,13 +395,13 @@ fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?runtime
|
|||||||
var status: i32 = 0;
|
var status: i32 = 0;
|
||||||
if (hello.version != protocol.version) {
|
if (hello.version != protocol.version) {
|
||||||
status = -1;
|
status = -1;
|
||||||
writeLine("device-manager: refused hello (version {d}) from process {d}\n", .{ hello.version, sender });
|
writeLine("/system/services/device-manager: refused hello (version {d}) from process {d}\n", .{ hello.version, sender });
|
||||||
} else if (driverByProcess(sender)) |driver| {
|
} else if (driverByProcess(sender)) |driver| {
|
||||||
driver.state = .running;
|
driver.state = .running;
|
||||||
writeLine("device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id });
|
writeLine("/system/services/device-manager: hello from {s} (device {d})\n", .{ driver.name(), hello.device_id });
|
||||||
} else {
|
} else {
|
||||||
status = -1;
|
status = -1;
|
||||||
writeLine("device-manager: hello from unknown process {d}\n", .{sender});
|
writeLine("/system/services/device-manager: hello from unknown process {d}\n", .{sender});
|
||||||
}
|
}
|
||||||
const hello_reply = protocol.HelloReply{ .status = status };
|
const hello_reply = protocol.HelloReply{ .status = status };
|
||||||
@memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply));
|
@memcpy(reply[0..protocol.reply_size], std.mem.asBytes(&hello_reply));
|
||||||
@@ -412,7 +417,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
|
|||||||
var status: i32 = 0;
|
var status: i32 = 0;
|
||||||
if (driverByProcess(sender)) |driver| {
|
if (driverByProcess(sender)) |driver| {
|
||||||
if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
|
if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
|
||||||
writeLine("device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ report.parent, report.bus_address, report.identity, driver.name() });
|
writeLine("/system/services/device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ report.parent, report.bus_address, report.identity, driver.name() });
|
||||||
if (status == 0) publishEvent(message[0..protocol.child_added_size]);
|
if (status == 0) publishEvent(message[0..protocol.child_added_size]);
|
||||||
// Matching from reports (M19.3): a registered child whose identity
|
// Matching from reports (M19.3): a registered child whose identity
|
||||||
// names a driver gets one, once — re-reports after a bus restart
|
// names a driver gets one, once — re-reports after a bus restart
|
||||||
@@ -473,7 +478,7 @@ fn onChildRemoved(message: []const u8, reply: []u8, sender: u32) usize {
|
|||||||
var status: i32 = -1;
|
var status: i32 = -1;
|
||||||
for (&children) |*child| {
|
for (&children) |*child| {
|
||||||
if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == sender) {
|
if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == sender) {
|
||||||
writeLine("device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
writeLine("/system/services/device-manager: child removed (device {d} port {d})\n", .{ child.parent, child.bus_address });
|
||||||
child.used = false;
|
child.used = false;
|
||||||
status = 0;
|
status = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! /system/services/init — the first user-space program, PID 1. Built as its own
|
//! /system/services/system/services/init: — the first user-space program, PID 1. Built as its own
|
||||||
//! freestanding binary (see build.zig), shipped on the boot volume at /system/services/init,
|
//! freestanding binary (see build.zig), shipped on the boot volume at /system/services/system/services/init:,
|
||||||
//! loaded by the bootloader, and started in ring 3 as a scheduled process by the
|
//! loaded by the bootloader, and started in ring 3 as a scheduled process by the
|
||||||
//! kernel (system/kernel/process.zig). It links against the shared user runtime
|
//! kernel (system/kernel/process.zig). It links against the shared user runtime
|
||||||
//! library `runtime` and talks to the kernel only through `runtime`'s system_call wrappers.
|
//! library `runtime` and talks to the kernel only through `runtime`'s system_call wrappers.
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
const runtime = @import("runtime");
|
const runtime = @import("runtime");
|
||||||
|
|
||||||
/// The system services init brings up at boot, in order. This is init's policy — the
|
/// The system services system/services/init: brings up at boot, in order. This is system/services/init:'s policy — the
|
||||||
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent
|
/// microkernel keeps such choices in user space, not the kernel. Drivers are absent
|
||||||
/// on purpose: the device manager owns those. (A future init reads this from a
|
/// on purpose: the device manager owns those. (A future system/services/init: reads this from a
|
||||||
/// manifest under /system/services instead of a hardcoded list.)
|
/// manifest under /system/services instead of a hardcoded list.)
|
||||||
const boot_services = [_][]const u8{ "vfs", "input", "device-manager" };
|
const boot_services = [_][]const u8{ "vfs", "input", "device-manager" };
|
||||||
|
|
||||||
@@ -23,26 +23,26 @@ pub fn main() void {
|
|||||||
// Prove the heap end to end: allocate through the runtime allocator (which
|
// Prove the heap end to end: allocate through the runtime allocator (which
|
||||||
// mmaps pages from the kernel and carves them with the free list), write into
|
// mmaps pages from the kernel and carves them with the free list), write into
|
||||||
// that heap buffer (exercising the widened debug_write bounds check), and
|
// that heap buffer (exercising the widened debug_write bounds check), and
|
||||||
// free it. A fault here would kill init before it heartbeats — so the init
|
// free it. A fault here would kill system/services/init: before it heartbeats — so the system/services/init:
|
||||||
// test doubles as the heap regression test. (C code links the same heap via
|
// test doubles as the heap regression test. (C code links the same heap via
|
||||||
// the extern malloc/free symbols; Zig code uses this allocator.)
|
// the extern malloc/free symbols; Zig code uses this allocator.)
|
||||||
const gpa = runtime.allocator();
|
const gpa = runtime.allocator();
|
||||||
if (gpa.alloc(u8, 64)) |buffer| {
|
if (gpa.alloc(u8, 64)) |buffer| {
|
||||||
const message = "init: heap ok\n";
|
const message = "/system/services/init: heap ok\n";
|
||||||
@memcpy(buffer[0..message.len], message);
|
@memcpy(buffer[0..message.len], message);
|
||||||
_ = runtime.system.write(buffer[0..message.len]);
|
_ = runtime.system.write(buffer[0..message.len]);
|
||||||
gpa.free(buffer);
|
gpa.free(buffer);
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
|
|
||||||
// Bring up the boot services. Best-effort and silent: each service announces its
|
// Bring up the boot services. Best-effort and silent: each service announces its
|
||||||
// own readiness (`vfs: ready`, ...), and in an isolation test that runs init with
|
// own readiness (`vfs: ready`, ...), and in an isolation test that runs system/services/init: with
|
||||||
// no initial-ramdisk the spawns simply no-op rather than deranging the heartbeat.
|
// no system/services/init:ial-ramdisk the spawns simply no-op rather than deranging the heartbeat.
|
||||||
for (boot_services) |service| {
|
for (boot_services) |service| {
|
||||||
_ = runtime.system.spawn(service);
|
_ = runtime.system.spawn(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
_ = runtime.system.write("init: heartbeat\n");
|
_ = runtime.system.write("/system/services/init: heartbeat\n");
|
||||||
runtime.system.sleep(1000);
|
runtime.system.sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,14 +115,14 @@ fn handle(message: []const u8, got: ipc.Received, out: []u8) usize {
|
|||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
const endpoint = ipc.createIpcEndpoint() orelse {
|
const endpoint = ipc.createIpcEndpoint() orelse {
|
||||||
_ = system.write("input: no endpoint\n");
|
_ = system.write("/system/services/input: no endpoint\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (!ipc.register(.input, endpoint)) {
|
if (!ipc.register(.input, endpoint)) {
|
||||||
_ = system.write("input: register failed\n");
|
_ = system.write("/system/services/input: register failed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ = system.write("input: ready\n");
|
_ = system.write("/system/services/input: ready\n");
|
||||||
|
|
||||||
var reply_buffer: [protocol.reply_size]u8 = undefined;
|
var reply_buffer: [protocol.reply_size]u8 = undefined;
|
||||||
var reply_len: usize = 0;
|
var reply_len: usize = 0;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ fn releaseClientHandles(client: u32) void {
|
|||||||
released += 1;
|
released += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (released != 0) writeLine("vfs: released {d} handle(s) for dead client {d}\n", .{ released, client });
|
if (released != 0) writeLine("/system/services/vfs: released {d} handle(s) for dead client {d}\n", .{ released, client });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle one request from `sender`; write the reply into `out`, return its length.
|
/// Handle one request from `sender`; write the reply into `out`, return its length.
|
||||||
@@ -144,9 +144,9 @@ fn handle(message: []const u8, out: []u8, sender: u32, capability: ?runtime.ipc.
|
|||||||
/// to release them (docs/process-lifecycle.md).
|
/// to release them (docs/process-lifecycle.md).
|
||||||
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
fn initialise(endpoint: runtime.ipc.Handle) bool {
|
||||||
if (!runtime.process.subscribeExits(endpoint)) {
|
if (!runtime.process.subscribeExits(endpoint)) {
|
||||||
_ = runtime.system.write("vfs: exit subscription failed\n");
|
_ = runtime.system.write("/system/services/vfs: exit subscription failed\n");
|
||||||
}
|
}
|
||||||
_ = runtime.system.write("vfs: ready\n");
|
_ = runtime.system.write("/system/services/vfs: ready\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-30
@@ -55,20 +55,16 @@ ARCHES = {
|
|||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Apple Silicon)
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Apple Silicon)
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Intel)
|
"/usr/local/share/qemu/edk2-i386-vars.fd", # macOS Homebrew (Intel)
|
||||||
],
|
],
|
||||||
# zig-out is a FHS-shaped image and the boot volume; the harness copies the
|
# zig-out is itself the FHS-shaped boot volume (docs/efi.md): the build
|
||||||
# boot-critical files from their FHS paths into a fresh ESP with the same
|
# installs BOOTX64.efi, the kernel, init, and the initial-ramdisk at their
|
||||||
# layout. (dest in ESP, source path under zig-out) — identical here.
|
# boot paths. The harness presents zig-out to the guest directly — exactly
|
||||||
"efi_app": ("EFI/BOOT/BOOTX64.efi", "EFI/BOOT/BOOTX64.efi"),
|
# as `zig build run-x86-64` does — so there is no separate ESP to assemble.
|
||||||
"kernel": ("system/kernel", "system/kernel"),
|
|
||||||
# The init user program and the initial-ramdisk (VFS server + drivers).
|
|
||||||
"extra": [("system/services/init", "system/services/init"),
|
|
||||||
("boot/initial-ramdisk.img", "boot/initial-ramdisk.img")],
|
|
||||||
# Built as a function so we can splice in per-run paths.
|
# Built as a function so we can splice in per-run paths.
|
||||||
"qemu_args": lambda a, esp, vars_fd, serial: [
|
"qemu_args": lambda a, boot_volume, vars_fd, serial: [
|
||||||
"-machine", "q35", "-m", "128M",
|
"-machine", "q35", "-m", "128M",
|
||||||
"-drive", f"if=pflash,format=raw,readonly=on,file={a['ovmf_code']}",
|
"-drive", f"if=pflash,format=raw,readonly=on,file={a['ovmf_code']}",
|
||||||
"-drive", f"if=pflash,format=raw,file={vars_fd}",
|
"-drive", f"if=pflash,format=raw,file={vars_fd}",
|
||||||
"-drive", f"format=raw,file=fat:rw:{esp}",
|
"-drive", f"format=raw,file=fat:rw:{boot_volume}",
|
||||||
"-net", "none",
|
"-net", "none",
|
||||||
"-vga", "none", "-device", "VGA,edid=on,xres=1280,yres=720",
|
"-vga", "none", "-device", "VGA,edid=on,xres=1280,yres=720",
|
||||||
"-display", "none",
|
"-display", "none",
|
||||||
@@ -404,24 +400,6 @@ def build(arch, case):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def make_esp(arch):
|
|
||||||
"""Assemble a fresh EFI System Partition from the freshly built binaries."""
|
|
||||||
esp = os.path.join(WORK, "esp")
|
|
||||||
if os.path.exists(esp):
|
|
||||||
shutil.rmtree(esp)
|
|
||||||
efi_dest, efi_src = arch["efi_app"]
|
|
||||||
kern_dest, kern_src = arch["kernel"]
|
|
||||||
fhs = os.path.join(REPO, "zig-out") # zig-out is the FHS image
|
|
||||||
os.makedirs(os.path.join(esp, os.path.dirname(efi_dest)), exist_ok=True)
|
|
||||||
os.makedirs(os.path.join(esp, os.path.dirname(kern_dest)), exist_ok=True)
|
|
||||||
shutil.copy(os.path.join(fhs, efi_src), os.path.join(esp, efi_dest))
|
|
||||||
shutil.copy(os.path.join(fhs, kern_src), os.path.join(esp, kern_dest))
|
|
||||||
for dest, src in arch.get("extra", []):
|
|
||||||
os.makedirs(os.path.join(esp, os.path.dirname(dest)), exist_ok=True)
|
|
||||||
shutil.copy(os.path.join(fhs, src), os.path.join(esp, dest))
|
|
||||||
return esp
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_firmware(arch):
|
def resolve_firmware(arch):
|
||||||
"""Collapse the ovmf_code/ovmf_vars candidate lists to the first path that
|
"""Collapse the ovmf_code/ovmf_vars candidate lists to the first path that
|
||||||
exists on this machine. Mutates `arch` in place; idempotent (a resolved
|
exists on this machine. Mutates `arch` in place; idempotent (a resolved
|
||||||
@@ -445,7 +423,8 @@ def run_case(arch, case):
|
|||||||
if err:
|
if err:
|
||||||
return False, "build failed:\n" + err
|
return False, "build failed:\n" + err
|
||||||
|
|
||||||
esp = make_esp(arch)
|
# zig-out is the FHS boot volume; hand it to the guest as-is (see qemu_args).
|
||||||
|
boot_volume = os.path.join(REPO, "zig-out")
|
||||||
vars_fd = os.path.join(WORK, "vars.fd")
|
vars_fd = os.path.join(WORK, "vars.fd")
|
||||||
shutil.copy(arch["ovmf_vars"], vars_fd)
|
shutil.copy(arch["ovmf_vars"], vars_fd)
|
||||||
serial = os.path.join(WORK, "serial.log")
|
serial = os.path.join(WORK, "serial.log")
|
||||||
@@ -455,7 +434,7 @@ def run_case(arch, case):
|
|||||||
expect = re.compile(case["expect"])
|
expect = re.compile(case["expect"])
|
||||||
fail = re.compile(case["fail"]) if case.get("fail") else None
|
fail = re.compile(case["fail"]) if case.get("fail") else None
|
||||||
|
|
||||||
cmd = [arch["qemu"]] + arch["qemu_args"](arch, esp, vars_fd, serial)
|
cmd = [arch["qemu"]] + arch["qemu_args"](arch, boot_volume, vars_fd, serial)
|
||||||
if case.get("smp"): # some cases need more than one core (e.g. parallelism)
|
if case.get("smp"): # some cases need more than one core (e.g. parallelism)
|
||||||
cmd += ["-smp", str(case["smp"])]
|
cmd += ["-smp", str(case["smp"])]
|
||||||
if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case
|
if case.get("qemu_extra"): # extra qemu args, e.g. -device intel-iommu for the IOMMU case
|
||||||
|
|||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# sort-lines-group-by-start — cluster lines that share their first
|
||||||
|
# whitespace-separated field ($1). Keys appear in first-seen order, and lines
|
||||||
|
# within a key keep their original order. It groups; it does NOT sort.
|
||||||
|
#
|
||||||
|
# Pass the log file as an argument; result is written to stdout:
|
||||||
|
#
|
||||||
|
# tools/sort-lines-group-by-start.sh filename.log
|
||||||
|
#
|
||||||
|
# Useful for a serial/boot log where several sources interleave and each line is
|
||||||
|
# prefixed with its source (the first field): this pulls every source's lines
|
||||||
|
# back together, in the order the sources first appeared, without reordering
|
||||||
|
# within a source.
|
||||||
|
#
|
||||||
|
# input output
|
||||||
|
# pci-bus: scan start pci-bus: scan start
|
||||||
|
# acpi: reported PNP0303 pci-bus: 5 functions
|
||||||
|
# pci-bus: 5 functions acpi: reported PNP0303
|
||||||
|
# acpi: reported PNP0501 acpi: reported PNP0501
|
||||||
|
|
||||||
|
awk '{lines[$1] = lines[$1] ? lines[$1] ORS $0 : $0; if (!seen[$1]++) order[++count] = $1} END {for (i=1; i<=count; i++) print lines[order[i]]}' "$@"
|
||||||
Reference in New Issue
Block a user