kernel: flatten the device/discovery code, merge power into acpi, rename fdt

Follow-up cleanup of the just-moved kernel device code:

- power.zig -> acpi.zig. Its reboot() is built entirely on the FADT reset
  register (acpi.power_information) plus the legacy 0xCF9/8042 fallbacks — it is
  ACPI reboot, so it becomes acpi.reboot (the "P" in ACPI). platform.reboot
  still delegates; soft-off/S5 stays the ring-3 acpi service's job as before.

- device-tree.zig -> fdt.zig. It is a discovery *backend* (the ARM/FDT parser,
  a sibling of acpi.zig), not part of the model — renaming it to its actual
  subject kills the confusing device-tree / device-model DeviceTree name clash
  and makes acpi.zig + fdt.zig read as the two parallel backends.

- Flatten: device-model.zig, acpi.zig, fdt.zig, platform.zig move out of the
  system/kernel/devices/ subdir up into system/kernel/, joining devices-broker.zig
  (already flat). The kernel dir is a flat pile by convention (only architecture/
  is a subdir), so the subdir — and its poor "devices" name — is gone.

Pure restructure; "platform" module name unchanged, all cross-file deps are
relative siblings that moved together. zig build + test green; smoke, discovery,
acpi-parse, acpi-ps2, acpi-report, power-button, orderly-shutdown, reboot pass.
This commit is contained in:
Daniel Samson 2026-07-22 21:49:20 +01:00
parent 3a155cdc7d
commit 11f567ee20
No known key found for this signature in database
GPG Key ID: A9EB2589C60F7268
12 changed files with 67 additions and 72 deletions

View File

