ASELSANMicrokernel
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: 5bff8ff634d0file 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: c8f24c74e1a4file 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: 73a4fd236e61file sha256: 9726dbf00f84
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8i_handoff -- --test-threads=1
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06