M11–M12: IRQ-as-IPC and bus drivers; expand names tree-wide
Two driver-model milestones plus a tree-wide naming pass. Suite 35/35 (QEMU) + host tests green. M11 — IRQ-as-IPC. A ring-3 driver now sleeps until its device interrupts it. New src/kernel/irq.zig: per-GSI endpoint bindings, comptime per-vector trampolines, dispatch = mask GSI -> LAPIC EOI -> notifyLocked, all under one lock region. irq_bind/irq_ack syscalls, gated by the device claim like mmio_map. interruptDispatch no longer EOIs — each handler owns its EOI, because a level line must be masked before it is acknowledged (irq_ack is the unmask). Bindings are keyed on the owning task and released on exit (a shared endpoint's siblings survive). hpetd rewritten interrupt-driven. Tests: hpet (rewritten, reads back the I/O APIC routing) and irqfree. M12 — bus drivers. DeviceDesc gains a parent, making the device table a tree. dev_register (device_register) lets a process publish children below a device it claimed; the kernel enforces resource containment (a child's resources must nest in its parent's), so a descriptor can't fabricate a window over kernel RAM. Descriptor copied in via copyFromUser (physmap walk — an unmapped user pointer fails the call instead of faulting the kernel). Per-parent child cap bounds table exhaustion. sbin/busd.zig is a worked bus driver. Test: bus. Naming — per docs/coding-standards.md: non-acronym abbreviations spelled out (message, descriptor, device_service, scheduler, runtime, physical, interpreter, ...); acronyms kept (IPC, MMIO, DMA, HCD, ...); files are kebab-case (ipc-synchronous.zig, device-service.zig, vfs-protocol.zig, ...). Exceptions: POSIX/C ABI names and Zig idioms (init/len/ptr) kept. Module collisions resolved by specific naming (config -> parameters, device.zig alias -> device_model). AML op/Op disambiguated: op = opcode, Op = operation; per-opcode parse handlers renamed opX -> parseX. New driver docs: drivers.md, driver-model.md (bus/class/HCD shapes + the proposed M13–M16 ABI), coding-standards.md.
This commit is contained in:
+260
-222
@@ -11,43 +11,43 @@
|
||||
//!
|
||||
//! ACPI tables live in `.acpi_tables` / `.acpi_nvs` memory, which the kernel
|
||||
//! identity-maps, so table addresses are dereferenced directly. PCIe ECAM is MMIO
|
||||
//! and is *not* mapped up front, so config-space pages are mapped on demand via
|
||||
//! the `Hal.mapMmio` callback the caller supplies (the arch VMM's map primitive).
|
||||
//! and is *not* mapped up front, so configuration-space pages are mapped on demand via
|
||||
//! the `Hal.mapMmio` callback the caller supplies (the architecture VMM's map primitive).
|
||||
|
||||
const std = @import("std");
|
||||
const danos = @import("danos");
|
||||
const config = @import("config");
|
||||
const device = @import("device.zig");
|
||||
const parameters = @import("parameters");
|
||||
const device_model = @import("device-model.zig");
|
||||
const aml = @import("aml/aml.zig");
|
||||
const DeviceTree = device.DeviceTree;
|
||||
const Hal = device.Hal;
|
||||
const DeviceTree = device_model.DeviceTree;
|
||||
const Hal = device_model.Hal;
|
||||
|
||||
/// A hardware register located either in MMIO or I/O-port space, as ACPI's
|
||||
/// Generic Address Structure describes. `address == 0` means "not present".
|
||||
pub const RegAccess = struct {
|
||||
pub const RegisterAccess = struct {
|
||||
/// true = system memory (MMIO), false = system I/O port space.
|
||||
mmio: bool = false,
|
||||
address: u64 = 0,
|
||||
/// Access width in bytes.
|
||||
width: u8 = 0,
|
||||
|
||||
pub fn present(self: RegAccess) bool {
|
||||
pub fn present(self: RegisterAccess) bool {
|
||||
return self.address != 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Everything the power subsystem needs, extracted from the FADT and the AML
|
||||
/// sleep packages during discovery. Populated by `discover`, read by `power`.
|
||||
pub const PowerInfo = struct {
|
||||
pub const PowerInformation = struct {
|
||||
/// The SMM command port and the value that switches the platform into ACPI mode.
|
||||
smi_cmd: u16 = 0,
|
||||
acpi_enable: u8 = 0,
|
||||
acpi_disable: u8 = 0,
|
||||
/// PM1 control registers — writing SLP_TYP|SLP_EN here enters a sleep state.
|
||||
pm1a_cnt: RegAccess = .{},
|
||||
pm1b_cnt: RegAccess = .{},
|
||||
pm1a_cnt: RegisterAccess = .{},
|
||||
pm1b_cnt: RegisterAccess = .{},
|
||||
/// The FADT reset register and the value to write to it.
|
||||
reset: RegAccess = .{},
|
||||
reset: RegisterAccess = .{},
|
||||
reset_value: u8 = 0,
|
||||
reset_supported: bool = false,
|
||||
/// SLP_TYP values for S5 (soft off) and S3 (suspend), from the AML sleep-state (`_Sx`) packages.
|
||||
@@ -56,7 +56,7 @@ pub const PowerInfo = struct {
|
||||
};
|
||||
|
||||
/// Filled in by `discover`; the power service reads it to reboot/shutdown.
|
||||
pub var power_info: PowerInfo = .{};
|
||||
pub var power_information: PowerInformation = .{};
|
||||
|
||||
/// A legacy ISA IRQ remapped to a different global system interrupt (GSI), from a
|
||||
/// MADT Interrupt Source Override. `flags` are the MPS INTI polarity/trigger bits.
|
||||
@@ -66,11 +66,11 @@ pub const IsoEntry = struct {
|
||||
flags: u16,
|
||||
};
|
||||
|
||||
/// Firmware facts the arch layer needs to avoid legacy assumptions (so danos boots
|
||||
/// Firmware facts the architecture layer needs to avoid legacy assumptions (so danos boots
|
||||
/// on legacy-free UEFI Class 3 machines). MMIO device *addresses* (HPET, IOAPIC)
|
||||
/// come from the device tree instead; this holds the scalar facts that have no
|
||||
/// natural device node.
|
||||
pub const PlatformInfo = struct {
|
||||
pub const PlatformInformation = struct {
|
||||
/// Whether the legacy 8259 PIC is present (MADT flags bit 0, PCAT_COMPAT). When
|
||||
/// false, the PIC must not be programmed (it may not exist).
|
||||
pic_present: bool = false,
|
||||
@@ -78,11 +78,11 @@ pub const PlatformInfo = struct {
|
||||
lapic_base: u64 = 0xFEE00000,
|
||||
/// The ACPI power-management timer — a fixed 3.579545 MHz counter usable as a
|
||||
/// calibration reference when no HPET is present.
|
||||
pm_timer: RegAccess = .{},
|
||||
/// true = 32-bit PM timer counter, false = 24-bit (FADT flag TMR_VAL_EXT).
|
||||
pm_timer: RegisterAccess = .{},
|
||||
/// true = 32-bit PM timer counter, false = 24-bit (FADT flag TMR_VALUE_EXT).
|
||||
pm_timer_32bit: bool = false,
|
||||
/// The console UART the firmware points at (SPCR), if any — MMIO or I/O port.
|
||||
spcr_uart: ?RegAccess = null,
|
||||
spcr_uart: ?RegisterAccess = null,
|
||||
/// SPCR interface type (0/1 = 16550/16450, …).
|
||||
spcr_kind: u8 = 0,
|
||||
/// ISA-IRQ-to-GSI remappings from the MADT (for future IOAPIC routing).
|
||||
@@ -90,8 +90,8 @@ pub const PlatformInfo = struct {
|
||||
override_count: usize = 0,
|
||||
};
|
||||
|
||||
/// Filled in by `discover`; the arch layer reads it during bring-up.
|
||||
pub var platform_info: PlatformInfo = .{};
|
||||
/// Filled in by `discover`; the architecture layer reads it during bring-up.
|
||||
pub var platform_information: PlatformInformation = .{};
|
||||
|
||||
/// One usable logical processor, from a MADT type-0 (Local APIC) record. The
|
||||
/// `apic_id` is the Local APIC ID that SMP bring-up targets to wake this core
|
||||
@@ -108,19 +108,19 @@ pub const Cpu = struct {
|
||||
/// The set of usable logical processors the MADT listed — the hardware's degree of
|
||||
/// parallelism. Includes the bootstrap processor danos already runs on; the rest
|
||||
/// are the application processors SMP bring-up would start (see docs/smp.md).
|
||||
pub const CpuInfo = struct {
|
||||
pub const CpuInformation = struct {
|
||||
/// A static pool sized well above any danos target (a desktop, two 4-core Pis).
|
||||
/// If the MADT ever lists more, the surplus is dropped and counted in `dropped`
|
||||
/// so the truncation is never silent.
|
||||
cpus: [max_cpus]Cpu = undefined,
|
||||
cpus: [maximum_cpus]Cpu = undefined,
|
||||
count: usize = 0,
|
||||
dropped: usize = 0,
|
||||
};
|
||||
|
||||
const max_cpus = config.max_cpus;
|
||||
const maximum_cpus = parameters.maximum_cpus;
|
||||
|
||||
/// Filled in by `discover` (from the MADT); SMP bring-up reads it to wake the APs.
|
||||
pub var cpu_info: CpuInfo = .{};
|
||||
pub var cpu_information: CpuInformation = .{};
|
||||
|
||||
/// Integrity/diagnostics for the AML parse. `consumed == total` means the parser
|
||||
/// walked every byte of the DSDT/SSDTs without desyncing.
|
||||
@@ -136,20 +136,20 @@ pub var aml_stats: AmlStats = .{};
|
||||
pub var namespace: ?aml.Namespace = null;
|
||||
|
||||
/// Physical address of the DSDT the FADT points at, or 0.
|
||||
pub var dsdt_phys: u64 = 0;
|
||||
pub var dsdt_physical: u64 = 0;
|
||||
|
||||
// AML blocks (DSDT + any SSDTs) collected during the table walk, as physical
|
||||
// address + length of each table's post-header bytecode. Scanned after the walk
|
||||
// for the sleep-state (`_Sx`) packages.
|
||||
var aml_block_phys: [32]u64 = undefined;
|
||||
var aml_block_physical: [32]u64 = undefined;
|
||||
var aml_block_len: [32]usize = undefined;
|
||||
var aml_block_count: usize = 0;
|
||||
|
||||
fn addAmlBlock(sdt_phys: u64) void {
|
||||
if (aml_block_count >= aml_block_phys.len or sdt_phys == 0) return;
|
||||
const h: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physToVirt(sdt_phys));
|
||||
fn addAmlBlock(sdt_physical: u64) void {
|
||||
if (aml_block_count >= aml_block_physical.len or sdt_physical == 0) return;
|
||||
const h: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physicalToVirtual(sdt_physical));
|
||||
if (h.length <= @sizeOf(SystemDescriptorTableHeader)) return;
|
||||
aml_block_phys[aml_block_count] = sdt_phys + @sizeOf(SystemDescriptorTableHeader);
|
||||
aml_block_physical[aml_block_count] = sdt_physical + @sizeOf(SystemDescriptorTableHeader);
|
||||
aml_block_len[aml_block_count] = h.length - @sizeOf(SystemDescriptorTableHeader);
|
||||
aml_block_count += 1;
|
||||
}
|
||||
@@ -362,47 +362,47 @@ const PciHeader = extern struct {
|
||||
|
||||
// --- Entry point ------------------------------------------------------------
|
||||
|
||||
/// Discover hardware from the ACPI tables rooted at `rsdp_phys` and populate
|
||||
/// `dt`. `hal` provides MMIO mapping (for PCIe ECAM) and port I/O. Also parses the
|
||||
/// FADT and the AML sleep-state (`_Sx`) packages into `power_info` for the power service.
|
||||
pub fn discover(rsdp_phys: u64, dt: *DeviceTree, hal: Hal) !void {
|
||||
if (rsdp_phys == 0) return error.NoRsdp;
|
||||
/// Discover hardware from the ACPI tables rooted at `rsdp_physical` and populate
|
||||
/// `device_tree`. `hal` provides MMIO mapping (for PCIe ECAM) and port I/O. Also parses the
|
||||
/// FADT and the AML sleep-state (`_Sx`) packages into `power_information` for the power service.
|
||||
pub fn discover(rsdp_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
|
||||
if (rsdp_physical == 0) return error.NoRsdp;
|
||||
|
||||
// Start clean so a re-run doesn't accumulate stale state.
|
||||
power_info = .{};
|
||||
platform_info = .{};
|
||||
power_information = .{};
|
||||
platform_information = .{};
|
||||
aml_stats = .{};
|
||||
namespace = null;
|
||||
dsdt_phys = 0;
|
||||
dsdt_physical = 0;
|
||||
aml_block_count = 0;
|
||||
|
||||
const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(danos.physToVirt(rsdp_phys));
|
||||
const rsdp: *const RootSystemDescriptionPointer = @ptrFromInt(danos.physicalToVirtual(rsdp_physical));
|
||||
if (!std.mem.eql(u8, &rsdp.signature, "RSD PTR ")) return error.BadRsdpSignature;
|
||||
// Revision 0 checksums only the first 20 bytes (the v1.0 RSDP).
|
||||
if (!checksumOk(@ptrFromInt(danos.physToVirt(rsdp_phys)), 20)) return error.BadRsdpChecksum;
|
||||
if (!checksumOk(@ptrFromInt(danos.physicalToVirtual(rsdp_physical)), 20)) return error.BadRsdpChecksum;
|
||||
|
||||
if (rsdp.revision >= 2) {
|
||||
const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(danos.physToVirt(rsdp_phys));
|
||||
if (!checksumOk(@ptrFromInt(danos.physToVirt(rsdp_phys)), xsdp.length)) return error.BadXsdpChecksum;
|
||||
try walkRoot(u64, xsdp.extended_system_descriptor_table_address, dt, hal);
|
||||
const xsdp: *const ExtendedSystemDescriptorPointer = @ptrFromInt(danos.physicalToVirtual(rsdp_physical));
|
||||
if (!checksumOk(@ptrFromInt(danos.physicalToVirtual(rsdp_physical)), xsdp.length)) return error.BadXsdpChecksum;
|
||||
try walkRoot(u64, xsdp.extended_system_descriptor_table_address, device_tree, hal);
|
||||
} else {
|
||||
try walkRoot(u32, rsdp.root_system_description_table_address, dt, hal);
|
||||
try walkRoot(u32, rsdp.root_system_description_table_address, device_tree, hal);
|
||||
}
|
||||
|
||||
// Now that the DSDT and any SSDTs are collected, build the AML namespace and
|
||||
// read the sleep types from it.
|
||||
var blocks: [aml_block_phys.len][]const u8 = undefined;
|
||||
var blocks: [aml_block_physical.len][]const u8 = undefined;
|
||||
for (0..aml_block_count) |i| {
|
||||
blocks[i] = @as([*]const u8, @ptrFromInt(danos.physToVirt(aml_block_phys[i])))[0..aml_block_len[i]];
|
||||
blocks[i] = @as([*]const u8, @ptrFromInt(danos.physicalToVirtual(aml_block_physical[i])))[0..aml_block_len[i]];
|
||||
}
|
||||
const active = blocks[0..aml_block_count];
|
||||
if (aml.parse(dt.allocator, active)) |pr| {
|
||||
if (aml.parse(device_tree.allocator, active)) |pr| {
|
||||
namespace = pr.namespace;
|
||||
aml_stats = .{ .nodes = namespace.?.nodeCount(), .consumed = pr.consumed, .total = pr.total };
|
||||
power_info.s5 = aml.sleepState(&namespace.?, 5);
|
||||
power_info.s3 = aml.sleepState(&namespace.?, 3);
|
||||
power_information.s5 = aml.sleepState(&namespace.?, 5);
|
||||
power_information.s3 = aml.sleepState(&namespace.?, 3);
|
||||
// Fold the namespace's Device objects into the generic tree.
|
||||
wireAcpiDevices(dt, &namespace.?, hal) catch {};
|
||||
wireAcpiDevices(device_tree, &namespace.?, hal) catch {};
|
||||
} else |_| {
|
||||
// AML parse failed (e.g. out of memory); power stays best-effort with
|
||||
// whatever the FADT alone provided.
|
||||
@@ -411,51 +411,51 @@ pub fn discover(rsdp_phys: u64, dt: *DeviceTree, hal: Hal) !void {
|
||||
|
||||
/// Walk the RSDT (Entry = u32) or XSDT (Entry = u64): validate it, then dispatch
|
||||
/// each SDT it points at. A bad individual table is skipped, not fatal.
|
||||
fn walkRoot(comptime Entry: type, root_phys: u64, dt: *DeviceTree, hal: Hal) !void {
|
||||
const header: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physToVirt(root_phys));
|
||||
if (!checksumOk(@ptrFromInt(danos.physToVirt(root_phys)), header.length)) return error.BadRootChecksum;
|
||||
fn walkRoot(comptime Entry: type, root_physical: u64, device_tree: *DeviceTree, hal: Hal) !void {
|
||||
const header: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physicalToVirtual(root_physical));
|
||||
if (!checksumOk(@ptrFromInt(danos.physicalToVirtual(root_physical)), header.length)) return error.BadRootChecksum;
|
||||
|
||||
const count = (header.length - @sizeOf(SystemDescriptorTableHeader)) / @sizeOf(Entry);
|
||||
const base: [*]const u8 = @ptrFromInt(danos.physToVirt(root_phys));
|
||||
const base: [*]const u8 = @ptrFromInt(danos.physicalToVirtual(root_physical));
|
||||
const entries: [*]align(1) const Entry = @ptrCast(base + @sizeOf(SystemDescriptorTableHeader));
|
||||
|
||||
for (entries[0..count]) |ent| {
|
||||
const sdt_phys: u64 = ent; // u32 entries widen; u64 pass through
|
||||
handleTable(dt, hal, sdt_phys) catch continue;
|
||||
const sdt_physical: u64 = ent; // u32 entries widen; u64 pass through
|
||||
handleTable(device_tree, hal, sdt_physical) catch continue;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch a single SDT on its signature.
|
||||
fn handleTable(dt: *DeviceTree, hal: Hal, sdt_phys: u64) !void {
|
||||
const header: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physToVirt(sdt_phys));
|
||||
fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
|
||||
const header: *const SystemDescriptorTableHeader = @ptrFromInt(danos.physicalToVirtual(sdt_physical));
|
||||
const sig = header.signature;
|
||||
if (std.mem.eql(u8, &sig, &APIC)) {
|
||||
try parseMadt(dt, header);
|
||||
try parseMadt(device_tree, header);
|
||||
} else if (std.mem.eql(u8, &sig, &MCFG)) {
|
||||
try parseMcfg(dt, hal, header);
|
||||
try parseMcfg(device_tree, hal, header);
|
||||
} else if (std.mem.eql(u8, &sig, &HPET)) {
|
||||
try parseHpet(dt, header);
|
||||
try parseHpet(device_tree, hal, header);
|
||||
} else if (std.mem.eql(u8, &sig, &FACP)) {
|
||||
parseFadt(header);
|
||||
} else if (std.mem.eql(u8, &sig, &SPCR)) {
|
||||
parseSpcr(header);
|
||||
} else if (std.mem.eql(u8, &sig, &SSDT)) {
|
||||
// Secondary namespace bytecode — collect for the sleep-state (`_Sx`) scan.
|
||||
addAmlBlock(sdt_phys);
|
||||
addAmlBlock(sdt_physical);
|
||||
}
|
||||
// Any other signature is recognised but left opaque for now.
|
||||
}
|
||||
|
||||
/// MADT -> one processor node per Local APIC, one interrupt_controller per I/O APIC.
|
||||
fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void {
|
||||
fn parseMadt(device_tree: *DeviceTree, header: *const SystemDescriptorTableHeader) !void {
|
||||
const madt: *const Madt = @ptrCast(header);
|
||||
const total: usize = header.length;
|
||||
const base: [*]const u8 = @ptrCast(header);
|
||||
var ioapic_index: usize = 0;
|
||||
|
||||
// MADT header: local APIC base + flags (bit 0 = 8259 PIC present).
|
||||
platform_info.lapic_base = madt.local_apic_address;
|
||||
platform_info.pic_present = madt.flags & 1 != 0;
|
||||
platform_information.lapic_base = madt.local_apic_address;
|
||||
platform_information.pic_present = madt.flags & 1 != 0;
|
||||
|
||||
var off: usize = @sizeOf(Madt);
|
||||
while (off + @sizeOf(MadtRecordHeader) <= total) {
|
||||
@@ -468,18 +468,18 @@ fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void
|
||||
if (la.flags & 1 != 0) {
|
||||
var nb: [24]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&nb, "cpu{d}", .{la.processor_id}) catch "cpu";
|
||||
_ = try dt.addChild(dt.root, .processor, nm);
|
||||
_ = try device_tree.addChild(device_tree.root, .processor, nm);
|
||||
// Also record it as a schedulable core (with the APIC ID an AP
|
||||
// wake needs, which the device node name doesn't preserve).
|
||||
if (cpu_info.count < cpu_info.cpus.len) {
|
||||
cpu_info.cpus[cpu_info.count] = .{
|
||||
if (cpu_information.count < cpu_information.cpus.len) {
|
||||
cpu_information.cpus[cpu_information.count] = .{
|
||||
.processor_id = la.processor_id,
|
||||
.apic_id = la.apic_id,
|
||||
.online_capable = la.flags & 2 != 0,
|
||||
};
|
||||
cpu_info.count += 1;
|
||||
cpu_information.count += 1;
|
||||
} else {
|
||||
cpu_info.dropped += 1;
|
||||
cpu_information.dropped += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -488,25 +488,25 @@ fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void
|
||||
var nb: [24]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&nb, "ioapic{d}", .{ioapic_index}) catch "ioapic";
|
||||
ioapic_index += 1;
|
||||
const d = try dt.addChild(dt.root, .interrupt_controller, nm);
|
||||
const d = try device_tree.addChild(device_tree.root, .interrupt_controller, nm);
|
||||
_ = d.addResource(.memory, io.address, 0x20);
|
||||
// The GSI range this I/O APIC handles, starting at gsi_base.
|
||||
_ = d.addResource(.irq, io.gsi_base, 0);
|
||||
},
|
||||
2 => {
|
||||
const iso: *const MadtIso = @ptrCast(base + off);
|
||||
if (platform_info.override_count < platform_info.overrides.len) {
|
||||
platform_info.overrides[platform_info.override_count] = .{
|
||||
if (platform_information.override_count < platform_information.overrides.len) {
|
||||
platform_information.overrides[platform_information.override_count] = .{
|
||||
.source = iso.source,
|
||||
.gsi = iso.gsi,
|
||||
.flags = iso.flags,
|
||||
};
|
||||
platform_info.override_count += 1;
|
||||
platform_information.override_count += 1;
|
||||
}
|
||||
},
|
||||
5 => {
|
||||
const ovr: *const MadtLapicOverride = @ptrCast(base + off);
|
||||
platform_info.lapic_base = ovr.address;
|
||||
platform_information.lapic_base = ovr.address;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -515,7 +515,7 @@ fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void
|
||||
}
|
||||
|
||||
/// MCFG -> a pci_host_bridge per ECAM segment, then a PCI enumeration underneath.
|
||||
fn parseMcfg(dt: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHeader) !void {
|
||||
fn parseMcfg(device_tree: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHeader) !void {
|
||||
const total: usize = header.length;
|
||||
const base: [*]const u8 = @ptrCast(header);
|
||||
|
||||
@@ -526,12 +526,12 @@ fn parseMcfg(dt: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHead
|
||||
|
||||
var nb: [24]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&nb, "pci{d}", .{alloc.segment_group}) catch "pci";
|
||||
const bridge = try dt.addChild(dt.root, .pci_host_bridge, nm);
|
||||
// ECAM window: 1 MiB of config space per bus.
|
||||
const bridge = try device_tree.addChild(device_tree.root, .pci_host_bridge, nm);
|
||||
// ECAM window: 1 MiB of configuration space per bus.
|
||||
_ = bridge.addResource(.memory, alloc.base_address, bus_count << 20);
|
||||
_ = bridge.addResource(.bus_range, alloc.start_bus, bus_count);
|
||||
|
||||
try enumeratePci(dt, bridge, hal, alloc.*);
|
||||
try enumeratePci(device_tree, bridge, hal, alloc.*);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,38 +539,38 @@ fn parseMcfg(dt: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHead
|
||||
/// bridge recursion yet: on the ECAM path the host bridge decodes every bus in
|
||||
/// the window, so scanning the declared range finds everything QEMU exposes.
|
||||
fn enumeratePci(
|
||||
dt: *DeviceTree,
|
||||
bridge: *device.Device,
|
||||
device_tree: *DeviceTree,
|
||||
bridge: *device_model.Device,
|
||||
hal: Hal,
|
||||
alloc: McfgAllocation,
|
||||
) !void {
|
||||
var bus: u16 = alloc.start_bus;
|
||||
while (bus <= alloc.end_bus) : (bus += 1) {
|
||||
var dev: u8 = 0;
|
||||
while (dev < 32) : (dev += 1) {
|
||||
const h0: *align(1) const PciHeader = @ptrCast(pciConfigPtr(alloc, hal, @intCast(bus), dev, 0));
|
||||
var device: u8 = 0;
|
||||
while (device < 32) : (device += 1) {
|
||||
const h0: *align(1) const PciHeader = @ptrCast(pciConfigurationPtr(alloc, hal, @intCast(bus), device, 0));
|
||||
if (h0.vendor_id == 0xFFFF) continue; // no function 0 => slot empty
|
||||
|
||||
const funcs: u8 = if (h0.header_type & 0x80 != 0) 8 else 1;
|
||||
var func: u8 = 0;
|
||||
while (func < funcs) : (func += 1) {
|
||||
const cfg = pciConfigPtr(alloc, hal, @intCast(bus), dev, func);
|
||||
const h: *align(1) const PciHeader = @ptrCast(cfg);
|
||||
var function: u8 = 0;
|
||||
while (function < funcs) : (function += 1) {
|
||||
const configuration = pciConfigurationPtr(alloc, hal, @intCast(bus), device, function);
|
||||
const h: *align(1) const PciHeader = @ptrCast(configuration);
|
||||
if (h.vendor_id == 0xFFFF) continue;
|
||||
|
||||
var nb: [24]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&nb, "{s}:{x:0>2}:{x:0>2}.{d}", .{
|
||||
bridge.name(), bus, dev, func,
|
||||
bridge.name(), bus, device, function,
|
||||
}) catch "pcidev";
|
||||
const node = try dt.addChild(bridge, .pci_device, nm);
|
||||
const node = try device_tree.addChild(bridge, .pci_device, nm);
|
||||
node.ids.pci_vendor = h.vendor_id;
|
||||
node.ids.pci_device = h.device_id;
|
||||
node.ids.pci_class = (@as(u24, h.class_code) << 16) |
|
||||
(@as(u24, h.subclass) << 8) | h.prog_if;
|
||||
node.ids.pci_bdf = (@as(u16, @intCast(bus)) << 8) | (@as(u16, dev) << 3) | func;
|
||||
node.ids.pci_bdf = (@as(u16, @intCast(bus)) << 8) | (@as(u16, device) << 3) | function;
|
||||
|
||||
// BARs only exist in header type 0 (normal devices), not bridges.
|
||||
if (h.header_type & 0x7F == 0) addBars(node, cfg);
|
||||
if (h.header_type & 0x7F == 0) addBars(node, configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -579,58 +579,96 @@ fn enumeratePci(
|
||||
/// Record and size the memory/IO windows named by a device's Base Address
|
||||
/// Registers. Sizing is the standard probe: disable decode, write all-ones, read
|
||||
/// back the writable (address) bits, restore. `size = ~mask + 1`.
|
||||
fn addBars(node: *device.Device, cfg: [*]align(1) u8) void {
|
||||
fn addBars(node: *device_model.Device, configuration: [*]align(1) u8) void {
|
||||
// Stop the device decoding its BARs while we transiently write all-ones.
|
||||
const command = rd(u16, cfg, 0x04);
|
||||
wr(u16, cfg, 0x04, command & ~@as(u16, 0b11));
|
||||
const command = rd(u16, configuration, 0x04);
|
||||
wr(u16, configuration, 0x04, command & ~@as(u16, 0b11));
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < 6) : (i += 1) {
|
||||
const off = 0x10 + i * 4;
|
||||
const orig = rd(u32, cfg, off);
|
||||
const orig = rd(u32, configuration, off);
|
||||
if (orig == 0) continue;
|
||||
|
||||
if (orig & 1 != 0) {
|
||||
// I/O-space BAR (16-bit address space on x86).
|
||||
wr(u32, cfg, off, 0xFFFF_FFFF);
|
||||
const readback = rd(u32, cfg, off);
|
||||
wr(u32, cfg, off, orig);
|
||||
wr(u32, configuration, off, 0xFFFF_FFFF);
|
||||
const readback = rd(u32, configuration, off);
|
||||
wr(u32, configuration, off, orig);
|
||||
const mask = readback & 0xFFFF_FFFC;
|
||||
const size: u32 = if (mask == 0) 0 else (~mask +% 1) & 0xFFFF;
|
||||
_ = node.addResource(.io_port, orig & 0xFFFF_FFFC, size);
|
||||
} else if ((orig >> 1) & 0x3 == 2) {
|
||||
// 64-bit memory BAR: this BAR pair spans two config slots.
|
||||
const orig_hi = rd(u32, cfg, off + 4);
|
||||
wr(u32, cfg, off, 0xFFFF_FFFF);
|
||||
wr(u32, cfg, off + 4, 0xFFFF_FFFF);
|
||||
const lo = rd(u32, cfg, off);
|
||||
const hi = rd(u32, cfg, off + 4);
|
||||
wr(u32, cfg, off, orig);
|
||||
wr(u32, cfg, off + 4, orig_hi);
|
||||
// 64-bit memory BAR: this BAR pair spans two configuration slots.
|
||||
const orig_hi = rd(u32, configuration, off + 4);
|
||||
wr(u32, configuration, off, 0xFFFF_FFFF);
|
||||
wr(u32, configuration, off + 4, 0xFFFF_FFFF);
|
||||
const lo = rd(u32, configuration, off);
|
||||
const hi = rd(u32, configuration, off + 4);
|
||||
wr(u32, configuration, off, orig);
|
||||
wr(u32, configuration, off + 4, orig_hi);
|
||||
const readback = (@as(u64, hi) << 32) | (lo & 0xFFFF_FFF0);
|
||||
const size: u64 = if (readback == 0) 0 else ~readback +% 1;
|
||||
const addr = (@as(u64, orig_hi) << 32) | (orig & 0xFFFF_FFF0);
|
||||
_ = node.addResource(.memory, addr, size);
|
||||
const address = (@as(u64, orig_hi) << 32) | (orig & 0xFFFF_FFF0);
|
||||
_ = node.addResource(.memory, address, size);
|
||||
i += 1; // consumed the high half
|
||||
} else {
|
||||
// 32-bit memory BAR.
|
||||
wr(u32, cfg, off, 0xFFFF_FFFF);
|
||||
const readback = rd(u32, cfg, off);
|
||||
wr(u32, cfg, off, orig);
|
||||
wr(u32, configuration, off, 0xFFFF_FFFF);
|
||||
const readback = rd(u32, configuration, off);
|
||||
wr(u32, configuration, off, orig);
|
||||
const mask = readback & 0xFFFF_FFF0;
|
||||
const size: u32 = if (mask == 0) 0 else ~mask +% 1;
|
||||
_ = node.addResource(.memory, orig & 0xFFFF_FFF0, size);
|
||||
}
|
||||
}
|
||||
|
||||
wr(u16, cfg, 0x04, command); // restore decode
|
||||
wr(u16, configuration, 0x04, command); // restore decode
|
||||
}
|
||||
|
||||
/// HPET -> a timer node with its register block as an MMIO resource.
|
||||
fn parseHpet(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void {
|
||||
/// HPET -> a timer node with its register block as an MMIO resource, plus the GSI
|
||||
/// its comparators can raise.
|
||||
///
|
||||
/// Unlike a PCI device or an ACPI `_CRS` node, the HPET table carries **no interrupt
|
||||
/// number**: which I/O APIC inputs a comparator may drive is advertised at runtime,
|
||||
/// as a bitmask in `Tn_INT_ROUTE_CAP` (bits 63:32 of the Timer 0 configuration register).
|
||||
/// So discovery maps the register block, reads the mask, and records one concrete
|
||||
/// `irq` resource — the GSI a driver is entitled to bind. The driver commits to it
|
||||
/// by writing `Tn_INT_ROUTE_CNF`; the kernel checks the binding against this
|
||||
/// resource (see process.ownedGsi), which is what keeps `irq_bind` a capability
|
||||
/// rather than a request for an arbitrary interrupt line.
|
||||
fn parseHpet(device_tree: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHeader) !void {
|
||||
const hpet: *const Hpet = @ptrCast(header);
|
||||
const d = try dt.addChild(dt.root, .timer, "hpet");
|
||||
const d = try device_tree.addChild(device_tree.root, .timer, "hpet");
|
||||
|
||||
// The GAS tag must say System Memory (0) before we treat `address` as a physical
|
||||
// address. The HPET spec mandates it, but firmware is not a thing to trust: a
|
||||
// System I/O (1) tag here would have us map an arbitrary page and read a bogus
|
||||
// route-capability mask out of it.
|
||||
if (hpet.address_space_id != gas_system_memory) return;
|
||||
|
||||
_ = d.addResource(.memory, hpet.address, 0x400);
|
||||
|
||||
const regs = hal.mapMmio(hpet.address, 0x400, true);
|
||||
const t0_configuration: *const volatile u64 = @ptrFromInt(regs + 0x100);
|
||||
const route_cap: u32 = @truncate(t0_configuration.* >> 32);
|
||||
if (hpetGsi(route_cap)) |gsi| _ = d.addResource(.irq, gsi, 1);
|
||||
}
|
||||
|
||||
/// ACPI Generic Address Structure address-space ids we care about.
|
||||
const gas_system_memory: u8 = 0;
|
||||
|
||||
/// Pick a GSI for the HPET out of its route-capability mask. Prefer an input at or
|
||||
/// above 16: the low ones overlap the legacy ISA lines (2 = cascaded PIT, 8 = RTC),
|
||||
/// which the MADT may separately override, whereas 16+ are the free upper inputs on
|
||||
/// every I/O APIC we care about. Falls back to the lowest bit set if there are none.
|
||||
fn hpetGsi(route_cap: u32) ?u32 {
|
||||
if (route_cap == 0) return null;
|
||||
var gsi: u32 = 16;
|
||||
while (gsi < 32) : (gsi += 1) {
|
||||
if (route_cap & (@as(u32, 1) << @intCast(gsi)) != 0) return gsi;
|
||||
}
|
||||
return @ctz(route_cap);
|
||||
}
|
||||
|
||||
// FADT field offsets (bytes from the table start). The FADT grew across ACPI
|
||||
@@ -645,45 +683,45 @@ const fadt_pm1b_cnt_blk = 68; // u32 (I/O port)
|
||||
const fadt_pm_tmr_blk = 76; // u32 (I/O port) — the PM timer counter
|
||||
const fadt_pm1_cnt_len = 89; // u8 (bytes)
|
||||
const fadt_flags = 112; // u32
|
||||
const fadt_reset_reg = 116; // GAS (12 bytes)
|
||||
const fadt_reset_register = 116; // GAS (12 bytes)
|
||||
const fadt_reset_value = 128; // u8
|
||||
const fadt_x_dsdt = 140; // u64
|
||||
const fadt_x_pm1a_cnt_blk = 172; // GAS
|
||||
const fadt_x_pm1b_cnt_blk = 184; // GAS
|
||||
const fadt_x_pm_tmr_blk = 208; // GAS
|
||||
const flag_reset_reg_supported = 1 << 10;
|
||||
const flag_tmr_val_ext = 1 << 8; // PM timer counter is 32-bit (else 24-bit)
|
||||
const flag_reset_register_supported = 1 << 10;
|
||||
const flag_tmr_value_ext = 1 << 8; // PM timer counter is 32-bit (else 24-bit)
|
||||
|
||||
/// FADT -> the power register map (into `power_info`) and the DSDT address, which
|
||||
/// FADT -> the power register map (into `power_information`) and the DSDT address, which
|
||||
/// is queued for the AML sleep-state (`_Sx`) scan. No AML interpretation happens here.
|
||||
fn parseFadt(header: *const SystemDescriptorTableHeader) void {
|
||||
const base: [*]align(1) const u8 = @ptrCast(header);
|
||||
const len: usize = header.length;
|
||||
const pi = &power_info;
|
||||
const pi = &power_information;
|
||||
|
||||
pi.smi_cmd = @truncate(fadt(u32, base, len, fadt_smi_cmd) orelse 0);
|
||||
pi.acpi_enable = fadt(u8, base, len, fadt_acpi_enable) orelse 0;
|
||||
pi.acpi_disable = fadt(u8, base, len, fadt_acpi_disable) orelse 0;
|
||||
|
||||
const cnt_width = fadt(u8, base, len, fadt_pm1_cnt_len) orelse 2;
|
||||
pi.pm1a_cnt = readCntReg(base, len, fadt_x_pm1a_cnt_blk, fadt_pm1a_cnt_blk, cnt_width);
|
||||
pi.pm1b_cnt = readCntReg(base, len, fadt_x_pm1b_cnt_blk, fadt_pm1b_cnt_blk, cnt_width);
|
||||
pi.pm1a_cnt = readCntRegister(base, len, fadt_x_pm1a_cnt_blk, fadt_pm1a_cnt_blk, cnt_width);
|
||||
pi.pm1b_cnt = readCntRegister(base, len, fadt_x_pm1b_cnt_blk, fadt_pm1b_cnt_blk, cnt_width);
|
||||
|
||||
const flags = fadt(u32, base, len, fadt_flags) orelse 0;
|
||||
pi.reset_supported = flags & flag_reset_reg_supported != 0;
|
||||
pi.reset = readGas(base, len, fadt_reset_reg) orelse .{};
|
||||
pi.reset_supported = flags & flag_reset_register_supported != 0;
|
||||
pi.reset = readGas(base, len, fadt_reset_register) orelse .{};
|
||||
pi.reset_value = fadt(u8, base, len, fadt_reset_value) orelse 0;
|
||||
|
||||
// The PM timer — a fixed-rate counter used as a calibration reference when no
|
||||
// HPET is present. Prefer the 64-bit-capable X_ GAS, fall back to the port.
|
||||
platform_info.pm_timer = readCntReg(base, len, fadt_x_pm_tmr_blk, fadt_pm_tmr_blk, 4);
|
||||
platform_info.pm_timer_32bit = flags & flag_tmr_val_ext != 0;
|
||||
platform_information.pm_timer = readCntRegister(base, len, fadt_x_pm_tmr_blk, fadt_pm_tmr_blk, 4);
|
||||
platform_information.pm_timer_32bit = flags & flag_tmr_value_ext != 0;
|
||||
|
||||
var dsdt: u64 = fadt(u32, base, len, fadt_dsdt) orelse 0;
|
||||
if (fadt(u64, base, len, fadt_x_dsdt)) |x| {
|
||||
if (x != 0) dsdt = x;
|
||||
}
|
||||
dsdt_phys = dsdt;
|
||||
dsdt_physical = dsdt;
|
||||
addAmlBlock(dsdt);
|
||||
}
|
||||
|
||||
@@ -698,15 +736,15 @@ fn parseSpcr(header: *const SystemDescriptorTableHeader) void {
|
||||
const len: usize = header.length;
|
||||
const gas = readGas(base, len, spcr_base_address) orelse return;
|
||||
if (gas.address == 0) return;
|
||||
platform_info.spcr_uart = gas;
|
||||
platform_info.spcr_kind = fadt(u8, base, len, spcr_interface_type) orelse 0;
|
||||
platform_information.spcr_uart = gas;
|
||||
platform_information.spcr_kind = fadt(u8, base, len, spcr_interface_type) orelse 0;
|
||||
}
|
||||
|
||||
// --- 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 PciCtx = struct { bridge: *device.Device, bus: u8 };
|
||||
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`)
|
||||
@@ -714,73 +752,73 @@ const PciCtx = struct { bridge: *device.Device, bus: u8 };
|
||||
/// 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(dt: *DeviceTree, nsp: *aml.Namespace, hal: Hal) !void {
|
||||
var arena = std.heap.ArenaAllocator.init(dt.allocator);
|
||||
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 ev = aml.Interp.init(nsp, .{
|
||||
var interpreter = aml.Interpreter.init(aml_namespace, .{
|
||||
.mapMmio = hal.mapMmio,
|
||||
.pioRead = hal.pioRead,
|
||||
.pioWrite = hal.pioWrite,
|
||||
}, arena.allocator());
|
||||
|
||||
const acpi_root = try dt.addChild(dt.root, .unknown, "acpi");
|
||||
try mirrorDevices(dt, nsp.root, acpi_root, null, &ev);
|
||||
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(dt: *DeviceTree, node: *aml.Node, parent_dev: *device.Device, ctx: ?PciCtx, ev: *aml.Interp) (error{OutOfMemory})!void {
|
||||
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(dt, c, parent_dev, ctx, ev);
|
||||
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(ev, c)) continue;
|
||||
if (!devicePresent(interpreter, c)) continue;
|
||||
|
||||
var gdev: *device.Device = undefined;
|
||||
var child_ctx = ctx;
|
||||
var mirrored_device: *device_model.Device = undefined;
|
||||
var child_context = context;
|
||||
|
||||
if (isPciRootNode(c)) {
|
||||
// The PCI root bridge folds onto the generic host bridge.
|
||||
gdev = matchHostBridge(dt) orelse
|
||||
try dt.addChild(parent_dev, .acpi_device, &c.seg);
|
||||
child_ctx = .{ .bridge = gdev, .bus = 0 };
|
||||
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.
|
||||
gdev = pick: {
|
||||
if (ctx) |pc| {
|
||||
mirrored_device = pick: {
|
||||
if (context) |pc| {
|
||||
if (readAdr(c)) |adr| {
|
||||
if (findPciNode(pc.bridge, pc.bus, adr)) |pnode| break :pick pnode;
|
||||
}
|
||||
}
|
||||
break :pick try dt.addChild(parent_dev, .acpi_device, &c.seg);
|
||||
break :pick try device_tree.addChild(parent_device, .acpi_device, &c.segment);
|
||||
};
|
||||
}
|
||||
|
||||
applyHid(gdev, c, ev);
|
||||
applyCrs(gdev, c, ev);
|
||||
try mirrorDevices(dt, c, gdev, child_ctx, ev);
|
||||
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(ev: *aml.Interp, node: *aml.Node) bool {
|
||||
fn devicePresent(interpreter: *aml.Interpreter, node: *aml.Node) bool {
|
||||
const sta = aml.Namespace.childOf(node, seg4("_STA")) orelse return true;
|
||||
const obj = ev.evaluate(sta, &.{}) catch return true;
|
||||
const status = obj.asInt() catch 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(dt: *DeviceTree) ?*device.Device {
|
||||
var c = dt.root.first_child;
|
||||
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;
|
||||
}
|
||||
@@ -788,12 +826,12 @@ fn matchHostBridge(dt: *DeviceTree) ?*device.Device {
|
||||
}
|
||||
|
||||
/// The PCI function node under `bridge` at the address the device's address object
|
||||
/// (`_ADR`) names (dev/func on
|
||||
/// (`_ADR`) names (device/function on
|
||||
/// `bus`), or null.
|
||||
fn findPciNode(bridge: *device.Device, bus: u8, adr: u32) ?*device.Device {
|
||||
const dev: u16 = @truncate((adr >> 16) & 0x1F);
|
||||
const func: u16 = @truncate(adr & 0x7);
|
||||
const target: u16 = (@as(u16, bus) << 8) | (dev << 3) | func;
|
||||
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| {
|
||||
@@ -832,13 +870,13 @@ fn isPciRootNode(node: *aml.Node) bool {
|
||||
/// 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(dev: *device.Device, node: *aml.Node, ev: *aml.Interp) void {
|
||||
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 = ev.evaluate(hid, &.{}) catch return;
|
||||
const obj = interpreter.evaluate(hid, &.{}) catch return;
|
||||
switch (obj) {
|
||||
.integer => |n| setEisaHid(dev, @truncate(n)),
|
||||
.string => |s| dev.setHid(s),
|
||||
.integer => |n| setEisaHid(device, @truncate(n)),
|
||||
.string => |s| device.setHid(s),
|
||||
else => {},
|
||||
}
|
||||
return;
|
||||
@@ -849,34 +887,34 @@ fn applyHid(dev: *device.Device, node: *aml.Node, ev: *aml.Interp) void {
|
||||
0x00, 0x01, 0xFF, 0x0A, 0x0B, 0x0C, 0x0E => {
|
||||
var p: usize = 0;
|
||||
const n = readIntObj(v, &p) orelse return;
|
||||
setEisaHid(dev, @truncate(n));
|
||||
setEisaHid(device, @truncate(n));
|
||||
},
|
||||
0x0D => dev.setHid(cstr(v[1..])), // StringPrefix
|
||||
0x0D => device.setHid(cstr(v[1..])), // StringPrefix
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn setEisaHid(dev: *device.Device, id: u32) void {
|
||||
dev.ids.acpi_hid = id;
|
||||
var buf: [8]u8 = undefined;
|
||||
dev.setHid(eisaIdToStr(id, &buf));
|
||||
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(dev: *device.Device, node: *aml.Node, ev: *aml.Interp) void {
|
||||
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 = ev.evaluate(crs, &.{}) catch return;
|
||||
const buf = switch (obj) {
|
||||
const obj = interpreter.evaluate(crs, &.{}) catch return;
|
||||
const buffer = switch (obj) {
|
||||
.buffer => |b| b,
|
||||
else => return,
|
||||
};
|
||||
parseResourceTemplate(dev, buf);
|
||||
parseResourceTemplate(device, buffer);
|
||||
}
|
||||
|
||||
/// Walk a ResourceTemplate byte list, adding recognised descriptors as resources.
|
||||
fn parseResourceTemplate(dev: *device.Device, bytes: []const u8) void {
|
||||
fn parseResourceTemplate(device: *device_model.Device, bytes: []const u8) void {
|
||||
var i: usize = 0;
|
||||
while (i < bytes.len) {
|
||||
const tag = bytes[i];
|
||||
@@ -890,14 +928,14 @@ fn parseResourceTemplate(dev: *device.Device, bytes: []const u8) void {
|
||||
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) _ = dev.addResource(.irq, b, 1);
|
||||
if (mask & (@as(u16, 1) << @intCast(b)) != 0) _ = device.addResource(.irq, b, 1);
|
||||
}
|
||||
},
|
||||
0x08 => if (len >= 7) { // IO port: min at +1, length at +6
|
||||
_ = dev.addResource(.io_port, rd16(bytes, body + 1), bytes[body + 6]);
|
||||
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
|
||||
_ = dev.addResource(.io_port, rd16(bytes, body), bytes[body + 2]);
|
||||
_ = device.addResource(.io_port, rd16(bytes, body), bytes[body + 2]);
|
||||
},
|
||||
0x0F => break, // EndTag
|
||||
else => {},
|
||||
@@ -910,20 +948,20 @@ fn parseResourceTemplate(dev: *device.Device, bytes: []const u8) void {
|
||||
const body = i + 3;
|
||||
if (body + len > bytes.len) break;
|
||||
switch (tag) {
|
||||
0x85 => if (len >= 17) { // Memory32: min at +1, length at +13
|
||||
_ = dev.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 13));
|
||||
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
|
||||
_ = dev.addResource(.memory, rd32(bytes, body + 1), rd32(bytes, body + 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) {
|
||||
_ = dev.addResource(.irq, rd32(bytes, body + 2 + k * 4), 1);
|
||||
_ = device.addResource(.irq, rd32(bytes, body + 2 + k * 4), 1);
|
||||
}
|
||||
},
|
||||
0x87, 0x88, 0x8A => parseAddressSpace(dev, tag, bytes[body .. body + len]),
|
||||
0x87, 0x88, 0x8A => parseAddressSpace(device, tag, bytes[body .. body + len]),
|
||||
else => {},
|
||||
}
|
||||
i = body + len;
|
||||
@@ -932,39 +970,39 @@ fn parseResourceTemplate(dev: *device.Device, bytes: []const u8) void {
|
||||
}
|
||||
|
||||
/// Word/DWord/QWord address-space descriptors: resource type at [0], then
|
||||
/// granularity/min/max/translation/length, each of width `w`.
|
||||
fn parseAddressSpace(dev: *device.Device, tag: u8, body: []const u8) void {
|
||||
/// 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 min = readN(body, 3 + w, w);
|
||||
const minimum = readN(body, 3 + w, w);
|
||||
const length = readN(body, 3 + 4 * w, w);
|
||||
const kind: device.ResourceKind = switch (body[0]) {
|
||||
const kind: device_model.ResourceKind = switch (body[0]) {
|
||||
0 => .memory,
|
||||
1 => .io_port,
|
||||
else => .bus_range,
|
||||
};
|
||||
_ = dev.addResource(kind, min, length);
|
||||
_ = device.addResource(kind, minimum, length);
|
||||
}
|
||||
|
||||
/// Decode a packed EISA id into its 7-char string (e.g. 0x030AD041 -> "PNP0A03").
|
||||
fn eisaIdToStr(id: u32, buf: *[8]u8) []const u8 {
|
||||
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;
|
||||
buf[0] = '@' + @as(u8, @intCast((mfg >> 10) & 0x1F));
|
||||
buf[1] = '@' + @as(u8, @intCast((mfg >> 5) & 0x1F));
|
||||
buf[2] = '@' + @as(u8, @intCast(mfg & 0x1F));
|
||||
buf[3] = hexDigit((b2 >> 4) & 0xF);
|
||||
buf[4] = hexDigit(b2 & 0xF);
|
||||
buf[5] = hexDigit((b3 >> 4) & 0xF);
|
||||
buf[6] = hexDigit(b3 & 0xF);
|
||||
return buf[0..7];
|
||||
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 {
|
||||
@@ -976,13 +1014,13 @@ fn seg4(comptime s: *const [4:0]u8) [4]u8 {
|
||||
}
|
||||
|
||||
fn cstr(bytes: []const u8) []const u8 {
|
||||
const idx = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
|
||||
return bytes[0..idx];
|
||||
const index = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
|
||||
return bytes[0..index];
|
||||
}
|
||||
|
||||
const PkgLen = struct { value: usize, size: usize };
|
||||
|
||||
fn pkgLen(bytes: []const u8, p: usize) ?PkgLen {
|
||||
fn packageLength(bytes: []const u8, p: usize) ?PkgLen {
|
||||
if (p >= bytes.len) return null;
|
||||
const lead = bytes[p];
|
||||
const follow: usize = lead >> 6;
|
||||
@@ -1049,9 +1087,9 @@ fn fadt(comptime T: type, base: [*]align(1) const u8, len: usize, off: usize) ?T
|
||||
return rd(T, base, off);
|
||||
}
|
||||
|
||||
/// Decode a Generic Address Structure at `off` into a `RegAccess`. GAS layout:
|
||||
/// Decode a Generic Address Structure at `off` into a `RegisterAccess`. GAS layout:
|
||||
/// address_space(u8), bit_width(u8), bit_offset(u8), access_size(u8), address(u64).
|
||||
fn readGas(base: [*]align(1) const u8, len: usize, off: usize) ?RegAccess {
|
||||
fn readGas(base: [*]align(1) const u8, len: usize, off: usize) ?RegisterAccess {
|
||||
if (off + 12 > len) return null;
|
||||
const address_space = rd(u8, base, off);
|
||||
const bit_width = rd(u8, base, off + 1);
|
||||
@@ -1065,7 +1103,7 @@ fn readGas(base: [*]align(1) const u8, len: usize, off: usize) ?RegAccess {
|
||||
|
||||
/// A PM1 control register: prefer the 64-bit-capable X_ GAS form; fall back to the
|
||||
/// legacy 32-bit I/O-port field. Width comes from PM1_CNT_LEN either way.
|
||||
fn readCntReg(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_off: usize, width: u8) RegAccess {
|
||||
fn readCntRegister(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_off: usize, width: u8) RegisterAccess {
|
||||
if (readGas(base, len, xoff)) |g| {
|
||||
if (g.address != 0) return .{ .mmio = g.mmio, .address = g.address, .width = width };
|
||||
}
|
||||
@@ -1073,16 +1111,16 @@ fn readCntReg(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_off: u
|
||||
return .{ .mmio = false, .address = port, .width = width };
|
||||
}
|
||||
|
||||
/// The mapped config space of one PCI function (its 4 KiB ECAM page). Mapped
|
||||
/// 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.
|
||||
fn pciConfigPtr(alloc: McfgAllocation, hal: Hal, bus: u8, dev: u8, func: u8) [*]align(1) u8 {
|
||||
const phys = alloc.base_address +
|
||||
fn pciConfigurationPtr(alloc: McfgAllocation, hal: Hal, bus: u8, device: u8, function: u8) [*]align(1) u8 {
|
||||
const physical = alloc.base_address +
|
||||
(@as(u64, bus - alloc.start_bus) << 20) +
|
||||
(@as(u64, dev) << 15) +
|
||||
(@as(u64, func) << 12);
|
||||
// Map the config page (writable, for BAR sizing) and use the virtual
|
||||
(@as(u64, device) << 15) +
|
||||
(@as(u64, function) << 12);
|
||||
// Map the configuration page (writable, for BAR sizing) and use the virtual
|
||||
// address the HAL hands back.
|
||||
return @ptrFromInt(hal.mapMmio(phys, danos.page_size, true));
|
||||
return @ptrFromInt(hal.mapMmio(physical, danos.page_size, true));
|
||||
}
|
||||
|
||||
/// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer.
|
||||
@@ -1101,30 +1139,30 @@ fn wr(comptime T: type, bytes: [*]align(1) u8, off: usize, value: T) void {
|
||||
// --- tests ------------------------------------------------------------------
|
||||
|
||||
test "eisaIdToStr decodes a packed EISA id" {
|
||||
var buf: [8]u8 = undefined;
|
||||
var buffer: [8]u8 = undefined;
|
||||
// 0x030AD041 is the well-known encoding of "PNP0A03" (PCI root bridge).
|
||||
try std.testing.expectEqualStrings("PNP0A03", eisaIdToStr(0x030AD041, &buf));
|
||||
try std.testing.expectEqualStrings("PNP0A03", eisaIdToStr(0x030AD041, &buffer));
|
||||
}
|
||||
|
||||
test "parseResourceTemplate extracts IO, IRQ, and fixed memory" {
|
||||
// ResourceTemplate { IO(min 0x60, len 8), IRQ(4), Memory32Fixed(0xFED00000, 0x1000) }
|
||||
const rt = [_]u8{
|
||||
// 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 dev = device.Device{};
|
||||
parseResourceTemplate(&dev, &rt);
|
||||
var device = device_model.Device{};
|
||||
parseResourceTemplate(&device, &runtime);
|
||||
|
||||
try std.testing.expectEqual(@as(u8, 3), dev.resource_count);
|
||||
const rs = dev.resources[0..dev.resource_count];
|
||||
try std.testing.expectEqual(device.ResourceKind.io_port, rs[0].kind);
|
||||
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.ResourceKind.irq, rs[1].kind);
|
||||
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.ResourceKind.memory, rs[2].kind);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user