boot: stop assuming the machine is an emulator
Every one of these works under QEMU and stops a real machine:
- MADT processors that are "online capable" but not enabled were
counted as CPUs. AMD firmware lists absent cores that way (so does
QEMU with maxcpus), and startothers() then waited for ever for an
AP that does not exist. Only enabled entries count now, x2APIC
(type 9) entries are read too, and the wait is bounded to a second;
an AP that misses it is parked with INIT and the boot goes on.
- The PIT calibration loop had no bound. Recent boards clock-gate the
8254: the output never rises (a hang) or floats high (TSC "0 MHz"
and every clock thousands of times fast). The loop is bounded and
checked, and falls back to the ACPI PM timer, then CPUID 0x15/0x16.
Under QEMU pit=off the PM timer gives the same figures the PIT did.
- Firmware that hands over in x2APIC mode left lapic[] reading
garbage: cpunum() panicked or the timer never started. Each CPU is
switched back to xAPIC first (x2APIC -> disabled -> xAPIC).
- The IOAPIC was always at 0xFEC00000 and interrupts were aimed at
APIC ID "0". Both now come from the firmware's tables.
- INIT-to-STARTUP was 100 us ("Bochs is slow"); it is the 10 ms the
MP specification asks for.
- mouseinit() drained the 8042 with an unbounded loop. With no
controller the port reads 0xFF and the loop never ends. It now
notices there is no controller and skips the mouse.
Tested in QEMU: i440fx and q35, -smp 4, -smp 2,maxcpus=4, pit=off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
parent
f1798e7443
commit
94c936ed98
7 changed files with 355 additions and 23 deletions
|
|
@ -28,6 +28,13 @@
|
|||
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
|
||||
|
|
@ -73,6 +80,7 @@ struct acpi_madt {
|
|||
#define MADT_LAPIC 0
|
||||
#define MADT_IOAPIC 1
|
||||
#define MADT_LAPIC_OVERRIDE 5
|
||||
#define MADT_X2APIC 9
|
||||
|
||||
struct madt_entry {
|
||||
uint8 type;
|
||||
|
|
@ -97,6 +105,18 @@ struct madt_ioapic {
|
|||
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;
|
||||
|
|
@ -211,6 +231,36 @@ find_table(struct acpi_rsdp *rsdp, const char *sig)
|
|||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
|
@ -218,6 +268,17 @@ acpi_init(void)
|
|||
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;
|
||||
|
|
@ -239,23 +300,28 @@ acpi_init(void)
|
|||
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++;
|
||||
}
|
||||
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;
|
||||
// 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) {
|
||||
// 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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue