Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
167 lines
5 KiB
C
167 lines
5 KiB
C
// Multiprocessor support
|
|
// Search memory for MP description structures.
|
|
// http://developer.intel.com/design/pentium/datashts/24201606.pdf
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/memlayout.h"
|
|
#include "arch/mp.h"
|
|
#include "arch/x86.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
|
|
struct cpu cpus[NCPU];
|
|
// Processors the firmware says exist.
|
|
int ncpu;
|
|
|
|
// Processors actually running, which is not the same number and must not be
|
|
// confused with it. Enumeration is cheap and startup is where the difficulty
|
|
// is: right now the APs are found and deliberately not started, so this stays
|
|
// at 1 while ncpu is 2, 4 or 16.
|
|
//
|
|
// /proc/cpuinfo reports *this* one, matching Linux, where cpuinfo lists online
|
|
// CPUs. Reporting ncpu there told a VM with four processors that it had four
|
|
// working ones, which was a comfortable thing to read and false.
|
|
int ncpu_online = 1;
|
|
uchar ioapicid;
|
|
// Where the I/O APIC's registers are. Every source that describes it -- the
|
|
// MADT, or this table -- says so, and ioapicinit() maps whatever they said.
|
|
uint64 ioapic_phys = IOAPIC_DEFAULT_PHYS;
|
|
|
|
static uchar
|
|
sum(uchar *addr, int len)
|
|
{
|
|
int i, sum;
|
|
|
|
sum = 0;
|
|
for(i=0; i<len; i++)
|
|
sum += addr[i];
|
|
return sum;
|
|
}
|
|
|
|
// Look for an MP structure in the len bytes at addr.
|
|
static struct mp*
|
|
mpsearch1(addr_t a, int len)
|
|
{
|
|
uchar *e, *p, *addr;
|
|
addr = P2V(a);
|
|
e = addr+len;
|
|
for(p = addr; p < e; p += sizeof(struct mp))
|
|
if(memcmp(p, "_MP_", 4) == 0 && sum(p, sizeof(struct mp)) == 0)
|
|
return (struct mp*)p;
|
|
return 0;
|
|
}
|
|
|
|
// Search for the MP Floating Pointer Structure, which according to the
|
|
// spec is in one of the following three locations:
|
|
// 1) in the first KB of the EBDA;
|
|
// 2) in the last KB of system base memory;
|
|
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
|
|
static struct mp*
|
|
mpsearch(void)
|
|
{
|
|
uchar *bda;
|
|
uint p;
|
|
struct mp *mp;
|
|
|
|
bda = (uchar *) P2V(0x400);
|
|
if((p = ((bda[0x0F]<<8)| bda[0x0E]) << 4)){
|
|
if((mp = mpsearch1(p, 1024)))
|
|
return mp;
|
|
} else {
|
|
p = ((bda[0x14]<<8)|bda[0x13])*1024;
|
|
if((mp = mpsearch1(p-1024, 1024)))
|
|
return mp;
|
|
}
|
|
return mpsearch1(0xF0000, 0x10000);
|
|
}
|
|
|
|
// Search for an MP configuration table. For now,
|
|
// don't accept the default configurations (physaddr == 0).
|
|
// Check for correct signature, calculate the checksum and,
|
|
// if correct, check the version.
|
|
// To do: check extended table checksum.
|
|
static struct mpconf*
|
|
mpconfig(struct mp **pmp)
|
|
{
|
|
struct mpconf *conf;
|
|
struct mp *mp;
|
|
|
|
if((mp = mpsearch()) == 0 || mp->physaddr == 0)
|
|
return 0;
|
|
conf = (struct mpconf*) P2V((addr_t) mp->physaddr);
|
|
if(memcmp(conf, "PCMP", 4) != 0)
|
|
return 0;
|
|
if(conf->version != 1 && conf->version != 4)
|
|
return 0;
|
|
if(sum((uchar*)conf, conf->length) != 0)
|
|
return 0;
|
|
*pmp = mp;
|
|
return conf;
|
|
}
|
|
|
|
void
|
|
mpinit(void)
|
|
{
|
|
uchar *p, *e;
|
|
struct mp *mp;
|
|
struct mpconf *conf;
|
|
struct mpproc *mproc;
|
|
struct mpioapic *ioapic;
|
|
|
|
if((conf = mpconfig(&mp)) == 0) {
|
|
cprintf("No other CPUs found.\n");
|
|
return;
|
|
}
|
|
lapic = (uint*)P2V(conf->lapicaddr_p);
|
|
for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; p<e; ){
|
|
switch(*p){
|
|
case MPPROC:
|
|
mproc = (struct mpproc*)p;
|
|
// Bit 0 is "usable". A disabled entry is a processor that is not
|
|
// there, and starting it means waiting for an answer that never comes.
|
|
if((mproc->flags & 0x01) && ncpu < NCPU) {
|
|
cpus[ncpu].apicid = mproc->apicid; // apicid may differ from ncpu
|
|
ncpu++;
|
|
}
|
|
p += sizeof(struct mpproc);
|
|
continue;
|
|
case MPIOAPIC:
|
|
ioapic = (struct mpioapic*)p;
|
|
if(ioapic_phys == IOAPIC_DEFAULT_PHYS || ioapic->addr_p == IOAPIC_DEFAULT_PHYS){
|
|
ioapicid = ioapic->apicno;
|
|
ioapic_phys = ioapic->addr_p;
|
|
}
|
|
p += sizeof(struct mpioapic);
|
|
continue;
|
|
case MPBUS:
|
|
case MPIOINTR:
|
|
case MPLINTR:
|
|
p += 8;
|
|
continue;
|
|
default:
|
|
panic("Major problem parsing mp config.");
|
|
break;
|
|
}
|
|
}
|
|
// "Seems we are" is doing a lot of work here, and the number is usually
|
|
// wrong. This walks the *legacy Intel MP table* (1994), which modern firmware
|
|
// fills in minimally or not at all -- QEMU/SeaBIOS emits exactly one
|
|
// processor entry regardless of -smp, because it expects the OS to enumerate
|
|
// CPUs from the ACPI MADT instead. Verified 2026-08-08: with -smp 4 the table
|
|
// is 208 bytes and contains one type-0 entry, then buses, an I/O APIC and
|
|
// interrupt assignments. So ncpu is 1 on QEMU no matter what, and SMP is
|
|
// silently never exercised there.
|
|
//
|
|
// See docs/architecture/boot.md -- this is half of the SMP bug, and the
|
|
// reason the other half (AP startup triple-faulting) cannot be reproduced
|
|
// under QEMU until the MADT is parsed.
|
|
cprintf("Seems we are SMP, ncpu = %d\n",ncpu);
|
|
if(mp->imcrp){
|
|
// Bochs doesn't support IMCR, so this doesn't run on Bochs.
|
|
// But it would on real hardware.
|
|
outb(0x22, 0x70); // Select IMCR
|
|
outb(0x23, inb(0x23) | 1); // Mask external interrupts.
|
|
}
|
|
}
|