S374 · SOURCE-BOUND GATE EVIDENCE
S374 · normal EL0 IPC CALL production writer guard integration
tam syscall Rust öğesi + exact acquire→handoff odağı → tam scheduler release/rejoin Rust öğesi → S247 guard modülü → Operations-bound focused test Bu sayfa yalnız S374 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S374Production writer guardOperations id exactsource SHA exacttest target exact
operation: g8l-s374-el0-ipc-call-writer-guard-integration-partial
production · S247 guard · focused test · Operations · 5 exact excerpt
sequence-bound=true · implementation-bound=true
01 · Test edilen uygulama/model kodu
Kapının yürüttüğü gerçek kaynak
tam Rust öğesiL628–L1298kapı odağı L1025–L1045
kernel/src/arch/aarch64/exceptions.rs::rust_el0_sync_handler
Tam kapsayıcı Rust öğesi gösterilir; vurgulu blok yalnız S374 exact production writer üyeliği sınırıdır. Komşu kod, guard kapsamı iddiası değildir.
/// Basit syscall dispatch + handler.
/// x8 = syscall numarası
/// x0..x5 = argümanlar (yazma için: x0=fd, x1=buf, x2=len)
#[no_mangle]
pub extern "C" fn rust_el0_sync_handler(ctx: &mut ExceptionContext) {
let esr: u64;
unsafe { core::arch::asm!("mrs {0}, esr_el1", out(reg) esr, options(nomem, nostack)) };
// The physical RPi5 G7c gate owns only its explicitly armed two-SVC
// window. A handled SVC returns through this vector's ordinary
// RESTORE_CONTEXT + eret path; none of the legacy noreturn user-return or
// scheduler/task-exit paths are entered.
#[cfg(feature = "board-rpi5")]
if crate::rpi5_g7c::try_handle_sync(ctx, esr) {
return;
}
if crate::percpu::current_cpu_id() != 0 {
// Generic EL0 scheduling is deliberately CPU0-only until K3 provides
// per-CPU current-task state and migration-safe run queues.
crate::arch::aarch64::disable_irqs();
loop {
unsafe { core::arch::asm!("wfe", options(nomem, nostack)) }
}
}
let ec = (esr >> 26) & 0x3f;
if !exception_originated_from_el0(ctx) {
kprintln!("\n[M4] lower-EL sync vector received a privileged-origin frame");
kprintln!(" ESR_EL1 = 0x{:016x}", esr);
dump_context(ctx);
panic!("non-EL0 frame reached lower-EL synchronous handler");
}
if !scheduler_tracks_current_el0_task() {
kprintln!("\n[M4] EL0 frame has no live user task owner");
dump_context(ctx);
panic!("cannot contain lower-EL fault without a live user task");
}
if ec != 0x15 {
// M5.5 + Multi-core: Data Abort handling
if ec == 0x24 {
let far: u64;
unsafe { core::arch::asm!("mrs {0}, far_el1", out(reg) far, options(nomem, nostack)) };
if handle_el0_data_abort(far, ctx) {
return;
}
let task_id = crate::task::current_task_id()
.expect("live EL0 fault containment requires the tracked task id");
kprintln!(
"[EL0-FAULT-CONTAINMENT] task#{} EC=0x{:02x} FAR_EL1=0x{:016x} ACTION=TERMINATE_CURRENT_EL0_TASK",
task_id,
ec,
far,
);
}
kprintln!("\n[M4] EL0 AArch64 Sync Exception (SVC değil)");
kprintln!(
" ESR_EL1 = 0x{:016x} (EC=0x{:02x} → {})",
esr,
ec,
decode_ec(esr)
);
kprintln!(" ELR_EL1 = 0x{:016x}", ctx.elr_el1);
dump_context(ctx);
unsafe {
crate::task::scheduler::terminate_current_task_due_to_fatal_error(
lower_el_fault_reason(ec),
Some(&*ctx),
);
}
}
// No timer-driven context switch may occur while a syscall holds a
// capability, endpoint or scheduler lock. Every successful syscall return
// uses ERET with the saved EL0 SPSR, which restores the caller's IRQ mask.
crate::arch::aarch64::disable_irqs();
let syscall_num = ctx.gpr[8];
let user_sp: u64;
unsafe {
core::arch::asm!("mrs {0}, sp_el0", out(reg) user_sp, options(nomem, nostack));
}
// Syscall işleyip dönüş değerini x0'a yazacağız
let ret: u64 = match syscall_num {
SYS_YIELD => {
// A yield may resume this same kernel continuation only after an
// arbitrary peer ran. Check the carrier before that switch; the
// common return boundary below deliberately does not run a
// second time after the continuation resumes.
#[cfg(feature = "board-qemu")]
unsafe {
if let Err(error) = crate::task::execute_armed_current_runtime_oom_if_target() {
panic!("S137 pre-yield EL0 SVC safe boundary failed: {:?}", error);
}
}
static mut YIELD_COUNT: u64 = 0;
let count = unsafe {
YIELD_COUNT = YIELD_COUNT.wrapping_add(1);
YIELD_COUNT
};
if count <= 5 || count % 5000 == 0 {
kprintln!(
"[M4.3-DEBUG] User yield #{} | ELR=0x{:x}",
count,
ctx.elr_el1
);
}
unsafe {
crate::task::save_user_context_for_yield(ctx, user_sp);
crate::task::yield_now();
}
0
}
SYS_WRITE => {
// fd, buf, len
let fd = ctx.gpr[0] as usize;
let user_address = ctx.gpr[1];
let len = ctx.gpr[2] as usize;
if fd != 1 {
kprintln!(
"[M4.3] sys_write: sadece fd=1 (stdout) destekleniyor (fd={})",
fd
);
u64::MAX
} else {
let mut fixed = [0u8; crate::user_copy::MAX_USER_COPY];
match crate::user_copy::copy_from_current_user(&mut fixed, user_address, len) {
Ok(written) => {
#[cfg(feature = "board-qemu")]
crate::task::observe_qemu_el0_ipc_return_marker(
crate::task::current_task_id().unwrap_or(0),
&fixed[..written],
);
#[cfg(feature = "board-qemu")]
crate::task::observe_qemu_s134_ipc_marker(
crate::task::current_task_id().unwrap_or(0),
&fixed[..written],
);
#[cfg(feature = "board-qemu")]
crate::task::observe_qemu_s135_ipc_marker(
crate::task::current_task_id().unwrap_or(0),
&fixed[..written],
);
for &byte in &fixed[..written] {
if byte == b'\n' {
crate::kprint!("\r\n");
} else {
crate::kprint!("{}", byte as char);
}
}
written as u64
}
Err(error) => {
kprintln!(
"[K1-COPYIN] sys_write rejected ptr=0x{:x} len={} error={:?}",
user_address,
len,
error
);
u64::MAX
}
}
}
}
SYS_EXIT => {
let status = ctx.gpr[0] as i32;
kprintln!("[M4.3-DEBUG] === SYS_EXIT called from EL0 ===");
kprintln!(
"[M4.3-DEBUG] status={}, ELR=0x{:x}, current_task will be marked Dead",
status,
ctx.elr_el1
);
unsafe {
crate::task::task_exit();
}
// unreachable
}
// M6.3 — Gerçek mesaj kopyalama + Reply Cap + basit Call akışı
crate::ipc::SYS_MINT_ENDPOINT => {
let badge = ctx.gpr[0];
let Some(current_id) = crate::task::current_task_id() else {
ctx.gpr[0] = 0;
kprintln!("[K2-MINT] endpoint mint rejected: no current task");
return;
};
match crate::ui::mint_endpoint(current_id, badge) {
Ok(cap) => {
ctx.gpr[0] = cap.id;
kprintln!(
"[M7.2] User task {} yeni endpoint mint etti: id={}",
current_id,
cap.id
);
}
Err(error) => {
// CapId zero is permanently invalid and is the frozen
// scalar failure result for SYS_MINT_ENDPOINT.
ctx.gpr[0] = 0;
kprintln!(
"[K2-MINT] endpoint mint rejected task={}: {}",
current_id,
error
);
}
}
return;
}
crate::ipc::SYS_LIST_ENDPOINTS => {
let current_id = crate::task::current_task_id().unwrap_or(0);
let endpoints = crate::ui::endpoints_of_task(current_id);
// M7 audit fix #7: magic 6 yerine explicit const.
// SYS_LIST_ENDPOINTS dönüş ABI'si: x0=count, x1..x6=ep ids (en fazla 6).
const MAX_ENDPOINT_LIST_RETURN: usize = 6;
let count = endpoints.len().min(MAX_ENDPOINT_LIST_RETURN);
ctx.gpr[0] = count as u64;
for i in 0..count {
ctx.gpr[i + 1] = endpoints[i].id;
}
kprintln!(
"[M7.2] Task {} endpoint listesi istendi ({} tane)",
current_id,
count
);
return;
}
SYS_IPC_CALL_TIMEOUT => {
handle_ipc_call_timeout(ctx, user_sp);
return;
}
SYS_IPC_RECV_TIMEOUT => {
handle_ipc_recv_timeout(ctx, user_sp);
return;
}
SYS_NOTIFICATION_SIGNAL => {
handle_notification_signal(ctx);
return;
}
SYS_NOTIFICATION_WAIT_TIMEOUT => {
handle_notification_wait_timeout(ctx, user_sp);
return;
}
SYS_IPC_CALL => {
let target_id = ctx.gpr[0];
let label = ctx.gpr[1];
let mr0 = ctx.gpr[2];
let mr1 = ctx.gpr[3];
let mr2 = ctx.gpr[4];
let mr3 = ctx.gpr[5];
let current_id = crate::task::current_task_id().unwrap_or(0);
kprintln!(
"[M7] SYS_IPC_CALL task={} → target={}, label=0x{:x}",
current_id,
target_id,
label
);
// Resolve only immutable routing metadata; cloning an endpoint
// would copy its complete rendezvous state and is never authority.
let Some(target_is_reply) = crate::ui::capability::ENDPOINT_REGISTRY
.lock()
.iter()
.find(|endpoint| endpoint.id == target_id)
.map(|endpoint| endpoint.is_reply_cap)
else {
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
};
if target_is_reply {
// S143 production broker commits are derived only from the
// explicit SYS_IPC_REPLY ABI. The legacy CALL-to-reply alias
// must not consume an armed one-shot authority behind the
// bridge's preflight/commit boundary.
if crate::mm::runtime_oom_reply_cap_is_bound(target_id) {
kprintln!(
"[K1-MEM2-REPLY-BRIDGE] CALL_ALIAS_REJECTED reply_cap={} task={}",
target_id,
current_id
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
let reply_message = crate::ui::capability::IpcMessage {
label,
badge: current_id,
data: [mr0, mr1, mr2, mr3],
};
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s375_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s375_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s375_el0_ipc_call_reply_alias_writer_guard_integration::acquire_s375_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S375 EL0 IPC-call reply-alias scheduler writer guard failed closed: {:?}",
error
)
});
let result = unsafe {
let scheduler =
&mut *core::ptr::addr_of_mut!(crate::task::scheduler::SCHEDULER);
scheduler.ipc_reply_commit(target_id, reply_message)
};
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
drop(s375_writer_access);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
drop(s375_irq_guard);
set_ipc_error(ctx, result);
return;
}
// A raw endpoint id is never EL0 authority. Kernel-shared and
// task-owned endpoints alike require a live CNode entry.
let endpoint_cap = crate::task::current_task_cnode()
.and_then(|cnode| cnode.lookup_capability_by_id(target_id).copied())
.filter(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Endpoint
});
let has_cap = endpoint_cap.is_some();
if !has_cap {
kprintln!(
"[M6.4] CALL REJECTED task={} target={} (owner mismatch / no cap)",
current_id,
target_id
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
let endpoint_generation = endpoint_cap
.expect("validated endpoint capability disappeared")
.generation;
// CALL always requires SEND. Receiving has a separate syscall;
// registry ownership never changes syscall semantics or authority.
let rights_ok = endpoint_cap.map_or(false, |capability| {
capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_SEND)
});
if !rights_ok {
kprintln!(
"[M7.5] HAK İHLALİ! task={} target={} (rights eksik)",
current_id,
target_id
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
// ===================== SEND / CALL PATH (M7 gerçek Call/Reply) =====================
// Mesaj + reply_cap gönderilir, client reply_cap üzerinde bloke olur.
// Server reply_cap'e CALL yapınca client uyanır.
let msg = crate::ui::capability::IpcMessage {
label,
badge: current_id,
data: [mr0, mr1, mr2, mr3],
};
let reply_cap = match crate::ui::mint_reply_endpoint_for_call(current_id, target_id) {
Ok(capability) => capability,
Err(error) => {
kprintln!(
"[K2-MINT] reply mint rejected task={}: {}",
current_id,
error
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
};
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s374_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s374_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration::acquire_s374_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S374 normal EL0 IPC-call scheduler writer guard failed closed: {:?}",
error
)
});
let commit = unsafe {
let scheduler = &mut *core::ptr::addr_of_mut!(crate::task::scheduler::SCHEDULER);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let commit = scheduler.ipc_call_commit_and_park(
ctx,
user_sp,
target_id,
endpoint_generation,
reply_cap.id,
msg,
s374_irq_guard,
s374_writer_access,
);
#[cfg(not(all(
target_arch = "aarch64",
target_os = "none",
feature = "board-rpi5"
)))]
let commit = scheduler.ipc_call_commit_and_park(
ctx,
user_sp,
target_id,
endpoint_generation,
reply_cap.id,
msg,
);
commit
};
if let Err(error) = commit {
let _ = crate::ui::capability::discard_unpublished_reply_endpoint(
reply_cap.id,
Some(current_id),
);
if error == IpcError::QueueFull {
kprintln!(
"[K2] CALL QueueFull task={} target={} capacity={}",
current_id,
target_id,
IPC_QUEUE_CAPACITY
);
}
set_ipc_error(ctx, error);
return;
}
// A parked CALL resumes this exact exception continuation. The
// scheduler copied the durable wake payload back into `ctx`.
return;
}
// M6.4 — Server tarafı: pending mesaj varsa al, yoksa block.
// Argümanlar: x0 = endpoint_id
// Dönüş: x0 = IpcError (M7.1)
// x1 = label, x2 = badge, x3..x6 = data[0..3]
// x7 = reply_cap_id (0 = reply yok, sadece SEND)
SYS_IPC_RECV => {
let ep_id = ctx.gpr[0];
let current_id = crate::task::current_task_id().unwrap_or(0);
kprintln!("[M6.4] SYS_IPC_RECV task={} ep={}", current_id, ep_id);
// A raw endpoint id or registry ownership is not receive
// authority. Revocation takes effect as soon as the CNode entry
// disappears.
let endpoint_cap = crate::task::current_task_cnode()
.and_then(|cnode| cnode.lookup_capability_by_id(ep_id).copied())
.filter(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Endpoint
});
let has_valid_ep = endpoint_cap.is_some();
if !has_valid_ep {
kprintln!(
"[M6.4] RECV REJECTED task={} ep={} (owner mismatch / no cap)",
current_id,
ep_id
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
let endpoint_generation = endpoint_cap
.expect("validated endpoint capability disappeared")
.generation;
// M7 audit fix #4: RECV hakkı kontrolü.
// Eskiden SYS_IPC_RECV hiç rights check etmiyordu — sadece SYS_IPC_CALL.
let recv_rights_ok = endpoint_cap.map_or(false, |capability| {
capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if !recv_rights_ok {
kprintln!(
"[M7.5] RECV REJECTED task={} ep={} (RECV hakkı yok)",
current_id,
ep_id
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s373_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s373_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration::acquire_s373_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S373 EL0 IPC-receive scheduler writer guard failed closed: {:?}",
error
)
});
let receive = unsafe {
let scheduler = &mut *core::ptr::addr_of_mut!(crate::task::scheduler::SCHEDULER);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let receive = scheduler.ipc_recv_or_park(
ctx,
user_sp,
ep_id,
endpoint_generation,
s373_irq_guard,
s373_writer_access,
);
#[cfg(not(all(
target_arch = "aarch64",
target_os = "none",
feature = "board-rpi5"
)))]
let receive = scheduler.ipc_recv_or_park(ctx, user_sp, ep_id, endpoint_generation);
receive
};
match receive {
Ok(Some(envelope)) => {
set_ipc_message_result(ctx, envelope.message, envelope.reply_cap_id);
return;
}
// The registered waiter was parked and has now resumed; its
// wake result was copied from Task.saved_user_gprs into ctx.
Ok(None) => return,
Err(error) => {
set_ipc_error(ctx, error);
return;
}
}
}
// M6.4 — Server cevap verir + reply cap one-shot revoke.
// Argümanlar: x0 = reply_cap_id
// x1 = label, x2..x5 = data[0..3]
SYS_IPC_REPLY => {
let reply_cap_id = ctx.gpr[0];
let label = ctx.gpr[1];
let mr0 = ctx.gpr[2];
let mr1 = ctx.gpr[3];
let mr2 = ctx.gpr[4];
let mr3 = ctx.gpr[5];
let current_id = crate::task::current_task_id().unwrap_or(0);
kprintln!(
"[M6.4] SYS_IPC_REPLY task={} reply_cap={} label=0x{:x}",
current_id,
reply_cap_id,
label
);
let reply_msg = crate::ui::capability::IpcMessage {
label,
badge: current_id,
data: [mr0, mr1, mr2, mr3],
};
let bridge_preflight: Result<
Option<crate::mm::RuntimeOomReplyPreflight>,
crate::mm::RuntimeOomReplyBridgeError,
> = crate::mm::preflight_runtime_oom_supervisor_reply(
current_id,
reply_cap_id,
reply_msg.label,
reply_msg.data,
);
let bridge_preflight = match bridge_preflight {
Ok(preflight) => preflight,
Err(error) => {
kprintln!(
"[K1-MEM2-REPLY-BRIDGE] PREFLIGHT_REJECTED task={} reply_cap={} error={:?}",
current_id,
reply_cap_id,
error
);
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
};
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s372_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s372_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s372_el0_ipc_reply_writer_guard_integration::acquire_s372_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S372 EL0 IPC-reply scheduler writer guard failed closed: {:?}",
error
)
});
let result = unsafe {
let scheduler = &mut *core::ptr::addr_of_mut!(crate::task::scheduler::SCHEDULER);
scheduler.ipc_reply_commit(reply_cap_id, reply_msg)
};
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
drop(s372_writer_access);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
drop(s372_irq_guard);
if result == IpcError::Ok {
if let Some(preflight) = bridge_preflight {
let event = crate::mm::commit_runtime_oom_supervisor_reply(
preflight,
TICKS.load(Ordering::Acquire),
)
.unwrap_or_else(|error| {
panic!(
"reply committed but runtime OOM broker bridge failed closed: {:?}",
error
)
});
kprintln!(
"[K1-MEM2-REPLY-BRIDGE] task={} reply_cap={} sequence={} source_event={} REPLY_DERIVED_BROKER_COMMIT=YES SESSION_CLOSE=AUTOMATIC",
current_id,
reply_cap_id,
event.sequence_id(),
event.source_event_id(),
);
}
}
kprintln!(
"[K2.1] REPLY result={:?} reply_cap consumed (registry+CNode+store)",
result
);
set_ipc_error(ctx, result);
return;
}
_ => {
kprintln!("[M4.3] bilinmeyen syscall: x8={}", syscall_num);
dump_context(ctx);
u64::MAX
}
};
// Dönüş değerini x0'a yaz (user koddan okunabilir)
ctx.gpr[0] = ret;
// S137 common EL0 SVC safe boundary: an armed current-task teardown is
// independent of the syscall kind and of SYS_WRITE success. The syscall
// result is committed to the saved frame before the audited carrier may
// retire the task and switch to a different kernel stack.
#[cfg(feature = "board-qemu")]
if syscall_num != SYS_YIELD {
unsafe {
if let Err(error) = crate::task::execute_armed_current_runtime_oom_if_target() {
panic!("S137 common EL0 SVC safe boundary failed: {:?}", error);
}
}
}
// Return normally to exceptions.S. It owns the one RESTORE_CONTEXT+eret
// epilogue for every non-fatal EL0 SVC, including a task that resumed
// after yield or IPC park. Bypassing that epilogue would leak this trap
// frame on the task's kernel stack on every syscall.
}snippet sha256: 69e991ebab2e…file sha256: 6f3a4c8dbf40…focus sha256: 0a67bacdfd82…
02 · Devredilen production üyeliği
Context-switch bırakma ve resume yeniden-katılım kodu
tam Rust öğesiL446–L3588
kernel/src/task/scheduler.rs::ipc_call_commit_and_park
impl Scheduler {
pub const fn new() -> Self {
Self {
ready_queue: BinaryHeap::new(),
current_task: None,
retired_task: None,
deferred_current_runtime_oom: None,
ticks_until_preempt: 0,
min_vruntime: 0,
ipc_blocked_tasks: spin::Mutex::new(Vec::new()),
}
}
/// Reap the task retired by an earlier context switch.
///
/// User roots remain paired with their non-zero ASID lease until this
/// later-stack reaper drops the complete page-table owner. The ASID is
/// returned only after those resources are gone.
fn reap_retired_task(&mut self) {
let Some(mut task) = self.retired_task.take() else {
return;
};
let deferred_match = self
.deferred_current_runtime_oom
.as_ref()
.filter(|carrier| carrier.witness.is_none())
.is_some_and(|carrier| {
task.id == carrier.binding.task_id
&& task.runtime_allocation_domain() == Some(carrier.binding.domain)
});
if deferred_match {
let mut carrier = self
.deferred_current_runtime_oom
.take()
.expect("deferred current OOM carrier disappeared");
let witness = reap_detached_runtime_oom_task(
task,
carrier.binding.domain,
carrier.ipc_lifecycle_closed,
);
carrier.witness = Some(witness);
self.deferred_current_runtime_oom = Some(carrier);
return;
}
if task.is_user && (task.asid == 0 || !task.address_space_quiesced) {
crate::kprintln!(
"[K1-LIFECYCLE] quarantine task #{} '{}' without complete address-space quiescence (asid={}); resources intentionally retained",
task.id,
task.name,
task.asid
);
core::mem::forget(task);
return;
}
crate::kprintln!(
"[K1-LIFECYCLE] reaping task #{} '{}' on a later task stack",
task.id,
task.name
);
unsafe {
task.release_owned_page_table_root();
}
task.owned_user_page_tables.take();
match crate::mm::address_space::reclaim_owned_user_frames(
&mut task.owned_user_frames,
&mut task.runtime_user_frames,
) {
Ok(Some(report)) if report.returned_to_baseline() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} BASELINE=PASS",
task.id,
report.released_frames,
report.baseline_free_frames,
report.observed_free_frames,
report.baseline_active_allocations,
report.observed_active_allocations,
),
Ok(Some(report)) if report.exactly_reconciled() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} RECONCILED=PASS BASELINE=CONCURRENT",
task.id,
report.released_frames,
report.reclaim_started_free_frames,
report.observed_free_frames,
report.reclaim_started_active_allocations,
report.observed_active_allocations,
),
Ok(Some(_)) => unreachable!("RuntimePmm reclaimer returned an unaudited delta"),
Ok(None) => {}
Err(error) => {
crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} BASELINE=FAIL error={:?}; ASID and remaining resources quarantined",
task.id,
error
);
core::mem::forget(task);
return;
}
}
// Root/intermediate storage is unreachable and the prior TLBI is
// complete. Only now may a fresh address space acquire this ASID.
let retired_asid = task.asid;
if retired_asid != 0 {
if let Err(error) = crate::mm::address_space::free_asid(retired_asid) {
crate::kprintln!(
"[K1-ASID] final reaper release failed asid={} error={:?}; identifier remains quarantined",
retired_asid,
error
);
core::mem::forget(task);
return;
}
task.asid = 0;
}
// `Task` drop now releases its owned kernel/user stack allocations.
// Raw ELF user stacks have no `OwnedStackAllocation` and are not
// reconstructed as a Box.
drop(task);
}
/// CFS tarzı weight hesabı (düşük priority = daha yüksek weight = daha yavaş vruntime artışı)
fn weight_of(priority: u8) -> u64 {
match priority {
0 => 1024, // En yüksek öncelik
1 => 820,
2 => 655,
3 => 524,
4 => 419,
5 => 335,
6 => 268,
7 => 215,
_ => 128,
}
}
/// Scheduler içinde vruntime güncelleme (CFS-lite)
fn update_vruntime(&mut self, task: &mut Task, delta_exec: u64) {
let weight = Self::weight_of(task.priority);
let delta = delta_exec * 1024 / weight.max(1);
task.vruntime = task.vruntime.wrapping_add(delta);
// min_vruntime'ı güncelle (CFS'te scheduler bunu takip eder)
if task.vruntime < self.min_vruntime {
self.min_vruntime = task.vruntime;
}
}
/// Builds a complete task without exposing it through a scheduler
/// container. S354 and S355 use this with an owned S255 vruntime snapshot
/// so all fallible user/kernel preparation finishes before the respective
/// publication writer. The old unguarded `Scheduler::spawn` publication
/// path no longer exists.
fn build_unpublished_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
is_user: bool,
user_entry: Option<extern "C" fn() -> !>,
initial_user_arg0: u64,
initial_vruntime: u64,
) -> Result<(Task, JoinHandle), TaskSpawnError> {
const GUARD_SIZE: usize = 0x1000;
let validated_user_entry = if is_user {
Some(user_entry.ok_or(TaskSpawnError::MissingUserEntry)?)
} else {
None
};
let total_user_stack_alloc = if is_user {
Some(
stack_size
.checked_add(GUARD_SIZE)
.ok_or(TaskSpawnError::StackSizeOverflow)?,
)
} else {
None
};
// Task ids are authority principals. Reserve one before ASIDs, page
// tables, or stacks so permanent id exhaustion has no side effects.
// A later spawn failure may consume an id, but ids are never reused.
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| TaskSpawnError::TaskIdExhausted)?;
// === M8.2+M7.4 fix (18-agent audit): page_table_root policy ===
// Kernel tasks share the live ROOT_PAGE_TABLE and never own or switch
// to a private root. The recorded value must nevertheless be its real
// address: framebuffer grants query this metadata and mutate the
// returned table. The old 0x4020_0000 placeholder points into the heap
// on current links and corrupts/walks allocator metadata as PTEs.
let kernel_root = unsafe {
let root = crate::arch::aarch64::mmu::get_kernel_root_table() as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let (new_root, new_asid) = if is_user {
// Reserve the scarce identifier before any root or stack Box.
// Exhaustion is returned to the user-task caller without mutation.
let a = crate::mm::address_space::try_allocate_asid().map_err(TaskSpawnError::Asid)?;
let r = crate::mm::allocate_page_table_root();
(r, a)
} else {
(kernel_root, 0u16)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let finished = Arc::new(AtomicBool::new(false));
// M5.5 (Audit #18) — EL0 stack + guard yalnız user task için gerekir.
// Kernel task'e ikinci, hiç kullanılmayan bir stack ayırmak hem heap'i
// tüketiyor hem de sahipliği kaybolan gereksiz bir allocation yaratıyordu.
let user_stack_allocation = total_user_stack_alloc.map(OwnedStackAllocation::new);
let (user_stack_ptr, usable_stack_bottom) = if let Some(owner) = &user_stack_allocation {
let user_stack_ptr = owner.base();
let usable_stack_bottom = unsafe { user_stack_ptr.add(GUARD_SIZE) };
// Guard sayfasının üstüne canary yaz (ileride overflow kontrolü).
unsafe {
core::ptr::write_volatile(
user_stack_ptr.add(GUARD_SIZE - 8) as *mut u64,
0xDEAD_BEEF_C0FFEE00,
);
}
(user_stack_ptr, usable_stack_bottom)
} else {
(core::ptr::null_mut(), core::ptr::null_mut())
};
// Sadece user task için per-task root mapping kur.
if is_user {
unsafe {
let root_table = crate::arch::aarch64::mmu::get_page_table_from_root(new_root);
// Düşük RAM + heap bölgesi (kernel tarafı NORMAL — kernel kodu için exec).
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x4000_0000),
crate::mm::PhysAddr::new(0x4000_0000),
0x0800_0000,
crate::mm::paging::PageTableFlags::NORMAL,
);
// UART
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0900_0000),
crate::mm::PhysAddr::new(0x0900_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// GIC
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0800_0000),
crate::mm::PhysAddr::new(0x0800_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// M5.5+M7.4 fix: User stack USER_NORMAL override.
// Heap NORMAL kalır (kernel isolation), sadece user stack range'i
// USER_NORMAL ile EL0'a açılır. Guard page (alt 4K) MAP EDİLMEZ →
// translation fault overflow yakalar (Strategy A — Linux/seL4 patterni).
//
// KRİTİK: Box::into_raw u8 alignment'lı bir pointer döner (4K aligned değil).
// map_range_4k_to_root 4K page boundary bekler — unaligned adresleri
// atlar (page'leri map etmez) → user EL0 stack write'da perm fault.
// Bu yüzden adresi 4K'ya yuvarla (aşağı → start, yukarı → end).
let usable_stack_phys = usable_stack_bottom as u64;
let aligned_start = usable_stack_phys & !0xFFF; // round down
let aligned_end = (usable_stack_phys + stack_size as u64 + 0xFFF) & !0xFFF;
let aligned_size = aligned_end - aligned_start;
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(aligned_start),
crate::mm::PhysAddr::new(aligned_start),
aligned_size,
crate::mm::paging::PageTableFlags::USER_NORMAL,
);
crate::kprintln!(
"[M5.5+M7.4] user '{}' root=0x{:x} asid={} stack USER_NORMAL [0x{:x}..0x{:x}] (size=0x{:x}, requested phys=0x{:x})",
name, new_root.as_u64(), new_asid, aligned_start, aligned_end, aligned_size, usable_stack_phys
);
}
}
let mut task = Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: finished.clone(),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation,
user_stack_bottom: usable_stack_bottom,
user_stack_size: if is_user { stack_size } else { 0 },
// M5.5: yalnız user task'te guard sayfası ayrılır.
stack_guard_page: if is_user { Some(user_stack_ptr) } else { None },
// M4.3
is_user,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: {
let mut registers = [0; 31];
if is_user {
registers[0] = initial_user_arg0;
}
registers
},
// Kernel task = live shared kernel root/asid=0; user task = unique owned root + ASID.
page_table_root: new_root,
owns_page_table_root: is_user,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: new_asid,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: !is_user,
// M5: Her task kendi CNode'unu alır (boş başlar)
cnode: crate::ui::capability::CNode::new(),
};
// Kernel context'i her zaman trampoline veya normal entry'ye işaret eder
// User task'ler için ilk girişe özel kurulum kullanıyoruz
unsafe {
if is_user {
task.context = TaskContext::new_for_user_first_entry(stack_top);
} else {
task.context = TaskContext::new(kernel_entry, stack_top);
}
}
// User task ise initial EL0 frame'i hemen hazırla
if let Some(entry) = validated_user_entry {
// Stack top = usable_bottom + size (guard hariç)
task.user_sp = usable_stack_bottom as u64 + stack_size as u64; // stack top (büyüme aşağı)
task.saved_user_elr = entry as u64;
task.saved_user_spsr = 0; // EL0t
// GPR'ler sıfır kalır, sadece PC ve SP önemli
}
let handle = JoinHandle {
finished,
task_id: id,
};
Ok((task, handle))
}
/// Build a kernel-task skeleton without publishing it in any scheduler
/// container. The raw-ELF path consumes this value, installs the complete
/// user address-space state, and performs exactly one final ready-queue
/// push. No CPU can therefore observe a placeholder entry/root/ASID.
fn build_unpublished_kernel_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
initial_vruntime: u64,
) -> Result<Box<Task>, &'static str> {
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| "task id space exhausted")?;
let kernel_root = {
let root =
unsafe { crate::arch::aarch64::mmu::get_kernel_root_table() } as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let mut task = Box::new(Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: Arc::new(AtomicBool::new(false)),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation: None,
user_stack_bottom: core::ptr::null_mut(),
user_stack_size: 0,
stack_guard_page: None,
is_user: false,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: [0; 31],
page_table_root: kernel_root,
owns_page_table_root: false,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: 0,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: true,
cnode: crate::ui::capability::CNode::new(),
});
task.context = unsafe { TaskContext::new(kernel_entry, stack_top) };
Ok(task)
}
/// Mevcut task'i kuyruğa geri koyar ve en yüksek öncelikli (en düşük vruntime) task'i seçer.
///
/// NOT: Şu anda kullanılmıyor (`yield_now` aynı işi inline yapıyor).
/// İleride farklı bir API (örn. IRQ-driven preemption) lazım olursa bu
/// fonksiyon kullanılabilir.
#[allow(dead_code)]
pub unsafe fn schedule(&mut self) -> Option<(*mut TaskContext, *mut TaskContext)> {
if let Some(mut curr) = self.current_task.take() {
curr.state = TaskState::Ready;
if curr.time_slice == 0 {
curr.time_slice = curr.default_time_slice;
}
self.ready_queue.push(PriorityTask::new(curr));
}
// min_vruntime'ı güncelle (daha adil seçim için)
self.recalculate_min_vruntime();
if let Some(prio_next) = self.ready_queue.pop() {
let mut next = prio_next.task;
next.state = TaskState::Running;
let new_ctx = &mut next.context as *mut TaskContext;
self.current_task = Some(next);
self.ticks_until_preempt = self.current_task.as_ref().unwrap().time_slice;
Some((core::ptr::null_mut(), new_ctx))
} else {
None
}
}
/// Timer her tick'te çağrılır.
/// Time slice bitince preemption tetikler + vruntime ve aging uygular.
pub unsafe fn tick(&mut self) -> bool {
static TICK_COUNTER: AtomicU64 = AtomicU64::new(0);
let n = TICK_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
// Aging + min_vruntime + normalizasyon (her 50 tick'te bir)
if n % 50 == 0 {
self.apply_aging();
self.recalculate_min_vruntime();
self.normalize_vruntime();
}
if let Some(curr) = &mut self.current_task {
if curr.time_slice > 0 {
curr.time_slice -= 1;
}
// Vruntime'ı da tick bazında hafifçe artır
let weight = Self::weight_of(curr.priority);
curr.vruntime = curr.vruntime.wrapping_add(1 * 1024 / weight.max(1));
if curr.vruntime < self.min_vruntime {
self.min_vruntime = curr.vruntime;
}
if curr.time_slice == 0 {
return true; // preemption gerekli
}
}
false
}
/// Aging mekanizması:
/// Uzun süredir bekleyen task'lerin vruntime'ını azaltarak önlerine geçmelerini sağlar.
/// Bu sayede düşük öncelikli task'ler bile zamanla CPU alabilir.
fn apply_aging(&mut self) {
let mut temp = Vec::new();
while let Some(mut ptask) = self.ready_queue.pop() {
// min_vruntime'tan çok geride kalanlara daha fazla bonus veriyoruz
let bonus = if ptask.task.vruntime + 50 < self.min_vruntime {
12
} else {
6
};
ptask.task.vruntime = ptask.task.vruntime.saturating_sub(bonus);
temp.push(ptask);
}
for p in temp {
self.ready_queue.push(p);
}
}
/// Mevcut task'in context pointer'ını döner (güvenli kullanım için)
pub fn current_context_ptr(&self) -> Option<*mut TaskContext> {
self.current_task
.as_ref()
.map(|t| &t.context as *const _ as *mut TaskContext)
}
/// Mevcut çalışan task'in ID'sini döndürür.
pub fn current_task_id(&self) -> Option<u64> {
self.current_task.as_ref().map(|t| t.id)
}
/// S211 producer-side observation of the real scheduler current task.
/// The target adapter holds a local IRQ guard while calling this method;
/// cross-CPU exclusion against legacy scheduler accesses remains open.
#[cfg(feature = "board-rpi5")]
pub(crate) fn observe_g8l_current_task(
&self,
) -> Option<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask,
>{
self.current_task.as_ref().map(|task| {
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
}
})
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn commit_g8l_current_task_owner(
&mut self,
runtime: &crate::g8l_runtime_contract::G8lRuntimeAuthority,
authority: &crate::g8l_target_dispatch_scheduler_owner::G8lSchedulerOwnerAuthority,
bridge: &crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_guarded_ack::G8lSchedulerMutationProductionGuardedAckBridge<'_>,
) -> Result<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
let observed = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
};
task.g8l_current_task_owner
.commit_from_guarded_bridge(runtime, authority, bridge, observed)
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn rollback_g8l_current_task_owner(
&mut self,
receipt: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
) -> Result<
(),
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
if task.id != receipt.task_id {
return Err(crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::TaskMismatch);
}
task.g8l_current_task_owner.rollback_exact(receipt)
}
fn record_runtime_oom_binding(
task: &Task,
location: RuntimeOomTaskLocation,
domain: crate::mm::AllocationDomain,
expected_frames: usize,
binding: &mut Option<RuntimeOomTaskBinding>,
) -> Result<(), RuntimeOomTaskExecutionError> {
if task.runtime_allocation_domain() != Some(domain) {
return Ok(());
}
if binding.is_some() {
return Err(RuntimeOomTaskExecutionError::AmbiguousDomainBinding);
}
let Some(ledger) = task.runtime_user_frames.as_ref() else {
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
};
let expected_state = match location {
RuntimeOomTaskLocation::Current => task.state == TaskState::Running,
RuntimeOomTaskLocation::Ready => task.state == TaskState::Ready,
RuntimeOomTaskLocation::Blocked => matches!(
task.state,
TaskState::Blocked
| TaskState::BlockedOnIpc { .. }
| TaskState::BlockedOnNotification { .. }
),
RuntimeOomTaskLocation::Retired => task.state == TaskState::Dead,
};
if !task.is_user
|| task.asid == 0
|| task.address_space_quiesced
|| task.owns_page_table_root
|| task.owned_user_page_tables.is_none()
|| ledger.frame_count() != expected_frames
|| task.owned_user_frames.len() != expected_frames
|| !ledger.physical_frames_match(&task.owned_user_frames)
|| !expected_state
{
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
}
*binding = Some(RuntimeOomTaskBinding {
task_id: task.id,
domain,
location,
asid: task.asid,
frame_count: expected_frames,
});
Ok(())
}
/// Allocation-free audit of every persistent scheduler container. A
/// domain must bind one strict RuntimePmm ELF task and the ticket's frame
/// count must equal that task's complete leaf ledger.
fn audit_runtime_oom_task_binding(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let expected_frames = usize::try_from(preflight.expected_reclaimable_frames)
.map_err(|_| RuntimeOomTaskExecutionError::InvalidTaskBinding)?;
let mut binding = None;
if let Some(task) = self.current_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Current,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
for priority_task in self.ready_queue.iter() {
Self::record_runtime_oom_binding(
&priority_task.task,
RuntimeOomTaskLocation::Ready,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
{
let blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Blocked,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
}
if let Some(task) = self.retired_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Retired,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
binding.ok_or(RuntimeOomTaskExecutionError::DomainNotBound)
}
fn preflight_runtime_oom_task(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let binding = self.audit_runtime_oom_task_binding(preflight)?;
match binding.location {
RuntimeOomTaskLocation::Current => {
Err(RuntimeOomTaskExecutionError::CurrentTaskRequiresDeferredExit)
}
RuntimeOomTaskLocation::Retired => {
Err(RuntimeOomTaskExecutionError::RetiredTaskQuarantined)
}
RuntimeOomTaskLocation::Ready | RuntimeOomTaskLocation::Blocked => Ok(binding),
}
}
/// Detach the exact preflighted non-current task without allocating. The
/// ready heap is rebuilt in-place from its existing Vec allocation.
fn take_runtime_oom_task(&mut self, binding: RuntimeOomTaskBinding) -> Option<Box<Task>> {
let mut ready = core::mem::take(&mut self.ready_queue).into_vec();
let ready_match = ready.iter().position(|priority_task| {
priority_task.task.id == binding.task_id
&& priority_task.task.runtime_allocation_domain() == Some(binding.domain)
});
let selected = ready_match.map(|index| ready.swap_remove(index).task);
self.ready_queue = BinaryHeap::from(ready);
if selected.is_some() {
return selected;
}
let mut blocked = self.ipc_blocked_tasks.lock();
let blocked_match = blocked.iter().position(|task| {
task.id == binding.task_id && task.runtime_allocation_domain() == Some(binding.domain)
});
blocked_match.map(|index| blocked.swap_remove(index))
}
pub(crate) fn capability_for_task(
&self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<crate::ui::capability::Capability> {
if let Some(capability) = self
.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ipc_blocked_tasks
.lock()
.iter()
.find(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ready_queue
.iter()
.find(|priority_task| priority_task.task.id == task_id)
.and_then(|priority_task| priority_task.task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
self.retired_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
}
pub(crate) fn notification_holder_count(
&self,
notification_id: crate::ui::capability::CapId,
) -> usize {
let holds = |task: &Task| {
task.cnode
.lookup_capability_by_id(notification_id)
.is_some_and(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
})
};
let mut count = usize::from(self.current_task.as_deref().is_some_and(holds));
count += self
.ready_queue
.iter()
.filter(|task| holds(&task.task))
.count();
count += self
.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| holds(task))
.count();
count += usize::from(self.retired_task.as_deref().is_some_and(holds));
count
}
fn notification_holder_for_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(notification_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(notification_id)
};
let notification_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != notification_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Notification
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| notification_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::NOTIFICATION_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_notification_holder_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::notification_holder_for_task(task, notification_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Generation-aware audit of every persistent notification holder. The
/// caller owns `IPC_TRANSACTION_LOCK`, so the matching purge observes the
/// same CNode graph.
pub(crate) fn preflight_notification_holder_purge(
&self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_notification_holder_task(
&task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_notification_holder_from_task(
task: &mut Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::notification_holder_for_task(task, notification_id, object_owner)
.expect("preflighted notification holder became malformed");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted notification holder exact revoke failed"
);
summary
.record(container)
.expect("notification holder count overflowed during commit");
}
/// Allocation-free exact purge paired with
/// `preflight_notification_holder_purge`.
pub(crate) fn purge_notification_holders_exact(
&mut self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_notification_holder_purge(notification_id, object_owner)
.expect("notification holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_notification_holder_from_task(
&mut task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"notification holder purge differed from exact preflight"
);
removed
}
fn endpoint_holder_for_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(endpoint_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(endpoint_id)
};
let endpoint_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != endpoint_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Endpoint
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| endpoint_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::ENDPOINT_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_endpoint_holder_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::endpoint_holder_for_task(task, endpoint_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Allocation-free, generation-aware audit of every scheduler-owned task
/// CNode that can outlive the current instruction. The caller holds the
/// global IPC transaction lock, so this snapshot remains stable through
/// the matching purge.
pub(crate) fn preflight_endpoint_holder_purge(
&self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_endpoint_holder_task(
&task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_endpoint_holder_from_task(
task: &mut Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::endpoint_holder_for_task(task, endpoint_id, object_owner)
.expect("preflighted endpoint holder became malformed inside one IPC transaction");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted endpoint holder exact revoke failed"
);
summary
.record(container)
.expect("preflighted endpoint holder count overflowed during commit");
}
/// Commit the exact holder snapshot without allocating. BinaryHeap is
/// converted into and rebuilt from its existing Vec allocation in place;
/// CNode changes cannot affect heap ordering.
pub(crate) fn purge_endpoint_holders_exact(
&mut self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_endpoint_holder_purge(endpoint_id, object_owner)
.expect("endpoint holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_endpoint_holder_from_task(
&mut task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"endpoint holder purge differed from its exact preflight"
);
removed
}
/// Belirli bir task'in page table root'unu döndürür (M4.4 büyük adım için).
pub fn get_task_page_table_root(&self, task_id: u64) -> Option<crate::mm::PhysAddr> {
// Basit lineer arama (demo için yeterli)
if let Some(ref curr) = self.current_task {
if curr.id == task_id {
return Some(curr.page_table_root);
}
}
for ptask in self.ready_queue.iter() {
if ptask.task.id == task_id {
return Some(ptask.task.page_table_root);
}
}
for task in self.ipc_blocked_tasks.lock().iter() {
if task.id == task_id {
return Some(task.page_table_root);
}
}
None
}
/// M5 — Belirli bir task'in CNode'una capability ekler (cross-task grant için kritik).
/// Hem current_task hem ready_queue içindeki task'leri tarar.
pub fn insert_cap_for_task(
&mut self,
task_id: u64,
cap: crate::ui::capability::Capability,
) -> Result<(usize, u64), &'static str> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
return curr.cnode.insert(cap);
}
}
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut result: Result<(usize, u64), &'static str> = Err("Task not found");
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
result = pt.task.cnode.insert(cap);
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
result
}
/// Private id-only CNode removal. Public callers must pass through the
/// global wrapper, which rejects endpoint identities in favor of typed
/// grant/object APIs.
fn revoke_cap_for_task(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
self.remove_cap_by_id(task_id, cap_id)
}
/// CNode seviyesinde id bazlı güçlü revoke (generation bump dahil)
fn remove_cap_by_id(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
if curr.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
}
// Reply owners normally wait outside the ready queue. Search this
// fixed authority location before the heap fallback so reply consume
// and endpoint teardown do not allocate a temporary queue at OOM.
let mut blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter_mut() {
if task.id == task_id && task.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
drop(blocked);
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut found: Option<usize> = None;
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
if pt.task.cnode.revoke_capability(cap_id).is_some() {
found = Some(0);
}
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
if found.is_some() {
return found;
}
None
}
/// Allocation-free removal of one complete CNode authority tuple. This
/// is used by typed endpoint/reply teardown where id-only revocation would
/// let a stale capability delete a newer generation.
pub(crate) fn revoke_cap_for_task_exact(
&mut self,
expected: &crate::ui::capability::Capability,
) -> bool {
let task_id = expected.owner;
if let Some(task) = self
.current_task
.as_deref_mut()
.filter(|task| task.id == task_id)
{
return task.cnode.revoke_capability_exact(expected).is_some();
}
{
let mut blocked = self.ipc_blocked_tasks.lock();
if let Some(task) = blocked.iter_mut().find(|task| task.id == task_id) {
return task.cnode.revoke_capability_exact(expected).is_some();
}
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
let removed = ready_tasks
.iter_mut()
.find(|task| task.task.id == task_id)
.map_or(false, |task| {
task.task.cnode.revoke_capability_exact(expected).is_some()
});
self.ready_queue = BinaryHeap::from(ready_tasks);
if removed {
return true;
}
self.retired_task
.as_deref_mut()
.filter(|task| task.id == task_id)
.map_or(false, |task| {
task.cnode.revoke_capability_exact(expected).is_some()
})
}
// =================================================================
// M6.2 — IPC Blocking / Waking (İskelet)
// =================================================================
fn save_current_ipc_context(&mut self, ctx: &ExceptionContext, user_sp: u64) {
if let Some(current) = &mut self.current_task {
current.is_user = true;
current.user_sp = user_sp;
current.saved_user_elr = ctx.elr_el1;
current.saved_user_spsr = ctx.spsr_el1;
current.saved_user_gprs[..30].copy_from_slice(&ctx.gpr);
current.saved_user_gprs[30] = ctx.lr;
}
}
/// Copy the wake result back into the still-live exception frame whose
/// kernel continuation was saved by `switch_after_ipc_park`.
fn restore_current_ipc_context(&self, ctx: &mut ExceptionContext) {
let current = self
.current_task
.as_ref()
.expect("IPC continuation resumed without its current task");
assert!(
current.is_user && current.state == TaskState::Running,
"IPC continuation resumed outside a running EL0 task"
);
ctx.gpr.copy_from_slice(¤t.saved_user_gprs[..30]);
ctx.lr = current.saved_user_gprs[30];
ctx.elr_el1 = current.saved_user_elr;
ctx.spsr_el1 = current.saved_user_spsr;
}
fn current_endpoint_authority_is_live(
&self,
task_id: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
required_right: crate::ui::capability::CapabilityRights,
) -> bool {
self.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(endpoint_id))
.map_or(false, |capability| {
capability.generation == expected_generation
&& capability.owner == task_id
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.rights.contains(required_right)
})
}
fn current_notification_authority_is_live(
&self,
task_id: u64,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
required_right: crate::ui::capability::CapabilityRights,
) -> bool {
self.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(notification_id))
.is_some_and(|capability| {
capability.generation == expected_generation
&& capability.owner == task_id
&& capability.kind == crate::ui::capability::CapabilityKind::Notification
&& capability.rights.contains(required_right)
})
}
fn write_ipc_delivery(
task: &mut Task,
message: crate::ui::capability::IpcMessage,
reply_cap_id: u64,
) {
task.saved_user_gprs[0] = crate::ipc::IpcError::Ok.as_u64();
task.saved_user_gprs[1] = message.label;
task.saved_user_gprs[2] = message.badge;
task.saved_user_gprs[3] = message.data[0];
task.saved_user_gprs[4] = message.data[1];
task.saved_user_gprs[5] = message.data[2];
task.saved_user_gprs[6] = message.data[3];
task.saved_user_gprs[7] = reply_cap_id;
}
/// A registered rendezvous receiver may be legacy (no deadline record) or
/// S145 timed. Any record for that task must be the exact receiver waiter;
/// another kind/generation is an authority-graph mismatch, not a hint to
/// deliver anyway.
fn exact_receive_deadline_for_waiter<const N: usize>(
deadlines: &crate::ipc_deadline::IpcCallDeadlineRegistry<N>,
receiver: crate::ipc_rendezvous::ReceiverWaiter,
endpoint_id: crate::ui::capability::CapId,
) -> Result<Option<crate::ipc_wait::WaitRecord>, crate::ipc::IpcError> {
let record = deadlines.task_snapshot(receiver.task_id());
if record.is_some_and(|record| {
record.kind().tag() != crate::ipc_wait::WaitKindTag::Receive
|| record.kind().object_id() != endpoint_id
|| record.kind().object_generation() != receiver.cap_generation()
|| record.kind().reply_cap_id().is_some()
}) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Ok(record)
}
fn retire_receive_deadline_after_delivery<const N: usize>(
deadlines: &mut crate::ipc_deadline::IpcCallDeadlineRegistry<N>,
record: Option<crate::ipc_wait::WaitRecord>,
) {
if let Some(record) = record {
deadlines
.complete_delivery_exact(record)
.expect("CALL delivered but exact RECV deadline did not retire");
}
}
fn switch_after_ipc_park_with_membership_handoff<BeforeSwitch, AfterResume>(
&mut self,
parked_context: *mut TaskContext,
before_switch: BeforeSwitch,
after_resume: AfterResume,
) where
BeforeSwitch: FnOnce(),
AfterResume: FnOnce(&mut Self),
{
let Some(priority_task) = self.ready_queue.pop() else {
panic!("transactional IPC parked the current task without a runnable successor");
};
let mut next = priority_task.task;
next.state = TaskState::Running;
let new_context = &mut next.context as *mut TaskContext;
let next_slice = next.default_time_slice;
// IPC blocking is a real scheduler transition, so it must install the
// next task's TTBR0/ASID just like yield_now does. The context saved by
// context_switch belongs to the parked Box<Task>; a stack-local dummy
// loses the live syscall continuation and can resume a stale frame.
unsafe {
prepare_task_for_context_switch(&next);
}
self.current_task = Some(next);
self.ticks_until_preempt = next_slice;
// A gate lease belongs to the task executing this continuation. It
// must not remain live while the selected task runs. S373 uses these
// callbacks to release immediately before the machine switch and to
// rejoin before reading the wake payload when this continuation later
// resumes. Other IPC paths retain the legacy no-op wrapper below.
before_switch();
unsafe {
crate::arch::aarch64::context_switch(parked_context, new_context);
}
after_resume(self);
}
fn switch_after_ipc_park(&mut self, parked_context: *mut TaskContext) {
self.switch_after_ipc_park_with_membership_handoff(parked_context, || {}, |_| {});
}
/// Atomically publishes a CALL and parks its caller under one lock order:
/// IPC transaction -> global endpoint table -> global blocked-task set.
/// The caller is in the reply wait set before a receiver becomes runnable.
pub fn ipc_call_commit_and_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s374_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s374_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration::G8lS374ProductionSchedulerWriterAccess,
) -> Result<(), crate::ipc::IpcError> {
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let caller_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_task = blocked.iter().find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
});
let receiver_authority_is_live = receiver_task
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.map_or(false, |capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let outcome = endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
.map_err(|error| match error {
CallError::QueueFull | CallError::ReplyTableFull => crate::ipc::IpcError::QueueFull,
_ => crate::ipc::IpcError::InvalidCapability,
})?;
match endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id)
{
Ok(FinishOutcome::Park) => {}
Ok(FinishOutcome::Ready(_)) | Ok(FinishOutcome::Cancelled) | Err(_) => {
// CALL, finish and scheduler publication are serialized by
// IPC_TRANSACTION_LOCK. No REPLY/revoke transition can run
// between `call` and this point, so any non-Park result means
// the model and scheduler have already diverged. Returning a
// recoverable syscall error here would strand a published
// request/reply record; fail-stop before making it worse.
panic!("CALL rendezvous changed inside one IPC transaction")
}
}
let mut caller = self
.current_task
.take()
.expect("preflighted CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
// Moving the Box through the blocked/ready/current containers does not
// move its Task pointee, so this exact SAVE target stays valid until
// the caller is scheduled and resumes this syscall continuation.
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("rendezvous receiver wait-token changed during CALL commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted rendezvous receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
caller_context,
|| {
drop(s374_writer_access);
drop(s374_irq_guard);
},
|scheduler| {
let s374_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s374_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration::acquire_s374_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S374 resumed normal EL0 IPC-call scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s374_resume_writer_access);
drop(s374_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(caller_context);
self.restore_current_ipc_context(ctx);
}
Ok(())
}
/// S144 deadline-bearing CALL. This is deliberately a distinct ABI path:
/// legacy SYS_IPC_CALL never interprets an unspecified x6 register. The
/// fixed wait record is armed under `IPC_TRANSACTION_LOCK` before the
/// rendezvous request becomes visible, and every fallible allocation or
/// authority check precedes that publication point.
#[allow(clippy::too_many_arguments)]
pub fn ipc_call_timeout_commit_and_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
reply_generation: u64,
message: crate::ui::capability::IpcMessage,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s379_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s379_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration::G8lS379ProductionSchedulerWriterAccess,
) -> Result<(), crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let caller_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let reply_authority_is_live = self
.current_task
.as_ref()
.and_then(|task| task.cnode.lookup_capability_by_id(reply_cap_id))
.is_some_and(|capability| {
capability.id == reply_cap_id
&& capability.owner == caller_task
&& capability.generation == reply_generation
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.parent.is_none()
});
if !reply_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
// Global lock order for every deadline race is:
// IPC transaction -> deadline registry -> endpoint/provenance ->
// scheduler blocked set. Timer and REPLY follow the same order.
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_task = blocked.iter().find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
});
let receiver_authority_is_live = receiver_task
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.is_some_and(|capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let deadline_record = deadlines
.register_call(
caller_task,
target_endpoint,
expected_generation,
reply_cap_id,
reply_generation,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
let outcome =
match endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
{
Ok(outcome) => outcome,
Err(error) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed CALL publication must roll back its exact deadline");
return Err(match error {
CallError::QueueFull | CallError::ReplyTableFull => {
crate::ipc::IpcError::QueueFull
}
_ => crate::ipc::IpcError::InvalidCapability,
});
}
};
match endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id)
{
Ok(FinishOutcome::Park) => {}
Ok(FinishOutcome::Ready(_)) | Ok(FinishOutcome::Cancelled) | Err(_) => {
panic!("deadline CALL rendezvous changed inside one IPC transaction")
}
}
let mut caller = self
.current_task
.take()
.expect("preflighted deadline CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("deadline CALL receiver wait-token changed during commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted deadline receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
caller_context,
|| {
drop(s379_writer_access);
drop(s379_irq_guard);
},
|scheduler| {
let s379_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s379_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration::acquire_s379_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S379 resumed EL0 IPC call-timeout scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s379_resume_writer_access);
drop(s379_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(caller_context);
self.restore_current_ipc_context(ctx);
}
Ok(())
}
/// Publish one synchronous kernel-supervisor request through the same
/// bounded endpoint rendezvous used by EL0 CALL, then block the current
/// kernel task until an ordinary EL0 REPLY wakes it. This is not an
/// ambient kernel message queue: the current task must own a live SEND
/// capability, the reply object is one-shot, and every transition remains
/// under the production IPC transaction/registry/scheduler lock order.
pub fn ipc_kernel_call_and_wait(
&mut self,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
) -> Result<crate::ui::capability::IpcMessage, crate::ipc::IpcError> {
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let caller_task = self
.current_task
.as_ref()
.filter(|task| !task.is_user)
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_authority_is_live = blocked
.iter()
.find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
})
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.map_or(false, |capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let outcome = endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
.map_err(|error| match error {
CallError::QueueFull | CallError::ReplyTableFull => crate::ipc::IpcError::QueueFull,
_ => crate::ipc::IpcError::InvalidCapability,
})?;
if !matches!(
endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id),
Ok(FinishOutcome::Park)
) {
panic!("kernel CALL rendezvous changed inside one IPC transaction");
}
let mut caller = self
.current_task
.take()
.expect("preflighted kernel CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("kernel CALL receiver wait-token changed during commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted kernel CALL receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
self.switch_after_ipc_park(caller_context);
let caller = self
.current_task
.as_ref()
.filter(|task| task.id == caller_task && !task.is_user)
.expect("kernel CALL continuation resumed under another task");
if caller.saved_user_gprs[0] != crate::ipc::IpcError::Ok.as_u64()
|| caller.saved_user_gprs[7] != 0
{
return Err(crate::ipc::IpcError::InvalidCapability);
}
Ok(crate::ui::capability::IpcMessage {
label: caller.saved_user_gprs[1],
badge: caller.saved_user_gprs[2],
data: [
caller.saved_user_gprs[3],
caller.saved_user_gprs[4],
caller.saved_user_gprs[5],
caller.saved_user_gprs[6],
],
})
}
/// Atomically performs RECV's FIFO-pop-or-waiter-registration decision.
/// `Some` is an immediate queued delivery; `None` means the task was
/// parked and a context switch was initiated.
pub fn ipc_recv_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s373_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s373_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration::G8lS373ProductionSchedulerWriterAccess,
) -> Result<Option<crate::ui::capability::IpcEnvelope>, crate::ipc::IpcError> {
use crate::ipc_rendezvous::{ReceiverWaiter, RecvOutcome};
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let receiver_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
receiver_task,
endpoint_id,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_RECV,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter =
ReceiverWaiter::new_with_generation(receiver_task, endpoint_id, expected_generation)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target = endpoints
.iter_mut()
.find(|endpoint| endpoint.id == endpoint_id && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let will_park = target.rendezvous.queued_len() == 0;
if will_park && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if will_park {
self.save_current_ipc_context(ctx, user_sp);
}
// Only the waiter-registration branch needs blocked-set capacity.
// Reserving on an immediate FIFO delivery could spuriously reject a
// receive under allocator pressure even though it does not allocate.
let mut blocked = if will_park {
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
Some(blocked)
} else {
None
};
match target
.rendezvous
.recv(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?
{
RecvOutcome::Deliver {
request,
reply_token,
..
} => {
drop(blocked);
drop(endpoints);
drop(transaction);
Ok(Some(crate::ui::capability::IpcEnvelope {
message: request,
reply_cap_id: reply_token,
}))
}
RecvOutcome::Registered => {
let mut blocked_guard = blocked
.take()
.expect("RECV registered without preflighted blocked capacity");
let mut receiver = self
.current_task
.take()
.expect("preflighted RECV current task disappeared");
receiver.state = TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
};
let receiver_context = &mut receiver.context as *mut TaskContext;
blocked_guard.push(receiver);
drop(blocked_guard);
drop(blocked);
drop(endpoints);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
receiver_context,
|| {
drop(s373_writer_access);
drop(s373_irq_guard);
},
|scheduler| {
let s373_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s373_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration::acquire_s373_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S373 resumed EL0 IPC-receive scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s373_resume_writer_access);
drop(s373_resume_irq_guard);
},
);
#[cfg(not(all(
target_arch = "aarch64",
target_os = "none",
feature = "board-rpi5"
)))]
{
self.switch_after_ipc_park(receiver_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
}
}
/// S145 opt-in deadline-bearing RECV. The legacy syscall above remains
/// byte-for-byte policy compatible and never interprets x6. Only an empty
/// endpoint arms a wait record; a queued FIFO message is returned with
/// `IMMEDIATE_DELIVERY_DEADLINE=UNARMED`.
pub fn ipc_recv_timeout_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s378_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s378_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s378_el0_ipc_receive_timeout_writer_guard_integration::G8lS378ProductionSchedulerWriterAccess,
) -> Result<Option<crate::ui::capability::IpcEnvelope>, crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_rendezvous::{ReceiverWaiter, RecvOutcome};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let receiver_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
receiver_task,
endpoint_id,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_RECV,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter =
ReceiverWaiter::new_with_generation(receiver_task, endpoint_id, expected_generation)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
// Shared CALL/RECV arbitration order: transaction -> deadline table ->
// endpoint -> blocked scheduler set.
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == endpoint_id && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let will_park = endpoints[target_index].rendezvous.queued_len() == 0;
if will_park && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
let mut blocked = if will_park {
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
Some(blocked)
} else {
None
};
if !will_park {
// S145 acceptance marker: IMMEDIATE_DELIVERY_DEADLINE=UNARMED.
let outcome = endpoints[target_index]
.rendezvous
.recv(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let RecvOutcome::Deliver {
request,
reply_token,
..
} = outcome
else {
panic!("non-empty endpoint registered a timed RECV waiter")
};
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
return Ok(Some(crate::ui::capability::IpcEnvelope {
message: request,
reply_cap_id: reply_token,
}));
}
let deadline_record = deadlines
.register_receive(
receiver_task,
endpoint_id,
expected_generation,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
let outcome = endpoints[target_index].rendezvous.recv(waiter);
match outcome {
Ok(RecvOutcome::Registered) => {}
Ok(RecvOutcome::Deliver { .. }) | Err(_) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed RECV publication must roll back its exact deadline");
return Err(crate::ipc::IpcError::InvalidCapability);
}
}
let mut blocked_guard = blocked
.take()
.expect("timed RECV registered without blocked capacity");
let mut receiver = self
.current_task
.take()
.expect("preflighted timed RECV current task disappeared");
receiver.state = TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
};
let receiver_context = &mut receiver.context as *mut TaskContext;
blocked_guard.push(receiver);
drop(blocked_guard);
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
receiver_context,
|| {
drop(s378_writer_access);
drop(s378_irq_guard);
},
|scheduler| {
let s378_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s378_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s378_el0_ipc_receive_timeout_writer_guard_integration::acquire_s378_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S378 resumed EL0 IPC receive-timeout scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s378_resume_writer_access);
drop(s378_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(receiver_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
/// S146 timed notification wait. Matching bits already pending are
/// consumed immediately without a deadline record. Otherwise the shared
/// deadline is published before the exact object waiter.
pub fn notification_wait_timeout_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
mask: u64,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s377_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s377_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration::G8lS377ProductionSchedulerWriterAccess,
) -> Result<Option<u64>, crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_notification::{NotificationWaitOutcome, NotificationWaiter};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let waiter_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_notification_authority_is_live(
waiter_task,
notification_id,
expected_generation,
crate::ui::capability::CapabilityRights::NOTIFICATION_WAIT,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter = NotificationWaiter::try_new(waiter_task, expected_generation, mask)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut notifications = crate::ui::capability::NOTIFICATION_REGISTRY.lock();
let target_index = notifications
.iter()
.position(|object| object.id() == notification_id)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let immediate = notifications[target_index].pending() & mask;
if immediate != 0 {
// S146 acceptance marker: IMMEDIATE_NOTIFICATION_DEADLINE=UNARMED.
let outcome = notifications[target_index]
.wait(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let NotificationWaitOutcome::Immediate { observed } = outcome else {
panic!("ready notification bits published a waiter")
};
drop(notifications);
drop(deadlines);
drop(transaction);
return Ok(Some(observed));
}
if self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
// NOTIFICATION_WAIT_ARM_BEFORE_WAITER: timer authority exists before
// the object can expose a parked waiter to a signal producer.
let deadline_record = deadlines
.register_notification(
waiter_task,
notification_id,
expected_generation,
mask,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
match notifications[target_index].wait(waiter) {
Ok(NotificationWaitOutcome::Registered) => {}
Ok(NotificationWaitOutcome::Immediate { .. }) | Err(_) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed notification waiter publication lost its deadline rollback");
return Err(crate::ipc::IpcError::InvalidCapability);
}
}
let mut task = self
.current_task
.take()
.expect("preflighted notification waiter task disappeared");
task.state = TaskState::BlockedOnNotification { notification_id };
let parked_context = &mut task.context as *mut TaskContext;
blocked.push(task);
drop(blocked);
drop(notifications);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
parked_context,
|| {
drop(s377_writer_access);
drop(s377_irq_guard);
},
|scheduler| {
let s377_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s377_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration::acquire_s377_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S377 resumed EL0 notification-wait scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s377_resume_writer_access);
drop(s377_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(parked_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
/// OR one signal into a coalescing notification. A matching waiter is
/// validated across object, shared deadline, CNode and scheduler state
/// before the allocation-free signal/wake commit.
pub fn notification_signal(
&mut self,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
bits: u64,
) -> Result<crate::ipc_notification::NotificationSignalOutcome, crate::ipc::IpcError> {
use crate::ipc_notification::NotificationSignalOutcome;
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let _transaction = IPC_TRANSACTION_LOCK.lock();
let signaler_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if bits == 0
|| !self.current_notification_authority_is_live(
signaler_task,
notification_id,
expected_generation,
crate::ui::capability::CapabilityRights::NOTIFICATION_SIGNAL,
)
{
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut notifications = crate::ui::capability::NOTIFICATION_REGISTRY.lock();
let target_index = notifications
.iter()
.position(|object| object.id() == notification_id)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let waiter = notifications[target_index].waiter_snapshot();
let will_wake = waiter.is_some_and(|waiter| {
(notifications[target_index].pending() | bits) & waiter.mask() != 0
});
if !will_wake {
return notifications[target_index]
.signal(bits)
.map_err(|_| crate::ipc::IpcError::InvalidCapability);
}
let waiter = waiter.expect("will_wake without a notification waiter");
let deadline_record = deadlines
.task_snapshot(waiter.task_id())
.filter(|record| {
record.kind().tag() == crate::ipc_wait::WaitKindTag::Notification
&& record.kind().object_id() == notification_id
&& record.kind().object_generation() == waiter.capability_generation()
&& record.kind().notification_mask() == Some(waiter.mask())
})
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter.task_id()
&& matches!(
task.state,
TaskState::BlockedOnNotification {
notification_id: blocked_id,
} if blocked_id == notification_id
)
&& task
.cnode
.lookup_capability_by_id(notification_id)
.is_some_and(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
&& capability.generation == waiter.capability_generation()
&& capability.rights.contains(
crate::ui::capability::CapabilityRights::NOTIFICATION_WAIT,
)
})
})
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.ipc_wake_capacity_available(1) {
return Err(crate::ipc::IpcError::NoReceiver);
}
let outcome = notifications[target_index]
.signal(bits)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let NotificationSignalOutcome::Wake {
waiter: committed_waiter,
observed,
..
} = outcome
else {
panic!("preflighted matching notification did not wake")
};
assert_eq!(committed_waiter, waiter);
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::Ok.as_u64();
task.saved_user_gprs[1] = observed;
for register in &mut task.saved_user_gprs[2..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
deadlines
.complete_delivery_exact(deadline_record)
.expect("matching notification signal lost exact deadline retirement");
Ok(outcome)
}
/// Commits a one-shot reply only after proving the caller is already in
/// the global blocked set. The reply object is removed after the response
/// is copied and the caller becomes runnable.
pub fn ipc_reply_commit(
&mut self,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
) -> crate::ipc::IpcError {
use crate::ipc_rendezvous::ReplyOutcome;
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let Some(responder_task) = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
else {
return crate::ipc::IpcError::InvalidCapability;
};
// Match lifecycle/mint lock order: IPC transaction -> capability
// provenance -> endpoint registry -> blocked scheduler set.
let mut capability_store = crate::ui::capability::get_capability_store();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let Some(reply_index) = endpoints
.iter()
.position(|endpoint| endpoint.id == reply_cap_id && endpoint.is_reply_cap)
else {
return crate::ipc::IpcError::InvalidCapability;
};
let caller_task = endpoints[reply_index].owner;
let Some(target_endpoint) = endpoints[reply_index].reply_target else {
return crate::ipc::IpcError::InvalidCapability;
};
let Some(target_index) = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
else {
return crate::ipc::IpcError::InvalidCapability;
};
let mut blocked = self.ipc_blocked_tasks.lock();
let Some(caller_position) = blocked.iter().position(|task| {
task.id == caller_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: true,
} if endpoint_id == reply_cap_id
)
}) else {
return crate::ipc::IpcError::InvalidCapability;
};
let reply_authority = blocked[caller_position]
.cnode
.lookup_capability_by_id(reply_cap_id)
.copied()
.filter(|capability| {
capability.owner == caller_task
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.parent.is_none()
});
let Some(reply_authority) = reply_authority else {
return crate::ipc::IpcError::InvalidCapability;
};
let deadline_record = deadlines.reply_snapshot(reply_cap_id);
if deadline_record.is_some_and(|record| {
record.key().task_id() != caller_task
|| record.kind().tag() != crate::ipc_wait::WaitKindTag::Call
|| record.kind().object_id() != target_endpoint
|| record.kind().reply_cap_id() != Some(reply_cap_id)
|| record.kind().reply_generation() != Some(reply_authority.generation)
}) {
return crate::ipc::IpcError::InvalidCapability;
}
if self.ready_queue.try_reserve(1).is_err() {
return crate::ipc::IpcError::NoReceiver;
}
match endpoints[target_index]
.rendezvous
.reply(responder_task, reply_cap_id, message)
{
Ok(ReplyOutcome::Wake {
caller_task: model_caller,
payload,
..
}) => {
if model_caller != caller_task {
panic!("reply model caller differs from blocked caller");
}
let retire_witness = endpoints[target_index]
.rendezvous
.retire(caller_task, reply_cap_id)
.expect("consumed reply record must retire exactly once");
assert_eq!(retire_witness.caller_task(), caller_task);
assert_eq!(retire_witness.reply_token(), reply_cap_id);
let mut caller = blocked.remove(caller_position);
assert_eq!(
caller.cnode.revoke_capability_exact(&reply_authority),
Some(reply_authority),
"retired reply exact caller CNode revoke failed"
);
Self::write_ipc_delivery(&mut caller, payload, 0);
caller.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(caller));
let removed_reply = endpoints.remove(reply_index);
assert_eq!(removed_reply.id, retire_witness.reply_token());
assert_eq!(removed_reply.owner, retire_witness.caller_task());
assert_eq!(removed_reply.reply_target, Some(target_endpoint));
if let Some(record) = deadline_record {
deadlines
.complete_reply_exact(record)
.expect("ordinary REPLY committed but exact deadline did not retire");
}
}
Ok(ReplyOutcome::StoredBeforePark) => {
// The blocked-set proof above and the model's `parked` bit
// must agree under the transaction lock. A recoverable error
// would leave a Replied record with a permanently parked
// caller, so treat disagreement as an integrity failure.
panic!("blocked caller has an unparked reply record")
}
Err(_) => return crate::ipc::IpcError::InvalidCapability,
}
drop(blocked);
drop(endpoints);
assert_eq!(
capability_store.revoke_endpoint_provenance(reply_cap_id, Some(responder_task)),
1,
"retired reply must have exactly one provenance record"
);
drop(capability_store);
drop(deadlines);
drop(transaction);
crate::ipc::IpcError::Ok
}
/// M7.5 — Revoke edildiğinde bloke task'leri uyandır
///
/// NOTE (M6/M7 Multi-core hazırlığı):
/// ipc_blocked_tasks şu anda Scheduler'ın içinde (tek global).
/// Gerçek SMP'de ya:
/// - Scheduler'ın tamamını spin::Mutex ile sarmak, veya
/// - Per-CPU blocked list + cross-CPU IPI ile wake.
///
/// Şu an için ENDPOINT_REGISTRY'nin yaptığı gibi .lock() ile korunması önerilir.
pub(crate) fn try_reserve_ipc_wake_capacity(&mut self, additional: usize) -> bool {
self.ready_queue.try_reserve(additional).is_ok()
}
/// Timer IRQ preflight must never allocate. All scheduler-created tasks
/// have already occupied the ready heap once, so a parked task can return
/// only when an existing slot is available. A false result leaves every
/// IPC/deadline object untouched for fail-closed handling.
pub(crate) fn ipc_wake_capacity_available(&self, additional: usize) -> bool {
self.ready_queue
.capacity()
.saturating_sub(self.ready_queue.len())
>= additional
}
/// Commit the scheduler half of one already-preflighted timeout. The
/// caller is selected by task id + reply object and its exact CNode
/// generation is retired before it becomes runnable with TimedOut.
pub(crate) fn wake_timed_out_ipc_caller_exact(
&mut self,
caller_task: u64,
reply_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed CALL wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let caller_position = blocked
.iter()
.position(|task| {
task.id == caller_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: true,
} if endpoint_id == reply_authority.id
)
&& task.cnode.can_revoke_capability_exact(&reply_authority)
})
.expect("preflighted timed CALL caller disappeared");
let mut caller = blocked.remove(caller_position);
assert_eq!(
caller.cnode.revoke_capability_exact(&reply_authority),
Some(reply_authority),
"timed CALL exact reply CNode revoke failed"
);
caller.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut caller.saved_user_gprs[1..=7] {
*register = 0;
}
caller.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(caller));
}
/// Wake one exact timed RECV without consuming its endpoint capability.
/// The timer-side graph preflight already removed the rendezvous waiter;
/// this commits only the allocation-free scheduler half.
pub(crate) fn wake_timed_out_ipc_receiver_exact(
&mut self,
receiver_task: u64,
endpoint_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed RECV wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == endpoint_authority.id
)
&& task
.cnode
.lookup_capability_by_id(endpoint_authority.id)
.copied()
== Some(endpoint_authority)
})
.expect("preflighted timed RECV receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
receiver.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut receiver.saved_user_gprs[1..=7] {
*register = 0;
}
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
pub(crate) fn notification_blocked_task_count_on(
&self,
task_id: u64,
notification_id: crate::ui::capability::CapId,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
task.id == task_id
&& matches!(
task.state,
TaskState::BlockedOnNotification {
notification_id: blocked_id,
} if blocked_id == notification_id
)
})
.count()
}
pub(crate) fn notification_blocked_count_on(
&self,
notification_id: crate::ui::capability::CapId,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
matches!(
task.state,
TaskState::BlockedOnNotification { notification_id: blocked_id }
if blocked_id == notification_id
)
})
.count()
}
/// Wake one waiter whose notification authority was revoked after wait
/// admission. Holder removal may already have committed, so identity is
/// proven by task/object/state under the same IPC transaction rather than
/// by re-reading the deleted CNode slot.
pub(crate) fn wake_revoked_notification_exact(
&mut self,
waiter_task: u64,
notification_id: crate::ui::capability::CapId,
) {
assert!(
self.ipc_wake_capacity_available(1),
"revoked notification wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter_task
&& matches!(
task.state,
TaskState::BlockedOnNotification { notification_id: blocked_id }
if blocked_id == notification_id
)
})
.expect("preflighted revoked notification waiter disappeared");
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::InvalidCapability.as_u64();
for register in &mut task.saved_user_gprs[1..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
}
pub(crate) fn wake_timed_out_notification_exact(
&mut self,
waiter_task: u64,
notification_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed notification wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter_task
&& matches!(
task.state,
TaskState::BlockedOnNotification { notification_id }
if notification_id == notification_authority.id
)
&& task
.cnode
.lookup_capability_by_id(notification_authority.id)
.copied()
== Some(notification_authority)
})
.expect("preflighted timed notification waiter disappeared");
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut task.saved_user_gprs[1..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
}
/// Allocation-free lifecycle audit: a parked rendezvous identity must map
/// to exactly one scheduler waiter before cancellation is committed.
pub(crate) fn ipc_blocked_count_on(&self, endpoint_id: crate::ui::capability::CapId) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id: blocked_endpoint,
..
} if blocked_endpoint == endpoint_id
)
})
.count()
}
pub(crate) fn ipc_blocked_task_count_on(
&self,
task_id: u64,
endpoint_id: crate::ui::capability::CapId,
is_call: bool,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
task.id == task_id
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id: blocked_endpoint,
is_call: blocked_is_call,
} if blocked_endpoint == endpoint_id && blocked_is_call == is_call
)
})
.count()
}
pub fn wake_tasks_on_revoked_endpoint(&mut self, endpoint_id: crate::ui::capability::CapId) {
// Multi-core safe: PerCpu üzerinden kilitle
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut blocked_list = self.ipc_blocked_tasks.lock();
let mut i = 0;
while i < blocked_list.len() {
let should_wake = matches!(
blocked_list[i].state,
TaskState::BlockedOnIpc { endpoint_id: eid, .. } if eid == endpoint_id
);
if should_wake {
let mut task = blocked_list.remove(i);
let task_asid = task.asid; // ASID'i kaydet (invalidate için)
let is_call = matches!(task.state, TaskState::BlockedOnIpc { is_call: true, .. });
if let Some(record) = deadlines.task_snapshot(task.id) {
match (is_call, record.kind().tag()) {
(true, crate::ipc_wait::WaitKindTag::Call) => assert_eq!(
record.kind().reply_cap_id(),
Some(endpoint_id),
"peer-close wake selected a different deadline reply"
),
(false, crate::ipc_wait::WaitKindTag::Receive) => assert_eq!(
record.kind().object_id(),
endpoint_id,
"peer-close wake selected a different deadline endpoint"
),
_ => panic!("peer-close wake selected a different IPC wait kind"),
}
deadlines
.complete_peer_closed_exact(record)
.expect("peer-close IPC wake lost its exact deadline record");
}
drop(blocked_list);
// M7 audit fix #10 + M8.2 ASID invalidate
task.saved_user_gprs[0] = crate::ipc::IpcError::InvalidCapability.as_u64();
for slot in &mut task.saved_user_gprs[1..=7] {
*slot = 0;
}
task.state = TaskState::Ready;
// Revoke sonrası ilgili ASID'in TLB girdilerini temizle (M8.2)
if task_asid != 0 {
unsafe {
crate::arch::aarch64::mmu::invalidate_asid(task_asid);
}
}
let task_id = task.id;
self.ready_queue.push(PriorityTask::new(task));
crate::kprintln!(
"[M7.5] Revoke nedeniyle task#{} uyandırıldı (ASID={} invalidate edildi)",
task_id,
task_asid
);
blocked_list = self.ipc_blocked_tasks.lock();
} else {
i += 1;
}
}
}
// Restored minimal versions of previously removed methods to make build pass
fn recalculate_min_vruntime(&mut self) {
// TODO: implement properly later
}
fn normalize_vruntime(&mut self) {
// TODO: implement properly later
}
pub fn current_vruntime() -> u64 {
#[cfg(feature = "board-rpi5")]
let _s255_scheduler_read_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s254_vruntime_read_access_guard_expansion::acquire_s255_production_scheduler_read_access()
.unwrap_or_else(|error| {
panic!(
"S255 current-vruntime scheduler read access failed closed: {:?}",
error
)
});
unsafe {
let sched = &*core::ptr::addr_of!(SCHEDULER);
sched.current_task.as_ref().map_or(0, |t| t.vruntime)
}
}
pub fn min_vruntime() -> u64 {
#[cfg(feature = "board-rpi5")]
let _s255_scheduler_read_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s254_vruntime_read_access_guard_expansion::acquire_s255_production_scheduler_read_access()
.unwrap_or_else(|error| {
panic!(
"S255 min-vruntime scheduler read access failed closed: {:?}",
error
)
});
unsafe {
let sched = &*core::ptr::addr_of!(SCHEDULER);
sched.min_vruntime
}
}
pub fn try_wake_tasks_waiting_on(&mut self, _endpoint_id: crate::ui::capability::CapId) {
// TODO
}
}snippet sha256: fc5979057655…file sha256: 838dd474448c…
03 · Ortak exclusion üyeliği
S247 production writer guard
tam Rust öğesiL208–L220
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration.rs::acquire_s374_production_scheduler_writer_access
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
pub fn acquire_s374_production_scheduler_writer_access(
) -> Result<G8lS374ProductionSchedulerWriterAccess, G8lS247WholeSchedulerAccessError> {
let caller_cpu =
crate::percpu::try_current_cpu_id().ok_or(G8lS247WholeSchedulerAccessError::InvalidCpu)?;
if caller_cpu != crate::g8l_runtime_contract::CPU0 {
return Err(G8lS247WholeSchedulerAccessError::InvalidCpu);
}
let access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s246_whole_scheduler_read_access_guard::S247_PRODUCTION_WHOLE_SCHEDULER_ACCESS_GATE
.try_acquire_exclusive_for_valid_cpu(caller_cpu)?;
Ok(G8lS374ProductionSchedulerWriterAccess { _access: access })
}snippet sha256: 033c6fd2ed44…file sha256: b61c4d955df3…
04 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam Rust öğesiL398–L407
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration.rs::normal_call_branch_has_exactly_one_initial_s374_acquire
#[test]
fn normal_call_branch_has_exactly_one_initial_s374_acquire() {
assert_eq!(
normal_call_boundary()
.matches("acquire_s374_production_scheduler_writer_access")
.count(),
1
);
}snippet sha256: 47fc5a5c8075…file sha256: 26e2c954f96f…
05 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL9794–L9953
website/src/lib/operations.ts::g8l-s374-el0-ipc-call-writer-guard-integration-partial
{
id: "g8l-s374-el0-ipc-call-writer-guard-integration-partial",
date: "2026-08-29",
sequence: 374,
status: "passed",
umbrella_status: "partial",
title: "S374 · normal EL0 IPC CALL production writer guard integration",
summary:
"S374, rust_el0_sync_handler içindeki SYS_IPC_CALL dalının yalnız normal non-reply yolundaki exact ipc_call_commit_and_park mutable scheduler sınırını S373 ve 44 production reader'ın kullandığı aynı statik S247 state word'e bağlar. CALL-to-reply aliası writer edinmeden daha önce döner ve ayrı S375 kapısı olarak kalır. Current-task identity, live normal Endpoint CNode authority, generation, ENDPOINT_SEND hakkı, immutable IpcMessage ve linked reply-cap mint writer'dan önce tamamlanır. RPi5 bare-metal kesişiminde dedicated IRQ guard kurulur, gerçek per-CPU kimliğiyle yalnız CPU0 için S374 exclusive writer alınır ve exact tek mutable SCHEDULER aliasıyla üyelik ipc_call_commit_and_park içine by-value taşınır. Scheduler caller SEND authority, linked one-shot reply provenance, optional receiver authority/deadline ve bütün fallible capacity koşullarını üyelik altında yeniden doğrular; CALL publish, caller Blocked publication ve optional receiver delivery aynı transaction'da tamamlanır. Global writer başka task çalışırken pinlenmesin diye writer ve IRQ gerçek context_switch'ten hemen önce bırakılır. Parked continuation reply ile döndüğünde önce yeni IRQ, sonra yeni S374 writer üyeliğine katılır; durable wake payload restore_current_ipc_context ile okunur ve iki üyelik commit sonucu incelenmeden önce bırakılır. Unpublished reply cleanup ve terminal error publication writer dışındadır. Guarded writer 47/69, açık writer 22, provider authority 0, whole-scheduler exclusion false ve supported-profile runtime observation=0'dır. S375 CALL-to-reply aliası sıradaki ayrı kapı; S376 notification signal da ayrı kalır.",
evidence: [
"İlk focused komut, S374 production modülü ve kernel/simulation registration henüz yokken compile RED verdi. Eksik kaynak bağı görünür kaldı; test-only model iskeleti production wiring yerine geçirilmedi.",
"Minimal source iskeleti, S373 preflight zinciri, host executor ve registration eklendikten sonra ilk dar test 1/1 PASS verdi.",
"Focused kapsam 76 assertion'a genişletildiğinde compiler recursion-limit reddi oluştu. Bu, S246→S374 nested typed error zincirinin test crate default sınırını aşmasıydı; yalnız test crate'e #![recursion_limit = \"256\"] eklendi, ürün veya coverage assertion'ı gevşetilmedi.",
"Sonraki geniş koşu 74/76 PASS verdi. İki RED, modül açıklamasında S375 exact cümlesinin satır bölünmesine bağlı source-literal eşleşmesiydi; davranış veya üyelik kapsamı değiştirilmeden açıklama tek exact cümleye getirildi.",
"Biçimlendirme sonrası final focused komut 1 grup / 76/76 PASS / 0 fail verdi. Artifact /tmp/aselsanos-s374-focused.40b9Mg altındadır.",
"S374 module constants ve pending-request outcome birlikte 44 guarded reader + 47/69 guarded writer + 22 open writer envanterini sabitler.",
"Exact production giriş sınırı arch/aarch64/exceptions.rs içindeki SYS_IPC_CALL match dalının target_is_reply erken dönüşünden sonraki normal CALL bölümüdür. S374 reply aliasını, timeout CALL helper'ını, kernel CALL helper'ını veya notification dalını coverage'a katmaz.",
"current_id önce owned scalar olarak alınır. target_id ve endpoint_generation register'lardan okunur; exact normal Endpoint capability, matching generation ve ENDPOINT_SEND hakkı current task CNode'unda writer edinilmeden doğrulanır.",
"target_is_reply true ise scheduler.ipc_reply_commit(target_id, reply_message) erken dalı çalışır ve normal CALL üyeliğine ulaşmaz. Bu mutation S375 için ayrı, açık ve tekil kapıdır.",
"Raw endpoint id, invalid kind, stale generation veya eksik SEND hakkı InvalidCapability ile writer edinilmeden kapanır. Preflight red yolları S247 gate state'ini ve reply registry'yi değiştirmez.",
"Immutable IpcMessage label, caller badge ve dört message register'ından writer öncesinde kurulur. mint_reply_endpoint_for_call de writer öncesindedir; mint başarısızsa mutable scheduler aliası kurulmaz.",
"Dedicated IrqGuard exact target_arch=aarch64, target_os=none, feature=board-rpi5 cfg kesişiminde S374 writer acquisition'dan önce kurulur; local interrupt re-entry mutable alias öncesinde kapanır.",
"Production wrapper try_current_cpu_id ile gerçek per-CPU kimliğini türetir ve yalnız CPU0'ı kabul eder. Caller-supplied production CPU parametresi yoktur; non-CPU0 fail-closed InvalidCpu verir.",
"Wrapper exact S247_PRODUCTION_WHOLE_SCHEDULER_ACCESS_GATE üzerinde try_acquire_exclusive_for_valid_cpu kullanır. Ayrı state word, reader lease'i veya model-only provider production'a taşınmaz.",
"Normal CALL bölümünde exact bir initial acquire_s374_production_scheduler_writer_access occurrence'ı ve exact bir mutable SCHEDULER aliası vardır. RPi5 production forward s374_irq_guard ile s374_writer_access değerlerini ipc_call_commit_and_park içine by-value taşır.",
"RPi5 dışı çağrı tarihsel altı argümanlı scheduler.ipc_call_commit_and_park(ctx, user_sp, target_id, endpoint_generation, reply_cap.id, msg) biçimini korur; S304 model audit source sözleşmesi bozulmaz.",
"ipc_call_commit_and_park girişinde production dışı legacy inner IrqGuard exact cfg-not ile korunur; RPi5 production yolunda ikinci nested IRQ guard kurulmaz.",
"Scheduler writer altında current caller id ve exact ENDPOINT_SEND authority yeniden doğrulanır. CNode authority exception preflight'inden sonra kaybolmuşsa mutation başlamadan InvalidCapability döner.",
"Normal endpoint ve linked reply object aynı ENDPOINT_REGISTRY lock snapshot'ında doğrulanır. Reply id'nin is_reply_cap, owner=current caller ve reply_target=target endpoint provenance tuple'ı mutation öncesi exact eşleşir.",
"Optional registered receiver varsa receiver task id, endpoint authority generation ve ENDPOINT_RECV hakkı doğrulanır; varsa receive deadline exact waiter'a bağlı olarak preflight edilir.",
"Ready queue, blocked queue, rendezvous ve deadline capacity dahil bütün fallible rezervasyonlar CALL publication'dan önce tamamlanır. QueueFull veya capacity reddi partial CALL graph yayımlamaz.",
"rendezvous.call ve rendezvous.finish_call aynı IPC transaction altında yürür. Caller reply bekleme durumuna ve BlockedOnIpc state'ine alınmadan optional receiver runnable yayımlanmaz.",
"Optional receiver delivery exact request message ve minted reply_cap_id ile yapılır; receive deadline retire edilir, receiver Ready olur ve ready_queue'ya guard altında eklenir.",
"IPC_TRANSACTION_LOCK, deadline, endpoint ve blocked-task lock'ları context-switch handoff'tan önce bırakılır. S247 writer bu nested registry lock'larını edinmeden önce değil, dış IRQ ve writer sırasıyla kurulmuştur.",
"switch_after_ipc_park_with_membership_handoff scheduler mutation'dan sonra next task'ı Running yapar, context pointer/time-slice/current_task publication'ını tamamlar ve before_switch callback'ini gerçek machine context_switch'ten hemen önce çağırır.",
"S374 before_switch callback'i exact drop(s374_writer_access) ardından drop(s374_irq_guard) yürütür. Writer token başka task çalışırken continuation stack'inde canlı tutulmaz.",
"Parked CALL reply ile yeniden schedule edildiğinde context_switch çağrısı aynı exception continuation'a döner. after_resume callback'i önce s374_resume_irq_guard kurar, sonra acquire_s374_production_scheduler_writer_access ile shared state word'e yeniden katılır.",
"restore_current_ipc_context(ctx), durable reply/error payload'ı current task'tan okumadan önce resume writer üyeliği aktiftir. Restore'dan sonra exact drop(s374_resume_writer_access) ve drop(s374_resume_irq_guard) sırası korunur.",
"Normal CALL her başarılı commit'te park eder; handler ancak scheduler continuation'ı döndükten sonra commit sonucunu inceler. S374 üyelikleri o noktada kapalıdır.",
"discard_unpublished_reply_endpoint, QueueFull diagnostic ve set_ipc_error terminal publication commit Err dalında writer dışındadır. Cleanup ikinci mutable scheduler aliası veya nested S374 acquire üretmez.",
"S373 receive dalı kendi acquire/release/rejoin yaşam döngüsünü korur ve S374 adı taşımaz. S373 ile S374 aynı gate token serisinde monoton fakat ayrı exclusive transaction'lardır.",
"Tarihsel S304 EL0 IPC CALL writer-authority audit 15/15, S305 CALL-to-reply alias audit 15/15 ve S303 receive audit 15/15 PASS verdi. S374 bu model audit'lerini production integration iddiasına dönüştürmeden korur.",
"S373 tarihsel focused testi ilk regresyonda 70/71 kaldı; tek RED artık geçersiz olan 'S374 açık' assertion'ıydı. Test, S373'in 23-open tarihsel snapshot'ını korurken canlı CALL dalında ayrı S374 acquire bulunduğunu doğrulayacak biçimde hizalandı; final S373 71/71 PASS verdi.",
"Seçili regresyon 20 grup / 392/392 PASS verdi: S374, S373, S372, S304, S305, S303, S316, S290, S252, capability_mint_source, ipc_queue source/host, rendezvous, wait ve altı IPC deadline/notification runtime grubu seri çalıştı.",
"Seçili regresyon artifact dizini /tmp/aselsanos-s374-regression-final.g4Fs4N'dir; focused veya tarihsel gruplardan hiçbiri filtrelenmedi.",
"S238–S374 dependency matrisi S240'ın iki distinct grubuyla 138 gruptur. İki bağımsız seri koşunun her biri 3113/3113 PASS / 0 fail verdi.",
"İki süre-dışı kanonik dependency özeti de 22773 B ve 3c0d7d5794b4684feb9e31aef9b39614c33953f77901102c597d155f477b79d7 SHA-256 ile byte-eşittir. Artifact /tmp/aselsanos-s374-dependency.TJjS3W'dir.",
"Exact yedi frozen G8h assertion dışındaki seri workspace 337 grup / 4978 PASS / 0 fail / 7 filtered verdi. Filtered log 72469 B / cb2fd6fd8cd33570594df0092e9392d6d7d43bdd8ae70afff40cad4f5e33c8b2; summary 31897 B / f10c184f9d374717f8a04c588ef29c8e5bc726c00c2d336f704f7cf04d5c9eff'tir.",
"Filtresiz workspace exit 101 ile yalnız frozen S96 wiring_does_not_mutate_timer_gic_boot_or_expand_runtime_scope reddinde durdu: 290 grup / 4723 PASS / 1 fail. Log 67691 B / fc94f7fbd140559a91ab52a0c7eb8294d4d371b9e639b9ecc214551dac50c54a; summary 27478 B / b794fdd08db3287951401464b4e31c26e245d5f91c9a9e16f418b0482429bf3a'dır. Global workspace GREEN iddia edilmez.",
"Fresh izole AArch64 profilleri 4/4 exit 0 verdi. board-qemu log 111561 B / 30fcbf01fc26b108d6dc6895f21e81f51cc4fb3d374fe2c49a22900a1ead7bd0, ELF 12622312 B / 8d96d37c3a1af6a3fce5099432febddf8511829f016826edaf42c494bab8f443'tür.",
"board-rpi4 log 150243 B / 6f6e9ee0c3db58cc81fb5d91d0760121ac78701926c30e0e14b5eb8e23d6540b, ELF 7747400 B / 7d029809c4318439a36315402a2a5d5edf8efee3ce2d43a0f133aaaa1b83117d'dir.",
"board-rpi5 log 613981 B / e3fcac0d1d53634f722db279ed11db55772bb9046920b11371a1bbceaf7ddd4e, ELF 14518744 B / edec0253f37022355a1b49adf8c3fa0c0123114f34871a859519c9509d832173'tür.",
"board-rpi5+smp log 613923 B / b7a4b9a769054b691d616efe636fcc4f6e0ef244ced17cd9e41b5aa756443985, ELF 14515960 B / f7836c93bd40e6e62d17076285c8f981f67e318530780e5e3618d86ee23776e3'tür. Zero-warning iddiası yoktur.",
"make verify-qemu exit 0 verdi. 116354 B log / 190aab3169e3ff2287f278fef7c1490b0bcf701474cd91bc435a857b75129476 SHA-256 ile strict ELF W^X 31/31, S130–S154+S271, RuntimePmm, EL0x4096, IPC reply 20/20 ve scheduler SEC5 PASS'tir.",
"QEMU artifact dizini /tmp/aselsanos-s374-qemu.IxLqst'dir. Bu ortak board-qemu regresyonudur; RPi5-only S374 production wrapper invocation kanıtı değildir ve runtime observations=0 alanını değiştirmez.",
"S1–S327 tarihsel Kod kataloğu tam 327/327 ayrı kapı olarak korunacaktır. S374 source-bound hedefi S1–S374 374/374 unique kapı, pre-S328 327/327, missing=none ve duplicate=0'dır.",
"S374 Code kaydı iki gerçek production kaynak katmanı gösterecektir: rust_el0_sync_handler içindeki exact normal CALL acquire→by-value handoff odağı ve task/scheduler.rs içindeki tam ipc_call_commit_and_park authority→publication→release→context-switch→rejoin→restore yaşam döngüsü.",
"Guard modülü, focused test ve exact Operations object'i production kaynaklardan ayrı code excerpt katmanlarıdır. Böylece test edilen uygulama kodu yalnız test komutuyla değil, dosya/satır/hash bağıyla da yayımlanır.",
"S328 öncesi S1–S327 kartları kendi Operations sequence, kalıcı source/test/command sözleşmesi ve exact source hash'iyle üretilir; S374 kodu eski karta geriye doğru yazılmaz ve S328–S374 kartlarıyla birleştirilmez.",
"Operations, Timeline/Yol Haritası, Phone OS konumlandırma ve Code S374'ü S373'ten ayrı kartta gösterir. S335–S400 toplu tamamlama veya tek birleşik kod kutusu üretilmez.",
"S374 sırasında güç, SD kart, Mac kart erişimi, UART capture, raw validation, archive veya promotion yapılmadı: physical/device operations=0 ve RUNBOOK_EXECUTED_IN_S374=NO.",
"S374 bazlı bağlayıcı olmayan planlama görünümü R1 S374–S404, R2 S429–S479, R3 S558+, kaba S534–S584 ve risk paylı merkez yaklaşık S559'dur. Bu projeksiyon yeni sıra veya ürün taahhüdü değildir.",
],
commands: [
"cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration -- --test-threads=1",
"run 20 exact S374/S373/S372/S304/S305/S303/S316/S290/S252/capability-mint/IPC source-host-runtime groups serially",
"run four fresh AArch64 dev/debug profile builds; run S238-S374 dependency list twice; run filtered and unfiltered serial workspace audits; make verify-qemu",
"npm run code:generate && npm test && npm run lint && npx tsc --noEmit && npm run build",
"npm run deploy",
"cache-busted curl + cmp for /code/, /operations/, /timeline/ and /yol-haritasi/",
],
terminalSessions: [
{
id: "g8l-s374-focused-source-contract",
title: "S374 focused normal EL0 IPC CALL writer membership",
commandLines: [
"cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration -- --test-threads=1",
],
outputLines: [
"initial result: compile RED; S374 module/source registration absent",
"first minimal result: ok; 1 passed; 0 failed",
"expanded compile result: RED; typed error recursion limit reached",
"expanded source result: 74 passed; 2 exact documentation literal assertions failed",
"final result: ok; S374 focused 1 group / 76 passed / 0 failed",
"shared S247 gate: 44 guarded readers + 47/69 guarded writers; 22 writers open",
"normal authority/message/reply-mint < IRQ < writer < CALL mutation < release/context switch/rejoin/restore < result/cleanup/error",
"direct production caller paths=1; runtime observations=0; provider authority=0",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s374-selected-regression",
title: "S374 selected normal-CALL/alias/receive/runtime regression",
commandLines: [
"run 20 exact S374/S373/S372/S304/S305/S303/S316/S290/S252/capability-mint/IPC groups serially",
],
outputLines: [
"initial historical result: S373 70/71; obsolete S374-open assertion RED",
"historical snapshot preserved; live-boundary assertion aligned to distinct integrated S374",
"final result: 20 groups / 392 passed / 0 failed",
"S374 76/76; S373 71/71; S304 15/15; S305 15/15; ipc_queue_source 18/18",
"normal CALL S374 and reply alias S375 remain distinct memberships",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s374-core-acceptance",
title: "S374 profiles, dependency, workspace and QEMU acceptance",
commandLines: [
"run four fresh AArch64 dev/debug profile builds",
"run S238-S374 dependency list twice and compare canonical summaries",
"run filtered and unfiltered serial workspace audits",
"make verify-qemu",
],
outputLines: [
"four fresh profiles 4/4 exit 0; log and ELF byte/hash measurements recorded separately",
"dependency 138 groups / 3113/3113 twice; canonical 22773-byte summaries are SHA-256 identical",
"filtered workspace 337 groups / 4978 PASS / 7 filtered; unfiltered frozen-S96 remains RED",
"QEMU W^X 31/31 + S130-S154 + S271 + IPC 20/20 + SEC5 PASS; not an S374 runtime observation",
"physical/device operations=0; RUNBOOK_EXECUTED_IN_S374=NO",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s374-production-publication",
title: "S374 Operations/Timeline/Code production publication",
commandLines: [
"npm run code:generate && npm test && npm run lint && npx tsc --noEmit && npm run build",
"npm run deploy",
"cache-busted curl + cmp for /code/, /operations/, /timeline/ and /yol-haritasi/",
],
outputLines: [
"source registry S1-S374: 374/374 unique gates / 1061 exact excerpts; pre-S328 S1-S327: 327/327; missing none; duplicate 0",
"S374 publishes complete handler, exact normal-CALL focus, complete scheduler lifecycle, guard, test and Operations layers",
"website 656/656 PASS; lint PASS; TypeScript exit 0; static routes 24/24; export files=201",
"initial production/main deployment 54fca124; 116 uploaded + 84 existing = 200 assets",
"custom-domain /code/, /operations/, /timeline/ and /yol-haritasi/ HTTP 200 and byte-exact=true",
"live /code labels 374/374 unique; pre-S328 327/327; S1=1; S327=1; S328=1; S374=1; S375=0",
"immutable 54fca124 hostname curl exit 28 / HTTP 000; custom-domain PASS remains authoritative",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"TAM ÇIKTI kayıtları S374 focused 76/76, seçili 20 grup / 392 PASS, iki kez 138 grup / 3113 PASS, filtreli workspace 337 grup / 4978 PASS, filtresiz yalnız frozen-S96 RED, dört AArch64 profil 4/4, ortak QEMU kabulü ve 54fca124 ilk production yayınını ayrı oturumlar halinde taşır. Normal EL0 CALL kodu, S375 reply-alias koduyla veya S373 receive koduyla tek kutuda birleştirilmez; `/code` her sequence için ayrı source-bound kart üretir ve S1–S327 aralığını 327 ayrı kimlik olarak korur.",
limitations: [
"S374 yalnız normal non-reply SYS_IPC_CALL writer'ını kapatır; CALL-to-reply aliası S375 ve notification signal S376 ayrı açık kapılardır.",
"Toplam 69 writer'ın 47'si guarded, 22'si hâlâ açıktır; whole-scheduler exclusion tamamlanmış değildir.",
"Production provider authority=0, S244 admission publication yok ve pending S245 request yalnız inspect edilir.",
"Fresh AArch64 compile ve ortak QEMU kabulü RPi5-only S374 runtime invocation gözlemi değildir; supported-profile runtime observations=0'dır.",
"Filtresiz workspace yalnız frozen-S96 exact-source assertion'ında RED kalır; global workspace GREEN iddia edilmez.",
"Generic SMP cross-CPU scheduler ownership, contention liveness/soak ve fiziksel cihaz kabulü açıktır.",
"S374'te fiziksel runbook çalıştırılmadı: physical/device operations=0 · RUNBOOK_EXECUTED_IN_S374=NO.",
"R1/R2/R3 sıra görünümü bağlayıcı olmayan planlama projeksiyonudur; ürün teslim taahhüdü değildir.",
],
},snippet sha256: a1470bfa8bb7…file sha256: 9726dbf00f84…
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S374-EL0-IPC-Call-Writer-Guard-Integration-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9