diff --git a/docs/smp.md b/docs/smp.md index 99b4717..9db00d5 100644 --- a/docs/smp.md +++ b/docs/smp.md @@ -114,7 +114,10 @@ active reconsideration in favour of resilience — see [vision.md](vision.md).) Whatever the top goal, the *sequence* is the same and seL4 validates starting simple: 1. **Enumerate cores** — needs [device discovery](discovery.md) (ACPI MADT on x86, - device tree on ARM). SMP is a concrete consumer of that work. + device tree on ARM). SMP is a concrete consumer of that work. **Done on x86:** the + MADT parse records every usable Local APIC — with the `apic_id` an AP wake targets — + and `platform.cpus()` returns the list (see [discovery.md](discovery.md)). The boot + log reports the count; the ARM (device-tree) path still needs it. 2. **Wake the APs** — INIT–SIPI–SIPI on x86; PSCI/spin-tables on ARM. Each core brings up its own tables, timer, and idle task. 3. **Start with a big kernel lock.** It's a legitimate first design, not a shortcut — diff --git a/src/device/acpi.zig b/src/device/acpi.zig index 7632e6c..1a8ecdf 100644 --- a/src/device/acpi.zig +++ b/src/device/acpi.zig @@ -91,6 +91,35 @@ pub const PlatformInfo = struct { /// Filled in by `discover`; the arch layer reads it during bring-up. pub var platform_info: PlatformInfo = .{}; +/// One usable logical processor, from a MADT type-0 (Local APIC) record. The +/// `apic_id` is the Local APIC ID that SMP bring-up targets to wake this core +/// (INIT–SIPI–SIPI); `processor_id` is the ACPI namespace handle. Only processors +/// the firmware marks *enabled* are recorded — a disabled one can't be started. +pub const Cpu = struct { + processor_id: u8, + apic_id: u8, + /// MADT flags bit 1: usable but firmware-started offline (hot-plug / deferred + /// bring-up), as opposed to already available. Informational for now. + online_capable: bool, +}; + +/// The set of usable logical processors the MADT listed — the hardware's degree of +/// parallelism. Includes the bootstrap processor danos already runs on; the rest +/// are the application processors SMP bring-up would start (see docs/smp.md). +pub const CpuInfo = struct { + /// A static pool sized well above any danos target (a desktop, two 4-core Pis). + /// If the MADT ever lists more, the surplus is dropped and counted in `dropped` + /// so the truncation is never silent. + cpus: [max_cpus]Cpu = undefined, + count: usize = 0, + dropped: usize = 0, +}; + +const max_cpus = 64; + +/// Filled in by `discover` (from the MADT); SMP bring-up reads it to wake the APs. +pub var cpu_info: CpuInfo = .{}; + /// Integrity/diagnostics for the AML parse. `consumed == total` means the parser /// walked every byte of the DSDT/SSDTs without desyncing. pub const AmlStats = struct { @@ -438,6 +467,18 @@ fn parseMadt(dt: *DeviceTree, header: *const SystemDescriptorTableHeader) !void var nb: [24]u8 = undefined; const nm = std.fmt.bufPrint(&nb, "cpu{d}", .{la.processor_id}) catch "cpu"; _ = try dt.addChild(dt.root, .processor, nm); + // Also record it as a schedulable core (with the APIC ID an AP + // wake needs, which the device node name doesn't preserve). + if (cpu_info.count < cpu_info.cpus.len) { + cpu_info.cpus[cpu_info.count] = .{ + .processor_id = la.processor_id, + .apic_id = la.apic_id, + .online_capable = la.flags & 2 != 0, + }; + cpu_info.count += 1; + } else { + cpu_info.dropped += 1; + } } }, 1 => { diff --git a/src/device/platform.zig b/src/device/platform.zig index 3729c5c..5429024 100644 --- a/src/device/platform.zig +++ b/src/device/platform.zig @@ -24,6 +24,7 @@ pub const AmlStats = acpi.AmlStats; pub const PlatformInfo = acpi.PlatformInfo; pub const RegAccess = acpi.RegAccess; pub const IsoEntry = acpi.IsoEntry; +pub const Cpu = acpi.Cpu; /// The register map + sleep types discovery extracted, for logging/diagnostics. pub fn powerInfo() PowerInfo { @@ -41,6 +42,22 @@ pub fn amlStats() AmlStats { return acpi.aml_stats; } +/// The usable logical processors discovered during enumeration — one entry per +/// core danos may schedule on, each carrying the Local APIC ID an SMP wake targets. +/// `len` is the hardware's degree of parallelism: how many tasks *could* run at the +/// same instant once the application processors are started. Today only the +/// bootstrap processor is actually running, so starting the rest is the pending SMP +/// step (see docs/smp.md). Borrowed from static storage populated by `discover`. +pub fn cpus() []const Cpu { + return acpi.cpu_info.cpus[0..acpi.cpu_info.count]; +} + +/// Non-zero only if enumeration found more processors than the static pool holds +/// (the surplus were dropped from `cpus()`); surfaced so the cap is never silent. +pub fn cpusDropped() usize { + return acpi.cpu_info.dropped; +} + /// Enumerate hardware into a fresh device tree. `hal` supplies the hardware /// primitives the backend needs (MMIO mapping for PCIe config space, port I/O for /// ACPI registers); pass the arch implementation. Errors leave nothing to clean up diff --git a/src/kernel/main.zig b/src/kernel/main.zig index 9c39835..31cc79c 100644 --- a/src/kernel/main.zig +++ b/src/kernel/main.zig @@ -200,6 +200,10 @@ fn kmain(boot_info: *const BootInfo) noreturn { log.write(" console UART: none in SPCR -> legacy COM1\n"); } log.print(" ioapic : base 0x{x}, {d} inputs (masked); entry0 low 0x{x}\n", .{ ioapic_base, arch.ioapicEntryCount(), arch.ioapicEntryLow(0) }); + const cores = platform.cpus(); + log.print(" cpus : {d} usable core(s); 1 running (BSP), {d} AP(s) parked (SMP bring-up pending)\n", .{ cores.len, if (cores.len > 0) cores.len - 1 else 0 }); + if (platform.cpusDropped() > 0) + log.print(" cpus : WARNING {d} core(s) beyond pool cap dropped\n", .{platform.cpusDropped()}); } else |err| { log.print("\ndanos: device discovery failed: {s}\n", .{@errorName(err)}); }