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>
154 lines
6.3 KiB
C
154 lines
6.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.
|
||
*/
|
||
|
||
/**
|
||
* spinlock.h - Kernel spinlocks (FABRIC-3.10.8 Stage MC-0b).
|
||
*
|
||
* One lock word: 0 = free, core_id + 1 = held by that core. Taken by
|
||
* compare-and-swap; interrupts are masked on the owning core while it
|
||
* holds any lock (the first lock taken saves the interrupt state in the
|
||
* per-core block, the last one released restores it -- percpu.h).
|
||
*
|
||
* The rule that makes this correct: **no lock is held across a context
|
||
* switch or into the idle point.** Every switch and every idle point
|
||
* checks it (sk_lock_check_none_held()) and counts a violation.
|
||
*
|
||
* Failures are counted, never silent (sk_lock_report() logs new ones):
|
||
* - recursion: this core takes a lock it already holds. On one core,
|
||
* "the owner is this core" can't tell a nested call from a second VM
|
||
* on the same core, so it is never treated as legitimate. The call is
|
||
* let through (spinning would hang the core) and counted;
|
||
* - bad release: releasing a lock this core doesn't hold;
|
||
* - held at switch: a switch or idle point reached with a lock held;
|
||
* - order violation: see the rank check below.
|
||
*
|
||
* sf_mutex_t (the vendored VM's dict_lock/tuning_lock) uses the same lock
|
||
* word through sk_lockword_acquire()/sk_lockword_release() (shim.c).
|
||
*
|
||
* THE KERNEL LOCKS, in acquisition order (a lock may be taken while
|
||
* holding only locks above it; MC-3's deadlock freedom rests on this):
|
||
*
|
||
* g_lock_hermes kernel-Hermes arena, queues, channels, ledger
|
||
* g_lock_switch switch-signal slot table and stamps
|
||
* (capsule_vm_switch_signal.c)
|
||
* g_lock_registry VM registry list and entries (capsule_birth.c)
|
||
* g_lock_stadium Stadium's global part: quota-slot allocation
|
||
* (grants) and the shared block table (MC-0d)
|
||
* (slot lock) one Stadium quota slot: its lists, reservoir,
|
||
* consumed, word row and cells (stadium.c's
|
||
* StadiumVMQuota.lock; at most one held at a time)
|
||
* g_lock_doe DoE region (doe_region.c)
|
||
* g_lock_log log region (log_region.c)
|
||
* g_lock_block block subsystem: device chain, caches, BAM,
|
||
* relocation table, and its device I/O
|
||
* (block_subsystem.c, MC-0b2)
|
||
* g_lock_console console output and line state (console.c)
|
||
* g_lock_heap kmalloc heap
|
||
* g_lock_vmm page tables (allocates tables from the PMM)
|
||
* g_lock_pmm physical page bitmap
|
||
*
|
||
* The switch table and the registry never call out while held: their walks
|
||
* copy one entry under the lock and judge it outside. Hermes hands the
|
||
* switch module its notifications after dropping its own lock (they read
|
||
* Hermes queues back). Stadium sits below Hermes (Hermes admits, evicts and
|
||
* decays cells) and below a VM's dict_lock (vm_core.c's heat bump dispatches
|
||
* the word under it), and above block (MIGRATE calls blk_flush()); nothing
|
||
* in the block path calls into Stadium.
|
||
*
|
||
* The order is checked at run time (FABRIC-3.10.8 K-11): every lock has a
|
||
* rank (sk_lock_rank_t, same order as above, with a VM's dict_lock and
|
||
* tuning_lock between the registry and Stadium), each core counts the
|
||
* locks it holds per rank, and taking a lock whose rank is not above every
|
||
* rank this core already holds counts an order violation. Two locks of one
|
||
* rank (two Stadium slots, two VMs' dict_locks) count as one too. Counted
|
||
* and logged like the other failures; never refused.
|
||
*/
|
||
#ifndef STARKERNEL_SPINLOCK_H
|
||
#define STARKERNEL_SPINLOCK_H
|
||
|
||
#include <stdint.h>
|
||
|
||
typedef enum sk_lock_rank {
|
||
SK_LOCK_RANK_NONE = 0, /* unranked: neither checked nor counted */
|
||
SK_LOCK_RANK_HERMES,
|
||
SK_LOCK_RANK_SWITCH,
|
||
SK_LOCK_RANK_REGISTRY,
|
||
SK_LOCK_RANK_VM_DICT, /* a VM's dict_lock (sf_mutex_t) */
|
||
SK_LOCK_RANK_VM_TUNING, /* a VM's tuning_lock (sf_mutex_t) */
|
||
SK_LOCK_RANK_STADIUM,
|
||
SK_LOCK_RANK_STADIUM_SLOT,
|
||
SK_LOCK_RANK_DOE,
|
||
SK_LOCK_RANK_LOG,
|
||
SK_LOCK_RANK_BLOCK,
|
||
SK_LOCK_RANK_CONSOLE,
|
||
SK_LOCK_RANK_HEAP,
|
||
SK_LOCK_RANK_VMM,
|
||
SK_LOCK_RANK_PMM,
|
||
SK_LOCK_RANK_COUNT
|
||
} sk_lock_rank_t;
|
||
|
||
typedef struct sk_spinlock {
|
||
volatile uint32_t owner; /* 0 = free, else core_id + 1 */
|
||
uint64_t acquired; /* updated while held, so exact */
|
||
uint64_t released;
|
||
uint32_t rank; /* sk_lock_rank_t */
|
||
} sk_spinlock_t;
|
||
|
||
#define SK_SPINLOCK_INIT(rank) { 0, 0, 0, (rank) }
|
||
|
||
void sk_lockword_acquire(volatile uint32_t *word, uint32_t rank);
|
||
void sk_lockword_release(volatile uint32_t *word, uint32_t rank);
|
||
|
||
void sk_spin_lock(sk_spinlock_t *l);
|
||
void sk_spin_unlock(sk_spinlock_t *l);
|
||
|
||
/* 1 if this core holds `l`. For paths that must not spin on a lock their
|
||
* own core may hold (the fatal exception printers). */
|
||
int sk_spin_held_here(const sk_spinlock_t *l);
|
||
|
||
/* Every switch and idle point: counts a violation if this core holds any
|
||
* lock. `where` names the site in the log. */
|
||
void sk_lock_check_none_held(const char *where);
|
||
|
||
/* Logs any failure counted since the last report. Call only with no lock
|
||
* held (the idle point does). */
|
||
void sk_lock_report(void);
|
||
|
||
uint64_t sk_lock_recursions(void);
|
||
uint64_t sk_lock_bad_releases(void);
|
||
uint64_t sk_lock_held_at_switch(void);
|
||
uint64_t sk_lock_order_violations(void);
|
||
|
||
extern sk_spinlock_t g_lock_registry;
|
||
extern sk_spinlock_t g_lock_stadium;
|
||
extern sk_spinlock_t g_lock_switch;
|
||
extern sk_spinlock_t g_lock_hermes;
|
||
extern sk_spinlock_t g_lock_doe;
|
||
extern sk_spinlock_t g_lock_log;
|
||
extern sk_spinlock_t g_lock_block;
|
||
extern sk_spinlock_t g_lock_console;
|
||
extern sk_spinlock_t g_lock_heap;
|
||
extern sk_spinlock_t g_lock_pmm;
|
||
extern sk_spinlock_t g_lock_vmm;
|
||
|
||
#endif /* STARKERNEL_SPINLOCK_H */
|