// SPDX-License-Identifier: GPL-2.0-or-later // // What the kernel uses AML for. See include/dev/acpi.h. // // Interrupt routing is the reason this exists. A PCI device's config space // names an interrupt line, but that number is the 8259's: the firmware // programmed it for an OS running the legacy PICs, and with the I/O APIC in // use the device is on some other pin entirely. Which pin is written down // only in the namespace, as each PCI root bridge's _PRT -- a table from // (device, INTx pin) to either a GSI or a "link device" whose _CRS holds the // GSI. Without reading it, every INTx device on a real board waited on the // wrong pin, and only MSI and the poller (kernel/dev/irq.c) kept anything // working at all. // // The rest is what every machine expects of an OS once it has ACPI: the // power button powers it off cleanly, and power-off and reset go through the // firmware's own registers rather than an emulator's magic ports. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/x86.h" #include "core/spinlock.h" #include "dev/pci.h" #include "core/irq.h" #include "dev/acpi.h" #include "fs/fat.h" #include #include #include #include #include #include #include static int ready; // \_S5's SLP_TYP values, read once at boot for acpi_poweroff_now(): -1 until // then, or if the firmware has none. static int s5_typ_a = -1, s5_typ_b = -1; static uacpi_interrupt_ret power_button(uacpi_handle ctx); void acpi_aml_init(void) { uacpi_status st; if (acpi_rsdp_phys() == 0) return; st = uacpi_initialize(0); if (uacpi_unlikely_error(st)) { cprintf("acpi: tables not usable: %s\n", uacpi_status_to_string(st)); return; } st = uacpi_namespace_load(); if (uacpi_unlikely_error(st)) { cprintf("acpi: AML would not load: %s\n", uacpi_status_to_string(st)); return; } // \_PIC(1): tell the firmware the I/O APIC is in use, so that _PRT answers // with I/O APIC inputs rather than PIC lines. Before _INI runs, which on // some boards reads the answer. uacpi_set_interrupt_model(UACPI_INTERRUPT_MODEL_IOAPIC); st = uacpi_namespace_initialize(); if (uacpi_unlikely_error(st)) { cprintf("acpi: namespace would not initialize: %s\n", uacpi_status_to_string(st)); return; } st = uacpi_install_fixed_event_handler(UACPI_FIXED_EVENT_POWER_BUTTON, power_button, 0); if (uacpi_unlikely_error(st) && st != UACPI_STATUS_COMPILED_OUT) kinfo("acpi: no fixed power button: %s\n", uacpi_status_to_string(st)); uacpi_finalize_gpe_initialization(); { uacpi_object *o = 0; uacpi_object_array p; uacpi_u64 ta, tb; if (uacpi_eval_package(0, "\\_S5", 0, &o) == UACPI_STATUS_OK) { if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK && p.count >= 1 && uacpi_object_get_integer(p.objects[0], &ta) == UACPI_STATUS_OK) { tb = ta; if (p.count >= 2) uacpi_object_get_integer(p.objects[1], &tb); s5_typ_a = ta & 7; s5_typ_b = tb & 7; } uacpi_object_unref(o); } } ready = 1; kinfo("acpi: namespace loaded\n"); } // --- power ------------------------------------------------------------------- // Run in the worker thread, not the SCI handler: syncing the filesystems // sleeps, and S5 runs AML. static void power_off_work(uacpi_handle ctx) { (void)ctx; cprintf("acpi: power button pressed; syncing and powering off\n"); schfs_ksync_all(); fat_ksync_all(1); acpi_poweroff(); cprintf("acpi: power-off failed; it is safe to switch off now\n"); } static uacpi_interrupt_ret power_button(uacpi_handle ctx) { static int once; (void)ctx; if (!once) { once = 1; uacpi_kernel_schedule_work(UACPI_WORK_NOTIFICATION, power_off_work, 0); } return UACPI_INTERRUPT_HANDLED; } int acpi_poweroff(void) { if (!ready) return -1; if (uacpi_unlikely_error(uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S5))) return -1; cli(); uacpi_enter_sleep_state(UACPI_SLEEP_STATE_S5); sti(); return -1; } // Power off from a dead kernel: the PSOGM, after it has been up long enough to // be read. acpi_poweroff() runs _PTS and other AML, which can sleep and take // locks that another processor died holding, so it is no use here. This is // only the last step of it -- SLP_TYP from \_S5, then SLP_EN, written straight // to the PM1 control registers. The firmware's preparations are skipped; a // crashed machine has nothing to save. Returns only if the machine is still on. void acpi_poweroff_now(void) { uacpi_u64 cnt = 0, a, b; if (!ready || s5_typ_a < 0) return; uacpi_read_register(UACPI_REGISTER_PM1_CNT, &cnt); cnt &= ~(uacpi_u64)((7 << 10) | (1 << 13)); a = cnt | ((uacpi_u64)s5_typ_a << 10); b = cnt | ((uacpi_u64)s5_typ_b << 10); uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a, b); uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a | (1 << 13), b | (1 << 13)); microdelay(100000); } int acpi_reboot(void) { if (!ready) return -1; uacpi_reboot(); return -1; } // --- PCI interrupt routing --------------------------------------------------- #define MAXBUS 256 // The bridge leading to each secondary bus, from the devices pci_init found. static struct pci_dev *upstream[MAXBUS]; static uacpi_namespace_node *root_bridge; static uacpi_iteration_decision found_root(void *user, uacpi_namespace_node *node, uacpi_u32 depth) { uacpi_u64 bbn = 0; (void)user; (void)depth; // _BBN is the bridge's first bus. Absent means 0, and bus 0 is the one // whose devices are all that most machines have. uacpi_eval_simple_integer(node, "_BBN", &bbn); if (bbn == 0 && root_bridge == 0) { root_bridge = node; return UACPI_ITERATION_DECISION_BREAK; } return UACPI_ITERATION_DECISION_CONTINUE; } // The namespace node of the device at (slot, func) below `parent`: the child // whose _ADR says so. static uacpi_namespace_node * child_at(uacpi_namespace_node *parent, int slot, int func) { uacpi_namespace_node *child = 0; uacpi_u64 adr; while (uacpi_namespace_node_next(parent, &child) == UACPI_STATUS_OK) { if (uacpi_eval_simple_integer(child, "_ADR", &adr) == UACPI_STATUS_OK && adr == (((uacpi_u64)slot << 16) | (uacpi_u64)func)) return child; } return 0; } struct irqfind { uint32 index; int gsi; int mode; }; static int mode_of(int triggering, int polarity) { int level = triggering == UACPI_TRIGGERING_LEVEL; int low = polarity == UACPI_POLARITY_ACTIVE_LOW; return level ? (low ? IRQ_LEVEL_LOW : IRQ_LEVEL_HIGH) : (low ? IRQ_EDGE_LOW : IRQ_EDGE_HIGH); } static uacpi_iteration_decision link_irq(void *user, uacpi_resource *r) { struct irqfind *f = user; if (r->type == UACPI_RESOURCE_TYPE_IRQ && r->irq.num_irqs > 0) { f->gsi = r->irq.irqs[f->index < r->irq.num_irqs ? f->index : 0]; f->mode = mode_of(r->irq.triggering, r->irq.polarity); return UACPI_ITERATION_DECISION_BREAK; } if (r->type == UACPI_RESOURCE_TYPE_EXTENDED_IRQ && r->extended_irq.num_irqs > 0) { f->gsi = r->extended_irq.irqs[f->index < r->extended_irq.num_irqs ? f->index : 0]; f->mode = mode_of(r->extended_irq.triggering, r->extended_irq.polarity); return UACPI_ITERATION_DECISION_BREAK; } return UACPI_ITERATION_DECISION_CONTINUE; } // Look (slot, pin) up in a _PRT. Returns the GSI, or -1. A link device's // current setting is used as it stands: firmware programs every link at // boot, and one it left disabled is reported rather than reconfigured. static int prt_lookup(uacpi_pci_routing_table *t, int slot, int pin, int *mode) { for (uacpi_size i = 0; i < t->num_entries; i++) { uacpi_pci_routing_table_entry *e = &t->entries[i]; if ((e->address >> 16) != (uint32)slot || e->pin != pin) continue; if (e->source == 0) { *mode = IRQ_LEVEL_LOW; // a bare GSI is PCI's signalling return e->index; } uacpi_resources *res; struct irqfind f = { e->index, -1, IRQ_LEVEL_LOW }; if (uacpi_get_current_resources(e->source, &res) != UACPI_STATUS_OK) return -1; uacpi_for_each_resource(res, link_irq, &f); uacpi_free_resources(res); if (f.gsi <= 0) { uacpi_object_name nm = uacpi_namespace_node_name(e->source); char name[5] = { nm.text[0], nm.text[1], nm.text[2], nm.text[3], 0 }; cprintf("acpi: link %s for slot %d is disabled\n", name, slot); return -1; } *mode = f.mode; return f.gsi; } return -1; } static int route_one(struct pci_dev *d, uacpi_pci_routing_table *roottab, int *mode) { // The chain of bridges from bus 0 down to this device's bus. struct pci_dev *chain[8]; int n = 0, bus = d->bus; while (bus != 0 && n < 8 && upstream[bus]) { chain[n++] = upstream[bus]; bus = upstream[bus]->bus; } if (bus != 0) return -1; // a bus not reached from bus 0 // Walk down the namespace alongside it, as far as the namespace describes // the bridges. The deepest bridge with its own _PRT is where the lookup // happens; below it, INTx pins are swizzled by the PCI-to-PCI bridge rule. uacpi_namespace_node *node = root_bridge; uacpi_pci_routing_table *tab = roottab, *owned = 0; int at = n; // chain[at..] lies below tab's bus for (int i = n - 1; i >= 0 && node; i--) { node = child_at(node, chain[i]->slot, chain[i]->func); uacpi_pci_routing_table *t; if (node && uacpi_get_pci_routing_table(node, &t) == UACPI_STATUS_OK) { if (owned) uacpi_free_pci_routing_table(owned); tab = owned = t; at = i; } } int pin = pci_read8(d->bus, d->slot, d->func, 0x3D) - 1; // 0 = INTA int slot = d->slot; for (int i = 0; i < at; i++) { pin = (pin + slot) % 4; slot = chain[i]->slot; } // The device is now (slot, pin) on the bus `tab` describes: that bridge's // secondary bus, or bus 0 for the root table. int gsi = prt_lookup(tab, slot, pin, mode); if (owned) uacpi_free_pci_routing_table(owned); return gsi; } void acpi_pci_route(void) { uacpi_pci_routing_table *roottab; int i, routed = 0, beyond = 0; if (!ready) return; uacpi_find_devices("PNP0A08", found_root, 0); // PCI Express if (!root_bridge) uacpi_find_devices("PNP0A03", found_root, 0); // conventional PCI if (!root_bridge || uacpi_get_pci_routing_table(root_bridge, &roottab) != UACPI_STATUS_OK) { kinfo("acpi: no _PRT on the root bridge; PCI lines left as found\n"); return; } for (i = 0; i < pci_count(); i++) { struct pci_dev *d = pci_get(i); if ((d->header_type & 0x7F) == 1) { int sec = pci_read8(d->bus, d->slot, d->func, 0x19); if (sec > 0 && sec < MAXBUS) upstream[sec] = d; } } for (i = 0; i < pci_count(); i++) { struct pci_dev *d = pci_get(i); int mode, gsi; if (pci_read8(d->bus, d->slot, d->func, 0x3D) == 0) continue; // uses no INTx pin at all gsi = route_one(d, roottab, &mode); if (gsi < 0) continue; if (gsi >= NIRQ_IOAPIC) { // A second I/O APIC's input. Not reachable here; MSI still is. AMD // boards wire every PCIe bridge's devices to the GNB's I/O APIC, so // on those this is most of the machine. d->irq = 0xFF; beyond++; continue; } if (gsi != d->irq) kinfo("acpi: %d:%d.%d INT%c is gsi %d, not %d\n", d->bus, d->slot, d->func, 'A' + pci_read8(d->bus, d->slot, d->func, 0x3D) - 1, gsi, d->irq); d->irq = gsi; d->irq_mode = mode; routed++; } uacpi_free_pci_routing_table(roottab); kinfo("acpi: %d PCI interrupt%s routed through _PRT\n", routed, routed == 1 ? "" : "s"); if (beyond) kinfo("acpi: %d more on a second I/O APIC, which is not driven; " "those devices need MSI\n", beyond); }