S379 · SOURCE-BOUND GATE EVIDENCE
S379 · EL0 IPC call-timeout 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 S379 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S379Production writer guardOperations id exactsource SHA exacttest target exact
operation: g8l-s379-el0-ipc-call-timeout-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 öğesiL293–L408kapı odağı L362–L385
kernel/src/arch/aarch64/exceptions.rs::handle_ipc_call_timeout
Tam kapsayıcı Rust öğesi gösterilir; vurgulu blok yalnız S379 exact production writer üyeliği sınırıdır. Komşu kod, guard kapsamı iddiası değildir.
/// S144 opt-in deadline-bearing CALL. Keeping this separate from the frozen
/// SYS_IPC_CALL arm ensures legacy callers never expose an unspecified x6 as
/// timeout policy.
fn handle_ipc_call_timeout(ctx: &mut ExceptionContext, user_sp: u64) {
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 timeout_ticks = ctx.gpr[6];
let now_tick = TICKS.load(Ordering::Acquire);
if crate::ipc_wait::TickDeadline::try_after(now_tick, timeout_ticks).is_err() {
set_ipc_error(ctx, IpcError::InvalidDeadline);
return;
}
let current_id = crate::task::current_task_id().unwrap_or(0);
kprintln!(
"[K2-S144] SYS_IPC_CALL_TIMEOUT task={} target={} timeout_ticks={} now={}",
current_id,
target_id,
timeout_ticks,
now_tick,
);
let target_is_normal = crate::ui::capability::ENDPOINT_REGISTRY
.lock()
.iter()
.any(|endpoint| endpoint.id == target_id && !endpoint.is_reply_cap);
if !target_is_normal {
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
}
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
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_SEND)
});
let Some(endpoint_cap) = endpoint_cap else {
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
};
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-S144] timeout 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 s379_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s379_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 EL0 IPC call-timeout 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_timeout_commit_and_park(
ctx,
user_sp,
target_id,
endpoint_cap.generation,
reply_cap.id,
reply_cap.generation,
msg,
now_tick,
timeout_ticks,
s379_irq_guard,
s379_writer_access,
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let commit = scheduler.ipc_call_timeout_commit_and_park(
ctx,
user_sp,
target_id,
endpoint_cap.generation,
reply_cap.id,
reply_cap.generation,
msg,
now_tick,
timeout_ticks,
);
commit
};
if let Err(error) = commit {
let _ = crate::ui::capability::discard_unpublished_reply_endpoint(
reply_cap.id,
Some(current_id),
);
set_ipc_error(ctx, error);
}
}snippet sha256: ebfed35940f7…file sha256: 6f3a4c8dbf40…focus sha256: 533f21fe7705…
02 · Devredilen production üyeliği
Context-switch bırakma ve resume yeniden-katılım kodu
tam Rust öğesiL446–L3588
kernel/src/task/scheduler.rs::ipc_call_timeout_commit_and_park
impl Scheduler {
pub const fn new() -> Self {
Self {
ready_queue: BinaryHeap::new(),
current_task: None,
retired_task: None,
deferred_current_runtime_oom: None,
ticks_until_preempt: 0,
min_vruntime: 0,
ipc_blocked_tasks: spin::Mutex::new(Vec::new()),
}
}
/// Reap the task retired by an earlier context switch.
///
/// User roots remain paired with their non-zero ASID lease until this
/// later-stack reaper drops the complete page-table owner. The ASID is
/// returned only after those resources are gone.
fn reap_retired_task(&mut self) {
let Some(mut task) = self.retired_task.take() else {
return;
};
let deferred_match = self
.deferred_current_runtime_oom
.as_ref()
.filter(|carrier| carrier.witness.is_none())
.is_some_and(|carrier| {
task.id == carrier.binding.task_id
&& task.runtime_allocation_domain() == Some(carrier.binding.domain)
});
if deferred_match {
let mut carrier = self
.deferred_current_runtime_oom
.take()
.expect("deferred current OOM carrier disappeared");
let witness = reap_detached_runtime_oom_task(
task,
carrier.binding.domain,
carrier.ipc_lifecycle_closed,
);
carrier.witness = Some(witness);
self.deferred_current_runtime_oom = Some(carrier);
return;
}
if task.is_user && (task.asid == 0 || !task.address_space_quiesced) {
crate::kprintln!(
"[K1-LIFECYCLE] quarantine task #{} '{}' without complete address-space quiescence (asid={}); resources intentionally retained",
task.id,
task.name,
task.asid
);
core::mem::forget(task);
return;
}
crate::kprintln!(
"[K1-LIFECYCLE] reaping task #{} '{}' on a later task stack",
task.id,
task.name
);
unsafe {
task.release_owned_page_table_root();
}
task.owned_user_page_tables.take();
match crate::mm::address_space::reclaim_owned_user_frames(
&mut task.owned_user_frames,
&mut task.runtime_user_frames,
) {
Ok(Some(report)) if report.returned_to_baseline() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} BASELINE=PASS",
task.id,
report.released_frames,
report.baseline_free_frames,
report.observed_free_frames,
report.baseline_active_allocations,
report.observed_active_allocations,
),
Ok(Some(report)) if report.exactly_reconciled() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} RECONCILED=PASS BASELINE=CONCURRENT",
task.id,
report.released_frames,
report.reclaim_started_free_frames,
report.observed_free_frames,
report.reclaim_started_active_allocations,
report.observed_active_allocations,
),
Ok(Some(_)) => unreachable!("RuntimePmm reclaimer returned an unaudited delta"),
Ok(None) => {}
Err(error) => {
crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} BASELINE=FAIL error={:?}; ASID and remaining resources quarantined",
task.id,
error
);
core::mem::forget(task);
return;
}
}
// Root/intermediate storage is unreachable and the prior TLBI is
// complete. Only now may a fresh address space acquire this ASID.
let retired_asid = task.asid;
if retired_asid != 0 {
if let Err(error) = crate::mm::address_space::free_asid(retired_asid) {
crate::kprintln!(
"[K1-ASID] final reaper release failed asid={} error={:?}; identifier remains quarantined",
retired_asid,
error
);
core::mem::forget(task);
return;
}
task.asid = 0;
}
// `Task` drop now releases its owned kernel/user stack allocations.
// Raw ELF user stacks have no `OwnedStackAllocation` and are not
// reconstructed as a Box.
drop(task);
}
/// CFS tarzı weight hesabı (düşük priority = daha yüksek weight = daha yavaş vruntime artışı)
fn weight_of(priority: u8) -> u64 {
match priority {
0 => 1024, // En yüksek öncelik
1 => 820,
2 => 655,
3 => 524,
4 => 419,
5 => 335,
6 => 268,
7 => 215,
_ => 128,
}
}
/// Scheduler içinde vruntime güncelleme (CFS-lite)
fn update_vruntime(&mut self, task: &mut Task, delta_exec: u64) {
let weight = Self::weight_of(task.priority);
let delta = delta_exec * 1024 / weight.max(1);
task.vruntime = task.vruntime.wrapping_add(delta);
// min_vruntime'ı güncelle (CFS'te scheduler bunu takip eder)
if task.vruntime < self.min_vruntime {
self.min_vruntime = task.vruntime;
}
}
/// Builds a complete task without exposing it through a scheduler
/// container. S354 and S355 use this with an owned S255 vruntime snapshot
/// so all fallible user/kernel preparation finishes before the respective
/// publication writer. The old unguarded `Scheduler::spawn` publication
/// path no longer exists.
fn build_unpublished_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
is_user: bool,
user_entry: Option<extern "C" fn() -> !>,
initial_user_arg0: u64,
initial_vruntime: u64,
) -> Result<(Task, JoinHandle), TaskSpawnError> {
const GUARD_SIZE: usize = 0x1000;
let validated_user_entry = if is_user {
Some(user_entry.ok_or(TaskSpawnError::MissingUserEntry)?)
} else {
None
};
let total_user_stack_alloc = if is_user {
Some(
stack_size
.checked_add(GUARD_SIZE)
.ok_or(TaskSpawnError::StackSizeOverflow)?,
)
} else {
None
};
// Task ids are authority principals. Reserve one before ASIDs, page
// tables, or stacks so permanent id exhaustion has no side effects.
// A later spawn failure may consume an id, but ids are never reused.
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| TaskSpawnError::TaskIdExhausted)?;
// === M8.2+M7.4 fix (18-agent audit): page_table_root policy ===
// Kernel tasks share the live ROOT_PAGE_TABLE and never own or switch
// to a private root. The recorded value must nevertheless be its real
// address: framebuffer grants query this metadata and mutate the
// returned table. The old 0x4020_0000 placeholder points into the heap
// on current links and corrupts/walks allocator metadata as PTEs.
let kernel_root = unsafe {
let root = crate::arch::aarch64::mmu::get_kernel_root_table() as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let (new_root, new_asid) = if is_user {
// Reserve the scarce identifier before any root or stack Box.
// Exhaustion is returned to the user-task caller without mutation.
let a = crate::mm::address_space::try_allocate_asid().map_err(TaskSpawnError::Asid)?;
let r = crate::mm::allocate_page_table_root();
(r, a)
} else {
(kernel_root, 0u16)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let finished = Arc::new(AtomicBool::new(false));
// M5.5 (Audit #18) — EL0 stack + guard yalnız user task için gerekir.
// Kernel task'e ikinci, hiç kullanılmayan bir stack ayırmak hem heap'i
// tüketiyor hem de sahipliği kaybolan gereksiz bir allocation yaratıyordu.
let user_stack_allocation = total_user_stack_alloc.map(OwnedStackAllocation::new);
let (user_stack_ptr, usable_stack_bottom) = if let Some(owner) = &user_stack_allocation {
let user_stack_ptr = owner.base();
let usable_stack_bottom = unsafe { user_stack_ptr.add(GUARD_SIZE) };
// Guard sayfasının üstüne canary yaz (ileride overflow kontrolü).
unsafe {
core::ptr::write_volatile(
user_stack_ptr.add(GUARD_SIZE - 8) as *mut u64,
0xDEAD_BEEF_C0FFEE00,
);
}
(user_stack_ptr, usable_stack_bottom)
} else {
(core::ptr::null_mut(), core::ptr::null_mut())
};
// Sadece user task için per-task root mapping kur.
if is_user {
unsafe {
let root_table = crate::arch::aarch64::mmu::get_page_table_from_root(new_root);
// Düşük RAM + heap bölgesi (kernel tarafı NORMAL — kernel kodu için exec).
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x4000_0000),
crate::mm::PhysAddr::new(0x4000_0000),
0x0800_0000,
crate::mm::paging::PageTableFlags::NORMAL,
);
// UART
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0900_0000),
crate::mm::PhysAddr::new(0x0900_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// GIC
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0800_0000),
crate::mm::PhysAddr::new(0x0800_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// M5.5+M7.4 fix: User stack USER_NORMAL override.
// Heap NORMAL kalır (kernel isolation), sadece user stack range'i
// USER_NORMAL ile EL0'a açılır. Guard page (alt 4K) MAP EDİLMEZ →
// translation fault overflow yakalar (Strategy A — Linux/seL4 patterni).
//
// KRİTİK: Box::into_raw u8 alignment'lı bir pointer döner (4K aligned değil).
// map_range_4k_to_root 4K page boundary bekler — unaligned adresleri
// atlar (page'leri map etmez) → user EL0 stack write'da perm fault.
// Bu yüzden adresi 4K'ya yuvarla (aşağı → start, yukarı → end).
let usable_stack_phys = usable_stack_bottom as u64;
let aligned_start = usable_stack_phys & !0xFFF; // round down
let aligned_end = (usable_stack_phys + stack_size as u64 + 0xFFF) & !0xFFF;
let aligned_size = aligned_end - aligned_start;
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(aligned_start),
crate::mm::PhysAddr::new(aligned_start),
aligned_size,
crate::mm::paging::PageTableFlags::USER_NORMAL,
);
crate::kprintln!(
"[M5.5+M7.4] user '{}' root=0x{:x} asid={} stack USER_NORMAL [0x{:x}..0x{:x}] (size=0x{:x}, requested phys=0x{:x})",
name, new_root.as_u64(), new_asid, aligned_start, aligned_end, aligned_size, usable_stack_phys
);
}
}
let mut task = Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: finished.clone(),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation,
user_stack_bottom: usable_stack_bottom,
user_stack_size: if is_user { stack_size } else { 0 },
// M5.5: yalnız user task'te guard sayfası ayrılır.
stack_guard_page: if is_user { Some(user_stack_ptr) } else { None },
// M4.3
is_user,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: {
let mut registers = [0; 31];
if is_user {
registers[0] = initial_user_arg0;
}
registers
},
// Kernel task = live shared kernel root/asid=0; user task = unique owned root + ASID.
page_table_root: new_root,
owns_page_table_root: is_user,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: new_asid,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: !is_user,
// M5: Her task kendi CNode'unu alır (boş başlar)
cnode: crate::ui::capability::CNode::new(),
};
// Kernel context'i her zaman trampoline veya normal entry'ye işaret eder
// User task'ler için ilk girişe özel kurulum kullanıyoruz
unsafe {
if is_user {
task.context = TaskContext::new_for_user_first_entry(stack_top);
} else {
task.context = TaskContext::new(kernel_entry, stack_top);
}
}
// User task ise initial EL0 frame'i hemen hazırla
if let Some(entry) = validated_user_entry {
// Stack top = usable_bottom + size (guard hariç)
task.user_sp = usable_stack_bottom as u64 + stack_size as u64; // stack top (büyüme aşağı)
task.saved_user_elr = entry as u64;
task.saved_user_spsr = 0; // EL0t
// GPR'ler sıfır kalır, sadece PC ve SP önemli
}
let handle = JoinHandle {
finished,
task_id: id,
};
Ok((task, handle))
}
/// Build a kernel-task skeleton without publishing it in any scheduler
/// container. The raw-ELF path consumes this value, installs the complete
/// user address-space state, and performs exactly one final ready-queue
/// push. No CPU can therefore observe a placeholder entry/root/ASID.
fn build_unpublished_kernel_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
initial_vruntime: u64,
) -> Result<Box<Task>, &'static str> {
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| "task id space exhausted")?;
let kernel_root = {
let root =
unsafe { crate::arch::aarch64::mmu::get_kernel_root_table() } as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let mut task = Box::new(Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: Arc::new(AtomicBool::new(false)),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation: None,
user_stack_bottom: core::ptr::null_mut(),
user_stack_size: 0,
stack_guard_page: None,
is_user: false,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: [0; 31],
page_table_root: kernel_root,
owns_page_table_root: false,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: 0,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: true,
cnode: crate::ui::capability::CNode::new(),
});
task.context = unsafe { TaskContext::new(kernel_entry, stack_top) };
Ok(task)
}
/// Mevcut task'i kuyruğa geri koyar ve en yüksek öncelikli (en düşük vruntime) task'i seçer.
///
/// NOT: Şu anda kullanılmıyor (`yield_now` aynı işi inline yapıyor).
/// İleride farklı bir API (örn. IRQ-driven preemption) lazım olursa bu
/// fonksiyon kullanılabilir.
#[allow(dead_code)]
pub unsafe fn schedule(&mut self) -> Option<(*mut TaskContext, *mut TaskContext)> {
if let Some(mut curr) = self.current_task.take() {
curr.state = TaskState::Ready;
if curr.time_slice == 0 {
curr.time_slice = curr.default_time_slice;
}
self.ready_queue.push(PriorityTask::new(curr));
}
// min_vruntime'ı güncelle (daha adil seçim için)
self.recalculate_min_vruntime();
if let Some(prio_next) = self.ready_queue.pop() {
let mut next = prio_next.task;
next.state = TaskState::Running;
let new_ctx = &mut next.context as *mut TaskContext;
self.current_task = Some(next);
self.ticks_until_preempt = self.current_task.as_ref().unwrap().time_slice;
Some((core::ptr::null_mut(), new_ctx))
} else {
None
}
}
/// Timer her tick'te çağrılır.
/// Time slice bitince preemption tetikler + vruntime ve aging uygular.
pub unsafe fn tick(&mut self) -> bool {
static TICK_COUNTER: AtomicU64 = AtomicU64::new(0);
let n = TICK_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
// Aging + min_vruntime + normalizasyon (her 50 tick'te bir)
if n % 50 == 0 {
self.apply_aging();
self.recalculate_min_vruntime();
self.normalize_vruntime();
}
if let Some(curr) = &mut self.current_task {
if curr.time_slice > 0 {
curr.time_slice -= 1;
}
// Vruntime'ı da tick bazında hafifçe artır
let weight = Self::weight_of(curr.priority);
curr.vruntime = curr.vruntime.wrapping_add(1 * 1024 / weight.max(1));
if curr.vruntime < self.min_vruntime {
self.min_vruntime = curr.vruntime;
}
if curr.time_slice == 0 {
return true; // preemption gerekli
}
}
false
}
/// Aging mekanizması:
/// Uzun süredir bekleyen task'lerin vruntime'ını azaltarak önlerine geçmelerini sağlar.
/// Bu sayede düşük öncelikli task'ler bile zamanla CPU alabilir.
fn apply_aging(&mut self) {
let mut temp = Vec::new();
while let Some(mut ptask) = self.ready_queue.pop() {
// min_vruntime'tan çok geride kalanlara daha fazla bonus veriyoruz
let bonus = if ptask.task.vruntime + 50 < self.min_vruntime {
12
} else {
6
};
ptask.task.vruntime = ptask.task.vruntime.saturating_sub(bonus);
temp.push(ptask);
}
for p in temp {
self.ready_queue.push(p);
}
}
/// Mevcut task'in context pointer'ını döner (güvenli kullanım için)
pub fn current_context_ptr(&self) -> Option<*mut TaskContext> {
self.current_task
.as_ref()
.map(|t| &t.context as *const _ as *mut TaskContext)
}
/// Mevcut çalışan task'in ID'sini döndürür.
pub fn current_task_id(&self) -> Option<u64> {
self.current_task.as_ref().map(|t| t.id)
}
/// S211 producer-side observation of the real scheduler current task.
/// The target adapter holds a local IRQ guard while calling this method;
/// cross-CPU exclusion against legacy scheduler accesses remains open.
#[cfg(feature = "board-rpi5")]
pub(crate) fn observe_g8l_current_task(
&self,
) -> Option<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask,
>{
self.current_task.as_ref().map(|task| {
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
}
})
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn commit_g8l_current_task_owner(
&mut self,
runtime: &crate::g8l_runtime_contract::G8lRuntimeAuthority,
authority: &crate::g8l_target_dispatch_scheduler_owner::G8lSchedulerOwnerAuthority,
bridge: &crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_guarded_ack::G8lSchedulerMutationProductionGuardedAckBridge<'_>,
) -> Result<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
let observed = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
};
task.g8l_current_task_owner
.commit_from_guarded_bridge(runtime, authority, bridge, observed)
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn rollback_g8l_current_task_owner(
&mut self,
receipt: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
) -> Result<
(),
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
if task.id != receipt.task_id {
return Err(crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::TaskMismatch);
}
task.g8l_current_task_owner.rollback_exact(receipt)
}
fn record_runtime_oom_binding(
task: &Task,
location: RuntimeOomTaskLocation,
domain: crate::mm::AllocationDomain,
expected_frames: usize,
binding: &mut Option<RuntimeOomTaskBinding>,
) -> Result<(), RuntimeOomTaskExecutionError> {
if task.runtime_allocation_domain() != Some(domain) {
return Ok(());
}
if binding.is_some() {
return Err(RuntimeOomTaskExecutionError::AmbiguousDomainBinding);
}
let Some(ledger) = task.runtime_user_frames.as_ref() else {
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
};
let expected_state = match location {
RuntimeOomTaskLocation::Current => task.state == TaskState::Running,
RuntimeOomTaskLocation::Ready => task.state == TaskState::Ready,
RuntimeOomTaskLocation::Blocked => matches!(
task.state,
TaskState::Blocked
| TaskState::BlockedOnIpc { .. }
| TaskState::BlockedOnNotification { .. }
),
RuntimeOomTaskLocation::Retired => task.state == TaskState::Dead,
};
if !task.is_user
|| task.asid == 0
|| task.address_space_quiesced
|| task.owns_page_table_root
|| task.owned_user_page_tables.is_none()
|| ledger.frame_count() != expected_frames
|| task.owned_user_frames.len() != expected_frames
|| !ledger.physical_frames_match(&task.owned_user_frames)
|| !expected_state
{
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
}
*binding = Some(RuntimeOomTaskBinding {
task_id: task.id,
domain,
location,
asid: task.asid,
frame_count: expected_frames,
});
Ok(())
}
/// Allocation-free audit of every persistent scheduler container. A
/// domain must bind one strict RuntimePmm ELF task and the ticket's frame
/// count must equal that task's complete leaf ledger.
fn audit_runtime_oom_task_binding(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let expected_frames = usize::try_from(preflight.expected_reclaimable_frames)
.map_err(|_| RuntimeOomTaskExecutionError::InvalidTaskBinding)?;
let mut binding = None;
if let Some(task) = self.current_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Current,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
for priority_task in self.ready_queue.iter() {
Self::record_runtime_oom_binding(
&priority_task.task,
RuntimeOomTaskLocation::Ready,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
{
let blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Blocked,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
}
if let Some(task) = self.retired_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Retired,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
binding.ok_or(RuntimeOomTaskExecutionError::DomainNotBound)
}
fn preflight_runtime_oom_task(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let binding = self.audit_runtime_oom_task_binding(preflight)?;
match binding.location {
RuntimeOomTaskLocation::Current => {
Err(RuntimeOomTaskExecutionError::CurrentTaskRequiresDeferredExit)
}
RuntimeOomTaskLocation::Retired => {
Err(RuntimeOomTaskExecutionError::RetiredTaskQuarantined)
}
RuntimeOomTaskLocation::Ready | RuntimeOomTaskLocation::Blocked => Ok(binding),
}
}
/// Detach the exact preflighted non-current task without allocating. The
/// ready heap is rebuilt in-place from its existing Vec allocation.
fn take_runtime_oom_task(&mut self, binding: RuntimeOomTaskBinding) -> Option<Box<Task>> {
let mut ready = core::mem::take(&mut self.ready_queue).into_vec();
let ready_match = ready.iter().position(|priority_task| {
priority_task.task.id == binding.task_id
&& priority_task.task.runtime_allocation_domain() == Some(binding.domain)
});
let selected = ready_match.map(|index| ready.swap_remove(index).task);
self.ready_queue = BinaryHeap::from(ready);
if selected.is_some() {
return selected;
}
let mut blocked = self.ipc_blocked_tasks.lock();
let blocked_match = blocked.iter().position(|task| {
task.id == binding.task_id && task.runtime_allocation_domain() == Some(binding.domain)
});
blocked_match.map(|index| blocked.swap_remove(index))
}
pub(crate) fn capability_for_task(
&self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<crate::ui::capability::Capability> {
if let Some(capability) = self
.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ipc_blocked_tasks
.lock()
.iter()
.find(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ready_queue
.iter()
.find(|priority_task| priority_task.task.id == task_id)
.and_then(|priority_task| priority_task.task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
self.retired_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
}
pub(crate) fn notification_holder_count(
&self,
notification_id: crate::ui::capability::CapId,
) -> usize {
let holds = |task: &Task| {
task.cnode
.lookup_capability_by_id(notification_id)
.is_some_and(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
})
};
let mut count = usize::from(self.current_task.as_deref().is_some_and(holds));
count += self
.ready_queue
.iter()
.filter(|task| holds(&task.task))
.count();
count += self
.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| holds(task))
.count();
count += usize::from(self.retired_task.as_deref().is_some_and(holds));
count
}
fn notification_holder_for_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(notification_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(notification_id)
};
let notification_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != notification_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Notification
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| notification_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::NOTIFICATION_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_notification_holder_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::notification_holder_for_task(task, notification_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Generation-aware audit of every persistent notification holder. The
/// caller owns `IPC_TRANSACTION_LOCK`, so the matching purge observes the
/// same CNode graph.
pub(crate) fn preflight_notification_holder_purge(
&self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_notification_holder_task(
&task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_notification_holder_from_task(
task: &mut Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::notification_holder_for_task(task, notification_id, object_owner)
.expect("preflighted notification holder became malformed");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted notification holder exact revoke failed"
);
summary
.record(container)
.expect("notification holder count overflowed during commit");
}
/// Allocation-free exact purge paired with
/// `preflight_notification_holder_purge`.
pub(crate) fn purge_notification_holders_exact(
&mut self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_notification_holder_purge(notification_id, object_owner)
.expect("notification holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_notification_holder_from_task(
&mut task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"notification holder purge differed from exact preflight"
);
removed
}
fn endpoint_holder_for_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(endpoint_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(endpoint_id)
};
let endpoint_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != endpoint_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Endpoint
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| endpoint_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::ENDPOINT_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_endpoint_holder_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::endpoint_holder_for_task(task, endpoint_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Allocation-free, generation-aware audit of every scheduler-owned task
/// CNode that can outlive the current instruction. The caller holds the
/// global IPC transaction lock, so this snapshot remains stable through
/// the matching purge.
pub(crate) fn preflight_endpoint_holder_purge(
&self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_endpoint_holder_task(
&task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_endpoint_holder_from_task(
task: &mut Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::endpoint_holder_for_task(task, endpoint_id, object_owner)
.expect("preflighted endpoint holder became malformed inside one IPC transaction");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted endpoint holder exact revoke failed"
);
summary
.record(container)
.expect("preflighted endpoint holder count overflowed during commit");
}
/// Commit the exact holder snapshot without allocating. BinaryHeap is
/// converted into and rebuilt from its existing Vec allocation in place;
/// CNode changes cannot affect heap ordering.
pub(crate) fn purge_endpoint_holders_exact(
&mut self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_endpoint_holder_purge(endpoint_id, object_owner)
.expect("endpoint holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_endpoint_holder_from_task(
&mut task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"endpoint holder purge differed from its exact preflight"
);
removed
}
/// Belirli bir task'in page table root'unu döndürür (M4.4 büyük adım için).
pub fn get_task_page_table_root(&self, task_id: u64) -> Option<crate::mm::PhysAddr> {
// Basit lineer arama (demo için yeterli)
if let Some(ref curr) = self.current_task {
if curr.id == task_id {
return Some(curr.page_table_root);
}
}
for ptask in self.ready_queue.iter() {
if ptask.task.id == task_id {
return Some(ptask.task.page_table_root);
}
}
for task in self.ipc_blocked_tasks.lock().iter() {
if task.id == task_id {
return Some(task.page_table_root);
}
}
None
}
/// M5 — Belirli bir task'in CNode'una capability ekler (cross-task grant için kritik).
/// Hem current_task hem ready_queue içindeki task'leri tarar.
pub fn insert_cap_for_task(
&mut self,
task_id: u64,
cap: crate::ui::capability::Capability,
) -> Result<(usize, u64), &'static str> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
return curr.cnode.insert(cap);
}
}
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut result: Result<(usize, u64), &'static str> = Err("Task not found");
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
result = pt.task.cnode.insert(cap);
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
result
}
/// Private id-only CNode removal. Public callers must pass through the
/// global wrapper, which rejects endpoint identities in favor of typed
/// grant/object APIs.
fn revoke_cap_for_task(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
self.remove_cap_by_id(task_id, cap_id)
}
/// CNode seviyesinde id bazlı güçlü revoke (generation bump dahil)
fn remove_cap_by_id(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
if curr.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
}
// Reply owners normally wait outside the ready queue. Search this
// fixed authority location before the heap fallback so reply consume
// and endpoint teardown do not allocate a temporary queue at OOM.
let mut blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter_mut() {
if task.id == task_id && task.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
drop(blocked);
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut found: Option<usize> = None;
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
if pt.task.cnode.revoke_capability(cap_id).is_some() {
found = Some(0);
}
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
if found.is_some() {
return found;
}
None
}
/// Allocation-free removal of one complete CNode authority tuple. This
/// is used by typed endpoint/reply teardown where id-only revocation would
/// let a stale capability delete a newer generation.
pub(crate) fn revoke_cap_for_task_exact(
&mut self,
expected: &crate::ui::capability::Capability,
) -> bool {
let task_id = expected.owner;
if let Some(task) = self
.current_task
.as_deref_mut()
.filter(|task| task.id == task_id)
{
return task.cnode.revoke_capability_exact(expected).is_some();
}
{
let mut blocked = self.ipc_blocked_tasks.lock();
if let Some(task) = blocked.iter_mut().find(|task| task.id == task_id) {
return task.cnode.revoke_capability_exact(expected).is_some();
}
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
let removed = ready_tasks
.iter_mut()
.find(|task| task.task.id == task_id)
.map_or(false, |task| {
task.task.cnode.revoke_capability_exact(expected).is_some()
});
self.ready_queue = BinaryHeap::from(ready_tasks);
if removed {
return true;
}
self.retired_task
.as_deref_mut()
.filter(|task| task.id == task_id)
.map_or(false, |task| {
task.cnode.revoke_capability_exact(expected).is_some()
})
}
// =================================================================
// M6.2 — IPC Blocking / Waking (İskelet)
// =================================================================
fn save_current_ipc_context(&mut self, ctx: &ExceptionContext, user_sp: u64) {
if let Some(current) = &mut self.current_task {
current.is_user = true;
current.user_sp = user_sp;
current.saved_user_elr = ctx.elr_el1;
current.saved_user_spsr = ctx.spsr_el1;
current.saved_user_gprs[..30].copy_from_slice(&ctx.gpr);
current.saved_user_gprs[30] = ctx.lr;
}
}
/// Copy the wake result back into the still-live exception frame whose
/// kernel continuation was saved by `switch_after_ipc_park`.
fn restore_current_ipc_context(&self, ctx: &mut ExceptionContext) {
let current = self
.current_task
.as_ref()
.expect("IPC continuation resumed without its current task");
assert!(
current.is_user && current.state == TaskState::Running,
"IPC continuation resumed outside a running EL0 task"
);
ctx.gpr.copy_from_slice(¤t.saved_user_gprs[..30]);
ctx.lr = current.saved_user_gprs[30];
ctx.elr_el1 = current.saved_user_elr;
ctx.spsr_el1 = current.saved_user_spsr;
}
fn current_endpoint_authority_is_live(
&self,
task_id: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
required_right: crate::ui::capability::CapabilityRights,
) -> bool {
self.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(endpoint_id))
.map_or(false, |capability| {
capability.generation == expected_generation
&& capability.owner == task_id
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.rights.contains(required_right)
})
}
fn current_notification_authority_is_live(
&self,
task_id: u64,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
required_right: crate::ui::capability::CapabilityRights,
) -> bool {
self.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(notification_id))
.is_some_and(|capability| {
capability.generation == expected_generation
&& capability.owner == task_id
&& capability.kind == crate::ui::capability::CapabilityKind::Notification
&& capability.rights.contains(required_right)
})
}
fn write_ipc_delivery(
task: &mut Task,
message: crate::ui::capability::IpcMessage,
reply_cap_id: u64,
) {
task.saved_user_gprs[0] = crate::ipc::IpcError::Ok.as_u64();
task.saved_user_gprs[1] = message.label;
task.saved_user_gprs[2] = message.badge;
task.saved_user_gprs[3] = message.data[0];
task.saved_user_gprs[4] = message.data[1];
task.saved_user_gprs[5] = message.data[2];
task.saved_user_gprs[6] = message.data[3];
task.saved_user_gprs[7] = reply_cap_id;
}
/// A registered rendezvous receiver may be legacy (no deadline record) or
/// S145 timed. Any record for that task must be the exact receiver waiter;
/// another kind/generation is an authority-graph mismatch, not a hint to
/// deliver anyway.
fn exact_receive_deadline_for_waiter<const N: usize>(
deadlines: &crate::ipc_deadline::IpcCallDeadlineRegistry<N>,
receiver: crate::ipc_rendezvous::ReceiverWaiter,
endpoint_id: crate::ui::capability::CapId,
) -> Result<Option<crate::ipc_wait::WaitRecord>, crate::ipc::IpcError> {
let record = deadlines.task_snapshot(receiver.task_id());
if record.is_some_and(|record| {
record.kind().tag() != crate::ipc_wait::WaitKindTag::Receive
|| record.kind().object_id() != endpoint_id
|| record.kind().object_generation() != receiver.cap_generation()
|| record.kind().reply_cap_id().is_some()
}) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Ok(record)
}
fn retire_receive_deadline_after_delivery<const N: usize>(
deadlines: &mut crate::ipc_deadline::IpcCallDeadlineRegistry<N>,
record: Option<crate::ipc_wait::WaitRecord>,
) {
if let Some(record) = record {
deadlines
.complete_delivery_exact(record)
.expect("CALL delivered but exact RECV deadline did not retire");
}
}
fn switch_after_ipc_park_with_membership_handoff<BeforeSwitch, AfterResume>(
&mut self,
parked_context: *mut TaskContext,
before_switch: BeforeSwitch,
after_resume: AfterResume,
) where
BeforeSwitch: FnOnce(),
AfterResume: FnOnce(&mut Self),
{
let Some(priority_task) = self.ready_queue.pop() else {
panic!("transactional IPC parked the current task without a runnable successor");
};
let mut next = priority_task.task;
next.state = TaskState::Running;
let new_context = &mut next.context as *mut TaskContext;
let next_slice = next.default_time_slice;
// IPC blocking is a real scheduler transition, so it must install the
// next task's TTBR0/ASID just like yield_now does. The context saved by
// context_switch belongs to the parked Box<Task>; a stack-local dummy
// loses the live syscall continuation and can resume a stale frame.
unsafe {
prepare_task_for_context_switch(&next);
}
self.current_task = Some(next);
self.ticks_until_preempt = next_slice;
// A gate lease belongs to the task executing this continuation. It
// must not remain live while the selected task runs. S373 uses these
// callbacks to release immediately before the machine switch and to
// rejoin before reading the wake payload when this continuation later
// resumes. Other IPC paths retain the legacy no-op wrapper below.
before_switch();
unsafe {
crate::arch::aarch64::context_switch(parked_context, new_context);
}
after_resume(self);
}
fn switch_after_ipc_park(&mut self, parked_context: *mut TaskContext) {
self.switch_after_ipc_park_with_membership_handoff(parked_context, || {}, |_| {});
}
/// Atomically publishes a CALL and parks its caller under one lock order:
/// IPC transaction -> global endpoint table -> global blocked-task set.
/// The caller is in the reply wait set before a receiver becomes runnable.
pub fn ipc_call_commit_and_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s374_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s374_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration::G8lS374ProductionSchedulerWriterAccess,
) -> Result<(), crate::ipc::IpcError> {
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let caller_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_task = blocked.iter().find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
});
let receiver_authority_is_live = receiver_task
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.map_or(false, |capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let outcome = endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
.map_err(|error| match error {
CallError::QueueFull | CallError::ReplyTableFull => crate::ipc::IpcError::QueueFull,
_ => crate::ipc::IpcError::InvalidCapability,
})?;
match endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id)
{
Ok(FinishOutcome::Park) => {}
Ok(FinishOutcome::Ready(_)) | Ok(FinishOutcome::Cancelled) | Err(_) => {
// CALL, finish and scheduler publication are serialized by
// IPC_TRANSACTION_LOCK. No REPLY/revoke transition can run
// between `call` and this point, so any non-Park result means
// the model and scheduler have already diverged. Returning a
// recoverable syscall error here would strand a published
// request/reply record; fail-stop before making it worse.
panic!("CALL rendezvous changed inside one IPC transaction")
}
}
let mut caller = self
.current_task
.take()
.expect("preflighted CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
// Moving the Box through the blocked/ready/current containers does not
// move its Task pointee, so this exact SAVE target stays valid until
// the caller is scheduled and resumes this syscall continuation.
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("rendezvous receiver wait-token changed during CALL commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted rendezvous receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
caller_context,
|| {
drop(s374_writer_access);
drop(s374_irq_guard);
},
|scheduler| {
let s374_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s374_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s374_el0_ipc_call_writer_guard_integration::acquire_s374_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S374 resumed normal EL0 IPC-call scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s374_resume_writer_access);
drop(s374_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(caller_context);
self.restore_current_ipc_context(ctx);
}
Ok(())
}
/// S144 deadline-bearing CALL. This is deliberately a distinct ABI path:
/// legacy SYS_IPC_CALL never interprets an unspecified x6 register. The
/// fixed wait record is armed under `IPC_TRANSACTION_LOCK` before the
/// rendezvous request becomes visible, and every fallible allocation or
/// authority check precedes that publication point.
#[allow(clippy::too_many_arguments)]
pub fn ipc_call_timeout_commit_and_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
reply_generation: u64,
message: crate::ui::capability::IpcMessage,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s379_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s379_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration::G8lS379ProductionSchedulerWriterAccess,
) -> Result<(), crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let caller_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let reply_authority_is_live = self
.current_task
.as_ref()
.and_then(|task| task.cnode.lookup_capability_by_id(reply_cap_id))
.is_some_and(|capability| {
capability.id == reply_cap_id
&& capability.owner == caller_task
&& capability.generation == reply_generation
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.parent.is_none()
});
if !reply_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
// Global lock order for every deadline race is:
// IPC transaction -> deadline registry -> endpoint/provenance ->
// scheduler blocked set. Timer and REPLY follow the same order.
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_task = blocked.iter().find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
});
let receiver_authority_is_live = receiver_task
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.is_some_and(|capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let deadline_record = deadlines
.register_call(
caller_task,
target_endpoint,
expected_generation,
reply_cap_id,
reply_generation,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
let outcome =
match endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
{
Ok(outcome) => outcome,
Err(error) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed CALL publication must roll back its exact deadline");
return Err(match error {
CallError::QueueFull | CallError::ReplyTableFull => {
crate::ipc::IpcError::QueueFull
}
_ => crate::ipc::IpcError::InvalidCapability,
});
}
};
match endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id)
{
Ok(FinishOutcome::Park) => {}
Ok(FinishOutcome::Ready(_)) | Ok(FinishOutcome::Cancelled) | Err(_) => {
panic!("deadline CALL rendezvous changed inside one IPC transaction")
}
}
let mut caller = self
.current_task
.take()
.expect("preflighted deadline CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("deadline CALL receiver wait-token changed during commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted deadline receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
caller_context,
|| {
drop(s379_writer_access);
drop(s379_irq_guard);
},
|scheduler| {
let s379_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s379_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration::acquire_s379_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S379 resumed EL0 IPC call-timeout scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s379_resume_writer_access);
drop(s379_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(caller_context);
self.restore_current_ipc_context(ctx);
}
Ok(())
}
/// Publish one synchronous kernel-supervisor request through the same
/// bounded endpoint rendezvous used by EL0 CALL, then block the current
/// kernel task until an ordinary EL0 REPLY wakes it. This is not an
/// ambient kernel message queue: the current task must own a live SEND
/// capability, the reply object is one-shot, and every transition remains
/// under the production IPC transaction/registry/scheduler lock order.
pub fn ipc_kernel_call_and_wait(
&mut self,
target_endpoint: crate::ui::capability::CapId,
expected_generation: u64,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
) -> Result<crate::ui::capability::IpcMessage, crate::ipc::IpcError> {
use crate::ipc_rendezvous::{CallError, CallOutcome, FinishOutcome};
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let caller_task = self
.current_task
.as_ref()
.filter(|task| !task.is_user)
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
caller_task,
target_endpoint,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_SEND,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let reply_is_linked = endpoints.iter().any(|endpoint| {
endpoint.id == reply_cap_id
&& endpoint.is_reply_cap
&& endpoint.owner == caller_task
&& endpoint.reply_target == Some(target_endpoint)
});
if !reply_is_linked {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiting_receiver = endpoints[target_index].rendezvous.waiting_receiver();
if waiting_receiver.is_none() && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if waiting_receiver.is_some() {
self.ready_queue
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
}
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
let receiver_deadline = if let Some(receiver) = waiting_receiver {
let receiver_authority_is_live = blocked
.iter()
.find(|task| {
task.id == receiver.task_id()
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == target_endpoint
)
})
.and_then(|task| task.cnode.lookup_capability_by_id(target_endpoint))
.map_or(false, |capability| {
capability.generation == receiver.cap_generation()
&& capability.owner == receiver.task_id()
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::ENDPOINT_RECV)
});
if receiver.wait_token() != target_endpoint || !receiver_authority_is_live {
return Err(crate::ipc::IpcError::InvalidCapability);
}
Self::exact_receive_deadline_for_waiter(&deadlines, receiver, target_endpoint)?
} else {
None
};
let outcome = endpoints[target_index]
.rendezvous
.call(caller_task, reply_cap_id, message)
.map_err(|error| match error {
CallError::QueueFull | CallError::ReplyTableFull => crate::ipc::IpcError::QueueFull,
_ => crate::ipc::IpcError::InvalidCapability,
})?;
if !matches!(
endpoints[target_index]
.rendezvous
.finish_call_park(caller_task, reply_cap_id),
Ok(FinishOutcome::Park)
) {
panic!("kernel CALL rendezvous changed inside one IPC transaction");
}
let mut caller = self
.current_task
.take()
.expect("preflighted kernel CALL current task disappeared");
caller.state = TaskState::BlockedOnIpc {
endpoint_id: reply_cap_id,
is_call: true,
};
let caller_context = &mut caller.context as *mut TaskContext;
blocked.push(caller);
match outcome {
CallOutcome::Queued => {}
CallOutcome::Deliver {
receiver_task,
receiver_wait_token,
request,
..
} => {
if receiver_wait_token != target_endpoint {
panic!("kernel CALL receiver wait-token changed during commit");
}
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc { endpoint_id, .. }
if endpoint_id == target_endpoint
)
})
.expect("preflighted kernel CALL receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
Self::retire_receive_deadline_after_delivery(&mut deadlines, receiver_deadline);
Self::write_ipc_delivery(&mut receiver, request, reply_cap_id);
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
}
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
self.switch_after_ipc_park(caller_context);
let caller = self
.current_task
.as_ref()
.filter(|task| task.id == caller_task && !task.is_user)
.expect("kernel CALL continuation resumed under another task");
if caller.saved_user_gprs[0] != crate::ipc::IpcError::Ok.as_u64()
|| caller.saved_user_gprs[7] != 0
{
return Err(crate::ipc::IpcError::InvalidCapability);
}
Ok(crate::ui::capability::IpcMessage {
label: caller.saved_user_gprs[1],
badge: caller.saved_user_gprs[2],
data: [
caller.saved_user_gprs[3],
caller.saved_user_gprs[4],
caller.saved_user_gprs[5],
caller.saved_user_gprs[6],
],
})
}
/// Atomically performs RECV's FIFO-pop-or-waiter-registration decision.
/// `Some` is an immediate queued delivery; `None` means the task was
/// parked and a context switch was initiated.
pub fn ipc_recv_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s373_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s373_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration::G8lS373ProductionSchedulerWriterAccess,
) -> Result<Option<crate::ui::capability::IpcEnvelope>, crate::ipc::IpcError> {
use crate::ipc_rendezvous::{ReceiverWaiter, RecvOutcome};
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let receiver_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
receiver_task,
endpoint_id,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_RECV,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter =
ReceiverWaiter::new_with_generation(receiver_task, endpoint_id, expected_generation)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target = endpoints
.iter_mut()
.find(|endpoint| endpoint.id == endpoint_id && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let will_park = target.rendezvous.queued_len() == 0;
if will_park && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
if will_park {
self.save_current_ipc_context(ctx, user_sp);
}
// Only the waiter-registration branch needs blocked-set capacity.
// Reserving on an immediate FIFO delivery could spuriously reject a
// receive under allocator pressure even though it does not allocate.
let mut blocked = if will_park {
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
Some(blocked)
} else {
None
};
match target
.rendezvous
.recv(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?
{
RecvOutcome::Deliver {
request,
reply_token,
..
} => {
drop(blocked);
drop(endpoints);
drop(transaction);
Ok(Some(crate::ui::capability::IpcEnvelope {
message: request,
reply_cap_id: reply_token,
}))
}
RecvOutcome::Registered => {
let mut blocked_guard = blocked
.take()
.expect("RECV registered without preflighted blocked capacity");
let mut receiver = self
.current_task
.take()
.expect("preflighted RECV current task disappeared");
receiver.state = TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
};
let receiver_context = &mut receiver.context as *mut TaskContext;
blocked_guard.push(receiver);
drop(blocked_guard);
drop(blocked);
drop(endpoints);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
receiver_context,
|| {
drop(s373_writer_access);
drop(s373_irq_guard);
},
|scheduler| {
let s373_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s373_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s373_el0_ipc_receive_writer_guard_integration::acquire_s373_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S373 resumed EL0 IPC-receive scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s373_resume_writer_access);
drop(s373_resume_irq_guard);
},
);
#[cfg(not(all(
target_arch = "aarch64",
target_os = "none",
feature = "board-rpi5"
)))]
{
self.switch_after_ipc_park(receiver_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
}
}
/// S145 opt-in deadline-bearing RECV. The legacy syscall above remains
/// byte-for-byte policy compatible and never interprets x6. Only an empty
/// endpoint arms a wait record; a queued FIFO message is returned with
/// `IMMEDIATE_DELIVERY_DEADLINE=UNARMED`.
pub fn ipc_recv_timeout_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
endpoint_id: crate::ui::capability::CapId,
expected_generation: u64,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s378_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s378_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s378_el0_ipc_receive_timeout_writer_guard_integration::G8lS378ProductionSchedulerWriterAccess,
) -> Result<Option<crate::ui::capability::IpcEnvelope>, crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_rendezvous::{ReceiverWaiter, RecvOutcome};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let receiver_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_endpoint_authority_is_live(
receiver_task,
endpoint_id,
expected_generation,
crate::ui::capability::CapabilityRights::ENDPOINT_RECV,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter =
ReceiverWaiter::new_with_generation(receiver_task, endpoint_id, expected_generation)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
// Shared CALL/RECV arbitration order: transaction -> deadline table ->
// endpoint -> blocked scheduler set.
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let target_index = endpoints
.iter()
.position(|endpoint| endpoint.id == endpoint_id && !endpoint.is_reply_cap)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let will_park = endpoints[target_index].rendezvous.queued_len() == 0;
if will_park && self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
let mut blocked = if will_park {
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
Some(blocked)
} else {
None
};
if !will_park {
// S145 acceptance marker: IMMEDIATE_DELIVERY_DEADLINE=UNARMED.
let outcome = endpoints[target_index]
.rendezvous
.recv(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let RecvOutcome::Deliver {
request,
reply_token,
..
} = outcome
else {
panic!("non-empty endpoint registered a timed RECV waiter")
};
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
return Ok(Some(crate::ui::capability::IpcEnvelope {
message: request,
reply_cap_id: reply_token,
}));
}
let deadline_record = deadlines
.register_receive(
receiver_task,
endpoint_id,
expected_generation,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
let outcome = endpoints[target_index].rendezvous.recv(waiter);
match outcome {
Ok(RecvOutcome::Registered) => {}
Ok(RecvOutcome::Deliver { .. }) | Err(_) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed RECV publication must roll back its exact deadline");
return Err(crate::ipc::IpcError::InvalidCapability);
}
}
let mut blocked_guard = blocked
.take()
.expect("timed RECV registered without blocked capacity");
let mut receiver = self
.current_task
.take()
.expect("preflighted timed RECV current task disappeared");
receiver.state = TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
};
let receiver_context = &mut receiver.context as *mut TaskContext;
blocked_guard.push(receiver);
drop(blocked_guard);
drop(blocked);
drop(endpoints);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
receiver_context,
|| {
drop(s378_writer_access);
drop(s378_irq_guard);
},
|scheduler| {
let s378_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s378_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s378_el0_ipc_receive_timeout_writer_guard_integration::acquire_s378_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S378 resumed EL0 IPC receive-timeout scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s378_resume_writer_access);
drop(s378_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(receiver_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
/// S146 timed notification wait. Matching bits already pending are
/// consumed immediately without a deadline record. Otherwise the shared
/// deadline is published before the exact object waiter.
pub fn notification_wait_timeout_or_park(
&mut self,
ctx: &mut ExceptionContext,
user_sp: u64,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
mask: u64,
now_tick: u64,
timeout_ticks: u64,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s377_irq_guard: crate::arch::aarch64::IrqGuard,
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
s377_writer_access: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration::G8lS377ProductionSchedulerWriterAccess,
) -> Result<Option<u64>, crate::ipc::IpcError> {
use crate::ipc_deadline::IpcCallDeadlineRegistryError;
use crate::ipc_notification::{NotificationWaitOutcome, NotificationWaiter};
use crate::ipc_wait::WaitError;
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let waiter_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.current_notification_authority_is_live(
waiter_task,
notification_id,
expected_generation,
crate::ui::capability::CapabilityRights::NOTIFICATION_WAIT,
) {
return Err(crate::ipc::IpcError::InvalidCapability);
}
let waiter = NotificationWaiter::try_new(waiter_task, expected_generation, mask)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut notifications = crate::ui::capability::NOTIFICATION_REGISTRY.lock();
let target_index = notifications
.iter()
.position(|object| object.id() == notification_id)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let immediate = notifications[target_index].pending() & mask;
if immediate != 0 {
// S146 acceptance marker: IMMEDIATE_NOTIFICATION_DEADLINE=UNARMED.
let outcome = notifications[target_index]
.wait(waiter)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let NotificationWaitOutcome::Immediate { observed } = outcome else {
panic!("ready notification bits published a waiter")
};
drop(notifications);
drop(deadlines);
drop(transaction);
return Ok(Some(observed));
}
if self.ready_queue.is_empty() {
return Err(crate::ipc::IpcError::NoReceiver);
}
self.save_current_ipc_context(ctx, user_sp);
let mut blocked = self.ipc_blocked_tasks.lock();
blocked
.try_reserve(1)
.map_err(|_| crate::ipc::IpcError::NoReceiver)?;
// NOTIFICATION_WAIT_ARM_BEFORE_WAITER: timer authority exists before
// the object can expose a parked waiter to a signal producer.
let deadline_record = deadlines
.register_notification(
waiter_task,
notification_id,
expected_generation,
mask,
now_tick,
timeout_ticks,
)
.map_err(|error| match error {
IpcCallDeadlineRegistryError::Wait(WaitError::TableFull) => {
crate::ipc::IpcError::QueueFull
}
IpcCallDeadlineRegistryError::Wait(
WaitError::DeadlineNotFuture | WaitError::DeadlineTooFar,
)
| IpcCallDeadlineRegistryError::WaitEpochExhausted => {
crate::ipc::IpcError::InvalidDeadline
}
IpcCallDeadlineRegistryError::Wait(_) => crate::ipc::IpcError::InvalidCapability,
})?;
match notifications[target_index].wait(waiter) {
Ok(NotificationWaitOutcome::Registered) => {}
Ok(NotificationWaitOutcome::Immediate { .. }) | Err(_) => {
deadlines
.cancel_exact(deadline_record)
.expect("failed notification waiter publication lost its deadline rollback");
return Err(crate::ipc::IpcError::InvalidCapability);
}
}
let mut task = self
.current_task
.take()
.expect("preflighted notification waiter task disappeared");
task.state = TaskState::BlockedOnNotification { notification_id };
let parked_context = &mut task.context as *mut TaskContext;
blocked.push(task);
drop(blocked);
drop(notifications);
drop(deadlines);
drop(transaction);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
self.switch_after_ipc_park_with_membership_handoff(
parked_context,
|| {
drop(s377_writer_access);
drop(s377_irq_guard);
},
|scheduler| {
let s377_resume_irq_guard = crate::arch::aarch64::IrqGuard::new();
let s377_resume_writer_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration::acquire_s377_production_scheduler_writer_access()
.unwrap_or_else(|error| {
panic!(
"S377 resumed EL0 notification-wait scheduler writer guard failed closed: {:?}",
error
)
});
scheduler.restore_current_ipc_context(ctx);
drop(s377_resume_writer_access);
drop(s377_resume_irq_guard);
},
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
{
self.switch_after_ipc_park(parked_context);
self.restore_current_ipc_context(ctx);
}
Ok(None)
}
/// OR one signal into a coalescing notification. A matching waiter is
/// validated across object, shared deadline, CNode and scheduler state
/// before the allocation-free signal/wake commit.
pub fn notification_signal(
&mut self,
notification_id: crate::ui::capability::CapId,
expected_generation: u64,
bits: u64,
) -> Result<crate::ipc_notification::NotificationSignalOutcome, crate::ipc::IpcError> {
use crate::ipc_notification::NotificationSignalOutcome;
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let _transaction = IPC_TRANSACTION_LOCK.lock();
let signaler_task = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if bits == 0
|| !self.current_notification_authority_is_live(
signaler_task,
notification_id,
expected_generation,
crate::ui::capability::CapabilityRights::NOTIFICATION_SIGNAL,
)
{
return Err(crate::ipc::IpcError::InvalidCapability);
}
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut notifications = crate::ui::capability::NOTIFICATION_REGISTRY.lock();
let target_index = notifications
.iter()
.position(|object| object.id() == notification_id)
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let waiter = notifications[target_index].waiter_snapshot();
let will_wake = waiter.is_some_and(|waiter| {
(notifications[target_index].pending() | bits) & waiter.mask() != 0
});
if !will_wake {
return notifications[target_index]
.signal(bits)
.map_err(|_| crate::ipc::IpcError::InvalidCapability);
}
let waiter = waiter.expect("will_wake without a notification waiter");
let deadline_record = deadlines
.task_snapshot(waiter.task_id())
.filter(|record| {
record.kind().tag() == crate::ipc_wait::WaitKindTag::Notification
&& record.kind().object_id() == notification_id
&& record.kind().object_generation() == waiter.capability_generation()
&& record.kind().notification_mask() == Some(waiter.mask())
})
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter.task_id()
&& matches!(
task.state,
TaskState::BlockedOnNotification {
notification_id: blocked_id,
} if blocked_id == notification_id
)
&& task
.cnode
.lookup_capability_by_id(notification_id)
.is_some_and(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
&& capability.generation == waiter.capability_generation()
&& capability.rights.contains(
crate::ui::capability::CapabilityRights::NOTIFICATION_WAIT,
)
})
})
.ok_or(crate::ipc::IpcError::InvalidCapability)?;
if !self.ipc_wake_capacity_available(1) {
return Err(crate::ipc::IpcError::NoReceiver);
}
let outcome = notifications[target_index]
.signal(bits)
.map_err(|_| crate::ipc::IpcError::InvalidCapability)?;
let NotificationSignalOutcome::Wake {
waiter: committed_waiter,
observed,
..
} = outcome
else {
panic!("preflighted matching notification did not wake")
};
assert_eq!(committed_waiter, waiter);
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::Ok.as_u64();
task.saved_user_gprs[1] = observed;
for register in &mut task.saved_user_gprs[2..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
deadlines
.complete_delivery_exact(deadline_record)
.expect("matching notification signal lost exact deadline retirement");
Ok(outcome)
}
/// Commits a one-shot reply only after proving the caller is already in
/// the global blocked set. The reply object is removed after the response
/// is copied and the caller becomes runnable.
pub fn ipc_reply_commit(
&mut self,
reply_cap_id: crate::ui::capability::CapId,
message: crate::ui::capability::IpcMessage,
) -> crate::ipc::IpcError {
use crate::ipc_rendezvous::ReplyOutcome;
let _irq_guard = crate::arch::aarch64::IrqGuard::new();
let transaction = IPC_TRANSACTION_LOCK.lock();
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let Some(responder_task) = self
.current_task
.as_ref()
.map(|task| task.id)
.filter(|task_id| *task_id != 0)
else {
return crate::ipc::IpcError::InvalidCapability;
};
// Match lifecycle/mint lock order: IPC transaction -> capability
// provenance -> endpoint registry -> blocked scheduler set.
let mut capability_store = crate::ui::capability::get_capability_store();
let mut endpoints = crate::ui::capability::ENDPOINT_REGISTRY.lock();
let Some(reply_index) = endpoints
.iter()
.position(|endpoint| endpoint.id == reply_cap_id && endpoint.is_reply_cap)
else {
return crate::ipc::IpcError::InvalidCapability;
};
let caller_task = endpoints[reply_index].owner;
let Some(target_endpoint) = endpoints[reply_index].reply_target else {
return crate::ipc::IpcError::InvalidCapability;
};
let Some(target_index) = endpoints
.iter()
.position(|endpoint| endpoint.id == target_endpoint && !endpoint.is_reply_cap)
else {
return crate::ipc::IpcError::InvalidCapability;
};
let mut blocked = self.ipc_blocked_tasks.lock();
let Some(caller_position) = blocked.iter().position(|task| {
task.id == caller_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: true,
} if endpoint_id == reply_cap_id
)
}) else {
return crate::ipc::IpcError::InvalidCapability;
};
let reply_authority = blocked[caller_position]
.cnode
.lookup_capability_by_id(reply_cap_id)
.copied()
.filter(|capability| {
capability.owner == caller_task
&& capability.kind == crate::ui::capability::CapabilityKind::Endpoint
&& capability.parent.is_none()
});
let Some(reply_authority) = reply_authority else {
return crate::ipc::IpcError::InvalidCapability;
};
let deadline_record = deadlines.reply_snapshot(reply_cap_id);
if deadline_record.is_some_and(|record| {
record.key().task_id() != caller_task
|| record.kind().tag() != crate::ipc_wait::WaitKindTag::Call
|| record.kind().object_id() != target_endpoint
|| record.kind().reply_cap_id() != Some(reply_cap_id)
|| record.kind().reply_generation() != Some(reply_authority.generation)
}) {
return crate::ipc::IpcError::InvalidCapability;
}
if self.ready_queue.try_reserve(1).is_err() {
return crate::ipc::IpcError::NoReceiver;
}
match endpoints[target_index]
.rendezvous
.reply(responder_task, reply_cap_id, message)
{
Ok(ReplyOutcome::Wake {
caller_task: model_caller,
payload,
..
}) => {
if model_caller != caller_task {
panic!("reply model caller differs from blocked caller");
}
let retire_witness = endpoints[target_index]
.rendezvous
.retire(caller_task, reply_cap_id)
.expect("consumed reply record must retire exactly once");
assert_eq!(retire_witness.caller_task(), caller_task);
assert_eq!(retire_witness.reply_token(), reply_cap_id);
let mut caller = blocked.remove(caller_position);
assert_eq!(
caller.cnode.revoke_capability_exact(&reply_authority),
Some(reply_authority),
"retired reply exact caller CNode revoke failed"
);
Self::write_ipc_delivery(&mut caller, payload, 0);
caller.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(caller));
let removed_reply = endpoints.remove(reply_index);
assert_eq!(removed_reply.id, retire_witness.reply_token());
assert_eq!(removed_reply.owner, retire_witness.caller_task());
assert_eq!(removed_reply.reply_target, Some(target_endpoint));
if let Some(record) = deadline_record {
deadlines
.complete_reply_exact(record)
.expect("ordinary REPLY committed but exact deadline did not retire");
}
}
Ok(ReplyOutcome::StoredBeforePark) => {
// The blocked-set proof above and the model's `parked` bit
// must agree under the transaction lock. A recoverable error
// would leave a Replied record with a permanently parked
// caller, so treat disagreement as an integrity failure.
panic!("blocked caller has an unparked reply record")
}
Err(_) => return crate::ipc::IpcError::InvalidCapability,
}
drop(blocked);
drop(endpoints);
assert_eq!(
capability_store.revoke_endpoint_provenance(reply_cap_id, Some(responder_task)),
1,
"retired reply must have exactly one provenance record"
);
drop(capability_store);
drop(deadlines);
drop(transaction);
crate::ipc::IpcError::Ok
}
/// M7.5 — Revoke edildiğinde bloke task'leri uyandır
///
/// NOTE (M6/M7 Multi-core hazırlığı):
/// ipc_blocked_tasks şu anda Scheduler'ın içinde (tek global).
/// Gerçek SMP'de ya:
/// - Scheduler'ın tamamını spin::Mutex ile sarmak, veya
/// - Per-CPU blocked list + cross-CPU IPI ile wake.
///
/// Şu an için ENDPOINT_REGISTRY'nin yaptığı gibi .lock() ile korunması önerilir.
pub(crate) fn try_reserve_ipc_wake_capacity(&mut self, additional: usize) -> bool {
self.ready_queue.try_reserve(additional).is_ok()
}
/// Timer IRQ preflight must never allocate. All scheduler-created tasks
/// have already occupied the ready heap once, so a parked task can return
/// only when an existing slot is available. A false result leaves every
/// IPC/deadline object untouched for fail-closed handling.
pub(crate) fn ipc_wake_capacity_available(&self, additional: usize) -> bool {
self.ready_queue
.capacity()
.saturating_sub(self.ready_queue.len())
>= additional
}
/// Commit the scheduler half of one already-preflighted timeout. The
/// caller is selected by task id + reply object and its exact CNode
/// generation is retired before it becomes runnable with TimedOut.
pub(crate) fn wake_timed_out_ipc_caller_exact(
&mut self,
caller_task: u64,
reply_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed CALL wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let caller_position = blocked
.iter()
.position(|task| {
task.id == caller_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: true,
} if endpoint_id == reply_authority.id
)
&& task.cnode.can_revoke_capability_exact(&reply_authority)
})
.expect("preflighted timed CALL caller disappeared");
let mut caller = blocked.remove(caller_position);
assert_eq!(
caller.cnode.revoke_capability_exact(&reply_authority),
Some(reply_authority),
"timed CALL exact reply CNode revoke failed"
);
caller.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut caller.saved_user_gprs[1..=7] {
*register = 0;
}
caller.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(caller));
}
/// Wake one exact timed RECV without consuming its endpoint capability.
/// The timer-side graph preflight already removed the rendezvous waiter;
/// this commits only the allocation-free scheduler half.
pub(crate) fn wake_timed_out_ipc_receiver_exact(
&mut self,
receiver_task: u64,
endpoint_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed RECV wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let receiver_position = blocked
.iter()
.position(|task| {
task.id == receiver_task
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id,
is_call: false,
} if endpoint_id == endpoint_authority.id
)
&& task
.cnode
.lookup_capability_by_id(endpoint_authority.id)
.copied()
== Some(endpoint_authority)
})
.expect("preflighted timed RECV receiver disappeared");
let mut receiver = blocked.remove(receiver_position);
receiver.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut receiver.saved_user_gprs[1..=7] {
*register = 0;
}
receiver.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(receiver));
}
pub(crate) fn notification_blocked_task_count_on(
&self,
task_id: u64,
notification_id: crate::ui::capability::CapId,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
task.id == task_id
&& matches!(
task.state,
TaskState::BlockedOnNotification {
notification_id: blocked_id,
} if blocked_id == notification_id
)
})
.count()
}
pub(crate) fn notification_blocked_count_on(
&self,
notification_id: crate::ui::capability::CapId,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
matches!(
task.state,
TaskState::BlockedOnNotification { notification_id: blocked_id }
if blocked_id == notification_id
)
})
.count()
}
/// Wake one waiter whose notification authority was revoked after wait
/// admission. Holder removal may already have committed, so identity is
/// proven by task/object/state under the same IPC transaction rather than
/// by re-reading the deleted CNode slot.
pub(crate) fn wake_revoked_notification_exact(
&mut self,
waiter_task: u64,
notification_id: crate::ui::capability::CapId,
) {
assert!(
self.ipc_wake_capacity_available(1),
"revoked notification wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter_task
&& matches!(
task.state,
TaskState::BlockedOnNotification { notification_id: blocked_id }
if blocked_id == notification_id
)
})
.expect("preflighted revoked notification waiter disappeared");
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::InvalidCapability.as_u64();
for register in &mut task.saved_user_gprs[1..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
}
pub(crate) fn wake_timed_out_notification_exact(
&mut self,
waiter_task: u64,
notification_authority: crate::ui::capability::Capability,
) {
assert!(
self.ipc_wake_capacity_available(1),
"timed notification wake lost preflighted ready capacity"
);
let mut blocked = self.ipc_blocked_tasks.lock();
let task_position = blocked
.iter()
.position(|task| {
task.id == waiter_task
&& matches!(
task.state,
TaskState::BlockedOnNotification { notification_id }
if notification_id == notification_authority.id
)
&& task
.cnode
.lookup_capability_by_id(notification_authority.id)
.copied()
== Some(notification_authority)
})
.expect("preflighted timed notification waiter disappeared");
let mut task = blocked.remove(task_position);
task.saved_user_gprs[0] = crate::ipc::IpcError::TimedOut.as_u64();
for register in &mut task.saved_user_gprs[1..=7] {
*register = 0;
}
task.state = TaskState::Ready;
self.ready_queue.push(PriorityTask::new(task));
}
/// Allocation-free lifecycle audit: a parked rendezvous identity must map
/// to exactly one scheduler waiter before cancellation is committed.
pub(crate) fn ipc_blocked_count_on(&self, endpoint_id: crate::ui::capability::CapId) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id: blocked_endpoint,
..
} if blocked_endpoint == endpoint_id
)
})
.count()
}
pub(crate) fn ipc_blocked_task_count_on(
&self,
task_id: u64,
endpoint_id: crate::ui::capability::CapId,
is_call: bool,
) -> usize {
self.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| {
task.id == task_id
&& matches!(
task.state,
TaskState::BlockedOnIpc {
endpoint_id: blocked_endpoint,
is_call: blocked_is_call,
} if blocked_endpoint == endpoint_id && blocked_is_call == is_call
)
})
.count()
}
pub fn wake_tasks_on_revoked_endpoint(&mut self, endpoint_id: crate::ui::capability::CapId) {
// Multi-core safe: PerCpu üzerinden kilitle
let mut deadlines = crate::ipc::IPC_CALL_DEADLINES.lock();
let mut blocked_list = self.ipc_blocked_tasks.lock();
let mut i = 0;
while i < blocked_list.len() {
let should_wake = matches!(
blocked_list[i].state,
TaskState::BlockedOnIpc { endpoint_id: eid, .. } if eid == endpoint_id
);
if should_wake {
let mut task = blocked_list.remove(i);
let task_asid = task.asid; // ASID'i kaydet (invalidate için)
let is_call = matches!(task.state, TaskState::BlockedOnIpc { is_call: true, .. });
if let Some(record) = deadlines.task_snapshot(task.id) {
match (is_call, record.kind().tag()) {
(true, crate::ipc_wait::WaitKindTag::Call) => assert_eq!(
record.kind().reply_cap_id(),
Some(endpoint_id),
"peer-close wake selected a different deadline reply"
),
(false, crate::ipc_wait::WaitKindTag::Receive) => assert_eq!(
record.kind().object_id(),
endpoint_id,
"peer-close wake selected a different deadline endpoint"
),
_ => panic!("peer-close wake selected a different IPC wait kind"),
}
deadlines
.complete_peer_closed_exact(record)
.expect("peer-close IPC wake lost its exact deadline record");
}
drop(blocked_list);
// M7 audit fix #10 + M8.2 ASID invalidate
task.saved_user_gprs[0] = crate::ipc::IpcError::InvalidCapability.as_u64();
for slot in &mut task.saved_user_gprs[1..=7] {
*slot = 0;
}
task.state = TaskState::Ready;
// Revoke sonrası ilgili ASID'in TLB girdilerini temizle (M8.2)
if task_asid != 0 {
unsafe {
crate::arch::aarch64::mmu::invalidate_asid(task_asid);
}
}
let task_id = task.id;
self.ready_queue.push(PriorityTask::new(task));
crate::kprintln!(
"[M7.5] Revoke nedeniyle task#{} uyandırıldı (ASID={} invalidate edildi)",
task_id,
task_asid
);
blocked_list = self.ipc_blocked_tasks.lock();
} else {
i += 1;
}
}
}
// Restored minimal versions of previously removed methods to make build pass
fn recalculate_min_vruntime(&mut self) {
// TODO: implement properly later
}
fn normalize_vruntime(&mut self) {
// TODO: implement properly later
}
pub fn current_vruntime() -> u64 {
#[cfg(feature = "board-rpi5")]
let _s255_scheduler_read_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s254_vruntime_read_access_guard_expansion::acquire_s255_production_scheduler_read_access()
.unwrap_or_else(|error| {
panic!(
"S255 current-vruntime scheduler read access failed closed: {:?}",
error
)
});
unsafe {
let sched = &*core::ptr::addr_of!(SCHEDULER);
sched.current_task.as_ref().map_or(0, |t| t.vruntime)
}
}
pub fn min_vruntime() -> u64 {
#[cfg(feature = "board-rpi5")]
let _s255_scheduler_read_access = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s254_vruntime_read_access_guard_expansion::acquire_s255_production_scheduler_read_access()
.unwrap_or_else(|error| {
panic!(
"S255 min-vruntime scheduler read access failed closed: {:?}",
error
)
});
unsafe {
let sched = &*core::ptr::addr_of!(SCHEDULER);
sched.min_vruntime
}
}
pub fn try_wake_tasks_waiting_on(&mut self, _endpoint_id: crate::ui::capability::CapId) {
// TODO
}
}snippet sha256: fc5979057655…file sha256: 838dd474448c…
03 · Ortak exclusion üyeliği
S247 production writer guard
tam Rust öğesiL212–L224
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration.rs::acquire_s379_production_scheduler_writer_access
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
pub fn acquire_s379_production_scheduler_writer_access(
) -> Result<G8lS379ProductionSchedulerWriterAccess, 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(G8lS379ProductionSchedulerWriterAccess { _access: access })
}snippet sha256: ffd9f040ef78…file sha256: 8dba899b7461…
04 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam Rust öğesiL377–L386
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration.rs::call_timeout_handler_has_exactly_one_initial_s379_acquire
#[test]
fn call_timeout_handler_has_exactly_one_initial_s379_acquire() {
assert_eq!(
call_timeout_handler()
.matches("acquire_s379_production_scheduler_writer_access")
.count(),
1
);
}snippet sha256: 502a326d60ff…file sha256: bf967009d799…
05 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL8917–L9098
website/src/lib/operations.ts::g8l-s379-el0-ipc-call-timeout-writer-guard-integration-partial
{
id: "g8l-s379-el0-ipc-call-timeout-writer-guard-integration-partial",
date: "2026-08-29",
sequence: 379,
status: "passed",
umbrella_status: "partial",
title: "S379 · EL0 IPC call-timeout production writer guard integration",
summary:
"S379, handle_ipc_call_timeout içindeki exact scheduler.ipc_call_timeout_commit_and_park(...) mutable scheduler sınırını S378 ve 44 production reader'ın kullandığı aynı statik S247 state word'e bağlar. Target/message/timeout/current-tick snapshot'ları, relative deadline, current-task identity, normal Endpoint şekli, current CNode Endpoint + ENDPOINT_SEND authority, immutable IpcMessage ve caller-owned linked one-shot reply-cap writer'dan önce tamamlanır. RPi5 bare-metal kesişiminde dedicated IRQ guard kurulur; gerçek per-CPU kimliğiyle yalnız CPU0 için S379 exclusive writer alınır ve exact tek mutable SCHEDULER aliası üzerinden IRQ/writer değerleri scheduler'a by-value aktarılır. Scheduler live SEND authority, exact reply generation/linkage, optional receiver authority/deadline ve bütün park kapasitelerini transaction altında yeniden doğrular. CALL deadline rendezvous'tan önce kaydedilir; publish hatasında exact cancel_exact rollback yapılır. Parked yol CALL/Blocked/optional receiver wake publication'ını membership altında tamamlar; writer ve IRQ gerçek context_switch'ten hemen önce bırakılır. Resume continuation önce yeni IRQ sonra yeni S379 writer ile durable reply/cancel/timeout payload okunmadan yeniden katılır. Tarihsel S309 audit'i, S374 normal CALL membership'i ve S378 timed receive membership'i ayrı kalır. Guarded writer 52/69, açık writer 17, provider authority 0, whole-scheduler exclusion false ve supported-profile runtime observation=0'dır. S380 QEMU S139 exact-ACK sıradaki ayrı kapıdır.",
evidence: [
"İlk focused komut ayrı S379 module/source registration ve production boundary henüz yokken compile RED verdi; S379 S378 kartına topluca eklenmedi.",
"İlk RED exit 101 verdi; log 1262 B / 3653f9215e68af31a992e45496b5d4bfb0c6ab2623ede33104769ab2b5f69a35 SHA-256 ve artifact /tmp/aselsanos-s379-focused.vlbC35/initial-red.log'dur.",
"Sözleşme 55 bağımsız assertion'a genişletildi. İlk geniş koşu 54/55 PASS verdi; tek RED dokümantasyonda gerçek ENDPOINT_SEND sabitinin exact yazılmamasıydı.",
"Üretim authority sabiti veya kod davranışı gevşetilmedi; yalnız source-contract metni exact ENDPOINT_SEND adıyla düzeltildi.",
"Final rustfmt-sonrası focused koşu 55/55 PASS / 0 fail verdi; log 4229 B / 2351cb3b0445279634f9b32d7e8c2464ddbcb007f2d7091adebabc90ac074429 SHA-256'dır.",
"S379 modülü S378 typed preflight outcome'unu yeniden doğrular; inherited 44 reader + 51 guarded writer + 18 open writer snapshot'ı saparsa InventoryDrift ile fail-closed kapanır.",
"S379 başarı outcome'u FiftySecondWriterGuardedAwaitingRemaining'dir ve exact 44 guarded reader + 52/69 guarded writer + 17 open writer envanterini sabitler.",
"Production wrapper exact target_arch=aarch64, target_os=none, feature=board-rpi5 cfg kesişimindedir; host executor production runtime observation diye sunulmaz.",
"acquire_s379_production_scheduler_writer_access gerçek try_current_cpu_id sonucunu kullanır; caller-supplied production CPU parametresi yoktur ve CPU0 dışı InvalidCpu verir.",
"Writer lease S247_PRODUCTION_WHOLE_SCHEDULER_ACCESS_GATE üzerinde try_acquire_exclusive_for_valid_cpu ile alınır; yeni static veya ayrı model gate yaratılmaz.",
"Host executor callback'i exact bir kez çağırır; canlı reader/writer callback'ten önce ExclusiveBusy üretir, CPU1–CPU_MAX InvalidCpu verir ve callback error RAII lease'i bırakır.",
"S378 ve S379 token'ları aynı shared gate üzerinde monotonic ve distinct'tir; iki kapı tek transaction, dispatcher veya Kod kartı değildir.",
"Exact production giriş sınırı arch/aarch64/exceptions.rs içindeki handle_ipc_call_timeout fonksiyonudur; S379 diğer IPC syscall branch'lerini coverage'a katmaz.",
"target_task_id, label, badge, data0, data1, timeout_ticks ve now_tick immutable owned scalar olarak writer edinilmeden önce alınır.",
"TickDeadline::try_after taşması InvalidDeadline ve terminal return ile IRQ/writer acquisition'dan önce kapanır; shared gate veya scheduler graph değişmez.",
"current_task_id writer öncesinde caller_task scalar'ına çevrilir; mutable scheduler aliasından identity reader türetilmez.",
"Target endpoint exact normal/non-reply object olmalıdır; missing veya reply endpoint writer membership'i açmaz.",
"Current CNode lookup exact target için CapabilityKind::Endpoint, object generation ve CapabilityRights::ENDPOINT_SEND üçlüsünü writer öncesinde doğrular.",
"Authority yoksa InvalidCapability terminal sonucu S379 IRQ/writer acquisition'dan önce üretilir; stale veya foreign SEND grant writer'ı açmaz.",
"IpcMessage dört immutable payload alanıyla writer öncesinde kurulur; scheduler aliası message snapshot üretmek için kullanılmaz.",
"Linked one-shot reply-cap mint writer öncesindedir; mint başarısızlığı writer membership'i açmadan exact error döndürür.",
"Dedicated s379_irq_guard mutable scheduler aliasından önce local interrupt re-entry'yi kapatır; s379_writer_access IRQ guard'dan sonra ve alias kurulmadan önce alınır.",
"handle_ipc_call_timeout production cfg yolunda exact bir acquire_s379_production_scheduler_writer_access, bir mutable SCHEDULER aliası ve bir ipc_call_timeout_commit_and_park forward'ı vardır.",
"Production forward original dokuz çekirdek argümanı ve ardından s379_irq_guard/s379_writer_access değerlerini by-value taşır.",
"Non-RPi5 fallback exact tarihsel dokuz çekirdek argümanı korur ve guard değerleri taşımaz; S309 regresyonu iki cfg forward'ını ayrı doğrular.",
"Scheduler ipc_call_timeout_commit_and_park production imzası S379 IrqGuard ve G8lS379ProductionSchedulerWriterAccess değerlerini by-value alır; caller bunları yeniden kullanamaz.",
"Scheduler mevcut IPC_TRANSACTION_LOCK → deadline table → endpoint registry → blocked scheduler set kilit sırasını korur; S379 alternatif exclusion alanı eklemez.",
"Current caller task nonzero ve canlı olmalıdır; current_endpoint_authority_is_live exact endpoint id/generation ve ENDPOINT_SEND right'ını transaction altında yeniden doğrular.",
"Caller CNode'daki reply capability exact minted reply id/generation ve caller ownership ile yeniden doğrulanır; stale/replaced reply park publication'ını açmaz.",
"Endpoint normal/non-reply şekli ve linked reply relation transaction altında yeniden doğrulanır; reply endpoint CALL hedefi olamaz.",
"Optional waiting receiver varsa exact receiver authority, endpoint generation ve compatible deadline identity publication öncesinde doğrulanır.",
"Ready queue, blocked-task collection, endpoint rendezvous ve deadline capacity kontrolleri ilk mutation'dan önce tamamlanır; NoReceiver/QueueFull kısmi CALL bırakmaz.",
"Exact CALL deadline rendezvous publication'ından önce register edilir; deadline identity caller/reply/endpoint bağı taşır.",
"Rendezvous publication başarısız olursa aynı deadline_record cancel_exact ile geri alınır; orphan timeout kaydı bırakılmaz.",
"finish_call_park, caller BlockedOnIpc state'i ve optional receiver delivery/wake aynı scheduler transaction'ında ve S379 writer membership canlıyken tamamlanır.",
"switch_after_ipc_park_with_membership_handoff pre-switch closure'ı gerçek machine context_switch çağrısına bitişiktir; yorum veya model switch release kanıtı sayılmaz.",
"Pre-switch closure exact drop(s379_writer_access) ardından drop(s379_irq_guard) taşır; global writer başka task çalışırken pinlenmez.",
"Post-resume closure önce yeni IrqGuard, sonra acquire_s379_production_scheduler_writer_access ile yeni membership alır; eski token across-switch taşınmaz.",
"Durable reply, cancellation veya timeout payload scheduler.restore_current_ipc_context(ctx) ile yalnız resume üyeliğine yeniden katıldıktan sonra okunur.",
"Post-resume exact writer→IRQ drop sırası terminal result projection ve unpublished-reply cleanup'tan önce tamamlanır.",
"Immediate ve pre-publication error yolları ordinary RAII release kullanır; parked yol explicit release/switch/rejoin kullanır. İki yaşam döngüsü sahte long-lived token değildir.",
"Tarihsel S309 writer-authority audit modelidir; canlı S379 membership'i S309 sequence kimliğini veya o kapının 69-open-writer snapshot'ını değiştirmez.",
"S374 normal non-timeout CALL ayrı handler branch'i ve S374 token'ı kullanır; S379 timeout CALL yaşam döngüsü S374 coverage'ına katılmaz.",
"S378 timed receive ayrı endpoint right, waiter ve membership taşır; call-timeout branch'inde S378 acquire yoktur.",
"CPU1 coverage service timer zincirinde S378'den sonra ve S242 sender service'ten önce çalışır; pending S245 view yalnız inspect edilir, request take edilmez ve writer acquire yoktur.",
"Seçili regresyonun ilk tarihsel S309 koşusu 14/15 PASS / 1 fail verdi; tek RED karşılıklı dışlayan cfg forward'larını kaynakta tek call sayan eski assertion'dı.",
"S309 ortak dokuz argüman sözleşmesi korunarak production + S379 guards ve fallback guardsız forward ayrı ayrı sabitlendi; audit kapsamı gevşetilmedi.",
"S378 komşu testi call-timeout branch'inde yalnız distinct S379 membership bulunduğunu ve CPU1 service sırasının S377 < S378 < S379 < S242 olduğunu doğrular.",
"Final seçili regresyon S284, S309, S361, S374, S378, S379 ve ipc_call_deadline_runtime ile 7 grup / 267/267 PASS / 0 fail verdi.",
"Selected final log 28654 B / b8f310b8d3b8d5c7ad6e14ae978444fe12819bda2b7c930cf39b32ac4e7ffc06 SHA-256; artifact /tmp/aselsanos-s379-selected.2ulF0x'tir.",
"S238–S379 dependency listesi 143 gruptur. İki bağımsız seri koşunun her biri 3364/3364 PASS / 0 fail verdi.",
"İki kanonik dependency özeti 23607 B ve 581e8a91e32eeabdef630a520f333e6c07a3d73f533874f7fdc5db909b154bcb SHA-256 ile byte-eşittir; artifact /tmp/aselsanos-s379-dependency.AX1VA9'dur.",
"Exact yedi frozen G8h assertion dışındaki seri workspace 342 sonuç grubu / 5234 PASS / 0 fail / 7 filtered verdi.",
"Filtered workspace log 521495 B / c27535a942fb363df459b61d1931405cae3b9ca0e83541b5d11f0d8cc602c1da; summary 32372 B / 5962221e14673b735de44029ad6f85582975adb82a9f7bbac647402b636a84e7 SHA-256'dır.",
"Filtresiz workspace exit 101 ile yalnız frozen S96 wiring_does_not_mutate_timer_gic_boot_or_expand_runtime_scope reddinde durdu: 295 grup / 4979 PASS / 1 fail; global workspace GREEN iddia edilmez.",
"Filtresiz log 489846 B / 37af40d611eaa1fe0d8245b24d030cd97ffda970ec0c9bba2d553461f03e723e; summary 27953 B / 80db6be4ee03d78cbbeed73b2ad7e779cb9fd1e8d273720a0c910e918e21eabf SHA-256 ve artifact /tmp/aselsanos-s379-workspace.S8lM3W'dir.",
"Fresh izole AArch64 profilleri 4/4 exit 0 verdi. board-qemu log 112254 B / 9c35031cf4e0b4e45a3a1f1ba3b11248ff44937f1594aaaf82f8b0a88a1e5892, 293 warning header; ELF 16813080 B / c293d4bdc03d1faaaf9ad9b1f20ee129c2eaacd2c8f522a2c60094d912a1b740'dır.",
"board-rpi4 log 150942 B / 145e6e18e6fe7f6bae21217a09db8f8afbdce42b8f877c56338bf84047479aee, 391 warning header; ELF 11925456 B / 0f18cb9c37741dcb1364e25b9ddb4bae783f2f81a7c981b518ced61534027614'dür.",
"board-rpi5 log 626487 B / 2d93faf040e820221b445b234c2bc38e816bf6893a0a36b2dd954a6717b6a0ad, 1426 warning header; ELF 15138928 B / 1e582418ef3009836651988897e27dc0a0a1efa9c9b067da79794e06d5923aa4'dür.",
"board-rpi5+smp log 626385 B / 4a1f846711c3d0449d4202a4bddacb4798269f5720669af97e0642d73a50d954, 1426 warning header; ELF 15122328 B / d6ccb7bf95fde4b8a9635677fd4a62c475957902d0d288bf93724b0bef94d29f'dir. Zero-warning iddiası yoktur.",
"CARGO_INCREMENTAL=0 make verify-qemu exit 0 verdi. 116354 B log / 383138b6654b591787942e52fddfa45eef15fb293eccb3bf1b5326eaadb69c46 SHA-256 ile W^X 31/31, S130–S154+S271, RuntimePmm, EL0x4096, IPC reply 20/20 ve scheduler SEC5 PASS'tir.",
"Ortak board-qemu regresyonu RPi5-only S379 production wrapper invocation kanıtı değildir; supported-profile runtime observations=0 kalır.",
"Yeni modül/test scoped rustfmt ve cargo fmt --all -- --check exit 0 verdi; global Rust format kapısı PASS'tir.",
"Source-bound Kod hedefi S1–S379 379/379 ayrı kapı, pre-S328 S1–S327 327/327, missing=none ve duplicate=0'dır. S379 kartı S309/S374/S378/S380 kartlarıyla birleştirilmez.",
"S379 Code kartı tam handle_ipc_call_timeout öğesi içinde exact preflight→IRQ→writer→single alias→by-value handoff odağını ve ayrı tam ipc_call_timeout_commit_and_park rollback→park→release→switch→rejoin→restore yaşam döngüsünü yayımlar.",
"Ayrı S379 guard modülü, focused test, proof ve exact Operations object'i kendi source excerpt'leriyle yayımlanır; her excerpt repository path, satır aralığı, file SHA-256 ve snippet SHA-256 taşır.",
"S1–S327 tarihsel kapılar Operations sequence kimliğiyle 327/327 ayrı kart olarak korunur; S328 öncesi kayıtlar generic placeholder içinde birleştirilmez veya S328 kartına eklenmez.",
"İlk source-bound registry S1–S379 aralığında 379/379 unique kapı, 1084 exact excerpt, pre-S328 S1–S327 327/327, missing=none ve duplicate=0 üretti. JSON 8273442 B / bb2f2d244639c62f1a3d0836cce2b5135bfc4ccd7118c442a9c5d55434b0d0f7 SHA-256; registry content SHA-256 85ea1b822490ac399ed78e40801a6ac94a1f9532e45e6e17be2a79ceab8a4220'dır.",
"Website kabulü 674/674 PASS, lint PASS, boş çıktılı TypeScript exit 0 ve 24/24 static page verdi; export 201 dosya, Timeline 218 ayrı gate-policy kartı ve S379 core policy 13970 karakter / 14509 UTF-8 byte'tır.",
"Website test logu 62594 B / 0f3f64905d965a8ad4ff4578463acb4c76b71b75c8744d5da6bc5cc5ac492d3d; lint logu 218 B / 79c084453e339ceb2efe76ed96d1d68be8ac51442957a7a048fd17dba3067ba2; build logu 1213 B / 445c5b75fe653e8bc8098c36aa747fef0082072a17dd06d12aa32030065fa01d SHA-256'dır.",
"Production/main deployment 5a6973d6-bf9c-44fb-9131-d01a0d650cc5 ile 116 uploaded + 84 existing = 200 asset yayımlandı; deploy logu 1714 B / ec1654e74caac4ccb13ce673c22b9beb82a39a8aaf1bed9404c0528720c15462 SHA-256'dır.",
"Cache-busted custom-domain /code/, /operations/, /timeline/ ve /yol-haritasi/ HTTP 200 ve deploy-sonrası yerel out'a raw byte-exact PASS verdi. /code/ 23372606 B / e30f7f385dcd7b9692fefc2e2de479aab8a14e9f3bf14065c4c6b75d58f43fc7; /operations/ 13610973 B / 4da1d42475f3373e1b85817bbc51f2f10933f36e2ed5bf59153f444421ee8346; /timeline/ 5468862 B / 9aa2432c38aa0169c0111d533c998d4f9f35c8fc9c72619e3c9f17f333a33e65; /yol-haritasi/ 5468610 B / 1875bd48d13fe37175eeb9956e2cabb7d3c439ddecf23ac82ec9bb89800671f3 SHA-256'dır.",
"Canlı /code no-transform header'ı taşır; literal kart sayımı total=379, unique=379, min=S1, max=S379, pre-S328=327, missing=none, duplicate=0, S1=1, S327=1, S328=1, S378=1 ve S379=1 verdi. Canlı Timeline policy kartı 218'dir.",
"İlk publication artifact dizini /tmp/aselsanos-s379-web-initial.IQ46DY'dir. Immutable 5a6973d6 hostname probe'u curl exit 28 / HTTP 000 verdi; custom-domain PASS bunun yerine geçirilmez. Evidence-sync deployment ölçülmüş ilk snapshot'ı yayımlar ve self-reference üretmemek için kendi kimliğini bu snapshot metnine geri yazmaz.",
"S379 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_S379=NO.",
"S379 bazlı bağlayıcı olmayan planlama görünümü R1 S379–S409, R2 S434–S484, R3 S563+, kaba S539–S589 ve risk paylı merkez yaklaşık S564'tür; ürün veya sıra taahhüdü değildir.",
],
commands: [
"cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration -- --test-threads=1",
"run S284, S309, S361, S374, S378, S379 and ipc_call_deadline_runtime serially",
"run four fresh isolated AArch64 profiles; run S238-S379 dependency list twice; run filtered and unfiltered serial workspace audits; CARGO_INCREMENTAL=0 make verify-qemu",
"python3 scripts/render-project-status.py --write && python3 scripts/render-project-status.py --check; cargo test -p aselsan_microkernel_simulation --test project_status_manifest -- --test-threads=1",
"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-s379-focused-source-contract",
title: "S379 focused IPC call-timeout writer membership",
commandLines: [
"cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration -- --test-threads=1",
],
outputLines: [
"initial result: compile RED; separate S379 module/source registration and production boundary absent",
"first expanded result: 54/55 PASS; exact ENDPOINT_SEND documentation assertion RED",
"final result: ok; S379 focused 1 group / 55 passed / 0 failed",
"shared S247 gate: 44 guarded readers + 52/69 guarded writers; 17 writers open",
"inputs/deadline/identity/endpoint/SEND/message/reply mint < IRQ < writer < deadline/CALL/park < release < switch < rejoin < durable restore",
"direct production caller paths=1; context-switch handoff=1; post-resume rejoin=1; runtime observations=0; provider authority=0",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s379-selected-regression",
title: "S379 selected CALL authority/handoff/deadline regression",
commandLines: [
"run S284, S309, S361, S374, S378, S379 and ipc_call_deadline_runtime serially",
],
outputLines: [
"initial historical result: S309 14/15; cfg-call-count source assertion RED",
"S309 aligned to two exact cfg forwards while preserving the common nine-argument and authority contract",
"S378 aligned to require distinct S379 membership without changing the S378 inventory snapshot",
"final result: 7 groups / 267 passed / 0 failed",
"final log 28654 bytes; SHA-256 b8f310b8d3b8d5c7ad6e14ae978444fe12819bda2b7c930cf39b32ac4e7ffc06",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s379-full-acceptance",
title: "S379 four-profile, dependency, workspace and QEMU acceptance",
commandLines: [
"run four fresh isolated AArch64 profile builds with CARGO_INCREMENTAL=0",
"run S238-S379 dependency list twice and compare canonical summaries",
"run filtered and unfiltered serial workspace audits",
"CARGO_INCREMENTAL=0 make verify-qemu",
],
outputLines: [
"four AArch64 profiles: 4/4 exit 0; individual log/ELF byte and SHA-256 identities recorded",
"dependency: 143 groups / 3364/3364 twice; 23607-byte canonical summaries byte-equal",
"filtered workspace: 342 groups / 5234 PASS / 0 fail / 7 frozen filtered",
"unfiltered workspace: exit 101; 295 groups / 4979 PASS / 1 frozen S96 failure; global GREEN=false",
"QEMU: W^X 31/31 + S130-S154/S271 + RuntimePmm + EL0x4096 + IPC 20/20 + scheduler SEC5 PASS",
"QEMU common regression is not an S379 RPi5 runtime observation",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s379-production-publication",
title: "S379 source-code registry and production publication",
commandLines: [
"npm run code:generate && npm test && npm run lint && npx tsc --noEmit && npm run build",
"npm run deploy",
"cache-busted curl + raw cmp for /code/, /operations/, /timeline/ and /yol-haritasi/",
],
outputLines: [
"source registry: S1-S379 379/379 unique gates; 1084 exact excerpts; pre-S328 S1-S327 327/327; missing=none; duplicate=0",
"S379 code layers: complete handle_ipc_call_timeout + exact guard handoff focus + complete scheduler park/resume lifecycle + S247 guard + focused test + proof + Operations object",
"website: 674/674 PASS; lint PASS; TypeScript empty; static pages 24/24; files 201; Timeline cards 218",
"deployment 5a6973d6-bf9c-44fb-9131-d01a0d650cc5; 116 uploaded + 84 existing = 200 assets",
"custom-domain four routes HTTP 200 and raw byte-exact; /code no-transform; live code 379/379; pre-S328 327/327; missing=none; duplicate=0; S379=1",
"immutable hostname curl exit 28 / HTTP 000; custom-domain result remains the publication authority",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"TAM ÇIKTI kayıtları S379 focused 55/55, seçili 7 grup / 267 PASS, iki kez 143 grup / 3364 PASS, filtreli workspace 342 grup / 5234 PASS, filtresiz yalnız frozen-S96 RED, dört AArch64 profil 4/4, ortak QEMU kabulü ve production publication'ı ayrı oturumlar halinde taşır. /code S1–S379 aralığını 379 ayrı source-bound kart olarak yayımlar; özellikle S1–S327 tarihsel kapsam 327/327 tekildir. Dört custom-domain rota HTTP 200/raw byte-exact, missing=none, duplicate=0 ve S379=1'dir; S380 ayrı ve açık kalır.",
limitations: [
"S379 yalnız EL0 IPC call-timeout mutable scheduler sınırını kapatır; S380 QEMU S139 exact-ACK ve kalan 17 writer açıktır.",
"Production provider authority=0, whole-scheduler exclusion=false ve S244 admission publication yoktur.",
"Supported-profile S379 writer runtime invocation gözlenmedi; QEMU ortak regresyonu bunun yerine geçirilmez.",
"Park/resume source, host model ve AArch64 compile ile doğrulandı; transient contention liveness/soak ve default-parallel PTY determinism ürün kabulü değildir.",
"Generic SMP cross-CPU scheduler ownership, CPU2/CPU3, migration, load balancing, hotplug ve cross-CPU ASID/TLB arbitration açıktır.",
"Filtresiz workspace frozen S96 source assertion'ı nedeniyle global GREEN değildir.",
"Fiziksel RPi latency/power/thermal, UART/raw ve product threshold kabulü yapılmadı; RUNBOOK_EXECUTED_IN_S379=NO.",
"R1/R2/R3 sıra görünümü bağlayıcı olmayan planlama projeksiyonudur; ürün teslim taahhüdü değildir.",
],
},snippet sha256: 63e451348e24…file sha256: 9726dbf00f84…
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s379_el0_ipc_call_timeout_writer_guard_integration -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S379-EL0-IPC-Call-Timeout-Writer-Guard-Integration-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9