From 11f567ee207073d757bdc6de4f33cc15e2afdf28 Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:49:20 +0100 Subject: [PATCH] kernel: flatten the device/discovery code, merge power into acpi, rename fdt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- build.zig | 6 +-- docs/README.md | 8 +-- docs/acpi.md | 4 +- docs/coding-standards.md | 2 +- docs/power.md | 6 +-- docs/system-requirements.md | 4 +- library/device/model/device-abi.zig | 2 +- system/kernel/{devices => }/acpi.zig | 49 +++++++++++++++++- system/kernel/{devices => }/device-model.zig | 0 system/kernel/devices/power.zig | 51 ------------------- .../{devices/device-tree.zig => fdt.zig} | 0 system/kernel/{devices => }/platform.zig | 7 ++- 12 files changed, 67 insertions(+), 72 deletions(-) rename system/kernel/{devices => }/acpi.zig (94%) rename system/kernel/{devices => }/device-model.zig (100%) delete mode 100644 system/kernel/devices/power.zig rename system/kernel/{devices/device-tree.zig => fdt.zig} (100%) rename system/kernel/{devices => }/platform.zig (96%) diff --git a/build.zig b/build.zig index bd6cec8..411872b 100644 --- a/build.zig +++ b/build.zig @@ -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 diff --git a/docs/README.md b/docs/README.md index dfa9a00..b3e7aae 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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/` | diff --git a/docs/acpi.md b/docs/acpi.md index e73024e..8582d3b 100644 --- a/docs/acpi.md +++ b/docs/acpi.md @@ -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…) diff --git a/docs/coding-standards.md b/docs/coding-standards.md index d4c7797..6d35f75 100644 --- a/docs/coding-standards.md +++ b/docs/coding-standards.md @@ -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.) diff --git a/docs/power.md b/docs/power.md index 1d790f0..6ef9b10 100644 --- a/docs/power.md +++ b/docs/power.md @@ -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 diff --git a/docs/system-requirements.md b/docs/system-requirements.md index 558313a..5aa40bd 100644 --- a/docs/system-requirements.md +++ b/docs/system-requirements.md @@ -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 diff --git a/library/device/model/device-abi.zig b/library/device/model/device-abi.zig index 9d002d8..63b8213 100644 --- a/library/device/model/device-abi.zig +++ b/library/device/model/device-abi.zig @@ -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 kernel↔user ABI is [[abi]]; the diff --git a/system/kernel/devices/acpi.zig b/system/kernel/acpi.zig similarity index 94% rename from system/kernel/devices/acpi.zig rename to system/kernel/acpi.zig index 9aa5aae..afed61e 100644 --- a/system/kernel/devices/acpi.zig +++ b/system/kernel/acpi.zig @@ -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 }); + } +} diff --git a/system/kernel/devices/device-model.zig b/system/kernel/device-model.zig similarity index 100% rename from system/kernel/devices/device-model.zig rename to system/kernel/device-model.zig diff --git a/system/kernel/devices/power.zig b/system/kernel/devices/power.zig deleted file mode 100644 index 552a96d..0000000 --- a/system/kernel/devices/power.zig +++ /dev/null @@ -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 }); - } -} diff --git a/system/kernel/devices/device-tree.zig b/system/kernel/fdt.zig similarity index 100% rename from system/kernel/devices/device-tree.zig rename to system/kernel/fdt.zig diff --git a/system/kernel/devices/platform.zig b/system/kernel/platform.zig similarity index 96% rename from system/kernel/devices/platform.zig rename to system/kernel/platform.zig index 3ae462a..2fef984 100644 --- a/system/kernel/devices/platform.zig +++ b/system/kernel/platform.zig @@ -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); }