On the TUF A15 Linux's r8169 found its PHY and read its ID as 0x02460246. mdio-bus keeps the register it has just read in r11 across preempt_enable(), which on x86 is `decl %gs:__preempt_count; je` to a call that preserves every register. LDLS gave __preempt_count zeroes, so the count reached zero and the call was made -- to a logging stub, whose syscall left RFLAGS (0x246) in r11. __preempt_count now starts at PREEMPT_ENABLED (x86 keeps NEED_RESCHED inverted), and preempt_schedule and its notrace twin are a bare ret. The same laptop reported its battery full and on AC, unplugged. The EC owns both, and with no EmbeddedControl handler _REG never told the firmware the EC was usable, so _BST and _PSR answered their fallbacks. The EC is found by PNP0C09 or the ECDT and served by the ACPI 12.3 byte protocol, installed before _INI. SCI queries are not handled yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
791 lines
24 KiB
C
791 lines
24 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>
|
|
#include <uacpi/opregion.h>
|
|
#include <uacpi/tables.h>
|
|
#include <uacpi/acpi.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);
|
|
static void cstate_find(void);
|
|
static void ec_init(void);
|
|
|
|
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;
|
|
}
|
|
|
|
// The Embedded Controller, before _INI: methods that run there may already
|
|
// read it, and on laptops it is where the battery and the adapter live.
|
|
ec_init();
|
|
|
|
// \_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");
|
|
cstate_find();
|
|
}
|
|
|
|
// --- the Embedded Controller ----------------------------------------------------
|
|
//
|
|
// A laptop's EC is a microcontroller behind two I/O ports that owns the
|
|
// battery gauge, the charger, the lid and the fans; AML reaches it through an
|
|
// OperationRegion in the EmbeddedControl space, and only the OS can serve
|
|
// that space (ACPI 12). Without a handler every EC field read fails -- and
|
|
// firmware guards against that: _REG(EmbeddedControl, 1) sets a flag (ASUS
|
|
// calls it ECAV), and until it is set _BST answers "full" and _PSR "on AC".
|
|
// That is what the TUF A15 reported, unplugged.
|
|
//
|
|
// The protocol (ACPI 12.3): wait for the input buffer to empty, write a
|
|
// command to the command port, then the address -- and for a read wait for
|
|
// the output buffer and take the byte from the data port. Ports come from
|
|
// the EC device's _CRS, data first; or from the ECDT when the DSDT has no
|
|
// PNP0C09 early enough. SCI events (_Qxx) are not taken yet: nothing here
|
|
// needs to be told the adapter changed, because everything reads on demand.
|
|
|
|
#define EC_OBF 0x01
|
|
#define EC_IBF 0x02
|
|
#define EC_RD 0x80
|
|
#define EC_WR 0x81
|
|
|
|
static uint16 ec_data, ec_cmd;
|
|
static uacpi_namespace_node *ec_node;
|
|
static struct spinlock eclock;
|
|
|
|
static int
|
|
ec_wait(int want_obf)
|
|
{
|
|
for (int i = 0; i < 100000; i++) { // ~100 ms; an EC answers in tens of µs
|
|
uint8 st = inb(ec_cmd);
|
|
if (want_obf ? (st & EC_OBF) : !(st & EC_IBF))
|
|
return 0;
|
|
microdelay(1);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
ec_byte(int write, uint8 addr, uint8 *v)
|
|
{
|
|
int r = -1;
|
|
acquire(&eclock);
|
|
if (ec_wait(0) < 0) goto out;
|
|
outb(ec_cmd, write ? EC_WR : EC_RD);
|
|
if (ec_wait(0) < 0) goto out;
|
|
outb(ec_data, addr);
|
|
if (write) {
|
|
if (ec_wait(0) < 0) goto out;
|
|
outb(ec_data, *v);
|
|
if (ec_wait(0) < 0) goto out;
|
|
} else {
|
|
if (ec_wait(1) < 0) goto out;
|
|
*v = inb(ec_data);
|
|
}
|
|
r = 0;
|
|
out:
|
|
release(&eclock);
|
|
return r;
|
|
}
|
|
|
|
static uacpi_status
|
|
ec_handler(uacpi_region_op op, uacpi_handle opdata)
|
|
{
|
|
if (op == UACPI_REGION_OP_ATTACH || op == UACPI_REGION_OP_DETACH)
|
|
return UACPI_STATUS_OK;
|
|
if (op != UACPI_REGION_OP_READ && op != UACPI_REGION_OP_WRITE)
|
|
return UACPI_STATUS_INVALID_ARGUMENT;
|
|
|
|
uacpi_region_rw_data *d = opdata;
|
|
int write = op == UACPI_REGION_OP_WRITE;
|
|
if (!write)
|
|
d->value = 0;
|
|
// Wider fields are consecutive bytes, low first.
|
|
for (int i = 0; i < d->byte_width; i++) {
|
|
uint8 b = write ? (uint8)(d->value >> (8 * i)) : 0;
|
|
if (d->address + i > 0xFF || ec_byte(write, (uint8)(d->address + i), &b) < 0)
|
|
return UACPI_STATUS_HARDWARE_TIMEOUT;
|
|
if (!write)
|
|
d->value |= (uacpi_u64)b << (8 * i);
|
|
}
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
struct ec_ports { int n; uint16 p[2]; };
|
|
|
|
static uacpi_iteration_decision
|
|
ec_port(void *user, uacpi_resource *r)
|
|
{
|
|
struct ec_ports *e = user;
|
|
if (e->n < 2 && r->type == UACPI_RESOURCE_TYPE_IO)
|
|
e->p[e->n++] = r->io.minimum;
|
|
else if (e->n < 2 && r->type == UACPI_RESOURCE_TYPE_FIXED_IO)
|
|
e->p[e->n++] = r->fixed_io.address;
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
}
|
|
|
|
static uacpi_iteration_decision
|
|
found_ec(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
|
|
{
|
|
(void)user; (void)depth;
|
|
struct ec_ports e = { 0, { 0, 0 } };
|
|
uacpi_for_each_device_resource(node, "_CRS", ec_port, &e);
|
|
if (e.n == 2) {
|
|
ec_node = node;
|
|
ec_data = e.p[0];
|
|
ec_cmd = e.p[1];
|
|
return UACPI_ITERATION_DECISION_BREAK;
|
|
}
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
ec_init(void)
|
|
{
|
|
uacpi_table t;
|
|
|
|
initlock(&eclock, "ec");
|
|
uacpi_find_devices("PNP0C09", found_ec, 0);
|
|
if (!ec_node &&
|
|
uacpi_table_find_by_signature("ECDT", &t) == UACPI_STATUS_OK) {
|
|
struct acpi_ecdt *e = t.ptr;
|
|
if (e->ec_control.address && e->ec_data.address) {
|
|
ec_cmd = (uint16)e->ec_control.address;
|
|
ec_data = (uint16)e->ec_data.address;
|
|
ec_node = uacpi_namespace_root();
|
|
}
|
|
uacpi_table_unref(&t);
|
|
}
|
|
if (!ec_node)
|
|
return;
|
|
|
|
// Installing runs _REG(3, 1) for the regions under it.
|
|
uacpi_status st = uacpi_install_address_space_handler(
|
|
ec_node, UACPI_ADDRESS_SPACE_EMBEDDED_CONTROLLER, ec_handler, 0);
|
|
if (uacpi_unlikely_error(st)) {
|
|
kinfo("acpi: EC at 0x%x/0x%x, handler refused: %s\n", ec_data, ec_cmd,
|
|
uacpi_status_to_string(st));
|
|
return;
|
|
}
|
|
uint8 probe = 0;
|
|
kinfo("acpi: EC at data 0x%x, command 0x%x%s\n", ec_data, ec_cmd,
|
|
ec_byte(0, 0, &probe) < 0 ? " -- not answering" : "");
|
|
}
|
|
|
|
// --- 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);
|
|
}
|
|
|
|
// --- batteries and adapters --------------------------------------------------
|
|
//
|
|
// A laptop's battery is an ACPI device, PNP0C0A, with methods that answer in
|
|
// packages: _BIX (or the older _BIF) for what it is -- capacities, units,
|
|
// names -- and _BST for how it is now. The adapter is ACPI0003 and _PSR says
|
|
// whether it is plugged in. Read on demand, from /sys/class/power_supply,
|
|
// which is where fastfetch and every battery widget look.
|
|
|
|
#define MAXPSU 4
|
|
static uacpi_namespace_node *psu_node[MAXPSU];
|
|
static int psu_isbat[MAXPSU];
|
|
static int npsu;
|
|
|
|
static uacpi_iteration_decision
|
|
found_psu(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
|
|
{
|
|
(void)depth;
|
|
if (npsu < MAXPSU) {
|
|
psu_node[npsu] = node;
|
|
psu_isbat[npsu] = user != 0;
|
|
npsu++;
|
|
}
|
|
return UACPI_ITERATION_DECISION_CONTINUE;
|
|
}
|
|
|
|
static int psu_found;
|
|
|
|
static void
|
|
psu_find(void)
|
|
{
|
|
if (psu_found || !ready)
|
|
return;
|
|
psu_found = 1;
|
|
uacpi_find_devices("PNP0C0A", found_psu, (void *)1);
|
|
uacpi_find_devices("ACPI0003", found_psu, 0);
|
|
}
|
|
|
|
int
|
|
acpi_psu_count(void)
|
|
{
|
|
psu_find();
|
|
return npsu;
|
|
}
|
|
|
|
static uint32
|
|
pkg_int(uacpi_object_array *p, uacpi_size i)
|
|
{
|
|
uacpi_u64 v;
|
|
if (i >= p->count || uacpi_object_get_integer(p->objects[i], &v) != UACPI_STATUS_OK)
|
|
return ACPI_UNKNOWN;
|
|
return (uint32)v;
|
|
}
|
|
|
|
static void
|
|
pkg_str(uacpi_object_array *p, uacpi_size i, char *out, int cap)
|
|
{
|
|
uacpi_data_view d;
|
|
out[0] = 0;
|
|
if (i >= p->count ||
|
|
uacpi_object_get_string_or_buffer(p->objects[i], &d) != UACPI_STATUS_OK)
|
|
return;
|
|
int n = 0;
|
|
for (uacpi_size k = 0; k < d.length && n < cap - 1 && d.const_text[k]; k++)
|
|
if (d.const_text[k] >= ' ' && d.const_text[k] < 127)
|
|
out[n++] = d.const_text[k];
|
|
// Firmware pads with spaces.
|
|
while (n > 0 && out[n - 1] == ' ')
|
|
n--;
|
|
out[n] = 0;
|
|
}
|
|
|
|
int
|
|
acpi_psu_get(int i, struct acpi_psu *b)
|
|
{
|
|
uacpi_object *o;
|
|
uacpi_object_array p;
|
|
uacpi_u32 sta = 0xF;
|
|
|
|
psu_find();
|
|
if (i < 0 || i >= npsu)
|
|
return -1;
|
|
memset(b, 0, sizeof(*b));
|
|
b->battery = psu_isbat[i];
|
|
|
|
if (!b->battery) {
|
|
uacpi_u64 v = 0;
|
|
uacpi_eval_simple_integer(psu_node[i], "_PSR", &v);
|
|
b->online = v != 0;
|
|
return 0;
|
|
}
|
|
|
|
// Bit 4 of _STA: a battery is in the bay. A missing _STA means present.
|
|
uacpi_eval_sta(psu_node[i], &sta);
|
|
b->present = (sta & 0x10) != 0;
|
|
b->rate = b->remaining = b->voltage = ACPI_UNKNOWN;
|
|
b->design = b->full = b->design_voltage = b->cycles = ACPI_UNKNOWN;
|
|
if (!b->present)
|
|
return 0;
|
|
|
|
// _BIX: a revision first, then _BIF's fields shifted by one, with the
|
|
// cycle count after the design voltage's neighbours.
|
|
if (uacpi_eval_package(psu_node[i], "_BIX", 0, &o) == UACPI_STATUS_OK) {
|
|
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
|
b->unit = (int)pkg_int(&p, 1);
|
|
b->design = pkg_int(&p, 2);
|
|
b->full = pkg_int(&p, 3);
|
|
b->technology = (int)pkg_int(&p, 4);
|
|
b->design_voltage = pkg_int(&p, 5);
|
|
b->cycles = pkg_int(&p, 8);
|
|
pkg_str(&p, 16, b->model, sizeof(b->model));
|
|
pkg_str(&p, 17, b->serial, sizeof(b->serial));
|
|
pkg_str(&p, 18, b->kind, sizeof(b->kind));
|
|
pkg_str(&p, 19, b->oem, sizeof(b->oem));
|
|
}
|
|
uacpi_object_unref(o);
|
|
} else if (uacpi_eval_package(psu_node[i], "_BIF", 0, &o) == UACPI_STATUS_OK) {
|
|
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
|
b->unit = (int)pkg_int(&p, 0);
|
|
b->design = pkg_int(&p, 1);
|
|
b->full = pkg_int(&p, 2);
|
|
b->technology = (int)pkg_int(&p, 3);
|
|
b->design_voltage = pkg_int(&p, 4);
|
|
pkg_str(&p, 9, b->model, sizeof(b->model));
|
|
pkg_str(&p, 10, b->serial, sizeof(b->serial));
|
|
pkg_str(&p, 11, b->kind, sizeof(b->kind));
|
|
pkg_str(&p, 12, b->oem, sizeof(b->oem));
|
|
}
|
|
uacpi_object_unref(o);
|
|
}
|
|
|
|
if (uacpi_eval_package(psu_node[i], "_BST", 0, &o) == UACPI_STATUS_OK) {
|
|
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
|
b->state = pkg_int(&p, 0);
|
|
b->rate = pkg_int(&p, 1);
|
|
b->remaining = pkg_int(&p, 2);
|
|
b->voltage = pkg_int(&p, 3);
|
|
}
|
|
uacpi_object_unref(o);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// --- idle states --------------------------------------------------------------
|
|
//
|
|
// An idle CPU used to run hlt and nothing else. hlt is C1: the core stops
|
|
// executing and stays powered. The states below it -- CC6 on Zen, C3 and
|
|
// deeper on Intel -- switch the core off, and the firmware lists how to reach
|
|
// them in each processor's _CST. Without them a laptop sitting at a shell
|
|
// keeps every core warm and the fans going.
|
|
//
|
|
// _CST is a count followed by one package per state: a register (a Generic
|
|
// Register descriptor in a buffer), a type (1 = C1, 2 = C2, 3 = C3), the
|
|
// worst-case wake-up latency in microseconds, and the power. A register in
|
|
// system I/O space is entered by reading it; one in functional fixed hardware
|
|
// is MWAIT, with the hint in the address.
|
|
//
|
|
// Only types 1 and 2 are taken. C3 asks the OS to stop bus-master activity
|
|
// or flush caches first (ACPI 8.1.4), which this does not do.
|
|
|
|
struct acpi_idle acpi_idle;
|
|
|
|
static int cst_done;
|
|
|
|
static void
|
|
cst_parse(uacpi_namespace_node *node)
|
|
{
|
|
uacpi_object *o = 0;
|
|
uacpi_object_array p, e;
|
|
uacpi_data_view d;
|
|
|
|
if (uacpi_eval_package(node, "_CST", 0, &o) != UACPI_STATUS_OK)
|
|
return;
|
|
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
|
for (uacpi_size i = 1; i < p.count; i++) {
|
|
if (uacpi_object_get_package(p.objects[i], &e) != UACPI_STATUS_OK ||
|
|
e.count < 4)
|
|
continue;
|
|
uint32 type = pkg_int(&e, 1), lat = pkg_int(&e, 2);
|
|
if (type != 2 || lat > 1000)
|
|
continue;
|
|
if (uacpi_object_get_buffer(e.objects[0], &d) != UACPI_STATUS_OK ||
|
|
d.length < 15 || d.const_bytes[0] != 0x82)
|
|
continue;
|
|
uint8 space = d.const_bytes[3];
|
|
uint64 addr = 0;
|
|
for (int k = 0; k < 8; k++)
|
|
addr |= (uint64)d.const_bytes[7 + k] << (8 * k);
|
|
if (space == UACPI_ADDRESS_SPACE_SYSTEM_IO && addr && addr < 0x10000) {
|
|
acpi_idle.kind = ACPI_IDLE_IO;
|
|
} else if (space == UACPI_ADDRESS_SPACE_FFIXEDHW && (d.const_bytes[4] == 1)) {
|
|
// Intel's FFH encoding: vendor 1, class 2 (native C-state), and
|
|
// the MWAIT hint in the low byte of the address.
|
|
acpi_idle.kind = ACPI_IDLE_MWAIT;
|
|
} else {
|
|
continue;
|
|
}
|
|
acpi_idle.addr = addr;
|
|
acpi_idle.latency = lat;
|
|
break;
|
|
}
|
|
}
|
|
uacpi_object_unref(o);
|
|
}
|
|
|
|
static uacpi_iteration_decision
|
|
found_cpu(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
|
|
{
|
|
(void)user; (void)depth;
|
|
if (cst_done)
|
|
return UACPI_ITERATION_DECISION_BREAK;
|
|
cst_parse(node);
|
|
cst_done = 1; // every processor's _CST is the same in practice
|
|
return UACPI_ITERATION_DECISION_BREAK;
|
|
}
|
|
|
|
static void
|
|
cstate_find(void)
|
|
{
|
|
uint32 a, b, c, dd;
|
|
|
|
// The local APIC timer is what wakes an idle CPU for the scheduler tick.
|
|
// Below C1 it may stop, unless the CPU says it keeps running (ARAT,
|
|
// CPUID 6 EAX bit 2); without that an idle CPU would sleep for good.
|
|
cpuid_leaf(6, &a, &b, &c, &dd);
|
|
if (!(a & 4)) {
|
|
kinfo("acpi: no always-running APIC timer; idle stays at C1\n");
|
|
return;
|
|
}
|
|
|
|
uacpi_namespace_for_each_child(uacpi_namespace_root(), found_cpu, 0,
|
|
UACPI_OBJECT_PROCESSOR_BIT,
|
|
UACPI_MAX_DEPTH_ANY, 0);
|
|
if (!cst_done)
|
|
uacpi_find_devices("ACPI0007", found_cpu, 0);
|
|
|
|
if (acpi_idle.kind == ACPI_IDLE_MWAIT) {
|
|
cpuid_leaf(1, &a, &b, &c, &dd);
|
|
if (!(c & (1u << 3))) // no MONITOR/MWAIT after all
|
|
acpi_idle.kind = ACPI_IDLE_HLT;
|
|
}
|
|
if (acpi_idle.kind == ACPI_IDLE_IO)
|
|
kinfo("acpi: idle CPUs enter C2 through port %x (wake-up %d us)\n",
|
|
(uint)acpi_idle.addr, acpi_idle.latency);
|
|
else if (acpi_idle.kind == ACPI_IDLE_MWAIT)
|
|
kinfo("acpi: idle CPUs enter C2 by MWAIT hint %x (wake-up %d us)\n",
|
|
(uint)acpi_idle.addr, acpi_idle.latency);
|
|
else
|
|
kinfo("acpi: no usable C2 in _CST; idle stays at C1\n");
|
|
}
|