ASELSANMicrokernel
S373 · SOURCE-BOUND GATE EVIDENCE

S373 · EL0 IPC receive 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 S373 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.

S373Production writer guardOperations id exactsource SHA exacttest target exact

operation: g8l-s373-el0-ipc-receive-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ğı L1135–L1152
kernel/src/arch/aarch64/exceptions.rs::rust_el0_sync_handler
Tam kapsayıcı Rust öğesi gösterilir; vurgulu blok yalnız S373 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: 69e991ebab2efile sha256: 6f3a4c8dbf40focus sha256: 8b0a727db7ef
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_recv_or_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(&current.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: fc5979057655file sha256: 838dd474448c
03 · Ortak exclusion üyeliği

S247 production writer guard

tam Rust öğesiL189–L201
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration.rs::acquire_s373_production_scheduler_writer_access

#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
pub fn acquire_s373_production_scheduler_writer_access(
) -> Result<G8lS373ProductionSchedulerWriterAccess, 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(G8lS373ProductionSchedulerWriterAccess { _access: access })
}
snippet sha256: 538da384c714file sha256: e7d31a12e770
04 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam Rust öğesiL383–L392
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration.rs::receive_branch_has_exactly_one_initial_s373_acquire

#[test]
fn receive_branch_has_exactly_one_initial_s373_acquire() {
    assert_eq!(
        receive_boundary()
            .matches("acquire_s373_production_scheduler_writer_access")
            .count(),
        1
    );
}
snippet sha256: f4189eec5bb5file sha256: 72a06a733987
05 · Kapı kimlik kaydı

Operations sıra, kimlik ve başlık bağı

