FABRIC-3.10.8 K-11: check the lock order at run time

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>
This commit is contained in:
Robert Allan James
2026-09-26 11:37:23 -04:00
co-authored by Claude Opus 5.5
parent a66c4f3d5e
commit 7252e2ed72
15 changed files with 46724 additions and 27 deletions
+81 -19
View File
@@ -28,25 +28,31 @@
#include "starkernel/spinlock.h"
#include "starkernel/percpu.h"
#include "starkernel/arch.h"
#include "starkernel/console.h" /* K-11: the order report's full line */
#include "freestanding/stdio.h" /* snprintf() */
#include "log.h"
sk_spinlock_t g_lock_registry = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_stadium = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_switch = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_hermes = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_doe = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_log = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_block = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_console = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_heap = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_pmm = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_vmm = SK_SPINLOCK_INIT;
sk_spinlock_t g_lock_registry = SK_SPINLOCK_INIT(SK_LOCK_RANK_REGISTRY);
sk_spinlock_t g_lock_stadium = SK_SPINLOCK_INIT(SK_LOCK_RANK_STADIUM);
sk_spinlock_t g_lock_switch = SK_SPINLOCK_INIT(SK_LOCK_RANK_SWITCH);
sk_spinlock_t g_lock_hermes = SK_SPINLOCK_INIT(SK_LOCK_RANK_HERMES);
sk_spinlock_t g_lock_doe = SK_SPINLOCK_INIT(SK_LOCK_RANK_DOE);
sk_spinlock_t g_lock_log = SK_SPINLOCK_INIT(SK_LOCK_RANK_LOG);
sk_spinlock_t g_lock_block = SK_SPINLOCK_INIT(SK_LOCK_RANK_BLOCK);
sk_spinlock_t g_lock_console = SK_SPINLOCK_INIT(SK_LOCK_RANK_CONSOLE);
sk_spinlock_t g_lock_heap = SK_SPINLOCK_INIT(SK_LOCK_RANK_HEAP);
sk_spinlock_t g_lock_pmm = SK_SPINLOCK_INIT(SK_LOCK_RANK_PMM);
sk_spinlock_t g_lock_vmm = SK_SPINLOCK_INIT(SK_LOCK_RANK_VMM);
/* Failure counters (spinlock.h). Written with atomics: a failure can be
* counted on any core. The first site of each kind is kept for the log. */
static uint64_t g_recursions;
static uint64_t g_bad_releases;
static uint64_t g_held_at_switch;
static uint64_t g_order_violations;
static uint32_t g_order_new_rank, g_order_held_rank;
static void *g_order_site;
static uint64_t g_reported_order;
static const volatile uint32_t *g_recursion_word;
static void *g_recursion_site;
static const volatile uint32_t *g_bad_release_word;
@@ -82,9 +88,21 @@ static inline void irq_restore(uint64_t f)
#endif
}
/* ---- the rank check (K-11) -------------------------------------------- */
/* Highest rank this core holds, or SK_LOCK_RANK_NONE. */
static uint32_t highest_held_rank(const sk_percpu_t *pc)
{
uint32_t r;
for (r = SK_LOCK_RANK_COUNT - 1u; r > SK_LOCK_RANK_NONE; r--) {
if (pc->held_rank[r]) return r;
}
return SK_LOCK_RANK_NONE;
}
/* ---- the lock word ---------------------------------------------------- */
static void lockword_acquire(volatile uint32_t *w, void *site)
static void lockword_acquire(volatile uint32_t *w, uint32_t rank, void *site)
{
sk_percpu_t *pc;
uint32_t me;
@@ -97,6 +115,21 @@ static void lockword_acquire(volatile uint32_t *w, void *site)
if (pc->lock_depth++ == 0) pc->saved_irq = f;
pc->lock_acquired++;
if (rank >= SK_LOCK_RANK_COUNT) rank = SK_LOCK_RANK_NONE;
/* Counted per rank even on recursion, so its release stays symmetric. */
if (rank != SK_LOCK_RANK_NONE) {
if (*w != me) {
uint32_t held = highest_held_rank(pc);
if (held != SK_LOCK_RANK_NONE && rank <= held &&
__atomic_fetch_add(&g_order_violations, 1, __ATOMIC_RELAXED) == 0) {
g_order_new_rank = rank;
g_order_held_rank = held;
g_order_site = site;
}
}
pc->held_rank[rank]++;
}
if (*w == me) {
/* Already ours: spinning would hang this core for good. Let it
@@ -117,7 +150,7 @@ static void lockword_acquire(volatile uint32_t *w, void *site)
}
}
static void lockword_release(volatile uint32_t *w, void *site)
static void lockword_release(volatile uint32_t *w, uint32_t rank, void *site)
{
sk_percpu_t *pc;
uint32_t me;
@@ -126,6 +159,10 @@ static void lockword_release(volatile uint32_t *w, void *site)
pc = sk_percpu();
me = pc->core_id + 1u;
if (rank < SK_LOCK_RANK_COUNT && rank != SK_LOCK_RANK_NONE && pc->held_rank[rank]) {
pc->held_rank[rank]--;
}
if (*w == me) {
__atomic_store_n(w, 0u, __ATOMIC_RELEASE);
} else if (__atomic_fetch_add(&g_bad_releases, 1, __ATOMIC_RELAXED) == 0) {
@@ -137,26 +174,26 @@ static void lockword_release(volatile uint32_t *w, void *site)
if (--pc->lock_depth == 0) irq_restore(pc->saved_irq);
}
void sk_lockword_acquire(volatile uint32_t *word)
void sk_lockword_acquire(volatile uint32_t *word, uint32_t rank)
{
lockword_acquire(word, __builtin_return_address(0));
lockword_acquire(word, rank, __builtin_return_address(0));
}
void sk_lockword_release(volatile uint32_t *word)
void sk_lockword_release(volatile uint32_t *word, uint32_t rank)
{
lockword_release(word, __builtin_return_address(0));
lockword_release(word, rank, __builtin_return_address(0));
}
void sk_spin_lock(sk_spinlock_t *l)
{
lockword_acquire(&l->owner, __builtin_return_address(0));
lockword_acquire(&l->owner, l->rank, __builtin_return_address(0));
l->acquired++;
}
void sk_spin_unlock(sk_spinlock_t *l)
{
l->released++;
lockword_release(&l->owner, __builtin_return_address(0));
lockword_release(&l->owner, l->rank, __builtin_return_address(0));
}
int sk_spin_held_here(const sk_spinlock_t *l)
@@ -175,6 +212,7 @@ void sk_lock_check_none_held(const char *where)
uint64_t sk_lock_recursions(void) { return g_recursions; }
uint64_t sk_lock_bad_releases(void) { return g_bad_releases; }
uint64_t sk_lock_held_at_switch(void) { return g_held_at_switch; }
uint64_t sk_lock_order_violations(void) { return g_order_violations; }
/* ---- reporting -------------------------------------------------------- */
@@ -194,6 +232,16 @@ static const char *lock_name(const volatile uint32_t *w)
return "vm sf_mutex or Stadium slot";
}
static const char *rank_name(uint32_t rank)
{
static const char *const names[SK_LOCK_RANK_COUNT] = {
"unranked", "hermes", "switch", "registry", "vm dict_lock",
"vm tuning_lock", "stadium", "Stadium slot", "doe", "log", "block",
"console", "heap", "vmm", "pmm"
};
return rank < SK_LOCK_RANK_COUNT ? names[rank] : "?";
}
/* Freestanding kvsnprintf has no %llx: hex by hand. */
static void hex64(char out[19], uint64_t v)
{
@@ -228,4 +276,18 @@ void sk_lock_report(void)
(unsigned long long)g_held_at_switch,
g_held_at_switch_where ? g_held_at_switch_where : "?");
}
if (g_order_violations != g_reported_order) {
char line[160];
g_reported_order = g_order_violations;
hex64(a, (uint64_t)(uintptr_t)g_order_site);
/* The full line to the console: log_message() cuts at LOG_LINE_MAX
* (64 bytes), which loses the lock names. The log line stays for
* the persisted record (same as parity.c's INCOMPLETE report). */
snprintf(line, sizeof line, "LOCK: order violation x%llu (first: took %s while holding %s, from %s)",
(unsigned long long)g_order_violations,
rank_name(g_order_new_rank), rank_name(g_order_held_rank), a);
console_println(line);
log_message(LOG_ERROR, "LOCK: order violation x%llu",
(unsigned long long)g_order_violations);
}
}
+5 -2
View File
@@ -402,6 +402,9 @@ void log_set_vm(struct VM *vm) { (void)vm; }
int sf_mutex_init(sf_mutex_t *mutex) {
if (!mutex) return -1;
mutex->state = 0;
/* FABRIC-3.10.8 K-11: a VM's dict_lock rank; vm_init() re-ranks its
* tuning_lock (the only two kernel sf_mutexes). */
mutex->rank = SK_LOCK_RANK_VM_DICT;
return 0;
}
@@ -434,7 +437,7 @@ uint64_t sf_mutex_destroyed_held_count(void) { return g_mutex_destroyed_held; }
*/
void sf_mutex_lock(sf_mutex_t *mutex) {
if (!mutex) return;
sk_lockword_acquire(&mutex->state);
sk_lockword_acquire(&mutex->state, mutex->rank);
}
/**
@@ -444,7 +447,7 @@ void sf_mutex_lock(sf_mutex_t *mutex) {
*/
void sf_mutex_unlock(sf_mutex_t *mutex) {
if (!mutex) return;
sk_lockword_release(&mutex->state);
sk_lockword_release(&mutex->state, mutex->rank);
}
/* -----------------------------------------------------------------------------
+2 -2
View File
@@ -152,8 +152,8 @@ typedef struct {
static StadiumVMQuota *stadium_quotas = (StadiumVMQuota *)0;
/* MC-0d: one quota slot's lock (see StadiumVMQuota.lock). */
static void slot_lock(int slot) { sk_lockword_acquire(&stadium_quotas[slot].lock); }
static void slot_unlock(int slot) { sk_lockword_release(&stadium_quotas[slot].lock); }
static void slot_lock(int slot) { sk_lockword_acquire(&stadium_quotas[slot].lock, SK_LOCK_RANK_STADIUM_SLOT); }
static void slot_unlock(int slot) { sk_lockword_release(&stadium_quotas[slot].lock, SK_LOCK_RANK_STADIUM_SLOT); }
/* Doubly-linked resident-list threading, one entry per cell, parallel to
* stadium_owner[] -- kmalloc'd at stadium_boot_init() to stadium_ncells
+2
View File
@@ -58,6 +58,7 @@
#ifdef __STARKERNEL__
#include "starkernel/vm/arena.h"
#include "starkernel/console.h"
#include "starkernel/spinlock.h" /* FABRIC-3.10.8 K-11 -- SK_LOCK_RANK_VM_TUNING */
#endif
#include <string.h>
@@ -184,6 +185,7 @@ void vm_init_with_host(VM* vm, const VMHostServices *host)
vm->error = 1;
return;
}
vm->tuning_lock.rank = SK_LOCK_RANK_VM_TUNING; /* FABRIC-3.10.8 K-11 (spinlock.h) */
vm->memory = (uint8_t*)vm_host_alloc(vm, VM_MEMORY_SIZE, sizeof(void*));
if (!vm->memory)