@ -278,7 +278,7 @@ pub fn build(b: *std.Build) void {
});
// The devices sub-project's public interface (the flat wire types), exposed as
// its own module like vfs-protocol importable by user space, unlike the
// kernel-internal device model it also feeds (system/kernel/devices/device-model.zig).
// kernel-internal device model it also feeds (system/kernel/device-model.zig).
const device_abi_module = b.addModule("device-abi", .{
.root_source_file = b.path("library/device/model/device-abi.zig"),
});
@ -351,9 +351,9 @@ pub fn build(b: *std.Build) void {
// "platform" and asks it to enumerate hardware into a backend-neutral device
// tree, never naming ACPI (or, later, device-tree) the same discipline the
// architecture module applies to CPU code. The backend is selected at runtime from
// the boot handoff (see system/kernel/devices/platform.zig).
// the boot handoff (see system/kernel/platform.zig).
const platform_module = b.addModule("platform", .{
.root_source_file = b.path("system/kernel/devices/platform.zig"),
.root_source_file = b.path("system/kernel/platform.zig"),
.imports = &.{
.{ .name = "boot-handoff", .module = boot_handoff_module }, // BootInformation (carries the ACPI RSDP), physicalToVirtual
.{ .name = "abi", .module = abi_module }, // acpi.zig works in page_size units

View File

@ -238,9 +238,9 @@ system/ → /system danos's own internals (the self-representation)
parameters.zig initial-ramdisk.zig shared contracts
kernel/ IPC, memory, scheduling, the VFS root, the private syscall dispatch
architecture/x86_64/ the `architecture` module (never named by generic code)
devices/ the kernel-internal device model + firmware discovery
(device-model, platform, acpi, device-tree, power) — the
implementation of the device tree /system/devices reflects
devices-broker.zig the syscall-facing device table
platform.zig acpi.zig fdt.zig device-model.zig firmware discovery + the kernel's
device model — the implementation of what /system/devices reflects
drivers/ pci-bus/ ps2-bus/ usb-xhci-bus/ one sub-project per driver → /system/drivers
services/ init/ fat/ device-manager/ system servers → /system/services (fat/ holds
fat.zig, engine.zig, on-disk.zig)
@ -300,7 +300,7 @@ exception in [coding-standards.md](coding-standards.md) applies to that seam.
| VFS root: mount table + kernel-served nodes (`fs_resolve`/`fs_node`); wire protocol in `library/protocol/vfs/vfs-protocol.zig` | `system/kernel/vfs.zig` |
| Device tree + claim capability + `device_register` containment | `system/kernel/devices-broker.zig` |
| IRQ-as-IPC: routing a device interrupt to a driver's endpoint | `system/kernel/irq.zig` |
| Hardware discovery (ACPI/device tree) behind one neutral device model | `system/kernel/devices/` |
| Hardware discovery (ACPI/device tree) behind one neutral device model | `system/kernel/platform.zig` |
| Framebuffer text console (mirrors to serial) | `system/kernel/console.zig` |
| In-kernel test cases | `system/kernel/tests.zig` |
| Arch-specific kernel code (`halt`, GDT/IDT/TSS, exception + interrupt stubs, page tables, APIC/IO-APIC/timer, serial, linker script) | `system/kernel/architecture/x86_64/` |

View File

@ -19,10 +19,10 @@ UEFI configuration table
BootInformation.acpi_rsdp (u64, in the loader↔kernel handoff) system/boot-handoff.zig
│ the kernel forwards the whole BootInformation
platform.discover(boot_information, …) system/kernel/devices/platform.zig
platform.discover(boot_information, …) system/kernel/platform.zig
│ reads boot_information.acpi_rsdp, hands it to the ACPI backend
acpi.discover(rsdp_phys, …) system/kernel/devices/acpi.zig
acpi.discover(rsdp_phys, …) system/kernel/acpi.zig
│ dereferences the RSDP, reads the pointer it contains
RSDP ──(a field in the struct)──► RSDT / XSDT ──► SDTs (MADT, MCFG, FADT, HPET, DSDT…)

View File

@ -135,7 +135,7 @@ Within those spelling rules, follow Zig's own conventions:
`notify_badge_bit`.
**File names are `kebab-case`.** A file named for a multi-word thing hyphenates it:
`device-tree.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `devices-broker.zig`. A
`device-model.zig`, `ipc-synchronous.zig`, `vfs-protocol.zig`, `devices-broker.zig`. A
single word or acronym needs no hyphen: `scheduler.zig`, `paging.zig`, `apic.zig`,
`idt.zig`. (The module *alias* a file is imported under still follows the code
conventions above — `snake_case` — because it's an identifier, not a filename.)

View File

@ -92,9 +92,9 @@ fails rather than hangs.
`acpi-tables` node ([discovery.md](discovery.md)) already put the PM1 control
ports in the acpi service's hands, so writing S5 from ring 3 is something it
could physically already do; formalizing it as a protocol operation added a
contract, not authority. The kernel's `system/kernel/devices/power.zig` keeps only
**reboot** (the FADT reset register plus the legacy fallbacks, which need no AML);
it has no poweroff path at all — S5 is not a kernel operation.
contract, not authority. The kernel keeps only **reboot** (`acpi.reboot` in
`system/kernel/acpi.zig`the FADT reset register plus the legacy fallbacks, which
need no AML); it has no poweroff path at all — S5 is not a kernel operation.
## Verifying it

View File

@ -115,7 +115,7 @@ hypervisor configured for UEFI firmware and an xHCI USB controller.
(`efi.zig:790`, `boot-handoff.zig:149`)
- **Required ACPI tables:** MADT (interrupt topology), MCFG (PCIe ECAM base),
FADT (power / PM timer). Optionally consumed: HPET, DMAR, SPCR.
(`system/kernel/devices/acpi.zig:3`)
(`system/kernel/acpi.zig:3`)
- The loader reads `/system/kernel` off the FAT boot volume, then loads user
space: a prebuilt `boot\system.img` capsule
([system-image.md](system-image.md)) when present, otherwise it walks
@ -221,7 +221,7 @@ named for reporting only; internal SATA / NVMe / IDE disks have no driver.
first VT-d DRHD unit and its capabilities are exposed via `PlatformInformation`
(`iommu_present`, `iommu_base`, `iommu_version`). No DMA-remapping tables are
programmed and no translation is enforced. An IOMMU is therefore **not required**
and does not currently constrain devices. (`system/kernel/devices/acpi.zig:96`)
and does not currently constrain devices. (`system/kernel/acpi.zig:96`)
## What is explicitly NOT supported

View File

@ -6,7 +6,7 @@
//! by name, and neither reaches into the other's files.
//!
//! It is also the **single source of truth** for `DeviceClass` and `ResourceKind`:
//! the kernel's rich, pointer-based device tree (system/kernel/devices/device-model.zig,
//! the kernel's rich, pointer-based device tree (system/kernel/device-model.zig,
//! which user space must never import) re-exports these, so the enum that a driver
//! matches on and the enum the kernel classifies with are the *same* type no
//! hand-kept "mirror in order" to drift. The core kerneluser ABI is [[abi]]; the

View File

@ -60,7 +60,7 @@ pub const PowerInformation = struct {
reset_supported: bool = false,
};
/// Filled in by `discover`; the power service reads it to reboot/shutdown.
/// Filled in by `discover`; `reboot` (below) reads it to restart the machine.
pub var power_information: PowerInformation = .{};
/// A legacy ISA IRQ remapped to a different global system interrupt (GSI), from a
@ -839,3 +839,50 @@ fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T {
const p: *align(1) const T = @ptrCast(bytes + off);
return p.*;
}
// --- machine reboot -------------------------------------------------------------
// Restart via the FADT reset register (from `power_information` above), with legacy
// fallbacks, driven through the injected `Hal`. Soft-off (ACPI S5) and suspend (S3)
// are NOT here: they need the AML sleep-state (`_Sx`) values, which the kernel no
// longer parses the ring-3 acpi service owns power management (it re-parses the
// blobs and writes the PM1 control register itself). Reboot stays in the kernel
// because it needs no AML only the FADT reset register and the well-known legacy
// fallbacks so it survives as a last-resort restart. See docs/power.md.
/// Restart the machine. Tries the ACPI reset register first, then the two legacy
/// fallbacks. Returns only if every method failed (very unlikely).
pub fn reboot(hal: device_model.Hal) void {
const pi = power_information;
// 1. The FADT reset register, when the firmware advertises support.
if (pi.reset_supported and pi.reset.present()) {
writeResetRegister(hal, pi.reset, pi.reset_value);
rebootDelay();
}
// 2. The PCI reset-control register at port 0xCF9 (RST_CPU | SYSTEM_RST).
hal.pioWrite(1, 0xCF9, 0x0E);
hal.pioWrite(1, 0xCF9, 0x06);
rebootDelay();
// 3. Pulse the 8042 keyboard controller's reset line.
hal.pioWrite(1, 0x64, 0xFE);
rebootDelay();
}
fn writeResetRegister(hal: device_model.Hal, register: RegisterAccess, value: u32) void {
if (register.mmio) {
const p: *align(1) volatile u32 = @ptrFromInt(hal.mapMmio(register.address, 4, true));
p.* = value;
} else {
hal.pioWrite(register.width, @intCast(register.address), value);
}
}
/// A short busy-wait so a reset takes effect before we fall through to the next
/// method. The empty asm is an architecture-neutral barrier that keeps the loop
/// from being optimised away.
fn rebootDelay() void {
var i: usize = 0;
while (i < 50_000_000) : (i += 1) {
asm volatile ("" ::: .{ .memory = true });
}
}

View File

@ -1,51 +0,0 @@
//! Machine reboot: restart via the FADT reset register, with legacy fallbacks.
//!
//! Built on the register map `acpi` extracted from the FADT, driven through the
//! injected `Hal` (port I/O and MMIO). Soft-off (ACPI S5) and suspend (S3) are
//! **not** here: they need the AML sleep-state (`_Sx`) values, which the kernel no
//! longer parses the ring-3 acpi service owns power management (it re-parses the
//! blobs and writes the PM1 control register itself). See docs/power.md. Reboot
//! stays in the kernel because it needs no AML only the FADT reset register and
//! the well-known legacy fallbacks so it survives as a last-resort restart.
const acpi = @import("acpi.zig");
const device_model = @import("device-model.zig");
const Hal = device_model.Hal;
/// Restart the machine. Tries the ACPI reset register first, then the two legacy
/// fallbacks. Returns only if every method failed (very unlikely).
pub fn reboot(hal: Hal) void {
const pi = acpi.power_information;
// 1. The FADT reset register, when the firmware advertises support.
if (pi.reset_supported and pi.reset.present()) {
writeRegister(hal, pi.reset, pi.reset_value);
delay();
}
// 2. The PCI reset-control register at port 0xCF9 (RST_CPU | SYSTEM_RST).
hal.pioWrite(1, 0xCF9, 0x0E);
hal.pioWrite(1, 0xCF9, 0x06);
delay();
// 3. Pulse the 8042 keyboard controller's reset line.
hal.pioWrite(1, 0x64, 0xFE);
delay();
}
fn writeRegister(hal: Hal, register: acpi.RegisterAccess, value: u32) void {
if (register.mmio) {
const p: *align(1) volatile u32 = @ptrFromInt(hal.mapMmio(register.address, 4, true));
p.* = value;
} else {
hal.pioWrite(register.width, @intCast(register.address), value);
}
}
/// A short busy-wait so a reset takes effect before we fall through to the next
/// method. The empty asm is an architecture-neutral barrier that keeps the loop
/// from being optimised away.
fn delay() void {
var i: usize = 0;
while (i < 50_000_000) : (i += 1) {
asm volatile ("" ::: .{ .memory = true });
}
}

View File

@ -12,8 +12,7 @@ const std = @import("std");
const boot_handoff = @import("boot-handoff");
const device_model = @import("device-model.zig");
const acpi = @import("acpi.zig");
const power = @import("power.zig");
const devicetree = @import("device-tree.zig");
const fdt = @import("fdt.zig");
pub const DeviceTree = device_model.DeviceTree;
pub const Device = device_model.Device;
@ -74,7 +73,7 @@ pub fn discover(
// No ACPI RSDP. A device-tree boot would parse its blob here; today that
// path is a stub, so this reports the machine described itself no way we
// understand yet.
try devicetree.discover(&device_tree);
try fdt.discover(&device_tree);
}
return device_tree;
@ -84,5 +83,5 @@ pub fn discover(
/// worked (extremely unlikely). Backend-agnostic entry the kernel calls. Soft-off
/// (S5) is not a kernel operation the ring-3 acpi service owns it (docs/power.md).
pub fn reboot(hal: Hal) void {
power.reboot(hal);
acpi.reboot(hal);
}