S377 · SOURCE-BOUND GATE EVIDENCE
S377 · EL0 notification wait-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 S377 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S377Production writer guardOperations id exactsource SHA exacttest target exact
operation: g8l-s377-el0-notification-wait-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 öğesiL493–L565kapı odağı L526–L546
kernel/src/arch/aarch64/exceptions.rs::handle_notification_wait_timeout
Tam kapsayıcı Rust öğesi gösterilir; vurgulu blok yalnız S377 exact production writer üyeliği sınırıdır. Komşu kod, guard kapsamı iddiası değildir.
fn handle_notification_wait_timeout(ctx: &mut ExceptionContext, user_sp: u64) {
let notification_id = ctx.gpr[0];
let mask = ctx.gpr[1];
let timeout_ticks = ctx.gpr[6];
let now_tick = TICKS.load(Ordering::Acquire);
if mask == 0 || crate::ipc_wait::TickDeadline::try_after(now_tick, timeout_ticks).is_err() {
set_ipc_error(ctx, IpcError::InvalidDeadline);
return;
}
let waiter_task = crate::task::current_task_id().unwrap_or(0);
let authority = crate::task::current_task_cnode()
.and_then(|cnode| cnode.lookup_capability_by_id(notification_id).copied())
.filter(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
&& capability
.rights
.contains(crate::ui::capability::CapabilityRights::NOTIFICATION_WAIT)
});
let Some(authority) = authority else {
set_ipc_error(ctx, IpcError::InvalidCapability);
return;
};
kprintln!(
"[K2-S146] SYS_NOTIFICATION_WAIT_TIMEOUT task={} notification={} mask=0x{:x} timeout_ticks={} now={}",
waiter_task,
notification_id,
mask,
timeout_ticks,
now_tick,
);
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s377_irq_guard = crate::arch::aarch64::IrqGuard::new();
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
let s377_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 EL0 notification-wait scheduler writer guard failed closed: {:?}",
error
)
});
let wait = 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 wait = scheduler.notification_wait_timeout_or_park(
ctx,
user_sp,
notification_id,
authority.generation,
mask,
now_tick,
timeout_ticks,
s377_irq_guard,
s377_writer_access,
);
#[cfg(not(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5")))]
let wait = scheduler.notification_wait_timeout_or_park(
ctx,
user_sp,
notification_id,
authority.generation,
mask,
now_tick,
timeout_ticks,
);
wait
};
match wait {
Ok(Some(observed)) => set_notification_result(ctx, observed),
Ok(None) => {}
Err(error) => set_ipc_error(ctx, error),
}
}snippet sha256: aa6839ab89a7…file sha256: 6f3a4c8dbf40…focus sha256: b75a72708154…
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::notification_wait_timeout_or_park
impl Scheduler {
pub const fn new() -> Self {
Self {
ready_queue: BinaryHeap::new(),
current_task: None,
retired_task: None,
deferred_current_runtime_oom: None,
ticks_until_preempt: 0,
min_vruntime: 0,
ipc_blocked_tasks: spin::Mutex::new(Vec::new()),
}
}
/// Reap the task retired by an earlier context switch.
///
/// User roots remain paired with their non-zero ASID lease until this
/// later-stack reaper drops the complete page-table owner. The ASID is
/// returned only after those resources are gone.
fn reap_retired_task(&mut self) {
let Some(mut task) = self.retired_task.take() else {
return;
};
let deferred_match = self
.deferred_current_runtime_oom
.as_ref()
.filter(|carrier| carrier.witness.is_none())
.is_some_and(|carrier| {
task.id == carrier.binding.task_id
&& task.runtime_allocation_domain() == Some(carrier.binding.domain)
});
if deferred_match {
let mut carrier = self
.deferred_current_runtime_oom
.take()
.expect("deferred current OOM carrier disappeared");
let witness = reap_detached_runtime_oom_task(
task,
carrier.binding.domain,
carrier.ipc_lifecycle_closed,
);
carrier.witness = Some(witness);
self.deferred_current_runtime_oom = Some(carrier);
return;
}
if task.is_user && (task.asid == 0 || !task.address_space_quiesced) {
crate::kprintln!(
"[K1-LIFECYCLE] quarantine task #{} '{}' without complete address-space quiescence (asid={}); resources intentionally retained",
task.id,
task.name,
task.asid
);
core::mem::forget(task);
return;
}
crate::kprintln!(
"[K1-LIFECYCLE] reaping task #{} '{}' on a later task stack",
task.id,
task.name
);
unsafe {
task.release_owned_page_table_root();
}
task.owned_user_page_tables.take();
match crate::mm::address_space::reclaim_owned_user_frames(
&mut task.owned_user_frames,
&mut task.runtime_user_frames,
) {
Ok(Some(report)) if report.returned_to_baseline() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} BASELINE=PASS",
task.id,
report.released_frames,
report.baseline_free_frames,
report.observed_free_frames,
report.baseline_active_allocations,
report.observed_active_allocations,
),
Ok(Some(report)) if report.exactly_reconciled() => crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} frames={} free={}->{} active_allocations={}->{} RECONCILED=PASS BASELINE=CONCURRENT",
task.id,
report.released_frames,
report.reclaim_started_free_frames,
report.observed_free_frames,
report.reclaim_started_active_allocations,
report.observed_active_allocations,
),
Ok(Some(_)) => unreachable!("RuntimePmm reclaimer returned an unaudited delta"),
Ok(None) => {}
Err(error) => {
crate::kprintln!(
"[K1-RUNTIME-ELF-RECLAIM] task={} BASELINE=FAIL error={:?}; ASID and remaining resources quarantined",
task.id,
error
);
core::mem::forget(task);
return;
}
}
// Root/intermediate storage is unreachable and the prior TLBI is
// complete. Only now may a fresh address space acquire this ASID.
let retired_asid = task.asid;
if retired_asid != 0 {
if let Err(error) = crate::mm::address_space::free_asid(retired_asid) {
crate::kprintln!(
"[K1-ASID] final reaper release failed asid={} error={:?}; identifier remains quarantined",
retired_asid,
error
);
core::mem::forget(task);
return;
}
task.asid = 0;
}
// `Task` drop now releases its owned kernel/user stack allocations.
// Raw ELF user stacks have no `OwnedStackAllocation` and are not
// reconstructed as a Box.
drop(task);
}
/// CFS tarzı weight hesabı (düşük priority = daha yüksek weight = daha yavaş vruntime artışı)
fn weight_of(priority: u8) -> u64 {
match priority {
0 => 1024, // En yüksek öncelik
1 => 820,
2 => 655,
3 => 524,
4 => 419,
5 => 335,
6 => 268,
7 => 215,
_ => 128,
}
}
/// Scheduler içinde vruntime güncelleme (CFS-lite)
fn update_vruntime(&mut self, task: &mut Task, delta_exec: u64) {
let weight = Self::weight_of(task.priority);
let delta = delta_exec * 1024 / weight.max(1);
task.vruntime = task.vruntime.wrapping_add(delta);
// min_vruntime'ı güncelle (CFS'te scheduler bunu takip eder)
if task.vruntime < self.min_vruntime {
self.min_vruntime = task.vruntime;
}
}
/// Builds a complete task without exposing it through a scheduler
/// container. S354 and S355 use this with an owned S255 vruntime snapshot
/// so all fallible user/kernel preparation finishes before the respective
/// publication writer. The old unguarded `Scheduler::spawn` publication
/// path no longer exists.
fn build_unpublished_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
is_user: bool,
user_entry: Option<extern "C" fn() -> !>,
initial_user_arg0: u64,
initial_vruntime: u64,
) -> Result<(Task, JoinHandle), TaskSpawnError> {
const GUARD_SIZE: usize = 0x1000;
let validated_user_entry = if is_user {
Some(user_entry.ok_or(TaskSpawnError::MissingUserEntry)?)
} else {
None
};
let total_user_stack_alloc = if is_user {
Some(
stack_size
.checked_add(GUARD_SIZE)
.ok_or(TaskSpawnError::StackSizeOverflow)?,
)
} else {
None
};
// Task ids are authority principals. Reserve one before ASIDs, page
// tables, or stacks so permanent id exhaustion has no side effects.
// A later spawn failure may consume an id, but ids are never reused.
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| TaskSpawnError::TaskIdExhausted)?;
// === M8.2+M7.4 fix (18-agent audit): page_table_root policy ===
// Kernel tasks share the live ROOT_PAGE_TABLE and never own or switch
// to a private root. The recorded value must nevertheless be its real
// address: framebuffer grants query this metadata and mutate the
// returned table. The old 0x4020_0000 placeholder points into the heap
// on current links and corrupts/walks allocator metadata as PTEs.
let kernel_root = unsafe {
let root = crate::arch::aarch64::mmu::get_kernel_root_table() as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let (new_root, new_asid) = if is_user {
// Reserve the scarce identifier before any root or stack Box.
// Exhaustion is returned to the user-task caller without mutation.
let a = crate::mm::address_space::try_allocate_asid().map_err(TaskSpawnError::Asid)?;
let r = crate::mm::allocate_page_table_root();
(r, a)
} else {
(kernel_root, 0u16)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let finished = Arc::new(AtomicBool::new(false));
// M5.5 (Audit #18) — EL0 stack + guard yalnız user task için gerekir.
// Kernel task'e ikinci, hiç kullanılmayan bir stack ayırmak hem heap'i
// tüketiyor hem de sahipliği kaybolan gereksiz bir allocation yaratıyordu.
let user_stack_allocation = total_user_stack_alloc.map(OwnedStackAllocation::new);
let (user_stack_ptr, usable_stack_bottom) = if let Some(owner) = &user_stack_allocation {
let user_stack_ptr = owner.base();
let usable_stack_bottom = unsafe { user_stack_ptr.add(GUARD_SIZE) };
// Guard sayfasının üstüne canary yaz (ileride overflow kontrolü).
unsafe {
core::ptr::write_volatile(
user_stack_ptr.add(GUARD_SIZE - 8) as *mut u64,
0xDEAD_BEEF_C0FFEE00,
);
}
(user_stack_ptr, usable_stack_bottom)
} else {
(core::ptr::null_mut(), core::ptr::null_mut())
};
// Sadece user task için per-task root mapping kur.
if is_user {
unsafe {
let root_table = crate::arch::aarch64::mmu::get_page_table_from_root(new_root);
// Düşük RAM + heap bölgesi (kernel tarafı NORMAL — kernel kodu için exec).
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x4000_0000),
crate::mm::PhysAddr::new(0x4000_0000),
0x0800_0000,
crate::mm::paging::PageTableFlags::NORMAL,
);
// UART
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0900_0000),
crate::mm::PhysAddr::new(0x0900_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// GIC
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(0x0800_0000),
crate::mm::PhysAddr::new(0x0800_0000),
0x0020_0000,
crate::mm::paging::PageTableFlags::DEVICE,
);
// M5.5+M7.4 fix: User stack USER_NORMAL override.
// Heap NORMAL kalır (kernel isolation), sadece user stack range'i
// USER_NORMAL ile EL0'a açılır. Guard page (alt 4K) MAP EDİLMEZ →
// translation fault overflow yakalar (Strategy A — Linux/seL4 patterni).
//
// KRİTİK: Box::into_raw u8 alignment'lı bir pointer döner (4K aligned değil).
// map_range_4k_to_root 4K page boundary bekler — unaligned adresleri
// atlar (page'leri map etmez) → user EL0 stack write'da perm fault.
// Bu yüzden adresi 4K'ya yuvarla (aşağı → start, yukarı → end).
let usable_stack_phys = usable_stack_bottom as u64;
let aligned_start = usable_stack_phys & !0xFFF; // round down
let aligned_end = (usable_stack_phys + stack_size as u64 + 0xFFF) & !0xFFF;
let aligned_size = aligned_end - aligned_start;
crate::arch::aarch64::mmu::map_range_4k_to_root(
root_table,
crate::mm::VirtAddr::new(aligned_start),
crate::mm::PhysAddr::new(aligned_start),
aligned_size,
crate::mm::paging::PageTableFlags::USER_NORMAL,
);
crate::kprintln!(
"[M5.5+M7.4] user '{}' root=0x{:x} asid={} stack USER_NORMAL [0x{:x}..0x{:x}] (size=0x{:x}, requested phys=0x{:x})",
name, new_root.as_u64(), new_asid, aligned_start, aligned_end, aligned_size, usable_stack_phys
);
}
}
let mut task = Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: finished.clone(),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation,
user_stack_bottom: usable_stack_bottom,
user_stack_size: if is_user { stack_size } else { 0 },
// M5.5: yalnız user task'te guard sayfası ayrılır.
stack_guard_page: if is_user { Some(user_stack_ptr) } else { None },
// M4.3
is_user,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: {
let mut registers = [0; 31];
if is_user {
registers[0] = initial_user_arg0;
}
registers
},
// Kernel task = live shared kernel root/asid=0; user task = unique owned root + ASID.
page_table_root: new_root,
owns_page_table_root: is_user,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: new_asid,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: !is_user,
// M5: Her task kendi CNode'unu alır (boş başlar)
cnode: crate::ui::capability::CNode::new(),
};
// Kernel context'i her zaman trampoline veya normal entry'ye işaret eder
// User task'ler için ilk girişe özel kurulum kullanıyoruz
unsafe {
if is_user {
task.context = TaskContext::new_for_user_first_entry(stack_top);
} else {
task.context = TaskContext::new(kernel_entry, stack_top);
}
}
// User task ise initial EL0 frame'i hemen hazırla
if let Some(entry) = validated_user_entry {
// Stack top = usable_bottom + size (guard hariç)
task.user_sp = usable_stack_bottom as u64 + stack_size as u64; // stack top (büyüme aşağı)
task.saved_user_elr = entry as u64;
task.saved_user_spsr = 0; // EL0t
// GPR'ler sıfır kalır, sadece PC ve SP önemli
}
let handle = JoinHandle {
finished,
task_id: id,
};
Ok((task, handle))
}
/// Build a kernel-task skeleton without publishing it in any scheduler
/// container. The raw-ELF path consumes this value, installs the complete
/// user address-space state, and performs exactly one final ready-queue
/// push. No CPU can therefore observe a placeholder entry/root/ASID.
fn build_unpublished_kernel_task(
name: &str,
kernel_entry: extern "C" fn() -> !,
stack_size: usize,
priority: u8,
time_slice: u32,
initial_vruntime: u64,
) -> Result<Box<Task>, &'static str> {
let id = TASK_ID_ALLOCATOR
.try_allocate()
.map_err(|_| "task id space exhausted")?;
let kernel_root = {
let root =
unsafe { crate::arch::aarch64::mmu::get_kernel_root_table() } as *mut _ as u64;
crate::mm::PhysAddr::new(root)
};
let kernel_stack_allocation = OwnedStackAllocation::new(stack_size);
let stack_top = unsafe { kernel_stack_allocation.top() };
let mut task = Box::new(Task {
id,
name: String::from(name),
state: TaskState::Ready,
context: TaskContext::default(),
priority,
time_slice,
default_time_slice: time_slice,
vruntime: initial_vruntime,
finished: Arc::new(AtomicBool::new(false)),
kernel_stack_allocation: Some(kernel_stack_allocation),
user_stack_allocation: None,
user_stack_bottom: core::ptr::null_mut(),
user_stack_size: 0,
stack_guard_page: None,
is_user: false,
user_sp: 0,
saved_user_elr: 0,
saved_user_spsr: 0,
saved_user_gprs: [0; 31],
page_table_root: kernel_root,
owns_page_table_root: false,
owned_user_page_tables: None,
owned_user_frames: alloc::vec::Vec::new(),
runtime_user_frames: None,
asid: 0,
#[cfg(feature = "board-rpi5")]
g8l_current_task_owner: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerSlot::new(),
address_space_quiesced: true,
cnode: crate::ui::capability::CNode::new(),
});
task.context = unsafe { TaskContext::new(kernel_entry, stack_top) };
Ok(task)
}
/// Mevcut task'i kuyruğa geri koyar ve en yüksek öncelikli (en düşük vruntime) task'i seçer.
///
/// NOT: Şu anda kullanılmıyor (`yield_now` aynı işi inline yapıyor).
/// İleride farklı bir API (örn. IRQ-driven preemption) lazım olursa bu
/// fonksiyon kullanılabilir.
#[allow(dead_code)]
pub unsafe fn schedule(&mut self) -> Option<(*mut TaskContext, *mut TaskContext)> {
if let Some(mut curr) = self.current_task.take() {
curr.state = TaskState::Ready;
if curr.time_slice == 0 {
curr.time_slice = curr.default_time_slice;
}
self.ready_queue.push(PriorityTask::new(curr));
}
// min_vruntime'ı güncelle (daha adil seçim için)
self.recalculate_min_vruntime();
if let Some(prio_next) = self.ready_queue.pop() {
let mut next = prio_next.task;
next.state = TaskState::Running;
let new_ctx = &mut next.context as *mut TaskContext;
self.current_task = Some(next);
self.ticks_until_preempt = self.current_task.as_ref().unwrap().time_slice;
Some((core::ptr::null_mut(), new_ctx))
} else {
None
}
}
/// Timer her tick'te çağrılır.
/// Time slice bitince preemption tetikler + vruntime ve aging uygular.
pub unsafe fn tick(&mut self) -> bool {
static TICK_COUNTER: AtomicU64 = AtomicU64::new(0);
let n = TICK_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
// Aging + min_vruntime + normalizasyon (her 50 tick'te bir)
if n % 50 == 0 {
self.apply_aging();
self.recalculate_min_vruntime();
self.normalize_vruntime();
}
if let Some(curr) = &mut self.current_task {
if curr.time_slice > 0 {
curr.time_slice -= 1;
}
// Vruntime'ı da tick bazında hafifçe artır
let weight = Self::weight_of(curr.priority);
curr.vruntime = curr.vruntime.wrapping_add(1 * 1024 / weight.max(1));
if curr.vruntime < self.min_vruntime {
self.min_vruntime = curr.vruntime;
}
if curr.time_slice == 0 {
return true; // preemption gerekli
}
}
false
}
/// Aging mekanizması:
/// Uzun süredir bekleyen task'lerin vruntime'ını azaltarak önlerine geçmelerini sağlar.
/// Bu sayede düşük öncelikli task'ler bile zamanla CPU alabilir.
fn apply_aging(&mut self) {
let mut temp = Vec::new();
while let Some(mut ptask) = self.ready_queue.pop() {
// min_vruntime'tan çok geride kalanlara daha fazla bonus veriyoruz
let bonus = if ptask.task.vruntime + 50 < self.min_vruntime {
12
} else {
6
};
ptask.task.vruntime = ptask.task.vruntime.saturating_sub(bonus);
temp.push(ptask);
}
for p in temp {
self.ready_queue.push(p);
}
}
/// Mevcut task'in context pointer'ını döner (güvenli kullanım için)
pub fn current_context_ptr(&self) -> Option<*mut TaskContext> {
self.current_task
.as_ref()
.map(|t| &t.context as *const _ as *mut TaskContext)
}
/// Mevcut çalışan task'in ID'sini döndürür.
pub fn current_task_id(&self) -> Option<u64> {
self.current_task.as_ref().map(|t| t.id)
}
/// S211 producer-side observation of the real scheduler current task.
/// The target adapter holds a local IRQ guard while calling this method;
/// cross-CPU exclusion against legacy scheduler accesses remains open.
#[cfg(feature = "board-rpi5")]
pub(crate) fn observe_g8l_current_task(
&self,
) -> Option<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask,
>{
self.current_task.as_ref().map(|task| {
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
}
})
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn commit_g8l_current_task_owner(
&mut self,
runtime: &crate::g8l_runtime_contract::G8lRuntimeAuthority,
authority: &crate::g8l_target_dispatch_scheduler_owner::G8lSchedulerOwnerAuthority,
bridge: &crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_guarded_ack::G8lSchedulerMutationProductionGuardedAckBridge<'_>,
) -> Result<
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
let observed = crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lObservedCurrentTask {
task_id: task.id,
running: task.state == TaskState::Running,
el0: task.is_user,
asid: task.asid,
root: task.page_table_root.as_u64(),
};
task.g8l_current_task_owner
.commit_from_guarded_bridge(runtime, authority, bridge, observed)
}
#[cfg(feature = "board-rpi5")]
pub(crate) fn rollback_g8l_current_task_owner(
&mut self,
receipt: crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitReceipt,
) -> Result<
(),
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError,
>{
let task = self.current_task.as_mut().ok_or(
crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::MissingCurrentTask,
)?;
if task.id != receipt.task_id {
return Err(crate::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_current_task::G8lSchedulerCurrentTaskOwnerCommitError::TaskMismatch);
}
task.g8l_current_task_owner.rollback_exact(receipt)
}
fn record_runtime_oom_binding(
task: &Task,
location: RuntimeOomTaskLocation,
domain: crate::mm::AllocationDomain,
expected_frames: usize,
binding: &mut Option<RuntimeOomTaskBinding>,
) -> Result<(), RuntimeOomTaskExecutionError> {
if task.runtime_allocation_domain() != Some(domain) {
return Ok(());
}
if binding.is_some() {
return Err(RuntimeOomTaskExecutionError::AmbiguousDomainBinding);
}
let Some(ledger) = task.runtime_user_frames.as_ref() else {
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
};
let expected_state = match location {
RuntimeOomTaskLocation::Current => task.state == TaskState::Running,
RuntimeOomTaskLocation::Ready => task.state == TaskState::Ready,
RuntimeOomTaskLocation::Blocked => matches!(
task.state,
TaskState::Blocked
| TaskState::BlockedOnIpc { .. }
| TaskState::BlockedOnNotification { .. }
),
RuntimeOomTaskLocation::Retired => task.state == TaskState::Dead,
};
if !task.is_user
|| task.asid == 0
|| task.address_space_quiesced
|| task.owns_page_table_root
|| task.owned_user_page_tables.is_none()
|| ledger.frame_count() != expected_frames
|| task.owned_user_frames.len() != expected_frames
|| !ledger.physical_frames_match(&task.owned_user_frames)
|| !expected_state
{
return Err(RuntimeOomTaskExecutionError::InvalidTaskBinding);
}
*binding = Some(RuntimeOomTaskBinding {
task_id: task.id,
domain,
location,
asid: task.asid,
frame_count: expected_frames,
});
Ok(())
}
/// Allocation-free audit of every persistent scheduler container. A
/// domain must bind one strict RuntimePmm ELF task and the ticket's frame
/// count must equal that task's complete leaf ledger.
fn audit_runtime_oom_task_binding(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let expected_frames = usize::try_from(preflight.expected_reclaimable_frames)
.map_err(|_| RuntimeOomTaskExecutionError::InvalidTaskBinding)?;
let mut binding = None;
if let Some(task) = self.current_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Current,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
for priority_task in self.ready_queue.iter() {
Self::record_runtime_oom_binding(
&priority_task.task,
RuntimeOomTaskLocation::Ready,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
{
let blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Blocked,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
}
if let Some(task) = self.retired_task.as_deref() {
Self::record_runtime_oom_binding(
task,
RuntimeOomTaskLocation::Retired,
preflight.domain,
expected_frames,
&mut binding,
)?;
}
binding.ok_or(RuntimeOomTaskExecutionError::DomainNotBound)
}
fn preflight_runtime_oom_task(
&self,
preflight: crate::mm::RuntimeOomTeardownPreflight,
) -> Result<RuntimeOomTaskBinding, RuntimeOomTaskExecutionError> {
let binding = self.audit_runtime_oom_task_binding(preflight)?;
match binding.location {
RuntimeOomTaskLocation::Current => {
Err(RuntimeOomTaskExecutionError::CurrentTaskRequiresDeferredExit)
}
RuntimeOomTaskLocation::Retired => {
Err(RuntimeOomTaskExecutionError::RetiredTaskQuarantined)
}
RuntimeOomTaskLocation::Ready | RuntimeOomTaskLocation::Blocked => Ok(binding),
}
}
/// Detach the exact preflighted non-current task without allocating. The
/// ready heap is rebuilt in-place from its existing Vec allocation.
fn take_runtime_oom_task(&mut self, binding: RuntimeOomTaskBinding) -> Option<Box<Task>> {
let mut ready = core::mem::take(&mut self.ready_queue).into_vec();
let ready_match = ready.iter().position(|priority_task| {
priority_task.task.id == binding.task_id
&& priority_task.task.runtime_allocation_domain() == Some(binding.domain)
});
let selected = ready_match.map(|index| ready.swap_remove(index).task);
self.ready_queue = BinaryHeap::from(ready);
if selected.is_some() {
return selected;
}
let mut blocked = self.ipc_blocked_tasks.lock();
let blocked_match = blocked.iter().position(|task| {
task.id == binding.task_id && task.runtime_allocation_domain() == Some(binding.domain)
});
blocked_match.map(|index| blocked.swap_remove(index))
}
pub(crate) fn capability_for_task(
&self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<crate::ui::capability::Capability> {
if let Some(capability) = self
.current_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ipc_blocked_tasks
.lock()
.iter()
.find(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
if let Some(capability) = self
.ready_queue
.iter()
.find(|priority_task| priority_task.task.id == task_id)
.and_then(|priority_task| priority_task.task.cnode.lookup_capability_by_id(cap_id))
.copied()
{
return Some(capability);
}
self.retired_task
.as_ref()
.filter(|task| task.id == task_id)
.and_then(|task| task.cnode.lookup_capability_by_id(cap_id))
.copied()
}
pub(crate) fn notification_holder_count(
&self,
notification_id: crate::ui::capability::CapId,
) -> usize {
let holds = |task: &Task| {
task.cnode
.lookup_capability_by_id(notification_id)
.is_some_and(|capability| {
capability.kind == crate::ui::capability::CapabilityKind::Notification
})
};
let mut count = usize::from(self.current_task.as_deref().is_some_and(holds));
count += self
.ready_queue
.iter()
.filter(|task| holds(&task.task))
.count();
count += self
.ipc_blocked_tasks
.lock()
.iter()
.filter(|task| holds(task))
.count();
count += usize::from(self.retired_task.as_deref().is_some_and(holds));
count
}
fn notification_holder_for_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(notification_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(notification_id)
};
let notification_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != notification_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Notification
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| notification_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::NOTIFICATION_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_notification_holder_task(
task: &Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::notification_holder_for_task(task, notification_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Generation-aware audit of every persistent notification holder. The
/// caller owns `IPC_TRANSACTION_LOCK`, so the matching purge observes the
/// same CNode graph.
pub(crate) fn preflight_notification_holder_purge(
&self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_notification_holder_task(
&task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_notification_holder_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_notification_holder_from_task(
task: &mut Task,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::notification_holder_for_task(task, notification_id, object_owner)
.expect("preflighted notification holder became malformed");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted notification holder exact revoke failed"
);
summary
.record(container)
.expect("notification holder count overflowed during commit");
}
/// Allocation-free exact purge paired with
/// `preflight_notification_holder_purge`.
pub(crate) fn purge_notification_holders_exact(
&mut self,
notification_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_notification_holder_purge(notification_id, object_owner)
.expect("notification holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_notification_holder_from_task(
&mut task.task,
notification_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_notification_holder_from_task(
task,
notification_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"notification holder purge differed from exact preflight"
);
removed
}
fn endpoint_holder_for_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<Option<crate::ui::capability::Capability>, EndpointHolderPurgeError> {
use crate::ui::capability::{CapabilityKind, CapabilityRights};
let Some(capability) = task.cnode.lookup_capability_by_id(endpoint_id).copied() else {
return Ok(None);
};
let is_object_owner = object_owner != 0 && task.id == object_owner;
let expected_parent = if is_object_owner {
None
} else {
Some(endpoint_id)
};
let endpoint_rights = capability.rights.intersect(CapabilityRights::FULL);
if capability.id != endpoint_id
|| capability.owner != task.id
|| capability.kind != CapabilityKind::Endpoint
|| capability.parent != expected_parent
|| capability.rights == CapabilityRights::NONE
|| endpoint_rights != capability.rights
|| (is_object_owner
&& !capability
.rights
.contains(CapabilityRights::ENDPOINT_REVOKE))
|| !task.cnode.can_revoke_capability_exact(&capability)
{
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(Some(capability))
}
fn audit_endpoint_holder_task(
task: &Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) -> Result<(), EndpointHolderPurgeError> {
if Self::endpoint_holder_for_task(task, endpoint_id, object_owner)?.is_some() {
summary.record(container)?;
}
Ok(())
}
/// Allocation-free, generation-aware audit of every scheduler-owned task
/// CNode that can outlive the current instruction. The caller holds the
/// global IPC transaction lock, so this snapshot remains stable through
/// the matching purge.
pub(crate) fn preflight_endpoint_holder_purge(
&self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> Result<EndpointHolderPurgeSummary, EndpointHolderPurgeError> {
let mut summary = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut summary,
)?;
}
for task in self.ready_queue.iter() {
Self::audit_endpoint_holder_task(
&task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut summary,
)?;
}
for task in self.ipc_blocked_tasks.lock().iter() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut summary,
)?;
}
if let Some(task) = self.retired_task.as_deref() {
Self::audit_endpoint_holder_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut summary,
)?;
}
if object_owner != 0 && summary.total == 0 {
return Err(EndpointHolderPurgeError::MalformedAuthority);
}
Ok(summary)
}
fn purge_endpoint_holder_from_task(
task: &mut Task,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
container: TaskContainer,
summary: &mut EndpointHolderPurgeSummary,
) {
let capability = Self::endpoint_holder_for_task(task, endpoint_id, object_owner)
.expect("preflighted endpoint holder became malformed inside one IPC transaction");
let Some(capability) = capability else {
return;
};
assert_eq!(
task.cnode.revoke_capability_exact(&capability),
Some(capability),
"preflighted endpoint holder exact revoke failed"
);
summary
.record(container)
.expect("preflighted endpoint holder count overflowed during commit");
}
/// Commit the exact holder snapshot without allocating. BinaryHeap is
/// converted into and rebuilt from its existing Vec allocation in place;
/// CNode changes cannot affect heap ordering.
pub(crate) fn purge_endpoint_holders_exact(
&mut self,
endpoint_id: crate::ui::capability::CapId,
object_owner: u64,
) -> EndpointHolderPurgeSummary {
let expected = self
.preflight_endpoint_holder_purge(endpoint_id, object_owner)
.expect("endpoint holder graph changed after teardown preflight");
let mut removed = EndpointHolderPurgeSummary::default();
if let Some(task) = self.current_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Current,
&mut removed,
);
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
for task in ready_tasks.iter_mut() {
Self::purge_endpoint_holder_from_task(
&mut task.task,
endpoint_id,
object_owner,
TaskContainer::Ready,
&mut removed,
);
}
self.ready_queue = BinaryHeap::from(ready_tasks);
for task in self.ipc_blocked_tasks.lock().iter_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Blocked,
&mut removed,
);
}
if let Some(task) = self.retired_task.as_deref_mut() {
Self::purge_endpoint_holder_from_task(
task,
endpoint_id,
object_owner,
TaskContainer::Retired,
&mut removed,
);
}
assert_eq!(
removed, expected,
"endpoint holder purge differed from its exact preflight"
);
removed
}
/// Belirli bir task'in page table root'unu döndürür (M4.4 büyük adım için).
pub fn get_task_page_table_root(&self, task_id: u64) -> Option<crate::mm::PhysAddr> {
// Basit lineer arama (demo için yeterli)
if let Some(ref curr) = self.current_task {
if curr.id == task_id {
return Some(curr.page_table_root);
}
}
for ptask in self.ready_queue.iter() {
if ptask.task.id == task_id {
return Some(ptask.task.page_table_root);
}
}
for task in self.ipc_blocked_tasks.lock().iter() {
if task.id == task_id {
return Some(task.page_table_root);
}
}
None
}
/// M5 — Belirli bir task'in CNode'una capability ekler (cross-task grant için kritik).
/// Hem current_task hem ready_queue içindeki task'leri tarar.
pub fn insert_cap_for_task(
&mut self,
task_id: u64,
cap: crate::ui::capability::Capability,
) -> Result<(usize, u64), &'static str> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
return curr.cnode.insert(cap);
}
}
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut result: Result<(usize, u64), &'static str> = Err("Task not found");
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
result = pt.task.cnode.insert(cap);
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
result
}
/// Private id-only CNode removal. Public callers must pass through the
/// global wrapper, which rejects endpoint identities in favor of typed
/// grant/object APIs.
fn revoke_cap_for_task(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
self.remove_cap_by_id(task_id, cap_id)
}
/// CNode seviyesinde id bazlı güçlü revoke (generation bump dahil)
fn remove_cap_by_id(
&mut self,
task_id: u64,
cap_id: crate::ui::capability::CapId,
) -> Option<usize> {
if let Some(ref mut curr) = self.current_task {
if curr.id == task_id {
if curr.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
}
// Reply owners normally wait outside the ready queue. Search this
// fixed authority location before the heap fallback so reply consume
// and endpoint teardown do not allocate a temporary queue at OOM.
let mut blocked = self.ipc_blocked_tasks.lock();
for task in blocked.iter_mut() {
if task.id == task_id && task.cnode.revoke_capability(cap_id).is_some() {
return Some(0);
}
}
drop(blocked);
let mut temp: alloc::vec::Vec<PriorityTask> = alloc::vec::Vec::new();
let mut found: Option<usize> = None;
while let Some(mut pt) = self.ready_queue.pop() {
if pt.task.id == task_id {
if pt.task.cnode.revoke_capability(cap_id).is_some() {
found = Some(0);
}
temp.push(pt);
break;
} else {
temp.push(pt);
}
}
for p in temp {
self.ready_queue.push(p);
}
if found.is_some() {
return found;
}
None
}
/// Allocation-free removal of one complete CNode authority tuple. This
/// is used by typed endpoint/reply teardown where id-only revocation would
/// let a stale capability delete a newer generation.
pub(crate) fn revoke_cap_for_task_exact(
&mut self,
expected: &crate::ui::capability::Capability,
) -> bool {
let task_id = expected.owner;
if let Some(task) = self
.current_task
.as_deref_mut()
.filter(|task| task.id == task_id)
{
return task.cnode.revoke_capability_exact(expected).is_some();
}
{
let mut blocked = self.ipc_blocked_tasks.lock();
if let Some(task) = blocked.iter_mut().find(|task| task.id == task_id) {
return task.cnode.revoke_capability_exact(expected).is_some();
}
}
let ready = core::mem::take(&mut self.ready_queue);
let mut ready_tasks = ready.into_vec();
let removed = ready_tasks
.iter_mut()
.find(|task| task.task.id == task_id)
.map_or(false, |task| {
task.task.cnode.revoke_capability_exact(expected).is_some()
});
self.ready_queue = BinaryHeap::from(ready_tasks);
if removed {
return true;
}
self.retired_task
.as_deref_mut()
.filter(|task| task.id == task_id)
.map_or(false, |task| {
task.cnode.revoke_capability_exact(expected).is_some()
})
}
// =================================================================
// M6.2 — IPC Blocking / Waking (İskelet)
// =================================================================
fn save_current_ipc_context(&mut self, ctx: &ExceptionContext, user_sp: u64) {
if let Some(current) = &mut self.current_task {
current.is_user = true;
current.user_sp = user_sp;
current.saved_user_elr = ctx.elr_el1;
current.saved_user_spsr = ctx.spsr_el1;
current.saved_user_gprs[..30].copy_from_slice(&ctx.gpr);
current.saved_user_gprs[30] = ctx.lr;
}
}
/// Copy the wake result back into the still-live exception frame whose
/// kernel continuation was saved by `switch_after_ipc_park`.
fn restore_current_ipc_context(&self, ctx: &mut ExceptionContext) {
let current = self
.current_task
.as_ref()
.expect("IPC continuation resumed without its current task");
assert!(
current.is_user && current.state == TaskState::Running,
"IPC continuation resumed outside a running EL0 task"
);
ctx.gpr.copy_from_slice(¤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 öğesiL196–L208
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration.rs::acquire_s377_production_scheduler_writer_access
#[cfg(all(target_arch = "aarch64", target_os = "none", feature = "board-rpi5"))]
pub fn acquire_s377_production_scheduler_writer_access(
) -> Result<G8lS377ProductionSchedulerWriterAccess, 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(G8lS377ProductionSchedulerWriterAccess { _access: access })
}snippet sha256: a1bcb7285418…file sha256: 469eaa1eac2a…
04 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam Rust öğesiL385–L397
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration.rs::wait_handler_has_exactly_one_s377_acquire_and_by_value_handoff
#[test]
fn wait_handler_has_exactly_one_s377_acquire_and_by_value_handoff() {
let handler = notification_wait_handler();
assert_eq!(
handler
.matches("acquire_s377_production_scheduler_writer_access")
.count(),
1
);
assert_eq!(handler.matches("s377_writer_access,").count(), 1);
assert_eq!(handler.matches("s377_irq_guard,").count(), 1);
}snippet sha256: 7a5ef952a045…file sha256: d26e0538006d…
05 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL9277–L9458
website/src/lib/operations.ts::g8l-s377-el0-notification-wait-timeout-writer-guard-integration-partial
{
id: "g8l-s377-el0-notification-wait-timeout-writer-guard-integration-partial",
date: "2026-08-29",
sequence: 377,
status: "passed",
umbrella_status: "partial",
title:
"S377 · EL0 notification wait-timeout production writer guard integration",
summary:
"S377, handle_notification_wait_timeout içindeki exact scheduler.notification_wait_timeout_or_park(...) mutable scheduler sınırını S376 ve 44 production reader'ın kullandığı aynı statik S247 state word'e bağlar. notification_id/mask/timeout/now, deadline geçerliliği, current-task identity ve live Notification + NOTIFICATION_WAIT CNode authority 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 S377 exclusive writer alınır ve exact tek mutable SCHEDULER aliası üzerinden IRQ/writer değerleri scheduler'a by-value aktarılır. Immediate/reject yolları RAII ile kapanır. Parked yol waiter/deadline/Blocked 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 S377 writer ile durable timeout/signal payload okunmadan önce yeniden katılır ve result split'ten önce iki guard'ı bırakır. Tarihsel S307 model audit'i ve S376 signal membership'i ayrı kalır. Guarded writer 50/69, açık writer 19, provider authority 0, whole-scheduler exclusion false ve supported-profile runtime observation=0'dır. S378 IPC receive-timeout sıradaki ayrı kapıdır.",
evidence: [
"İlk focused komut, ayrı S377 production modülü, kernel/simulation registration, CPU1 coverage service ve handle_notification_wait_timeout membership'i henüz yokken compile RED verdi; S377 S376 kartına topluca eklenmedi.",
"İlk RED logu 2133 B / fc9930288e9fb4e255bce1ac254d56580c8a17cf40813ea4d4da43df2158fa0d SHA-256; artifact /tmp/aselsanos-s377-focused.KT21na/initial-red.log'dur.",
"S377 modülü S376 typed preflight outcome'unu yeniden doğrular; inherited inventory exact 44 reader + 49 guarded writer + 20 open writer değilse InventoryDrift ile fail-closed kapanır.",
"S377 başarı outcome'u FiftiethWriterGuardedAwaitingRemaining'dir ve 44 guarded reader + 50/69 guarded writer + 19 open writer envanterini sabitler.",
"Production wrapper exact target_arch=aarch64, target_os=none, feature=board-rpi5 cfg kesişimindedir; host executor production CPU kimliği veya supported-profile runtime invocation diye sunulmaz.",
"acquire_s377_production_scheduler_writer_access, try_current_cpu_id ile gerçek per-CPU kimliğini türetir; caller-supplied production CPU parametresi yoktur ve CPU0 dışı InvalidCpu ile fail-closed kapanır.",
"Writer lease S247_PRODUCTION_WHOLE_SCHEDULER_ACCESS_GATE üzerinde try_acquire_exclusive_for_valid_cpu ile alınır; 44 reader ve önceki 49 writer'dan ayrı model gate veya yeni static yaratılmaz.",
"Host executor callback'i exact bir kez çağırır; canlı reader veya writer callback'ten önce ExclusiveBusy üretir, CPU1–CPU_MAX InvalidCpu verir ve callback error RAII lease'i exact bir kez bırakır.",
"S376 ve S377 host executor token'ları aynı gate üzerinde monoton ve distinct'tir; iki kapı tek transaction, tek sequence dispatcher veya tek Kod kartı olarak birleştirilmez.",
"Exact production giriş sınırı arch/aarch64/exceptions.rs içindeki handle_notification_wait_timeout fonksiyonudur; S377 rust_el0_sync_handler'ın başka IPC branch'lerini coverage'a katmaz.",
"notification_id=ctx.gpr[0], mask=ctx.gpr[1], timeout_ticks=ctx.gpr[6] ve now_tick=TICKS.load(Ordering::Acquire) immutable scalar snapshot'ları writer edinilmeden önce alınır.",
"mask==0 veya TickDeadline::try_after taşması InvalidDeadline ve terminal return ile S377 IRQ/writer acquisition'dan önce kapanır; scheduler graph ve shared gate değişmez.",
"current_task_id writer öncesinde owned waiter_task scalar'ına çevrilir; mutable scheduler aliasından kimlik reader'ı türetilmez.",
"current_task_cnode lookup exact notification_id için yapılır; CapabilityKind::Notification ve CapabilityRights::NOTIFICATION_WAIT birlikte writer öncesinde doğrulanır.",
"Authority yoksa set_ipc_error(ctx, IpcError::InvalidCapability) ve return S377 IRQ/writer acquisition'dan önce gerçekleşir; stale veya foreign grant writer'ı açmaz.",
"Dedicated s377_irq_guard mutable scheduler aliasından önce local interrupt re-entry'yi kapatır; s377_writer_access IRQ guard'dan sonra, alias kurulmadan önce alınır.",
"handle_notification_wait_timeout production cfg yolunda exact bir acquire_s377_production_scheduler_writer_access, bir mutable SCHEDULER aliası ve bir notification_wait_timeout_or_park forward'ı vardır.",
"Exact production forward ctx, user_sp, notification_id, authority.generation, mask, now_tick, timeout_ticks, s377_irq_guard ve s377_writer_access değerlerini bu sırayla taşır.",
"Non-RPi5 fallback exact tarihsel yedi çekirdek argümanı korur; yalnız production cfg yolu iki guard değerini ekler. S307 regresyonu iki yolun ortak argüman dizisini ayrı ayrı doğrular.",
"Scheduler notification_wait_timeout_or_park production imzası S377 IrqGuard ve G8lS377ProductionSchedulerWriterAccess değerlerini by-value alır; caller guard'ı çağrıdan sonra yeniden kullanamaz.",
"Scheduler existing IPC_TRANSACTION_LOCK ve notification/deadline registry lock sırasını korur; S377 bu lock'lara alternatif bir exclusion alanı eklemez.",
"Current waiter task nonzero ve canlı olmalıdır; current_notification_authority_is_live exact notification id/generation ve NOTIFICATION_WAIT right'ı transaction altında yeniden doğrular.",
"Notification object exact id/generation ile çözülür; stale object veya authority generation waiter publication'ını açmaz.",
"Matching pending bits immediate yolunda mask ile tüketilir; deadline veya waiter arm edilmeden owned Some(observed) sonucu döner ve RAII guard'lar normal dönüşte kapanır.",
"Park yolunda duplicate waiter reddedilir; NotificationWaiter::try_new exact waiter task, generation ve mask'i birlikte taşır.",
"Deadline slot ve notification waiter kapasitesi publication'dan önce doğrulanır; TableFull/QueueFull kısmi task state veya orphan deadline bırakmaz.",
"Notification waiter, exact deadline identity ve task Blocked publication'ı aynı scheduler transaction'ında, S377 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; comment veya model switch S377 release kanıtı diye sayılmaz.",
"Pre-switch closure exact drop(s377_writer_access) ardından drop(s377_irq_guard) taşır; global writer token başka task çalışırken pinlenmez.",
"Post-resume closure önce yeni IrqGuard, sonra acquire_s377_production_scheduler_writer_access ile yeni membership alır; eski token across-switch taşınmaz.",
"Durable wake payload scheduler.restore_current_ipc_context(ctx) ile yalnız resume üyeliğine yeniden katıldıktan sonra okunur.",
"Post-resume exact drop(s377_resume_writer_access) ardından drop(s377_resume_irq_guard) result split'ten önce çalışır; Ok(Some), Ok(None) ve Err publication dalları membership dışındadır.",
"Immediate ve error yollarında context switch yoktur ve ordinary RAII release kullanılır; parked yol explicit handoff/rejoin kullanır. İki yaşam döngüsü tek bir sahte long-lived token olarak modellenmez.",
"Tarihsel S307 yalnız writer-authority audit modelidir. Canlı S377 membership'i S307 sequence kimliğini veya kapanış anındaki 69-open-writer model snapshot'ını değiştirmez.",
"S376 notification_signal synchronous membership'i ayrı handler ve token kullanır; S377 wait-timeout park/resume yaşam döngüsünü S376 coverage'ına katmaz.",
"S378 handle_ipc_recv_timeout branch'i S377 acquire taşımadığı source assertion'ıyla ayrı tutulur; receive-timeout writer kapısı açık kalır.",
"CPU1 coverage service timer zincirinde S376'dan sonra ve S242 sender service'ten önce çalışır; yalnız pending S245 view inspect eder, request take veya production writer acquire etmez.",
"İlk production focused koşu 45/48 verdi. Üç RED davranış veya üyelik değil; current-task exact source spelling'i, 19-open-writer documentation literal'i ve comment'i context_switch sanan fazla geniş eşleşmeydi.",
"İlk production logu 5740 B / 6d64037e7312dbe0899e6ffac1ea3ec391cc898ce1afec9e6d66de6423cd7a6f SHA-256; artifact /tmp/aselsanos-s377-focused.KT21na/first-production.log'dur.",
"Üç assertion canlı source spelling, exact documentation ve gerçek machine call'a hizalandı; product behavior, inventory, release/rejoin veya fail-closed kapsamı gevşetilmedi.",
"Pre-format GREEN 48/48; 3927 B / f1a7f745adac7cd98a48fb1165ce2c16692479c2bee0a62ba876352261c8cf14 SHA-256'dır.",
"Final focused S377 48/48 PASS / 0 fail verdi. Log 3927 B / 547ca8c2016f72133bcb997e274eb6afaee6c570f29df3391f8a3bddb8b2237a; artifact /tmp/aselsanos-s377-focused.KT21na/green-final.log'dur.",
"Seçili 9 grupluk ilk koşu 234 PASS / 1 fail verdi; tek RED S307'nin production/non-production cfg forward'ını tek call sanan tarihsel source-count assertion'ıydı.",
"S307 common yedi argümanın iki cfg yolunda exact tekrarını, production yolunun iki S377 guard değerini ve non-production yolunun guard'sız kalmasını doğrulayacak şekilde sıkılaştırıldı; tarihsel authority/inventory kapsamı korunup product assertion gevşetilmedi.",
"Final seçili regresyon S377/S376/S307/S306/S373 ve dört notification/deadline runtime grubunda 9 grup / 235/235 PASS / 0 fail verdi.",
"Selected initial log 32128 B / 730b1ff054c2c34af72c5ba69d9e0ab4812f2ba840b113a4a6142113908f0321; final log 49995 B / c6a5cc83cbb66e8da03e9f27eec33f55f9ecfa8f5fd23c2b75a63df6cde1dfc7 SHA-256'dır. Artifact /tmp/aselsanos-s377-selected.c2DBtt'dir.",
"S238–S377 dependency listesi 141 gruptur. İki bağımsız resmi seri koşunun her biri 3261/3261 PASS / 0 fail verdi.",
"İki kanonik dependency özeti 23278 B ve f60014872f4e67da937f72aa7064dfbe264f25878df095185158412903fd125f SHA-256 ile byte-eşittir; artifact /tmp/aselsanos-s377-dependency-final.Ug7HE5'tir.",
"İlk kısmi dependency denemesi 109/141'de yalnız gereksiz seri compile maliyeti nedeniyle kanıt sayılmadan kesildi; aynı 141 hedef tek --no-run prebuild sonrasında iki resmi koşuda sıfırdan yürütüldü.",
"Exact yedi frozen G8h assertion dışındaki seri workspace 340 sonuç grubu / 5129 PASS / 0 fail / 7 filtered verdi.",
"Filtered workspace log 513281 B / 5429630717cf2cedb91f3b5121b032e54894c74399b0511e2c009f2ef1b25e93; summary 25720 B / dd688c9d61926d11399674a5d117132ba746be42c72ebc8f2515931cbf0febda'dır.",
"Filtresiz workspace exit 101 ile yalnız frozen S96 wiring_does_not_mutate_timer_gic_boot_or_expand_runtime_scope reddinde durdu: 293 grup / 4874 PASS / 1 fail; global workspace GREEN iddia edilmez.",
"Filtresiz log 481631 B / 68e1761be0fa2c10ec58471426ec6b89b852562e2c1d6c0f88de704eb7c5d6f7; summary 22194 B / 6704ff276880a3c5a3a63fb698ec78ee3ad1a07d4cd4bbe56f629b64525317de'dir. Workspace artifact /tmp/aselsanos-s377-workspace.750tXv'dir.",
"Fresh izole AArch64 profilleri 4/4 exit 0 verdi. board-qemu log 111450 B / 3872a09f43f7e332be0aa9514e242040386523d35c010d71db3db3806a5f2944, ELF 12217112 B / 1f22d5135c07099bd7903f0340b50b656be92dac6a73c090f456e273de6b0a0e'dir.",
"board-rpi4 log 150191 B / 5d921b475ffbb8ed63acfc3e387ee50a1050a72c5b72c1e2e80b07b32ddf6ac7, ELF 7421664 B / 1f4f28e4fa1700d53e2a1524a52c07dbefa3b42f2c91c799887a26b3eda87d25'tir.",
"board-rpi5 log 621007 B / 687c6c3c0288cdb4dd4532b20111123734b5b43f8c4350a6271461457d0c91a1, ELF 10869712 B / 46dacb69c3f907de173d5d93f10a278cc1510a4eb75cacd4b9bc0c08d6426b7a'dır.",
"board-rpi5+smp log 620949 B / e9cb2b10a20f5f8d904aab5cbae88aca4270ea725060e3d541f7f1b2e013f475, ELF 10888240 B / c4d227c049df48ca2b2bdd5551710f48a559e7c93fcd0c323d9c7e2b0a86e505'tir. Zero-warning iddiası yoktur.",
"CARGO_INCREMENTAL=0 make verify-qemu exit 0 verdi. 116354 B log / 7f6063286b474738260d043c817c97ed715748a4694c041a9a6fb966982ecd62 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 S377 production wrapper invocation kanıtı değildir; supported-profile runtime observations=0 kalır.",
"cargo fmt --all -- --check exit 0 ve boş çıktı verdi; global Rust format kapısı PASS'tir.",
"Source-bound Kod hedefi S1–S377 377/377 ayrı kapı, pre-S328 S1–S327 327/327, missing=none ve duplicate=0'dır. S377 kartı S307/S376/S378 kartlarıyla birleştirilmez.",
"S377 Code kartı tam handle_notification_wait_timeout Rust öğesi içinde exact acquire→single alias→by-value handoff odağını ve ayrı tam notification_wait_timeout_or_park release→switch→rejoin→restore yaşam döngüsünü yayımlar.",
"Ayrı S377 guard modülü, focused test 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–S377 aralığında 377/377 unique kapı / 1074 exact excerpt / pre-S328 S1–S327 327/327 / missing=none üretti; duplicate=0'dır. JSON 7921701 B / 25bee7000a72308c30383b607e2172af09d365ade9db023079319e7c84898bff dosya SHA-256 ve 760e175410d4c4598cf78040400c55ad0d1d5ee4c4d33decc8ee5f85e60b821d registry SHA-256 taşır.",
"Website kabulü 668/668 test, lint PASS, TypeScript exit 0 ve boş çıktı, 24/24 static route, 201 export dosyası verdi. Test logu 61957 B / 1c3b45acada18cfae66f5ec4219454891bb90c6c2ee7bd02661ce5cb60e17e35; build logu 1213 B / 61a4fede8cab1433e59fda9c70a255003106baf76c54ff623abc3f7eacfe1a3c SHA-256'dır.",
"Production/main deployment 024b7430 ile 116 uploaded + 84 existing = 200 asset yayımlandı. Deploy logu 1714 B / 9fd07f84da29fe29e027e8c164cd8b2580ad708e571321cab043e50fb5031b49 SHA-256; artifact /tmp/aselsanos-s377-web-initial.3or5UM'dir.",
"Cache-busted custom-domain /code/ 22610559 B / 1c394066383b2f3b6fd0660b86d8cbeac546d83d5e4d2ea84ab8c19b207eb898, /operations/ 13397572 B / c4264d8dd6b05e63c7713c4ef93b53b22fd7267f62f4df7267317f6a7765cb70, /timeline/ 5346556 B / 4046d351de800d0162c75cd634402d9862879ca32fafa2b5989362da95ae2609 ve /yol-haritasi/ 5346304 B / c13374d21bcf729281486b749aa6de71d33d33a4675718e0eacb2bad9a773b88 SHA-256 ile HTTP 200 ve deploy-sonrası yerel out'a raw byte-exact PASS verdi.",
"Canlı /code/ no-transform header'ı taşır; literal data-code-gate sayımı 377/377 unique, pre-S328 S1–S327 327/327, S1=1, S327=1, S328=1, S377=1 ve S378=0'dır. Timeline data-gate-policy sayımı 216'dır.",
"Immutable 024b7430 hostname probe'u 15 saniyede curl exit 28 / HTTP 000 verdi; custom-domain dört-rota HTTP 200 ve byte-exact PASS bunun yerine geçirilmez.",
"S377 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_S377=NO.",
"S377 bazlı bağlayıcı olmayan planlama görünümü R1 S377–S407, R2 S432–S482, R3 S561+, kaba S537–S587 ve risk paylı merkez yaklaşık S562'dir; ü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_s377_el0_notification_wait_timeout_writer_guard_integration -- --test-threads=1",
"run S377, S376, S307, S306, S373 and four notification/deadline runtime groups serially",
"run four fresh isolated AArch64 profiles; run S238-S377 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-s377-focused-source-contract",
title: "S377 focused notification wait-timeout writer membership",
commandLines: [
"cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration -- --test-threads=1",
],
outputLines: [
"initial result: compile RED; separate S377 module/source registration and production boundary absent",
"first production result: 45 passed; 3 exact source/documentation assertions failed",
"final result: ok; S377 focused 1 group / 48 passed / 0 failed",
"shared S247 gate: 44 guarded readers + 50/69 guarded writers; 19 writers open",
"inputs/deadline/identity/CNode authority < IRQ < writer < publish/park < release < switch < rejoin < durable restore < result",
"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-s377-selected-regression",
title:
"S377 selected notification authority/handoff/runtime regression",
commandLines: [
"run S377, S376, S307, S306, S373 and four notification/deadline runtime groups serially",
],
outputLines: [
"initial result: 234 passed; 1 historical S307 cfg-call-count assertion failed",
"S307 aligned to two exact cfg forwards while preserving the common argument and authority contract",
"final result: 9 groups / 235 passed / 0 failed",
"S377 48/48; S376 49/49; S307 15/15; S306 15/15; S373 71/71; runtime groups 37/37",
"final log 49995 bytes; SHA-256 c6a5cc83cbb66e8da03e9f27eec33f55f9ecfa8f5fd23c2b75a63df6cde1dfc7",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s377-full-acceptance",
title: "S377 four-profile, dependency, workspace and QEMU acceptance",
commandLines: [
"run four fresh isolated AArch64 profile builds with CARGO_INCREMENTAL=0",
"run S238-S377 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: 141 groups / 3261/3261 twice; 23278-byte canonical summaries byte-equal",
"filtered workspace: 340 groups / 5129 PASS / 0 fail / 7 frozen filtered",
"unfiltered workspace: exit 101; 293 groups / 4874 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 S377 RPi5 runtime observation",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8l-s377-production-publication",
title: "S377 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: [
"target source registry: S1-S377 377/377 unique gates; pre-S328 S1-S327 327/327; missing=none; duplicate=0",
"S377 code layers: complete handle_notification_wait_timeout + exact guard handoff focus + complete scheduler park/resume lifecycle + S247 guard + focused test + Operations object",
"source registry: 1074 exact excerpts; JSON 7921701 bytes; registry SHA-256 760e175410d4c4598cf78040400c55ad0d1d5ee4c4d33decc8ee5f85e60b821d",
"website: 668/668 PASS; lint PASS; TypeScript exit 0; static routes 24/24; export files=201",
"production/main deployment 024b7430; 116 uploaded + 84 existing = 200 assets",
"custom-domain four routes HTTP 200 and raw byte-exact=true; /code no-transform=true",
"live /code: 377/377 unique; pre-S328 327/327; S1=1; S327=1; S328=1; S377=1; S378=0",
"immutable 024b7430 hostname curl exit 28 / HTTP 000; custom-domain PASS is authoritative",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"TAM ÇIKTI kayıtları S377 focused 48/48, seçili 9 grup / 235 PASS, iki kez 141 grup / 3261 PASS, filtreli workspace 340 grup / 5129 PASS, filtresiz yalnız frozen-S96 RED, dört AArch64 profil 4/4 ve ortak QEMU kabulünü ayrı oturumlar halinde taşır. Production/main deployment 024b7430 ve dört özel-alan-adı rotasının raw byte-exact readback'i PASS'tir. `/code` S1–S377 aralığını 377 ayrı source-bound kart olarak yayımlar; S1–S327 tarihsel kapsam 327/327 tekildir. S378 ayrı ve açık kalır.",
limitations: [
"S377 yalnız EL0 notification wait-timeout mutable scheduler sınırını kapatır; S378 IPC receive-timeout ve kalan 19 writer açıktır.",
"Production provider authority=0, whole-scheduler exclusion=false ve S244 admission publication yoktur.",
"Supported-profile S377 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_S377=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: 0d2fcb04b2d4…file sha256: 9726dbf00f84…
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s377_el0_notification_wait_timeout_writer_guard_integration -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S377-EL0-Notification-Wait-Timeout-Writer-Guard-Integration-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06