// 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 "schboot.h" // SCHBOOT_RSDP_ADDR: the loader/kernel hand-off #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 uint64 ioapic_phys; // The ACPI power-management timer: a 3.579545 MHz counter at an I/O port the // FADT names. 0 when there is none. lapic.c times the TSC against it when the // PIT does not count -- see calibrate_without_pit(). uint32 acpi_pmtmr_port; int acpi_pmtmr_32; // 32 bits wide rather than 24 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 #define MADT_X2APIC 9 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)); // The same as MADT_LAPIC, for a processor whose APIC ID does not fit in a // byte -- or whose firmware simply prefers this form. Firmware that hands over // in x2APIC mode is entitled to describe every processor this way and none // the old way, and then a parser that reads only type 0 finds no CPUs at all. struct madt_x2apic { struct madt_entry e; uint16 reserved; uint32 apic_id; uint32 flags; uint32 acpi_uid; } __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) { // A UEFI loader hands the address over directly, because the scan below is a // BIOS convention that UEFI firmware need not honour and OVMF does not: the // kernel found no RSDP, so no MADT, so no IOAPIC, and nothing routed IRQ 4 -- // it reached a shell prompt on polled serial output and then ignored every // key. // // Validated, never trusted. The BIOS path does not write this slot, so there // it holds whatever happened to be in memory, and following a wild pointer // faults before there is anything to report the fault with. The bound is what // the direct map covers; the signature and checksum below do the rest. // // Deliberately *not* requiring 16-byte alignment. That rule applies to // searching the BIOS area, not to a pointer handed over by firmware -- OVMF's // sits at 0x3f77e014, and demanding alignment threw away the very address // this code was added to use. uint64 handed = *(uint64 *)P2V(SCHBOOT_RSDP_ADDR); if (handed && handed < 0x100000000ULL) { struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(handed); if (memcmp(r->sig, "RSD PTR ", 8) == 0 && checksum_ok(r, 20)) return r; } // 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(). // One processor from the MADT. // // Enabled processors only. "Online capable" means the processor is *not* // running but could be brought up later by hot-plug, which this kernel does // not do -- and AMD firmware routinely lists every core the socket could have // that way, present or not. Counting those as CPUs sent startothers() a // STARTUP to an APIC that does not exist, and then it waited for an answer // for ever: a hang after "acpi: N cpus" on exactly the machines whose table // was the most complete. // // An ID above 254 is refused too: xAPIC addresses a processor with eight bits // (255 is broadcast), and that is the mode this kernel runs the APIC in. static void add_cpu(uint32 apic_id, uint32 flags) { if (!(flags & LAPIC_ENABLED)) return; if (apic_id > 254) { cprintf("acpi: cpu with apic id %u needs x2APIC; not using it\n", apic_id); return; } for (int i = 0; i < ncpu; i++) if (cpus[i].apicid == apic_id) return; // listed as both type 0 and type 9 if (ncpu < NCPU) { cpus[ncpu].apicid = (uchar)apic_id; ncpu++; } } int acpi_init(void) { struct acpi_rsdp *rsdp = find_rsdp(); if (!rsdp) return 0; // The FADT first, for the PM timer: PM_TMR_BLK at offset 76, and bit 8 of // the flags at 112 says whether it counts 32 bits or 24. A hardware-reduced // platform has neither and says so with a zero port. struct acpi_sdt *fadt = find_table(rsdp, "FACP"); if (fadt && fadt->length >= 116 && checksum_ok(fadt, fadt->length)) { acpi_pmtmr_port = *(uint32 *)((uint8 *)fadt + 76); acpi_pmtmr_32 = (*(uint32 *)((uint8 *)fadt + 112) >> 8) & 1; if (acpi_pmtmr_port > 0xFFFF) acpi_pmtmr_port = 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; add_cpu(l->apic_id, l->flags); break; } case MADT_X2APIC: { struct madt_x2apic *x = (struct madt_x2apic *)p; add_cpu(x->apic_id, x->flags); break; } case MADT_IOAPIC: { struct madt_ioapic *io = (struct madt_ioapic *)p; // One only: the one whose inputs start at GSI 0, which is where the ISA // lines and the chipset's PCI lines are. 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. // // And its address from the table rather than 0xFEC00000. That is the // usual place and not a promised one: a second I/O APIC listed first, // or a board that moved the first, used to get its registers written // at an address where there were none. if (!found_ioapic || io->gsi_base == 0) { ioapicid = io->id; ioapic_phys = io->addr; 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); } kinfo("acpi: %d cpu%s, lapic at %p, ioapic id %d\n", ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid); return 1; }