"acpi: 0 PCI interrupts routed through _PRT" on an AMD board meant that every PCIe bridge's devices are wired to the second I/O APIC, which is not driven. It says so now, and that those devices need MSI. "vga: framebuffer disagreement" on an NVIDIA card compared the GOP's framebuffer against BAR0, a 16 MB register block big enough to pass for a screen. The BAR that contains the framebuffer is preferred now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
340 lines
10 KiB
C
340 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// What the kernel uses AML for. See include/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 "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "x86.h"
|
|
#include "spinlock.h"
|
|
#include "pci.h"
|
|
#include "irq.h"
|
|
#include "acpi.h"
|
|
|
|
#include <uacpi/uacpi.h>
|
|
#include <uacpi/utilities.h>
|
|
#include <uacpi/namespace.h>
|
|
#include <uacpi/resources.h>
|
|
#include <uacpi/event.h>
|
|
#include <uacpi/sleep.h>
|
|
|
|
static int ready;
|
|
|
|
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();
|
|
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();
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|