// 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; }