Files
LithosAnanake/include/starkernel/percpu.h
T
Robert Allan JamesandClaude Opus 5.5 71e0be96b0 FABRIC-3.10.8 MC-3b: waking a worker core
Each worker core sets up its own IPI receiver before going online (amd64
LAPIC + vector 0x22 with its own stub, aarch64 banked GIC SGI 0, riscv64
sie.SSIE); core 0 sends by ICR / GICD_SGIR / SBI IPI. Park lets exactly
one interrupt in without a lost-wake window. Boot self-test: IPI round
trips 3/3 and 'woke x3, IPIs x3' on all three ISAs; worker vCPU threads
0.0% on the host. 3-ISA acceptance clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:31:01 -04:00

126 lines
5.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* percpu.h - Per-core state (FABRIC-3.10.8 Stage MC-0a).
*
* Each core owns one sk_percpu_t, reached through that ISA's per-CPU
* register, so code running on a core -- including an interrupt handler --
* finds its own block without a lookup:
* - amd64: the GS base (IA32_GS_BASE); the block's first field points
* back at itself, so `mov %gs:0` reads the pointer.
* - aarch64: TPIDR_EL1 (reachable from EL2 as well, so either boot EL).
* - riscv64: the tp register (the kernel owns it: no user mode, no TLS;
* the trap entry never touches it).
*
* MC-0a: only the boot core exists, and its block is a single statically
* allocated object (not a table of cores -- secondary cores' blocks are
* allocated as they come online, MC-1). What moves in first is the switch
* mechanism's "who is running on this core" (was switch.c's global
* g_switch_current_vm).
*/
#ifndef STARKERNEL_PERCPU_H
#define STARKERNEL_PERCPU_H
#include <stdint.h>
#include "starkernel/spinlock.h" /* SK_LOCK_RANK_COUNT */
struct VM;
typedef struct sk_percpu {
struct sk_percpu *self; /* must stay first: amd64 reads it as %gs:0 */
uint32_t core_id; /* 0 = the boot core (the system core, MC-R1) */
struct VM *current_vm; /* who is running on this core (switch.c) */
/* MC-0b (spinlock.c): locks this core holds right now, and the
* interrupt state to restore when that count returns to 0. Kept here,
* not in each lock, so interrupts come back correctly whatever order
* the locks are released in. */
uint32_t lock_depth;
uint64_t saved_irq;
uint64_t lock_acquired; /* every acquire on this core, ever */
uint64_t lock_released; /* every release on this core, ever */
/* K-11 (spinlock.c): locks this core holds right now, per rank -- the
* lock-order check compares a new lock's rank with the highest held. */
uint32_t held_rank[SK_LOCK_RANK_COUNT];
/* MC-1 (hal/smp.c): the core's hardware id (APIC id / MPIDR / hartid;
* never the lock owner word), and what it reports on coming online. */
uint64_t hw_id;
uint64_t reg_readback; /* its per-CPU register, read on arrival */
volatile uint32_t online; /* 1 once parked (release store) */
volatile uint64_t park_wakes; /* times woken while parked */
/* MC-3a (switch.c): the VM this core just switched away from, released
* (running_on cleared) by whatever next runs here -- only once its
* context is saved. And this core's "re-decide at the next checkpoint". */
struct VM *release_after;
volatile uint32_t decision_due;
struct VM *entry_vm; /* the VM a first-entry trampoline starts as */
/* MC-3b (hal/smp.c): how to reach this core with an IPI (the APIC id,
* the GIC target mask, the hartid), and how many it has received. */
uint64_t ipi_target;
volatile uint64_t ipi_received;
} sk_percpu_t;
/* Installs the boot core's block in this core's per-CPU register. Call
* once, as early as possible (kernel_main, straight after
* arch_early_init(): on amd64 that reloads GS, which would reset the base). */
void sk_percpu_boot_init(void);
/* 1 once the boot core's block is installed (MC-0b). Before that the
* per-CPU register still holds whatever the firmware left there -- on
* riscv64 the monolithic loader's kernel_main() prints from
* arch_early_init() with tp still the firmware's -- so the spinlocks
* (spinlock.c) do nothing until it is set: early boot is one core with
* interrupts off. */
extern volatile int sk_percpu_ready;
/* This core's block. Valid from sk_percpu_boot_init() on. */
static inline sk_percpu_t *sk_percpu(void)
{
sk_percpu_t *p;
#if defined(__x86_64__)
__asm__ volatile ("movq %%gs:0, %0" : "=r"(p));
#elif defined(__aarch64__)
__asm__ volatile ("mrs %0, tpidr_el1" : "=r"(p));
#elif defined(__riscv)
__asm__ volatile ("mv %0, tp" : "=r"(p));
#else
#error "percpu.h: unsupported architecture"
#endif
return p;
}
/* Architecture hook: point this core's per-CPU register at `p`. */
static inline void sk_percpu_install(sk_percpu_t *p)
{
#if defined(__x86_64__)
uint64_t v = (uint64_t)(uintptr_t)p;
__asm__ volatile ("wrmsr" : : "c"(0xC0000101u) /* IA32_GS_BASE */,
"a"((uint32_t)v), "d"((uint32_t)(v >> 32)) : "memory");
#elif defined(__aarch64__)
__asm__ volatile ("msr tpidr_el1, %0" : : "r"(p) : "memory");
#elif defined(__riscv)
__asm__ volatile ("mv tp, %0" : : "r"(p) : "memory");
#endif
}
#endif /* STARKERNEL_PERCPU_H */