tam Operations kaydıL9954–L10116
website/src/lib/operations.ts::g8l-s373-el0-ipc-receive-writer-guard-integration-partial
  {
    id: "g8l-s373-el0-ipc-receive-writer-guard-integration-partial",
    date: "2026-08-29",
    sequence: 373,
    status: "passed",
    umbrella_status: "partial",
    title: "S373 · EL0 IPC receive production writer guard integration",
    summary:
      "S373, rust_el0_sync_handler içindeki SYS_IPC_RECV dalının exact ipc_recv_or_park mutable scheduler sınırını S372 ve 44 production reader'ın kullandığı aynı statik S247 state word'e bağlar. Current-task identity ile live Endpoint CNode authority, generation ve ENDPOINT_RECV hakkı writer'dan önce doğrulanır. RPi5 bare-metal kesişiminde dedicated IRQ guard kurulur, gerçek per-CPU kimliğiyle yalnız CPU0 için S373 exclusive writer alınır ve exact tek mutable SCHEDULER aliası receive çağrısına taşınır. Immediate/error yolları owned result ile RAII release yapar. Parked yol waiter publication, blocked state ve next-task seçimini guard altında tamamlar; global writer token başka task çalışırken tutulmasın diye writer ve IRQ guard gerçek context_switch'ten hemen önce bırakılır, continuation resume olduğunda yeni IRQ ve S373 writer üyeliğiyle durable wake payload okunmadan önce yeniden katılır. Result split iki üyelik de kapandıktan sonra yapılır. Guarded writer 46/69, açık writer 23, provider authority 0, whole-scheduler exclusion false ve supported-profile runtime observation=0'dır. S374 EL0 IPC CALL sıradaki ayrı kapıdır; CALL-to-reply aliası S375 olarak ayrı kalır.",
    evidence: [
      "Focused S373 sözleşmesinin ilk minimal koşusu production modülü, exceptions.rs membership'i, scheduler handoff'u, kernel/simulation registration ve CPU1 coverage service henüz yokken compile RED verdi. Eksik kaynak bağı görünür kılındı; ürün wiring'i olmadan GREEN kabul edilmedi.",
      "İlk dar iskelet sonrası minimal focused test 1/1 PASS verdi. Kapsam 71 assertion'a genişletildiğinde ilk kapsamlı koşu 67/71 PASS ve 4 RED üretti; bu dört red ürün davranışı değil test selector/literal eşleşmeleriydi.",
      "Selector ve exact literal sınırları kaynak üyeliğini gevşetmeden düzeltildi. Final focused hedef 1 grup / 71/71 PASS verdi.",
      "S373 module constants, pending-request outcome ve production source katmanları birlikte 44 guarded reader + 46/69 guarded writer + 23 open writer envanterini sabitler.",
      "Exact production giriş sınırı arch/aarch64/exceptions.rs içindeki SYS_IPC_RECV match dalıdır. S373 başka syscall dalını, timeout helper'ını veya test-only callback'i production membership olarak saymaz.",
      "current_task_id önce owned scalar olarak alınır. ep_id ve endpoint_generation ExceptionContext'ten okunur; current task CNode'unda exact Endpoint capability, matching generation ve ENDPOINT_RECV hakkı writer edinilmeden doğrulanır.",
      "Invalid capability, stale generation veya eksik receive hakkı terminal InvalidCapability yayımlar ve S373 writer almadan döner. Preflight red yolları global gate state'ini değiştirmez.",
      "Dedicated IrqGuard exact target_arch=aarch64, target_os=none, feature=board-rpi5 cfg kesişiminde S373 writer acquisition'dan önce kurulur. Local interrupt re-entry mutable scheduler alias kurulmadan kapanır.",
      "Production wrapper try_current_cpu_id ile gerçek per-CPU kimliğini türetir; caller-supplied production CPU parametresi yoktur. CPU0 dışındaki kimlikler 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.",
      "SYS_IPC_RECV dalında exact bir acquire_s373_production_scheduler_writer_access occurrence'ı ve exact bir mutable SCHEDULER aliası vardır. RPi5 production çağrısı s373_irq_guard ve s373_writer_access değerlerini ipc_recv_or_park içine by-value taşır.",
      "RPi5 dışı tarihsel çağrı exact scheduler.ipc_recv_or_park(ctx, user_sp, ep_id, endpoint_generation) dört argümanlı biçimini korur. Bu ayrım S303 tarihsel source sözleşmesini bozmaz.",
      "ipc_recv_or_park immediate delivery, validation error ve registration failure yollarında guard değerlerini başka task'a taşımaz; fonksiyon dönüşündeki normal RAII release owned receive sonucu dışarı çıkmadan gerçekleşir.",
      "Registered receive yolunda endpoint waiter publication, current task state=Blocked, ready-queue seçimi, next task Running publication ve current task değişimi writer üyeliği canlıyken tamamlanır.",
      "switch_after_ipc_park_with_membership_handoff helper'ı scheduler mutation'dan sonra ve machine context_switch'ten hemen önce before_switch callback'ini exact-once çağırır. S373 callback'i önce writer'ı, ardından IRQ guard'ı bırakır.",
      "Parked branch exact drop(s373_writer_access) ardından drop(s373_irq_guard) çalıştırır; resume branch exact acquire_s373_production_scheduler_writer_access ile yeniden katılır ve restore_current_ipc_context(ctx) sonrasında resume writer/IRQ değerlerini bırakır.",
      "Writer token context_switch boyunca canlı tutulmaz. Böylece parked task global S247 exclusive membership'i pinleyip yeni çalışan task'ın guarded scheduler erişimlerini kilitlemez.",
      "Parked continuation yeniden schedule edilip context_switch çağrısından döndüğünde after_resume callback'i önce yeni IrqGuard kurar, sonra aynı S373 production wrapper üzerinden yeni CPU0-only exclusive writer alır.",
      "restore_current_ipc_context(ctx) durable wake payload ve saved IPC sonucu current task'tan okumadan önce resume üyeliği aktiftir. Restore bittikten sonra resume writer ve IRQ guard explicit sırayla bırakılır.",
      "Immediate ve parked dönüşlerin ortak result split'i S373 writer/IRQ membership'leri kapandıktan sonra çalışır. Envelope register publication, reply-cap register publication ve terminal error publication S373 mutable scheduler aliasına katılmaz.",
      "Generic switch_after_ipc_park çağrıları no-op before/after callbacks kullanan wrapper üzerinden tarihsel davranışını korur. S373'e özgü release/rejoin yalnız receive parked path'ine verilir.",
      "S372 ordinary reply membership'i ayrı kalır ve 45/69 tarihsel snapshot'ı değişmez. S372 focused testi compatibility güncellemesi sonrasında 55/55 PASS verdi.",
      "Tarihsel S303 EL0 IPC receive authority audit'i exact non-RPi5 dört argümanlı forward ve preflight sırasını koruyarak 15/15 PASS verdi.",
      "ipc_queue_source context-switch extraction'ı yeni generic handoff helper'ının gerçek context-switch body’sini doğrulayacak biçimde hizalandı ve 18/18 PASS verdi.",
      "S374 SYS_IPC_CALL normal commit ayrı açık writer'dır. S375 CALL-to-reply aliası da ayrı mutation sınırı olarak kalır; S373 receive üyeliği ikisini kapsadığını iddia etmez.",
      "Host-testable execute_s373_guarded_el0_ipc_receive_commit yalnız CPU0 callback'ini nonzero token ile exact-once çalıştırır ve owned output receipt'i taşır.",
      "Non-CPU0, active reader veya active writer yolları callback başlamadan fail-closed olur. Live writer yeni reader'ı, live reader writer'ı aynı S247 state word üzerinde engeller.",
      "Host callback success ve error yolları membership'i exact-once bırakır. Callback error sonrasında active token kalmaz ve gate yeni writer tarafından tekrar edinilebilir.",
      "S373 preflight S372'nin 44 reader / 45 guarded writer / 24 open snapshot'ını exact doğrular; doğru zincir 46/69 guarded ve 23 open üretir. Drift ayrı InventoryDrift error'ıdır.",
      "Pending S245 request yalnız non-consuming view ile incelenir. Request id korunur, take edilmez, S244 admission yayınlanmaz ve provider authority oluşturulmaz.",
      "CPU1 coverage service S372 service'inden sonra ve tarihsel S242 consumer'dan önce bağlıdır. Service S247 writer edinmez, SYS_IPC_RECV çalıştırmaz ve runtime observation üretmez.",
      "Direct production caller envanteri exact SYS_IPC_RECV branch'i için 1 path'tir. Bu statik source wiring sayısıdır; supported-profile S373 invocation veya cihaz gözlemi değildir.",
      "Focused 71-test matrisinde sabit envanter, idle/pending/drift preflight, request non-consumption, CPU0 admission, non-CPU0 fail-closed, reader↔writer karşılıklı exclusion, exact-once callback, success/error release ve S372→S373 token monotonluğu ayrı assertion'lardır.",
      "Focused source assertions kernel main ve simulation module registration'ını, CPU1 service sırasını, exact board-rpi5 cfg kesişimini, gerçek per-CPU identity türetmesini ve shared S247 static gate kullanımını ayrı ayrı sabitler.",
      "Receive-branch assertions authority/generation/right preflight < IRQ < writer < tek mutable alias < production forward kaynak sırasını; scheduler assertions mutation < writer/IRQ release < context switch < IRQ/writer rejoin < durable restore < release sırasını bağımsız indeks karşılaştırmalarıyla doğrular.",
      "Seçili regresyon 18 ayrı grup / 473/473 PASS verdi: S373, S372, S371, S370, S369, S367, S316, S305–S302, S290, S252, ipc_queue_source ve beş runtime-OOM/EL0 IPC transport-return grubu seri çalıştı.",
      "Seçili regresyon artifact dizini /tmp/aselsanos-s373-selected.omVFeg'dir; hiçbir focused veya tarihsel grup filtrelenmedi.",
      "S238–S373 dependency matrisi S240'ın iki distinct grubuyla 137 gruptur; iki bağımsız seri koşunun her biri 3037/3037 PASS verdi.",
      "Dependency raw özetleri 33167 B olup 1085c024b55ee6783c0f09c8d45dac0dd22feb8908767352e05fe911818f80b9 ve 174864276107202fe7cbe073645b6858cef24b8e29f76fd9d384b2ee08b3b8bc SHA-256 ile timing alanlarında ayrıştı.",
      "Süre-normalize dependency özetleri 33304 B / 765c22a8c8b07d74566125e53254198d81722351fed1b123d25036c358789f08 SHA-256 ile byte-eşittir. Artifact /tmp/aselsanos-s373-dependency.tRqyW9'dur.",
      "Exact yedi frozen G8h assertion dışındaki seri workspace 336 grup / 4901 PASS / 0 fail / 7 filtered verdi. Filtered log 72277 B / 8bce4a7e12167405fba8a109215ce25635053c4298096b2d83c4aa8f5933af45 ve summary 31802 B / e131f7d82c34d31ab10bfb44b1d47fe61fd7e3bcbb48be635df5e2f2310ebdb4 olarak ölçüldü.",
      "Filtresiz workspace exit 101 ile yalnız frozen S96 wiring_does_not_mutate_timer_gic_boot_or_expand_runtime_scope source-identity reddinde durdu: 289 grup / 4646 PASS / 1 fail. Log 67499 B / d4fdd202555f94abedfeab80775bd787cf74eaab6dd7f6af89f3b777907e4827, summary 27383 B / 26e8a315946aac08c7beec7c09c05a74b9f6cfc21d0a07ed78c1383b7a1db152'dir. Global workspace GREEN iddia edilmez.",
      "Workspace artifact dizini /tmp/aselsanos-s373-workspace.wah781'dir. Dışlama listesi isimle sabittir; S373 testi veya yeni production assertion'ı filtrelenmez.",
      "Fresh izole dev/debug AArch64 profilleri 4/4 exit 0 verdi. board-qemu logu 111450 B / 16971d45885ee820dbbc89356bfbb8d53ff159c2533ef458629aeb4506ac64a9, ELF 12622376 B / 9d884962892f9aa9ff7cd9b934aa82b377c9255ed2c3a373c5ec268c28dcc123'tür.",
      "board-rpi4 logu 150191 B / 85d7b4796e1235ce9d39f145052f3ea0a6efc26e96c2a27ce0bcf011c28211ed, ELF 7747416 B / 27d1f1bb63e0efdfc9316cf8f6f1457d84d6b7ea8cb9d8e1e2da015913711acd'dir.",
      "board-rpi5 logu 611811 B / c3788feafd68339dd9bb5a9eebe3941c87b1d55633d379d0e48fc9def76053e6, ELF 14434856 B / 26673442998e81d7e0d5875601d974947105a8aee2224ceb6195b8f9a3ce5492'dir.",
      "board-rpi5+smp logu 611753 B / 42dbabaded0f8e00b638fdf97c1d80a7a59bfe91380b8ec608208996435a8793, ELF 14431424 B / 6116d835df3752e1a5a291388fef81595ca8f186910f88df35b7e1dd591f9ab4'tür. Dört profil artifact'i /tmp/aselsanos-s373-profiles.iwU3An altındadır; zero-warning iddiası yoktur.",
      "make verify-qemu exit 0 verdi. 116354 B log / 85e5aff8a31065cfc28c8c5240d2ab50f99def97c2afd67c9483296922f110c8 SHA-256 ile strict W^X 31/31, S130–S154+S271, RuntimePmm, EL0x4096, IPC 20/20 ve scheduler SEC5 PASS'tir.",
      "QEMU artifact dizini /tmp/aselsanos-s373-qemu.ocdyhH'dir. Bu board-qemu ortak regresyonudur; RPi5-only S373 runtime invocation kanıtı değildir ve runtime observations=0 alanını değiştirmez.",
      "S1–S327 tarihsel Kod kataloğu 327/327 ayrı kimlik olarak korunur. S373 source-bound hedefi S1–S373 373/373 ayrı kapı, pre-S328 327/327, missing=none ve duplicate=0'dır.",
      "S373 Code kaydı iki gerçek production kaynağı gösterecektir: rust_el0_sync_handler içindeki exact SYS_IPC_RECV acquire→by-value handoff odağı ve task/scheduler.rs içindeki tam ipc_recv_or_park release→context-switch→rejoin→restore yaşam döngüsü. Guard modülü, focused test ve Operations kimliği ayrı katmanlardır.",
      "S328 öncesindeki S1–S327 kapılar Operations sequence'i, kendi kalıcı source/test/command sözleşmesi ve exact source hash'iyle ayrı ayrı üretilir; sonraki gate kodu tarihsel karta geriye doğru yazılmaz.",
      "İlk publication source registry'si S1–S373 aralığında 373/373 ayrı kapı, 1056 exact excerpt, pre-S328 327/327, missing=none ve duplicate=0 üretti. JSON payload 7472660 B / 5a10026d7604c3ca057a9643fe05ad9b7d5688a573e76a1eb9225fd32c084320; registry content SHA-256 1938b0edecc2a40e5efae8037201bd8569de52e230d6573903147ee04a940624'tür.",
      "İlk website kabulü 653/653 PASS, lint PASS, TypeScript boş çıktılı exit 0 ve 24/24 static page verdi. Test 60394 B / feeced9e1f166e8812930e209b476b69d2b7ef50a0384876256488f56426bc59; lint 218 B / 79c084453e339ceb2efe76ed96d1d68be8ac51442957a7a048fd17dba3067ba2; TypeScript 0 B / e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ve build 1213 B / 04bb2b42acb7fbfd267a0f35745d8d7181da2c0708e304805b201dd0dc931e30 olarak ölçüldü.",
      "S373 production/main deployment b33418a7 ile https://b33418a7.aselsan-microkernel.pages.dev adresine 116 upload + 84 existing = 200 asset olarak tamamlandı; deploy log 1691 B / ac67bfe8e1ceb4d1e207e2f969028c8c64f356264e7a052d82409135874bbbf2'dir.",
      "İlk cache-busted custom-domain doğrulamasında /code/ 21608247 B / d2a6041f9ac8b41ff64325400916bd3c56f87716976102c7248b85d7d87f1a3a, /operations/ 12991959 B / d71c2a20aae5f2a09b2269ff2cce5faf867cf194cdd5a4f32962e60a34890556, /timeline/ 4999109 B / 6535eb004a95eb628211391cee479506e08aa1cca10ce6c55d33a1bb2bca70ba ve /yol-haritasi/ 4998857 B / ef6f0879f4f93d4e9e788dabe02361b096cabc628720c6001470b66fbe09f77d ile HTTP 200 ve yerel out'a raw byte-exact PASS verdi.",
      "/code/ Cache-Control public, max-age=0, must-revalidate, no-transform taşıdı. Canlı data-code-gate envanteri 373/373 unique, pre-S328 327/327, S1=1, S327=1, S328=1, S373=1, S374=0 ve duplicate=0'dır; rust_el0_sync_handler, ipc_recv_or_park, switch_after_ipc_park_with_membership_handoff ve restore_current_ipc_context source marker'ları vardır.",
      "İlk publication artifact dizini /tmp/aselsanos-s373-web-initial.UwGDfV'dir. Immutable b33418a7 hostname probe'u curl exit 28 / HTTP 000 verdi; custom-domain PASS bunun yerine geçirilmez. Sonraki evidence-sync registry hash'i self-reference oluşturmamak için ilk snapshot'tan ayrı tutulacaktır.",
      "Operations, Timeline/Yol Haritası, Phone OS konumlandırma ve Code S373'ü S372'den ayrı kartta gösterir. S335–S400 toplu tamamlama etiketi veya tek birleşik kod kutusu üretilmez.",
      "S373 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_S373=NO.",
      "S373 bazlı bağlayıcı olmayan planlama görünümü R1 S373–S403, R2 S428–S478, R3 S557+, kaba S533–S583 ve risk paylı merkez yaklaşık S558'dir. 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_s373_el0_ipc_receive_writer_guard_integration -- --test-threads=1",
      "run 18 exact S373/S372/S371/S370/S369/S367/S316/S302-S305/S290/S252/IPC source and runtime groups serially",
      "run four fresh AArch64 dev/debug profile builds; run S238-S373 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-s373-focused-source-contract",
        title: "S373 focused EL0 IPC receive writer membership",
        commandLines: [
          "cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration -- --test-threads=1",
        ],
        outputLines: [
          "initial result: compile RED; S373 module/source registration absent",
          "first minimal result: ok; 1 passed; 0 failed",
          "expanded initial result: 67 passed; 4 test selector/literal assertions failed",
          "final result: ok; S373 focused 1 group / 71 passed / 0 failed",
          "shared S247 gate: 44 guarded readers + 46/69 guarded writers; 23 writers open",
          "authority/generation/right preflight < IRQ < writer < receive mutation < writer/IRQ handoff < context switch < IRQ/writer rejoin < restore < release < result split",
          "direct production caller paths=1; runtime observations=0; provider authority=0",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
      {
        id: "g8l-s373-selected-regression",
        title: "S373 selected receive/handoff/source regression",
        commandLines: [
          "run 18 exact S373/S372/S371/S370/S369/S367/S316/S302-S305/S290/S252/IPC groups serially",
        ],
        outputLines: [
          "final result: 18 groups / 473 passed / 0 failed",
          "S373 71/71; S372 55/55; S303 15/15; ipc_queue_source 18/18",
          "historical non-RPi5 four-argument receive forward preserved",
          "S374 normal CALL and S375 CALL-to-reply alias remain separate",
        ],
        exitCode: 0,
        outputMode: "selected",
      },
      {
        id: "g8l-s373-core-acceptance",
        title: "S373 four-profile, dependency, workspace and QEMU acceptance",
        commandLines: [
          "run four fresh AArch64 dev/debug profile builds",
          "run S238-S373 dependency list twice and normalize timing fields",
          "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 137 groups / 3037/3037 twice; normalized 33304-byte summaries are SHA-256 identical",
          "filtered workspace 336 groups / 4901 PASS / 7 filtered; unfiltered frozen-S96 remains RED",
          "QEMU W^X 31/31 + S130-S154 + S271 + IPC 20/20 + SEC5 PASS; not an S373 runtime observation",
          "physical/device operations=0; RUNBOOK_EXECUTED_IN_S373=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
      {
        id: "g8l-s373-production-publication",
        title: "S373 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-S373: 373/373 gates; 1056 exact excerpts; pre-S328 327/327; missing none; duplicate 0",
          "website tests 653/653 PASS; lint PASS; TypeScript empty-output PASS; static pages 24/24",
          "deployment b33418a7; 116 uploaded + 84 existing = 200 assets",
          "four custom-domain routes HTTP 200 and byte-exact with local out; /code no-transform",
          "live code labels 373/373 unique; pre-S328 327/327; S373=1; S374=0; duplicate=0",
          "S373 handler and scheduler handoff/rejoin source markers present",
          "immutable b33418a7 hostname curl exit 28 / HTTP 000; not substituted by custom-domain PASS",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "Terminal blokları kapıya göre ayrıdır: focused membership, seçili regresyon, çekirdek kabul ve canlı publication tek kutuda birleştirilmez. İlk production publication tamamlandı; evidence-sync ölçümü ayrı tutulur.",
    limitations: [
      "S373 yalnız SYS_IPC_RECV ipc_recv_or_park mutable scheduler yaşam döngüsünü kapatır; S374 normal CALL ve S375 CALL-to-reply aliası açık ayrı kapılardır.",
      "Production provider authority=0, whole-scheduler exclusion=false, S245 request take=false ve S244 admission publication=false olarak kalır.",
      "Bir direct source path runtime invocation değildir; supported-profile S373 observation=0 ve physical/device operations=0'dır.",
      "Filtresiz global workspace frozen S96 source-identity assertion'ı nedeniyle RED'dir; Generic SMP, liveness/soak ve fiziksel kabul açık kalır.",
    ],
  },
snippet sha256: ee1db3ebd5f8file sha256: 9726dbf00f84
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S373-EL0-IPC-Receive-Writer-Guard-Integration-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9