SchOS/kernel/acpi/aml.c
Nexoniarz c65bfa3fa3 boot: driver chatter goes to the log; the console clears its margins
ACPI, DMI, SMP, xHCI, USB, I2C, NVMe, r8169 and ramdisk progress lines are
kinfo now: in dmesg and on a verbose boot, off the screen of a working
machine. Failures stay cprintf.

vt_repaint also paints the strip right of the last column and below the
last row, which kept the bottom of the X desktop after Ctrl+Alt+F-key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 01:53:46 +02:00

386 lines
12 KiB
C

// 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 <uacpi/uacpi.h>
#include <uacpi/utilities.h>
#include <uacpi/namespace.h>
#include <uacpi/resources.h>
#include <uacpi/event.h>
#include <uacpi/sleep.h>
#include <uacpi/registers.h>
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 };
kinfo("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);
}