SchOS/kernel/arch/x86_64/acpi.c
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00

264 lines
7.8 KiB
C

// Find the processors, the way firmware from this century describes them.
//
// mpinit() walks the Intel MP table, which is from 1994. Modern firmware fills
// it in minimally or omits it: measured 2026-08-08, QEMU/SeaBIOS with -smp 4
// publishes a 208-byte MP table containing exactly *one* processor entry, then
// buses and interrupt assignments. Everything since has expected the operating
// system to read the ACPI MADT instead.
//
// The consequence was not that SMP was slow, it was that ncpu was 1 and SMP was
// silently never exercised. Machines whose firmware does publish a full MP
// table -- VMware, older hardware -- took the other path, started their APs,
// and triple faulted. So this file is a prerequisite for debugging that: until
// the CPUs are visible under QEMU there is no way to reproduce the crash except
// on the hardware it happens on.
//
// Written from the ACPI specification. No table layout here is copied from
// another kernel; they are all in chapter 5 of the published standard.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
extern struct cpu cpus[NCPU];
extern int ncpu;
extern uchar ioapicid;
extern volatile uint *lapic;
// The Root System Description Pointer. Found by signature, not by being told
// where it is -- it sits either in the Extended BIOS Data Area or in the BIOS
// read-only region, on a 16-byte boundary.
struct acpi_rsdp {
char sig[8]; // "RSD PTR "
uint8 checksum; // over the first 20 bytes
char oemid[6];
uint8 revision; // 0 = ACPI 1.0 (RSDT), 2+ = 2.0 (XSDT)
uint32 rsdt_addr;
// ACPI 2.0 and later only, and only valid when revision >= 2.
uint32 length;
uint64 xsdt_addr;
uint8 ext_checksum; // over `length` bytes
uint8 reserved[3];
} __attribute__((packed));
// Every ACPI table starts with this.
struct acpi_sdt {
char sig[4];
uint32 length;
uint8 revision;
uint8 checksum;
char oemid[6];
char oem_table_id[8];
uint32 oem_revision;
uint32 creator_id;
uint32 creator_revision;
} __attribute__((packed));
// The Multiple APIC Description Table: signature "APIC".
struct acpi_madt {
struct acpi_sdt h;
uint32 lapic_addr;
uint32 flags; // bit 0: legacy 8259 PICs are present
} __attribute__((packed));
#define MADT_PCAT_COMPAT 1
// MADT entry types. Only these three are read; the rest are interrupt
// routing overrides that ioapic.c does not consult yet.
#define MADT_LAPIC 0
#define MADT_IOAPIC 1
#define MADT_LAPIC_OVERRIDE 5
struct madt_entry {
uint8 type;
uint8 length;
} __attribute__((packed));
struct madt_lapic {
struct madt_entry e;
uint8 acpi_id;
uint8 apic_id;
uint32 flags; // bit 0: enabled, bit 1: online-capable
} __attribute__((packed));
#define LAPIC_ENABLED 1
#define LAPIC_ONLINE_CAPABLE 2
struct madt_ioapic {
struct madt_entry e;
uint8 id;
uint8 reserved;
uint32 addr;
uint32 gsi_base;
} __attribute__((packed));
struct madt_lapic_override {
struct madt_entry e;
uint16 reserved;
uint64 addr;
} __attribute__((packed));
static int
checksum_ok(const void *p, uint32 len)
{
const uint8 *b = p;
uint8 sum = 0;
for (uint32 i = 0; i < len; i++)
sum += b[i];
return sum == 0;
}
// Scan a physical range for the RSDP signature. It is 16-byte aligned by
// specification, which makes this cheap enough to do twice.
static struct acpi_rsdp *
scan_rsdp(uint64 base, uint64 len)
{
for (uint64 off = 0; off + sizeof(struct acpi_rsdp) <= len; off += 16) {
struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(base + off);
if (memcmp(r->sig, "RSD PTR ", 8) != 0)
continue;
// Signature alone is not enough: the string can appear in unrelated data,
// and following a bad pointer here means faulting before there is anything
// to report the fault with.
if (!checksum_ok(r, 20))
continue;
return r;
}
return 0;
}
static struct acpi_rsdp *
find_rsdp(void)
{
// The EBDA, whose segment address the BIOS leaves at 0x40E.
uint16 ebda_seg = *(uint16 *)P2V(0x40E);
if (ebda_seg) {
struct acpi_rsdp *r = scan_rsdp((uint64)ebda_seg << 4, 1024);
if (r)
return r;
}
return scan_rsdp(0xE0000, 0x20000);
}
// Walk the RSDT or XSDT looking for a table by signature.
//
// Two nearly identical loops rather than one clever one: the only difference is
// the pointer width -- 32-bit entries in the RSDT, 64-bit in the XSDT -- and
// reading a 64-bit pointer out of a 32-bit array is exactly the sort of bug
// that produces a plausible address pointing at nothing.
static struct acpi_sdt *
find_table(struct acpi_rsdp *rsdp, const char *sig)
{
struct acpi_sdt *root;
int wide;
if (rsdp->revision >= 2 && rsdp->xsdt_addr) {
root = (struct acpi_sdt *)P2V(rsdp->xsdt_addr);
wide = 1;
} else {
root = (struct acpi_sdt *)P2V((uint64)rsdp->rsdt_addr);
wide = 0;
}
if (!checksum_ok(root, root->length))
return 0;
uint32 n = (root->length - sizeof(struct acpi_sdt)) / (wide ? 8 : 4);
for (uint32 i = 0; i < n; i++) {
uint64 pa;
if (wide) {
// Byte-wise, because the array is packed and 8-byte entries in it are
// not guaranteed to be 8-byte aligned.
uint8 *p = (uint8 *)(root + 1) + i * 8;
pa = 0;
for (int b = 7; b >= 0; b--)
pa = (pa << 8) | p[b];
} else {
pa = ((uint32 *)(root + 1))[i];
}
struct acpi_sdt *t = (struct acpi_sdt *)P2V(pa);
if (memcmp(t->sig, sig, 4) == 0)
return t;
}
return 0;
}
// Returns 1 if the processors were enumerated, 0 to fall back to mpinit().
int
acpi_init(void)
{
struct acpi_rsdp *rsdp = find_rsdp();
if (!rsdp)
return 0;
struct acpi_madt *madt = (struct acpi_madt *)find_table(rsdp, "APIC");
if (!madt || !checksum_ok(madt, madt->h.length))
return 0;
lapic = (uint *)P2V((uint64)madt->lapic_addr);
uint8 *p = (uint8 *)madt + sizeof(struct acpi_madt);
uint8 *e = (uint8 *)madt + madt->h.length;
int found_ioapic = 0;
while (p + sizeof(struct madt_entry) <= e) {
struct madt_entry *ent = (struct madt_entry *)p;
// A zero length would spin here forever. Firmware tables are not always
// well formed, and an infinite loop this early is indistinguishable from a
// hang with no output at all.
if (ent->length < sizeof(struct madt_entry))
break;
switch (ent->type) {
case MADT_LAPIC: {
struct madt_lapic *l = (struct madt_lapic *)p;
// Skip processors that are neither enabled nor online-capable: those are
// sockets the firmware is describing but not offering.
if (!(l->flags & (LAPIC_ENABLED | LAPIC_ONLINE_CAPABLE)))
break;
if (ncpu < NCPU) {
cpus[ncpu].apicid = l->apic_id;
ncpu++;
}
break;
}
case MADT_IOAPIC: {
struct madt_ioapic *io = (struct madt_ioapic *)p;
// The first one only. Multi-I/O-APIC routing needs the interrupt source
// overrides too, which nothing here reads yet, so claiming to support it
// would be worse than not.
if (!found_ioapic) {
ioapicid = io->id;
found_ioapic = 1;
}
break;
}
case MADT_LAPIC_OVERRIDE: {
struct madt_lapic_override *o = (struct madt_lapic_override *)p;
lapic = (uint *)P2V(o->addr);
break;
}
}
p += ent->length;
}
if (ncpu == 0)
return 0;
// The 8259s are still there and still wired to the CPU. On a system with an
// IMCR, this switches interrupt delivery from the PICs to the APIC; on one
// without, the ports are unclaimed and the writes go nowhere. mpinit() does
// the same thing guarded by the MP table's imcrp flag.
if (madt->flags & MADT_PCAT_COMPAT) {
outb(0x22, 0x70);
outb(0x23, inb(0x23) | 1);
}
cprintf("acpi: %d cpu%s, lapic at %p, ioapic id %d\n",
ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid);
return 1;
}