Every lock carries a rank (hermes < switch < registry < VM dict_lock < VM tuning_lock < stadium < slot < doe < log < block < console < heap < vmm < pmm); each core counts held locks per rank and an acquisition not above the highest held rank counts an order violation, reported in full at the idle point. Probe inversion reported exactly once; O-A, a 28-VM churn and the standard sequence show 0 violations on all three ISAs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
4.3 KiB
C
110 lines
4.3 KiB
C
/*
|
||
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];
|
||
} 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 */
|