S158 · SOURCE-BOUND GATE EVIDENCE
G8i: per-CPU handoff ve lock disiplini modeli
Operations --test hedefi → test hedefiyle aynı adlı uygulama/model modülü → kaynak kesiti Bu sayfa yalnız S158 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S158Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8i-per-cpu-handoff-lock-discipline-model-partial
uygulama/model · focused test · Operations · 3 exact excerpt
sequence-bound=true · implementation-bound=false
01 · Testin bağlı olduğu uygulama/model kodu
Kapının yürüttüğü gerçek kaynak
tam Rust öğesiL1–L297
simulation/src/g8i_handoff.rs::HandoffState
//! G8i bounded per-CPU handoff and lock-discipline model.
//!
//! This source/model slice closes the G8i STOP conditions around a future
//! scheduler handoff: local runqueue ownership is never held across a
//! context switch or WFI, a local IRQ publication is never lost while its
//! CPU lock is held or a switch is in flight, IRQ service cannot enter those
//! unsafe regions, and there is no lockless global scheduler escape hatch.
//! It does not wire production AArch64 assembly, a live scheduler, QEMU, or
//! a physical device.
use crate::g8i_runqueue::{TaskToken, MAX_CPUS};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HandoffState {
Idle,
Running,
ContextSwitch,
InException,
Wfi,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ContextSwitchTicket {
cpu: usize,
generation: u64,
current_task_id: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HandoffError {
InvalidCpu,
ForeignMutation,
LockAlreadyHeld,
LockNotHeld,
ContextSwitchWithLock,
WfiWithLock,
LocalIrqWouldDeadlock,
IrqDuringContextSwitch,
LocalIrqAlreadyPending,
NoPendingLocalIrq,
PendingLocalIrqMustBeServiced,
InvalidStateTransition,
StaleTicket,
CurrentTaskMismatch,
NextTaskForeignOwner,
NextTaskAlreadyCurrent,
NoCurrentTask,
GlobalSchedulerAccessForbidden,
}
pub struct PerCpuHandoffModel {
state: [HandoffState; MAX_CPUS],
current: [Option<TaskToken>; MAX_CPUS],
local_lock_owner: [Option<usize>; MAX_CPUS],
pending_local_irq: [bool; MAX_CPUS],
next_generation: [u64; MAX_CPUS],
}
impl PerCpuHandoffModel {
pub const fn new() -> Self {
Self {
state: [HandoffState::Idle; MAX_CPUS],
current: [None; MAX_CPUS],
local_lock_owner: [None; MAX_CPUS],
pending_local_irq: [false; MAX_CPUS],
next_generation: [0; MAX_CPUS],
}
}
fn valid_cpu(cpu: usize) -> Result<(), HandoffError> {
if cpu < MAX_CPUS {
Ok(())
} else {
Err(HandoffError::InvalidCpu)
}
}
fn current_task_elsewhere(&self, cpu: usize, task_id: u64) -> bool {
self.current.iter().enumerate().any(|(owner_cpu, task)| {
owner_cpu != cpu && task.is_some_and(|task| task.id == task_id)
})
}
pub fn start_running(
&mut self,
caller_cpu: usize,
task: TaskToken,
) -> Result<(), HandoffError> {
Self::valid_cpu(caller_cpu)?;
Self::valid_cpu(task.owner_cpu)?;
if caller_cpu != task.owner_cpu {
return Err(HandoffError::ForeignMutation);
}
if self.state[caller_cpu] != HandoffState::Idle
|| self.current[caller_cpu].is_some()
|| self.local_lock_owner[caller_cpu].is_some()
{
return Err(HandoffError::InvalidStateTransition);
}
if self.current_task_elsewhere(caller_cpu, task.id) {
return Err(HandoffError::NextTaskAlreadyCurrent);
}
self.current[caller_cpu] = Some(task);
self.state[caller_cpu] = HandoffState::Running;
Ok(())
}
pub fn acquire_local_lock(
&mut self,
caller_cpu: usize,
target_cpu: usize,
) -> Result<(), HandoffError> {
Self::valid_cpu(caller_cpu)?;
Self::valid_cpu(target_cpu)?;
if caller_cpu != target_cpu {
return Err(HandoffError::ForeignMutation);
}
if self.local_lock_owner[target_cpu].is_some() {
return Err(HandoffError::LockAlreadyHeld);
}
if matches!(
self.state[target_cpu],
HandoffState::ContextSwitch | HandoffState::Wfi
) {
return Err(HandoffError::InvalidStateTransition);
}
self.local_lock_owner[target_cpu] = Some(caller_cpu);
Ok(())
}
pub fn release_local_lock(
&mut self,
caller_cpu: usize,
target_cpu: usize,
) -> Result<(), HandoffError> {
Self::valid_cpu(caller_cpu)?;
Self::valid_cpu(target_cpu)?;
if caller_cpu != target_cpu {
return Err(HandoffError::ForeignMutation);
}
if self.local_lock_owner[target_cpu] != Some(caller_cpu) {
return Err(HandoffError::LockNotHeld);
}
self.local_lock_owner[target_cpu] = None;
Ok(())
}
pub fn begin_context_switch(
&mut self,
caller_cpu: usize,
expected_task_id: u64,
) -> Result<ContextSwitchTicket, HandoffError> {
Self::valid_cpu(caller_cpu)?;
if self.local_lock_owner[caller_cpu].is_some() {
return Err(HandoffError::ContextSwitchWithLock);
}
if self.state[caller_cpu] != HandoffState::Running {
return Err(HandoffError::InvalidStateTransition);
}
if self.pending_local_irq[caller_cpu] {
return Err(HandoffError::PendingLocalIrqMustBeServiced);
}
let current = self.current[caller_cpu].ok_or(HandoffError::NoCurrentTask)?;
if current.id != expected_task_id {
return Err(HandoffError::CurrentTaskMismatch);
}
self.next_generation[caller_cpu] = self.next_generation[caller_cpu]
.checked_add(1)
.ok_or(HandoffError::StaleTicket)?;
self.state[caller_cpu] = HandoffState::ContextSwitch;
Ok(ContextSwitchTicket {
cpu: caller_cpu,
generation: self.next_generation[caller_cpu],
current_task_id: current.id,
})
}
pub fn complete_context_switch(
&mut self,
caller_cpu: usize,
ticket: ContextSwitchTicket,
next: Option<TaskToken>,
) -> Result<(), HandoffError> {
Self::valid_cpu(caller_cpu)?;
if self.local_lock_owner[caller_cpu].is_some() {
return Err(HandoffError::ContextSwitchWithLock);
}
if ticket.cpu != caller_cpu
|| ticket.generation != self.next_generation[caller_cpu]
|| self.state[caller_cpu] != HandoffState::ContextSwitch
|| self.current[caller_cpu].is_none_or(|task| task.id != ticket.current_task_id)
{
return Err(HandoffError::StaleTicket);
}
if let Some(next_task) = next {
Self::valid_cpu(next_task.owner_cpu)?;
if next_task.owner_cpu != caller_cpu {
return Err(HandoffError::NextTaskForeignOwner);
}
if self.current_task_elsewhere(caller_cpu, next_task.id) {
return Err(HandoffError::NextTaskAlreadyCurrent);
}
self.current[caller_cpu] = Some(next_task);
self.state[caller_cpu] = HandoffState::Running;
} else {
self.current[caller_cpu] = None;
self.state[caller_cpu] = HandoffState::Idle;
}
Ok(())
}
pub fn raise_local_irq(&mut self, cpu: usize) -> Result<(), HandoffError> {
Self::valid_cpu(cpu)?;
if self.pending_local_irq[cpu] {
return Err(HandoffError::LocalIrqAlreadyPending);
}
// Publication is a latch, not exception entry. A lock or an in-flight
// context switch may delay service, but must never drop the event.
self.pending_local_irq[cpu] = true;
if self.state[cpu] == HandoffState::Wfi {
self.state[cpu] = HandoffState::Idle;
}
Ok(())
}
pub fn service_local_irq(&mut self, cpu: usize) -> Result<(), HandoffError> {
Self::valid_cpu(cpu)?;
if self.local_lock_owner[cpu].is_some() {
return Err(HandoffError::LocalIrqWouldDeadlock);
}
if self.state[cpu] == HandoffState::ContextSwitch {
return Err(HandoffError::IrqDuringContextSwitch);
}
if !self.pending_local_irq[cpu] {
return Err(HandoffError::NoPendingLocalIrq);
}
if self.state[cpu] != HandoffState::Running || self.current[cpu].is_none() {
return Err(HandoffError::InvalidStateTransition);
}
self.pending_local_irq[cpu] = false;
self.state[cpu] = HandoffState::InException;
Ok(())
}
pub fn return_from_local_irq(&mut self, cpu: usize) -> Result<(), HandoffError> {
Self::valid_cpu(cpu)?;
if self.local_lock_owner[cpu].is_some() {
return Err(HandoffError::LocalIrqWouldDeadlock);
}
if self.state[cpu] != HandoffState::InException {
return Err(HandoffError::InvalidStateTransition);
}
self.state[cpu] = HandoffState::Running;
Ok(())
}
pub fn enter_wfi(&mut self, cpu: usize) -> Result<(), HandoffError> {
Self::valid_cpu(cpu)?;
if self.local_lock_owner[cpu].is_some() {
return Err(HandoffError::WfiWithLock);
}
if self.state[cpu] != HandoffState::Idle
|| self.current[cpu].is_some()
|| self.pending_local_irq[cpu]
{
return Err(HandoffError::InvalidStateTransition);
}
self.state[cpu] = HandoffState::Wfi;
Ok(())
}
pub fn state(&self, cpu: usize) -> Result<HandoffState, HandoffError> {
Self::valid_cpu(cpu)?;
Ok(self.state[cpu])
}
pub fn current(&self, cpu: usize) -> Result<Option<TaskToken>, HandoffError> {
Self::valid_cpu(cpu)?;
Ok(self.current[cpu])
}
pub fn local_lock_held(&self, cpu: usize) -> Result<bool, HandoffError> {
Self::valid_cpu(cpu)?;
Ok(self.local_lock_owner[cpu].is_some())
}
pub fn local_irq_pending(&self, cpu: usize) -> Result<bool, HandoffError> {
Self::valid_cpu(cpu)?;
Ok(self.pending_local_irq[cpu])
}
pub fn global_scheduler_access(&self, cpu: usize) -> Result<(), HandoffError> {
Self::valid_cpu(cpu)?;
Err(HandoffError::GlobalSchedulerAccessForbidden)
}
}snippet sha256: 5bff8ff634d0…file sha256: 87d141f1a684…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam Rust öğesiL155–L179
simulation/tests/g8i_handoff.rs::duplicate_current_foreign_next_and_global_scheduler_access_fail_closed
#[test]
fn duplicate_current_foreign_next_and_global_scheduler_access_fail_closed() {
let mut model = PerCpuHandoffModel::new();
model.start_running(0, task(40, 0)).unwrap();
assert_eq!(
model.service_local_irq(0),
Err(HandoffError::NoPendingLocalIrq)
);
assert_eq!(
model.start_running(1, task(40, 1)),
Err(HandoffError::NextTaskAlreadyCurrent)
);
let ticket = model.begin_context_switch(0, 40).unwrap();
assert_eq!(
model.complete_context_switch(0, ticket, Some(task(41, 1))),
Err(HandoffError::NextTaskForeignOwner)
);
assert_eq!(model.state(0), Ok(HandoffState::ContextSwitch));
assert_eq!(
model.global_scheduler_access(0),
Err(HandoffError::GlobalSchedulerAccessForbidden)
);
assert_eq!(model.state(4), Err(HandoffError::InvalidCpu));
}snippet sha256: c8f24c74e1a4…file sha256: 75aebc7bdb64…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL23648–L23689
website/src/lib/operations.ts::g8i-per-cpu-handoff-lock-discipline-model-partial
{
id: "g8i-per-cpu-handoff-lock-discipline-model-partial",
date: "2026-08-24",
sequence: 158,
status: "passed",
umbrella_status: "partial",
title: "G8i: per-CPU handoff ve lock disiplini modeli",
summary:
"S158, S157 IRQ stack/context ownership modelinin açık bıraktığı G8i STOP koşullarını bounded host model olarak kapattı: dört CPU exact local lock owner, foreign mutation ve reentrant lock reddi; lock tutulurken context switch/WFI reddi; local IRQ publication'ın lock veya context-switch altında kayıpsız pending latch'e alınması; unsafe service blokajı ve pending koruması; duplicate/pending-before-switch reddi; lock-free context handoff, transactional stale/replay ticket, duplicate current, foreign next owner ve lockless global scheduler erişimi fail-closed 8/8 geçti. Production scheduler, exception assembly, QEMU ve fiziksel runtime açılmadı.",
evidence: [
"g8i_handoff: 8/8 PASS; dört CPU başlangıç izolasyonu, exact local lock owner, reentrancy ve context-switch sırasında lock acquisition sınırı doğrulandı.",
"Birleşik S155–S158 G8i model kapısı 4 test binary / 32/32 PASS; runqueue, dormant runtime, IRQ-stack ve handoff aynı ağaçta korundu.",
"Lock-held context switch ve WFI mutasyonsuz reddedildi; lock serbest bırakılınca exact ticket ile bounded handoff tamamlandı.",
"Local IRQ publication lock veya context-switch altında kayıpsız latch'lendi; unsafe service reddedildi, pending korunup lock-free InException→Running dönüşü tamamlandı.",
"Duplicate publication, pending IRQ varken context switch, stale/foreign/replay ticket, duplicate current, foreign next-task owner ve lockless global scheduler erişimi fail-closed kaldı; WFI wake pending'i service edilene kadar korundu.",
"Kalıcı kapsam: `docs/M8.1-RPi5-G8i-PerCpu-Handoff-Lock-Discipline-Proof.md`.",
"S158 fiziksel/device operasyonu yapmadı: physical/device operations=0 ve RUNBOOK_EXECUTED_IN_S158=NO.",
],
commands: [
"cargo test -p aselsan_microkernel_simulation --test g8i_handoff -- --test-threads=1",
],
terminalSessions: [
{
id: "s158-g8i-handoff-lock-discipline-model",
title: "G8i per-CPU handoff/lock discipline host model kapısı",
commandLines: [
"cargo test -p aselsan_microkernel_simulation --test g8i_handoff -- --test-threads=1",
],
outputLines: ["running 8 tests", "test result: ok. 8 passed; 0 failed"],
exitCode: 0,
outputMode: "selected",
},
],
terminalSessionsNote:
"S158 yalnız bounded host/model handoff sözleşmesidir; production AArch64 exception assembly, scheduler/context switch, QEMU, fiziksel RPi ve generic SMP runtime sonucu değildir.",
limitations: [
"Gerçek scheduler/context-switch assembly, exception trampoline ve production mailbox wiring'e bağlanmadı.",
"G8j shared-root/ASID0 migration, G8k ASID/TLB shootdown, QEMU ve fiziksel runtime sonraki kapılardır.",
"Generic SMP arbitration, CPU2/CPU3, hotplug, long soak ve signed product budgets kapsam dışıdır.",
"Fiziksel/device operations=0; RUNBOOK_EXECUTED_IN_S158=NO.",
],
},snippet sha256: 73a4fd236e61…file sha256: 9726dbf00f84…
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8i_handoff -- --test-threads=1Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06