FABRIC-3.10.8 MC-1b: aarch64's second core online and parked

Cores from the DTB /cpus or, on QEMU (ACPI, no DTB), the MADT GICC
entries; the MADT lookup is now shared (hal/acpi.c) with amd64. PSCI
CPU_ON through the discovered conduit into ap_entry.S, which copies the
boot core's MMU/control state, sets TPIDR_EL1 and parks. Core 1 online
at EL1 with its own block; its vCPU thread 0.0% on the host. QEMU aarch64
-smp 2 -name debug-threads=on. 3-ISA acceptance clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-26 13:16:33 -04:00
co-authored by Claude Opus 5.5
parent 24bde47481
commit 3a2ba5149f
16 changed files with 27949 additions and 48 deletions
+70
View File
@@ -0,0 +1,70 @@
/*
* ap_entry.S (aarch64) -- secondary-core entry, FABRIC-3.10.8 MC-1b.
*
* PSCI CPU_ON lands here at EL1 with the MMU and caches off and
* x0 = the core's boot block (arch/aarch64/smp.c: a64_boot_t; the kernel
* is identity-mapped, so its address is physical too). The boot core has
* cleaned the block to the point of coherency, since these first loads
* bypass the caches.
*
* The core takes the boot core's own translation and control state --
* MAIR, TCR, TTBR0/1, VBAR, CPACR (FP/SIMD: kernel C uses it) -- then
* SCTLR, which turns the MMU and caches on at this same, identity-mapped
* address. It sets TPIDR_EL1 to its per-core block before any C code,
* records CurrentEL, and calls sk_smp_secondary_main(block) on its own
* stack. No symbol is referenced: every address comes from the block
* (this file is also assembled for the PE/COFF loader).
*/
.section .text
.balign 4
.equ B_MAIR, 0
.equ B_TCR, 8
.equ B_TTBR0, 16
.equ B_TTBR1, 24
.equ B_VBAR, 32
.equ B_CPACR, 40
.equ B_SCTLR, 48
.equ B_PERCPU, 56
.equ B_STACK, 64
.equ B_ENTRY, 72
.equ B_EL, 80
.global sk_ap_entry_a64
sk_ap_entry_a64:
msr daifset, #0xf
mov x19, x0
ldr x1, [x19, #B_MAIR]
msr mair_el1, x1
ldr x1, [x19, #B_TCR]
msr tcr_el1, x1
ldr x1, [x19, #B_TTBR0]
msr ttbr0_el1, x1
ldr x1, [x19, #B_TTBR1]
msr ttbr1_el1, x1
ldr x1, [x19, #B_VBAR]
msr vbar_el1, x1
ldr x1, [x19, #B_CPACR]
msr cpacr_el1, x1
isb
tlbi vmalle1
dsb nsh
ic iallu
dsb nsh
isb
ldr x1, [x19, #B_SCTLR]
msr sctlr_el1, x1
isb
ldr x1, [x19, #B_PERCPU]
msr tpidr_el1, x1
ldr x1, [x19, #B_STACK]
mov sp, x1
mrs x2, CurrentEL
str x2, [x19, #B_EL]
ldr x3, [x19, #B_ENTRY]
ldr x0, [x19, #B_PERCPU]
blr x3
1:
wfi
b 1b
+21
View File
@@ -192,6 +192,27 @@ void aarch64_psci_conduit_init(const void *dtb)
}
}
/**
* @brief PSCI CPU_ON (SMC64, 0xC4000003) through the discovered conduit.
*
* FABRIC-3.10.8 MC-1b: starts core @p mpidr at physical @p entry with
* x0 = @p context_id. Returns PSCI's status (0 = SUCCESS, -4 ALREADY_ON,
* -2 INVALID_PARAMETERS, ...).
*/
int64_t aarch64_psci_cpu_on(uint64_t mpidr, uint64_t entry, uint64_t context_id)
{
register uint64_t x0 __asm__("x0") = 0xC4000003ull;
register uint64_t x1 __asm__("x1") = mpidr;
register uint64_t x2 __asm__("x2") = entry;
register uint64_t x3 __asm__("x3") = context_id;
if (s_psci_use_smc) {
__asm__ volatile ("smc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
} else {
__asm__ volatile ("hvc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
}
return (int64_t)x0;
}
void arch_cold_reset(void)
{
arch_disable_interrupts();
+208
View File
@@ -0,0 +1,208 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
*/
/**
* smp.c (aarch64) - secondary-core bring-up, FABRIC-3.10.8 MC-1b.
*
* Cores come from the devicetree's /cpus when the firmware hands one over
* (the Raspberry Pi 5), otherwise from the ACPI MADT's GICC entries (QEMU's
* AAVMF hands over ACPI and no DTB). The hardware id is the MPIDR affinity
* (Aff3..Aff0). Each core is started with PSCI CPU_ON through the conduit
* arch.c discovered, into ap_entry.S, with x0 = its boot block.
*/
#include "starkernel/smp.h"
#include "starkernel/acpi.h"
#include "starkernel/fdt.h"
#include "starkernel/kmalloc.h"
#include "starkernel/console.h"
#include "freestanding/stdio.h"
#include "uefi.h"
extern void sk_ap_entry_a64(void);
extern int64_t aarch64_psci_cpu_on(uint64_t mpidr, uint64_t entry, uint64_t context_id);
#define MPIDR_AFF_MASK 0xFF00FFFFFFull /* Aff3 [39:32], Aff2..Aff0 [23:0] */
/* The core's boot block (ap_entry.S, B_* offsets). Heap-allocated per core
* and kept: the core reads it with its MMU off, then writes B_EL. */
typedef struct {
uint64_t mair, tcr, ttbr0, ttbr1, vbar, cpacr, sctlr;
uint64_t percpu, stack, entry;
volatile uint64_t el;
} a64_boot_t;
static uint64_t be_cells(const uint8_t *p, uint32_t cells)
{
uint64_t v = 0;
uint32_t i;
for (i = 0; i < cells * 4u; i++) v = (v << 8) | p[i];
return v;
}
static int id_seen(const uint64_t *ids, uint32_t n, uint64_t id)
{
uint32_t i;
for (i = 0; i < n; i++) if (ids[i] == id) return 1;
return 0;
}
static void add_id(uint64_t *ids, uint32_t cap, uint32_t *n, uint64_t id)
{
if (cap) {
if (id_seen(ids, *n < cap ? *n : cap, id)) return;
if (*n < cap) ids[*n] = id;
}
(*n)++;
}
static uint32_t enumerate_dtb(const void *dtb, uint64_t *ids, uint32_t cap)
{
const void *cpus = fdt_find_node_by_name(dtb, "cpus");
const void *c = (const void *)0;
const uint8_t *ac;
uint32_t len, cells = 1, n = 0;
if (!cpus) return 0;
ac = (const uint8_t *)fdt_find_prop_in_node(dtb, cpus, "#address-cells", &len);
if (ac && len == 4) cells = (uint32_t)be_cells(ac, 1);
if (cells < 1 || cells > 2) cells = 1;
while ((c = fdt_next_child_node(dtb, cpus, c)) != (const void *)0) {
const char *type = (const char *)fdt_find_prop_in_node(dtb, c, "device_type", &len);
const uint8_t *reg;
if (!type || len < 4 || type[0] != 'c' || type[1] != 'p' || type[2] != 'u' || type[3] != 0) continue;
reg = (const uint8_t *)fdt_find_prop_in_node(dtb, c, "reg", &len);
if (!reg || len < cells * 4u) continue;
add_id(ids, cap, &n, be_cells(reg, cells) & MPIDR_AFF_MASK);
}
return n;
}
/* MADT type 0x0B (GICC): flags at +12 (bit 0 enabled), MPIDR at +68. */
static uint32_t enumerate_madt(const void *rsdp, uint64_t *ids, uint32_t cap)
{
const uint8_t *madt = acpi_find_table(rsdp, "APIC");
const uint8_t *p, *end;
uint32_t n = 0;
if (!madt) return 0;
end = madt + acpi_table_length(madt);
for (p = madt + 44; p + 2 <= end; p += p[1]) {
uint32_t flags;
uint64_t mpidr;
uint32_t k;
if (p[1] == 0 || p + p[1] > end) break;
if (p[0] != 0x0B || p[1] < 76) continue;
flags = (uint32_t)p[12] | ((uint32_t)p[13] << 8) | ((uint32_t)p[14] << 16) | ((uint32_t)p[15] << 24);
if (!(flags & 1u)) continue;
mpidr = 0;
for (k = 0; k < 8; k++) mpidr |= (uint64_t)p[68 + k] << (8 * k);
add_id(ids, cap, &n, mpidr & MPIDR_AFF_MASK);
}
return n;
}
uint64_t arch_smp_boot_hw_id(void)
{
uint64_t m;
__asm__ volatile ("mrs %0, mpidr_el1" : "=r"(m));
return m & MPIDR_AFF_MASK;
}
uint32_t arch_smp_enumerate(BootInfo *boot_info, uint64_t *hw_ids, uint32_t cap)
{
uint32_t n = 0;
if (boot_info && fdt_valid(boot_info->dtb)) n = enumerate_dtb(boot_info->dtb, hw_ids, cap);
if (n == 0 && boot_info) n = enumerate_madt(boot_info->acpi_table, hw_ids, cap);
if (n == 0) {
if (cap) hw_ids[0] = arch_smp_boot_hw_id();
return 1;
}
return n;
}
void arch_smp_park_once(void)
{
__asm__ volatile ("msr daifset, #0xf\n\twfi" ::: "memory");
}
/* Clean [p, p+len) to the point of coherency: the core reads it with its
* MMU and caches off. */
static void clean_to_poc(const void *p, uint64_t len)
{
uint64_t a = (uint64_t)(uintptr_t)p & ~63ull, end = (uint64_t)(uintptr_t)p + len;
for (; a < end; a += 64) __asm__ volatile ("dc cvac, %0" :: "r"(a) : "memory");
__asm__ volatile ("dsb sy" ::: "memory");
}
/* Each started core's block, found again by arch_smp_arrival(): keyed by
* the per-core block, so a list (heap), not a table. */
typedef struct a64_started { sk_percpu_t *pc; a64_boot_t *b; struct a64_started *next; } a64_started_t;
static a64_started_t *g_started;
int arch_smp_start(BootInfo *boot_info, sk_percpu_t *pc, void *stack_top)
{
a64_boot_t *b;
a64_started_t *s;
int64_t rc;
uint64_t v;
(void)boot_info;
b = (a64_boot_t *)kmalloc(sizeof(a64_boot_t));
s = (a64_started_t *)kmalloc(sizeof(a64_started_t));
if (!b || !s) {
if (b) kfree(b);
if (s) kfree(s);
return -1;
}
__asm__ volatile ("mrs %0, mair_el1" : "=r"(v)); b->mair = v;
__asm__ volatile ("mrs %0, tcr_el1" : "=r"(v)); b->tcr = v;
__asm__ volatile ("mrs %0, ttbr0_el1" : "=r"(v)); b->ttbr0 = v;
__asm__ volatile ("mrs %0, ttbr1_el1" : "=r"(v)); b->ttbr1 = v;
__asm__ volatile ("mrs %0, vbar_el1" : "=r"(v)); b->vbar = v;
__asm__ volatile ("mrs %0, cpacr_el1" : "=r"(v)); b->cpacr = v;
__asm__ volatile ("mrs %0, sctlr_el1" : "=r"(v)); b->sctlr = v;
b->percpu = (uint64_t)(uintptr_t)pc;
b->stack = (uint64_t)(uintptr_t)stack_top & ~15ull;
b->entry = (uint64_t)(uintptr_t)&sk_smp_secondary_main;
b->el = 0;
s->pc = pc; s->b = b; s->next = g_started; g_started = s;
clean_to_poc(b, sizeof(*b));
clean_to_poc(pc, sizeof(*pc));
clean_to_poc((const void *)&sk_ap_entry_a64, 256);
rc = aarch64_psci_cpu_on(pc->hw_id, (uint64_t)(uintptr_t)&sk_ap_entry_a64, (uint64_t)(uintptr_t)b);
if (rc != 0) {
char line[96];
snprintf(line, sizeof line, "SMP: PSCI CPU_ON returned %lld", (long long)rc);
console_println(line);
return -1;
}
return 0;
}
const char *arch_smp_arrival(const sk_percpu_t *pc)
{
a64_started_t *s;
for (s = g_started; s; s = s->next) {
if (s->pc == pc) {
switch ((s->b->el >> 2) & 3u) {
case 1: return "EL1";
case 2: return "EL2";
case 3: return "EL3";
default: return "EL0?";
}
}
}
return "?";
}