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.
264 lines
7.8 KiB
C
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;
|
|
}
|