FABRIC-3.10.8 MC-1c: riscv64's second hart online and parked; MC-1 complete
Harts from the DTB /cpus or, on QEMU (ACPI, no DTB), the MADT RINTC entries; the boot hart is the one SBI HSM reports STARTED. SBI HSM hart_start into ap_entry.S (stvec/satp copied, FPU on, tp set), park. Hart 1 online in S-mode with its own block; its vCPU thread 0.0% on the host. QEMU riscv64 -smp 2 -name debug-threads=on. All three ISAs now boot two cores; 3-ISA acceptance clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3a2ba5149f
commit
c081bcf87a
@@ -915,3 +915,36 @@ Core 1 is halted, not spinning.
|
||||
**Found, not changed:** core 0's vCPU burns about 64% of a host CPU while the system is idle. The boot core's idle loop (the console poll and idle point) polls rather than halting. This predates MC-1. MC-3's criterion "idle worker cores halted, not spinning" applies to the new cores; whether core 0 should halt when idle too is a separate question for MC-3's design.
|
||||
|
||||
**MC-1b is complete.** Next: MC-1c (riscv64, SBI HSM `hart_start`).
|
||||
|
||||
### 5.3 MC-1c — riscv64's second hart online and parked; MC-1 complete (2026-09-26)
|
||||
|
||||
**Change.**
|
||||
- **Enumeration** (`arch/riscv64/smp.c`): the devicetree's `/cpus` children (`device_type = "cpu"`, not `status = "disabled"`, `reg` = hartid) when there is a DTB. Otherwise, the ACPI MADT's enabled RINTC entries (type 0x18, hartid at +8). QEMU's EDK2 hands over ACPI and no DTB.
|
||||
- **The boot hart:** S-mode cannot read `mhartid`. After probing that the HSM extension exists, the filling pass asks SBI HSM `hart_get_status` for every hart, prints each status, and takes the one STARTED hart as the boot hart. If it isn't exactly one, it says so and brings up no other hart.
|
||||
- `hal/smp.c` now reads the boot core's hw id **after** the filling pass, for this reason. It makes no difference on amd64 and aarch64.
|
||||
- **Start:** SBI HSM `hart_start(hartid, entry, opaque)` into `ap_entry.S` with a1 = the hart's boot block. A non-zero SBI error is printed and the start refused.
|
||||
- **`ap_entry.S`:**
|
||||
- It masks SIE and clears `sie`, records the a0 hartid in its boot block, and takes the boot hart's `stvec` and `satp` (Bare today, item 4.3.5a).
|
||||
- It sets `sstatus.FS = Initial` with `fcsr = 0` (kernel C is lp64d), sets `tp` to its block, loads its stack, and calls `sk_smp_secondary_main()`.
|
||||
- Neither `gp` nor `sscratch` is used by this kernel, so neither is copied.
|
||||
- **Park:** `csrci sstatus, SIE` and `wfi`.
|
||||
- **The online line** says "S-mode, a0 = its hartid" when the hartid SBI passed matches the one started.
|
||||
- **QEMU:** `-smp 2 -name debug-threads=on` on the `make qemu` riscv64 line only.
|
||||
|
||||
**Evidence** (clean builds; the source diff is identical to the saved stage diff):
|
||||
|
||||
| ISA | log | SMP lines | standard sequence |
|
||||
|---|---|---|---|
|
||||
| riscv64 (`-smp 2`) | `logs/20260926-131910` | OpenSBI: `Platform HART Count: 2`, `Boot HART ID: 0`. `SMP: hart 0 HSM status 0 (started)`; `SMP: hart 1 HSM status 1 (stopped)`; `SMP: 2 core(s) found`; `SMP: core 1 online: hw id 0x1, block 0x0000000083fff130, per-CPU register 0x0000000083fff130 (its own block), S-mode, a0 = its hartid, 740 us, parked`; `SMP: 2 core(s) online` | K-10 hashes, G A=40 B=40 byte-identical, HHH, all ok `3`, ledger unchanged, 0 B idle, 0 / 0; **0 park-wake lines, 0 `LOCK:` lines, 0 `timed out`** |
|
||||
| amd64 (`-smp 2`) | `logs/20260926-133004` | core 1 online, long mode, its own block, 82 us | same; 0 park wakes, 0 `LOCK:` |
|
||||
| aarch64 (`-smp 2`) | `logs/20260926-133959` | core 1 online, EL1, its own block, 3314 us | same; 0 park wakes, 0 `LOCK:` |
|
||||
|
||||
The boot hart HSM chose (0) is the one OpenSBI names as `Boot HART ID`.
|
||||
|
||||
**Host side, riscv64** (`ps -L` after O-A, then 20 s later): `CPU 0/TCG` 64.9%, 5:14 → 5:27; `CPU 1/TCG` **0.0%, 0:00 → 0:00**. Core 1 is halted.
|
||||
|
||||
**MC-1 is complete:** every ISA boots two cores, and the second is parked, halted, and never woken.
|
||||
|
||||
**Before MC-2 (observers get core-0 affinity) and MC-3 (worker cores claim), open findings:**
|
||||
- Core 0's idle loop polls at about 64% of a host CPU (§5.2).
|
||||
- In MC-1 the parked cores run no timer, take no interrupts, and have no GIC/PLIC/LAPIC setup of their own. MC-3's wake-by-IPI needs each core's interrupt controller initialised: the LAPIC on amd64, the GIC CPU interface on aarch64, and the SIE/SSIP path on riscv64.
|
||||
|
||||
+5
-1
@@ -484,6 +484,7 @@ LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/ap_trampoline.S
|
||||
endif
|
||||
ifeq ($(ARCH),riscv64)
|
||||
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/kernel_entry.S
|
||||
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/ap_entry.S
|
||||
endif
|
||||
ifeq ($(ARCH),aarch64)
|
||||
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/ap_entry.S
|
||||
@@ -503,7 +504,8 @@ LOADER_ARCH_SRCS += \
|
||||
endif
|
||||
ifeq ($(ARCH),riscv64)
|
||||
LOADER_ARCH_SRCS += \
|
||||
$(KERNEL_SRC)/arch/$(ARCH)/plic.c
|
||||
$(KERNEL_SRC)/arch/$(ARCH)/plic.c \
|
||||
$(KERNEL_SRC)/arch/$(ARCH)/smp.c
|
||||
endif
|
||||
ifeq ($(ARCH),aarch64)
|
||||
LOADER_ARCH_SRCS += \
|
||||
@@ -1050,6 +1052,8 @@ else ifeq ($(ARCH),riscv64)
|
||||
qemu-system-riscv64 \
|
||||
-machine virt \
|
||||
-cpu rv64 \
|
||||
-smp 2 \
|
||||
-name debug-threads=on \
|
||||
-m 1024 \
|
||||
-drive if=pflash,format=raw,unit=0,file=$$EDK2_RISCV_CODE,readonly=on \
|
||||
$$PFLASH_VARS_ARG \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* ap_entry.S (riscv64) -- secondary-hart entry, FABRIC-3.10.8 MC-1c.
|
||||
*
|
||||
* SBI HSM hart_start lands here in S-mode with satp = 0 and
|
||||
* a0 = hartid, a1 = the hart's boot block (arch/riscv64/smp.c: rv_boot_t).
|
||||
* The kernel itself runs in Bare mode (satp cleared at boot, item 4.3.5a),
|
||||
* so physical addresses are the kernel's addresses; satp is still copied
|
||||
* from the boot hart, so the two stay the same if that ever changes.
|
||||
*
|
||||
* The hart masks its interrupts, takes the boot hart's trap vector and
|
||||
* satp, turns the FPU on (sstatus.FS = Initial -- kernel C is lp64d),
|
||||
* sets tp to its per-core block before any C code, and calls
|
||||
* sk_smp_secondary_main(block) on its own stack. No symbol is referenced:
|
||||
* every address comes from the block.
|
||||
*/
|
||||
|
||||
.section .text
|
||||
.balign 4
|
||||
|
||||
.equ B_STVEC, 0
|
||||
.equ B_SATP, 8
|
||||
.equ B_PERCPU, 16
|
||||
.equ B_STACK, 24
|
||||
.equ B_ENTRY, 32
|
||||
.equ B_HARTID, 40
|
||||
|
||||
.global sk_ap_entry_rv
|
||||
sk_ap_entry_rv:
|
||||
csrci sstatus, 2 /* SIE off */
|
||||
csrw sie, zero
|
||||
sd a0, B_HARTID(a1) /* what hart_start says we are */
|
||||
ld t0, B_STVEC(a1)
|
||||
csrw stvec, t0
|
||||
ld t0, B_SATP(a1)
|
||||
csrw satp, t0
|
||||
sfence.vma
|
||||
li t0, (1 << 13) /* sstatus.FS = Initial */
|
||||
csrs sstatus, t0
|
||||
csrw fcsr, zero
|
||||
ld tp, B_PERCPU(a1)
|
||||
ld sp, B_STACK(a1)
|
||||
ld t1, B_ENTRY(a1)
|
||||
mv a0, tp
|
||||
jalr t1
|
||||
1:
|
||||
wfi
|
||||
j 1b
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
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 (riscv64) - secondary-hart bring-up, FABRIC-3.10.8 MC-1c.
|
||||
*
|
||||
* Harts come from the devicetree's /cpus when the firmware hands one over,
|
||||
* otherwise from the ACPI MADT's RINTC entries (QEMU's EDK2 hands over ACPI
|
||||
* and no DTB). The hardware id is the hartid. S-mode cannot read mhartid,
|
||||
* so the boot hart is the one SBI HSM reports STARTED while its siblings
|
||||
* are STOPPED; the choice is printed with every hart's status. Each hart
|
||||
* is started with SBI HSM hart_start into ap_entry.S, a1 = 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_rv(void);
|
||||
|
||||
#define SBI_EXT_BASE 0x10
|
||||
#define SBI_BASE_PROBE_EXT 3
|
||||
#define SBI_EXT_HSM 0x48534Dul
|
||||
#define SBI_HSM_HART_START 0
|
||||
#define SBI_HSM_HART_STATUS 2
|
||||
#define HSM_STARTED 0
|
||||
#define HSM_STOPPED 1
|
||||
|
||||
/* The hart's boot block (ap_entry.S, B_* offsets), heap-allocated, kept. */
|
||||
typedef struct {
|
||||
uint64_t stvec, satp, percpu, stack, entry;
|
||||
volatile uint64_t hartid;
|
||||
} rv_boot_t;
|
||||
|
||||
typedef struct { long error; long value; } sbiret_t;
|
||||
|
||||
static sbiret_t sbi_call(unsigned long eid, unsigned long fid,
|
||||
unsigned long arg0, unsigned long arg1, unsigned long arg2)
|
||||
{
|
||||
register unsigned long a0 __asm__("a0") = arg0;
|
||||
register unsigned long a1 __asm__("a1") = arg1;
|
||||
register unsigned long a2 __asm__("a2") = arg2;
|
||||
register unsigned long a6 __asm__("a6") = fid;
|
||||
register unsigned long a7 __asm__("a7") = eid;
|
||||
sbiret_t r;
|
||||
__asm__ volatile ("ecall" : "+r"(a0), "+r"(a1) : "r"(a2), "r"(a6), "r"(a7) : "memory");
|
||||
r.error = (long)a0;
|
||||
r.value = (long)a1;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int hsm_present(void)
|
||||
{
|
||||
sbiret_t r = sbi_call(SBI_EXT_BASE, SBI_BASE_PROBE_EXT, SBI_EXT_HSM, 0, 0);
|
||||
return r.error == 0 && r.value != 0;
|
||||
}
|
||||
|
||||
static long hsm_status(uint64_t hartid)
|
||||
{
|
||||
sbiret_t r = sbi_call(SBI_EXT_HSM, SBI_HSM_HART_STATUS, hartid, 0, 0);
|
||||
return r.error == 0 ? r.value : -1000 + r.error;
|
||||
}
|
||||
|
||||
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 char *st;
|
||||
const uint8_t *reg;
|
||||
if (!type || len < 4 || type[0] != 'c' || type[1] != 'p' || type[2] != 'u' || type[3] != 0) continue;
|
||||
st = (const char *)fdt_find_prop_in_node(dtb, c, "status", &len);
|
||||
if (st && len >= 4 && st[0] == 'd') continue; /* "disabled" */
|
||||
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));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* MADT type 0x18 (RINTC): flags at +4 (bit 0 enabled), hartid at +8. */
|
||||
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, k;
|
||||
uint64_t hartid = 0;
|
||||
if (p[1] == 0 || p + p[1] > end) break;
|
||||
if (p[0] != 0x18 || p[1] < 16) continue;
|
||||
flags = (uint32_t)p[4] | ((uint32_t)p[5] << 8) | ((uint32_t)p[6] << 16) | ((uint32_t)p[7] << 24);
|
||||
if (!(flags & 1u)) continue;
|
||||
for (k = 0; k < 8; k++) hartid |= (uint64_t)p[8 + k] << (8 * k);
|
||||
add_id(ids, cap, &n, hartid);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Found by arch_smp_enumerate(): the one hart HSM reports STARTED. */
|
||||
static uint64_t g_boot_hartid;
|
||||
|
||||
uint64_t arch_smp_boot_hw_id(void)
|
||||
{
|
||||
return g_boot_hartid;
|
||||
}
|
||||
|
||||
uint32_t arch_smp_enumerate(BootInfo *boot_info, uint64_t *hw_ids, uint32_t cap)
|
||||
{
|
||||
uint32_t n = 0, i, started = 0;
|
||||
uint64_t first_started = 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 || !hsm_present()) {
|
||||
if (n > 1) console_println("SMP: SBI HSM extension absent -- boot hart only");
|
||||
if (cap) hw_ids[0] = g_boot_hartid;
|
||||
return 1;
|
||||
}
|
||||
if (!cap) return n;
|
||||
|
||||
/* The filling pass also finds the boot hart and prints every status. */
|
||||
for (i = 0; i < n && i < cap; i++) {
|
||||
long st = hsm_status(hw_ids[i]);
|
||||
char line[80];
|
||||
snprintf(line, sizeof line, "SMP: hart %llu HSM status %ld%s",
|
||||
(unsigned long long)hw_ids[i], st,
|
||||
st == HSM_STARTED ? " (started)" : st == HSM_STOPPED ? " (stopped)" : "");
|
||||
console_println(line);
|
||||
if (st == HSM_STARTED) {
|
||||
if (started == 0) first_started = hw_ids[i];
|
||||
started++;
|
||||
}
|
||||
}
|
||||
if (started != 1) {
|
||||
console_println("SMP: not exactly one hart STARTED -- the boot hart is unknown; boot hart only");
|
||||
g_boot_hartid = 0;
|
||||
hw_ids[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
g_boot_hartid = first_started;
|
||||
return n;
|
||||
}
|
||||
|
||||
void arch_smp_park_once(void)
|
||||
{
|
||||
__asm__ volatile ("csrci sstatus, 2\n\twfi" ::: "memory");
|
||||
}
|
||||
|
||||
typedef struct rv_started { sk_percpu_t *pc; rv_boot_t *b; struct rv_started *next; } rv_started_t;
|
||||
static rv_started_t *g_started;
|
||||
|
||||
int arch_smp_start(BootInfo *boot_info, sk_percpu_t *pc, void *stack_top)
|
||||
{
|
||||
rv_boot_t *b;
|
||||
rv_started_t *s;
|
||||
sbiret_t r;
|
||||
uint64_t v;
|
||||
(void)boot_info;
|
||||
|
||||
b = (rv_boot_t *)kmalloc(sizeof(rv_boot_t));
|
||||
s = (rv_started_t *)kmalloc(sizeof(rv_started_t));
|
||||
if (!b || !s) {
|
||||
if (b) kfree(b);
|
||||
if (s) kfree(s);
|
||||
return -1;
|
||||
}
|
||||
__asm__ volatile ("csrr %0, stvec" : "=r"(v)); b->stvec = v;
|
||||
__asm__ volatile ("csrr %0, satp" : "=r"(v)); b->satp = 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->hartid = ~0ull;
|
||||
s->pc = pc; s->b = b; s->next = g_started; g_started = s;
|
||||
__asm__ volatile ("fence rw, rw" ::: "memory");
|
||||
|
||||
r = sbi_call(SBI_EXT_HSM, SBI_HSM_HART_START, pc->hw_id,
|
||||
(unsigned long)(uintptr_t)&sk_ap_entry_rv, (unsigned long)(uintptr_t)b);
|
||||
if (r.error != 0) {
|
||||
char line[80];
|
||||
snprintf(line, sizeof line, "SMP: SBI hart_start returned %ld", r.error);
|
||||
console_println(line);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *arch_smp_arrival(const sk_percpu_t *pc)
|
||||
{
|
||||
rv_started_t *s;
|
||||
for (s = g_started; s; s = s->next) {
|
||||
if (s->pc == pc) {
|
||||
return s->b->hartid == pc->hw_id ? "S-mode, a0 = its hartid" : "S-mode, a0 = WRONG hartid";
|
||||
}
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
@@ -64,10 +64,10 @@ void sk_smp_secondary_main(sk_percpu_t *pc)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(__aarch64__)
|
||||
#if defined(__x86_64__) || defined(__aarch64__) || defined(__riscv)
|
||||
#define SMP_ISA_BRINGS_UP 1
|
||||
#else
|
||||
/* MC-1c (riscv64): not yet -- the boot core only. */
|
||||
/* An ISA without bring-up: the boot core only. */
|
||||
#define SMP_ISA_BRINGS_UP 0
|
||||
uint32_t arch_smp_enumerate(BootInfo *b, uint64_t *ids, uint32_t cap)
|
||||
{
|
||||
@@ -88,8 +88,6 @@ void sk_smp_init(BootInfo *boot_info)
|
||||
uint64_t boot_id, *ids;
|
||||
sk_percpu_t *boot = sk_percpu();
|
||||
|
||||
boot_id = arch_smp_boot_hw_id();
|
||||
boot->hw_id = boot_id;
|
||||
boot->online = 1;
|
||||
|
||||
n = SMP_ISA_BRINGS_UP ? arch_smp_enumerate(boot_info, (uint64_t *)0, 0) : 1;
|
||||
@@ -106,12 +104,14 @@ void sk_smp_init(BootInfo *boot_info)
|
||||
return;
|
||||
}
|
||||
if (SMP_ISA_BRINGS_UP) {
|
||||
/* The filling pass drops duplicates the counting pass can't see. */
|
||||
/* The filling pass drops duplicates the counting pass can't see
|
||||
* (and on riscv64 finds the boot hart, hence the id read after). */
|
||||
uint32_t filled = arch_smp_enumerate(boot_info, ids, n);
|
||||
if (filled < n) n = filled;
|
||||
} else {
|
||||
ids[0] = boot_id;
|
||||
}
|
||||
boot_id = arch_smp_boot_hw_id();
|
||||
boot->hw_id = boot_id;
|
||||
if (!SMP_ISA_BRINGS_UP) ids[0] = boot_id;
|
||||
for (i = 0; i < n; i++) { g_cores[i] = (sk_percpu_t *)0; g_wakes_reported[i] = 0; }
|
||||
g_cores[0] = boot;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user