//! /system/services/acpi — the ACPI discovery service: the x86 firmware //! interpreter, moved out of ring 0 (docs/m19-m20-plan.md, M20). Claims the //! `acpi-tables` node the kernel publishes (the AML blobs, the broad io_port //! grant, a broad irq window, the SCI), and runs the **shared AML module** in //! ring 3 — the same parser and interpreter the kernel uses. //! //! M20.2 (this increment): after parsing, walk the namespace and, for each //! present Device with a hardware id (`_HID`), evaluate its current resource //! settings (`_CRS`) through a ring-3 `Hal` (port I/O over the claimed node), //! register it under the acpi-tables node (its I/O ports and IRQs contained by //! the node's broad grants), and report it to the device manager with its //! EISA-decoded hid as identity. Matching those reports to drivers (ps2-bus) //! and retiring the kernel's own device build follow in M20.3. const std = @import("std"); const runtime = @import("runtime"); const aml = @import("aml"); const device = runtime.device; const protocol = runtime.device_manager_protocol; fn writeLine(comptime fmt: []const u8, arguments: anytype) void { var line: [128]u8 = undefined; _ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return); } // The claimed acpi-tables node and the resource index of its broad io_port // window — the Hal routes every port access through this one claim. var node_id: u64 = 0; var io_resource_index: u64 = 0; // Pass-1 registration record (see main): what pass 2 reports. const Registered = struct { hid: [8]u8 = .{0} ** 8, hid_len: usize = 0, device_id: u64 = 0, resource_count: u64 = 0 }; var registered: [64]Registered = undefined; var registered_count: usize = 0; // A scratch page returned for SystemMemory OperationRegion maps: the service // cannot map arbitrary physical memory from ring 3, so such regions are // unsupported and degrade to harmless zeros rather than faulting. The M20.2 // targets (ps2, the legacy devices) use SystemIO and static templates. var mmio_scratch: [4096]u8 align(4096) = .{0} ** 4096; fn halMapMmio(physical: u64, len: u64, writable: bool) u64 { _ = physical; _ = len; _ = writable; return @intFromPtr(&mmio_scratch); } fn halPioRead(width: u8, port: u16) u32 { return device.ioRead(node_id, io_resource_index, port, width) orelse 0; } fn halPioWrite(width: u8, port: u16, value: u32) void { _ = device.ioWrite(node_id, io_resource_index, port, width, value); } fn findTablesNode(buffer: []device.DeviceDescriptor) ?device.DeviceDescriptor { const total = device.enumerate(buffer); for (buffer[0..@min(total, buffer.len)]) |d| { if (d.class == @intFromEnum(device.DeviceClass.acpi_tables)) return d; } return null; } pub fn main(init: runtime.process.Init) void { // When the acpi-parse scenario spawns this directly, argv[1] is the kernel's // own device count to self-verify against — deterministic, no log-scraping. const expected: ?usize = if (init.arguments.get(1)) |a| (std.fmt.parseInt(usize, a, 10) catch null) else null; const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { _ = runtime.system.write("acpi: out of memory\n"); return; }; const node = findTablesNode(buffer) orelse { _ = runtime.system.write("acpi: no acpi-tables node to claim\n"); return; }; node_id = node.id; if (!device.claim(node_id)) { _ = runtime.system.write("acpi: unable to claim acpi-tables\n"); return; } // Map each memory resource (an AML blob) and note the io_port resource. var blocks: [8][]const u8 = undefined; var block_count: usize = 0; var found_io = false; for (node.resources[0..@intCast(node.resource_count)], 0..) |resource, index| { if (resource.kind == @intFromEnum(device.ResourceKind.io_port) and !found_io) { io_resource_index = index; found_io = true; continue; } if (resource.kind != @intFromEnum(device.ResourceKind.memory)) continue; const base = device.mmioMap(node_id, index) orelse continue; const pointer: [*]const u8 = @ptrFromInt(base); blocks[block_count] = pointer[0..@intCast(resource.len)]; block_count += 1; if (block_count == blocks.len) break; } if (block_count == 0) { _ = runtime.system.write("acpi: no AML blobs on the node\n"); return; } const result = aml.parse(runtime.allocator(), blocks[0..block_count]) catch { _ = runtime.system.write("acpi: AML parse failed\n"); return; }; var namespace = result.namespace; const devices = aml.deviceCount(&namespace); writeLine("acpi: parsed {d} AML blob(s), {d} namespace devices\n", .{ block_count, devices }); if (expected) |want| { if (devices == want) { _ = runtime.system.write("acpi-parse: ok\n"); } else { writeLine("acpi-parse: mismatch (ring-3 {d} vs kernel {d})\n", .{ devices, want }); } // Self-verify mode is standalone (no manager); stop before reporting. while (true) runtime.system.sleep(1000); } // Register + report the present _HID devices (M20.2). var arena = std.heap.ArenaAllocator.init(runtime.allocator()); var interpreter = aml.Interpreter.init(&namespace, .{ .mapMmio = halMapMmio, .pioRead = halPioRead, .pioWrite = halPioWrite, }, arena.allocator()); // Pass 1: register every present _HID device under acpi-tables, remembering // each (hid, device id). Pass 2: report them all. Registering before any // report reaches the manager means a driver it spawns on the first report // already sees the whole set (no keyboard-before-mouse race for ps2-bus). registered_count = 0; walkDevices(namespace.root, &interpreter); const manager = runtime.ipc.lookup(.device_manager); var i: usize = 0; while (i < registered_count) : (i += 1) { 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 }); if (manager) |h| { var report = protocol.ChildAdded{ .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id, }; @memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]); var reply: [protocol.message_maximum]u8 = undefined; _ = runtime.ipc.call(h, std.mem.asBytes(&report), &reply) catch {}; } } writeLine("acpi: reported {d} device(s) to the manager\n", .{registered_count}); // Stay resident: the claim holds, and the service is here to grow into the // supervised discoverer (M20.3, then the M21 event side on the SCI). while (true) runtime.system.sleep(1000); } /// Depth-first walk: register + report each present device with a _HID, then /// descend. Scopes (\_SB, \_GPE …) are descended without producing a node. fn walkDevices(node: *aml.Node, interpreter: *aml.Interpreter) void { var child = node.first_child; while (child) |c| : (child = c.next_sibling) { if (c.kind != .device) { walkDevices(c, interpreter); continue; } if (!devicePresent(interpreter, c)) continue; // absent: skip it and its subtree if (readHid(c, interpreter)) |hid| { // Skip PCI roots — pci-bus already reports PCI functions; ACPI adds // only the non-PCI _HID devices (docs/m19-m20-plan.md M20.2). if (!std.mem.eql(u8, hid[0..7], "PNP0A03") and !std.mem.eql(u8, hid[0..7], "PNP0A08")) { registerDevice(c, hid, interpreter); } } walkDevices(c, interpreter); } } fn registerDevice(node: *aml.Node, hid: [8]u8, interpreter: *aml.Interpreter) void { if (registered_count >= registered.len) return; var descriptor = std.mem.zeroes(device.DeviceDescriptor); descriptor.class = @intFromEnum(device.DeviceClass.acpi_device); descriptor.pci_class = device.no_pci_class; const hid_len: u64 = std.mem.indexOfScalar(u8, &hid, 0) orelse hid.len; descriptor.hid_len = hid_len; @memcpy(descriptor.hid[0..@intCast(hid_len)], hid[0..@intCast(hid_len)]); applyCrs(&descriptor, node, interpreter); const id = device.register(node_id, &descriptor) orelse { writeLine("acpi: register refused for {s}\n", .{hid[0..@intCast(hid_len)]}); return; }; registered[registered_count] = .{ .hid = hid, .hid_len = @intCast(hid_len), .device_id = id, .resource_count = descriptor.resource_count }; registered_count += 1; } /// _STA bit 0 (present); absent method or a failed evaluation is treated as /// present, per the ACPI rules. 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; } /// The device's EISA-decoded _HID (e.g. "PNP0303"), or null. fn readHid(node: *aml.Node, interpreter: *aml.Interpreter) ?[8]u8 { const hid = aml.Namespace.childOf(node, seg4("_HID")) orelse return null; var buffer: [8]u8 = .{0} ** 8; if (hid.kind == .method) { const obj = interpreter.evaluate(hid, &.{}) catch return null; switch (obj) { .integer => |n| { _ = eisaIdToStr(@truncate(n), &buffer); return buffer; }, else => return null, } } if (hid.kind != .name or hid.value.len == 0) return null; 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 null; _ = eisaIdToStr(@truncate(n), &buffer); return buffer; }, else => return null, } } // --- _CRS resource-template decode (ported from the kernel's acpi.zig) -------- fn applyCrs(descriptor: *device.DeviceDescriptor, 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 bytes = switch (obj) { .buffer => |b| b, else => return, }; var i: usize = 0; while (i < bytes.len) { const tag = bytes[i]; if (tag & 0x80 == 0) { const len: usize = tag & 0x07; const body = i + 1; if (body + len > bytes.len) break; switch ((tag >> 3) & 0x0F) { 0x04 => if (len >= 2) { // IRQ mask 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) addResource(descriptor, .irq, b, 1); } }, 0x08 => 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]), 0x0F => break, else => {}, } i = body + len; } else { 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) 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)), 0x89 => if (len >= 2) { const count = bytes[body + 1]; var k: usize = 0; while (k < count and body + 2 + k * 4 + 4 <= body + len) : (k += 1) { addResource(descriptor, .irq, rd32(bytes, body + 2 + k * 4), 1); } }, else => {}, } i = body + len; } } } fn addResource(descriptor: *device.DeviceDescriptor, kind: device.ResourceKind, start: u64, len: u64) void { if (descriptor.resource_count >= descriptor.resources.len) return; descriptor.resources[@intCast(descriptor.resource_count)] = .{ .kind = @intFromEnum(kind), .start = start, .len = len }; descriptor.resource_count += 1; } // --- small helpers ported verbatim from the kernel's acpi.zig ---------------- fn seg4(comptime s: *const [4:0]u8) [4]u8 { return s[0..4].*; } fn hexDigit(n: u8) u8 { return if (n < 10) '0' + n else 'A' + (n - 10); } 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); buffer[7] = 0; return buffer[0..7]; } fn readIntObj(bytes: []const u8, p: *usize) ?u64 { if (p.* >= bytes.len) return null; const op = bytes[p.*]; p.* += 1; switch (op) { 0x00 => return 0, 0x01 => return 1, 0xFF => return 1, 0x0A => { if (p.* >= bytes.len) return null; const v = bytes[p.*]; p.* += 1; return v; }, 0x0B => { if (p.* + 2 > bytes.len) return null; const v = rd16(bytes, p.*); p.* += 2; return v; }, 0x0C => { if (p.* + 4 > bytes.len) return null; const v = rd32(bytes, p.*); p.* += 4; return v; }, else => return null, } } fn rd16(bytes: []const u8, off: usize) u64 { return @as(u64, bytes[off]) | (@as(u64, bytes[off + 1]) << 8); } fn rd32(bytes: []const u8, off: usize) u64 { return rd16(bytes, off) | (rd16(bytes, off + 2) << 16); } pub const panic = runtime.panic; comptime { _ = &runtime.start._start; // pull the runtime entry shim into the image }