use crate::cap::cnode; use crate::cap::object::ObjectTag; use crate::cap::ops; use crate::cap::pool::POOL; use crate::cap::table::{CapRef, Rights}; use crate::error::KernelError; use crate::ipc::{IpcOutcome, endpoint, notification}; use crate::proc::context::IpcMessage; use crate::proc::{BlockedReason, PROCESSES, ProcessState}; use crate::tests::helpers::{alloc_endpoint_cap, alloc_notification_cap}; use crate::types::Priority; use lancer_core::object_layout::{EndpointObject, NotificationObject}; const MAX_NOTIFICATION_WAITERS: usize = 4; crate::kernel_test!( fn notification_waiter_overflow() { let (id, generation, cap) = alloc_notification_cap(); let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let pids: [crate::types::Pid; 5] = core::array::from_fn(|_| { let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); ptable.simulate_dispatch(pid); pid }); (0..MAX_NOTIFICATION_WAITERS).for_each(|i| { let result = notification::do_wait(&cap, pids[i], &mut ptable); assert!( matches!(result, Ok(IpcOutcome::Blocked)), "waiter {} should block", i ); }); let overflow_result = notification::do_wait(&cap, pids[4], &mut ptable); assert!( matches!(overflow_result, Err(KernelError::ResourceExhausted)), "5th waiter should fail with ResourceExhausted" ); assert!( ptable[pids[4]].state() != ProcessState::Blocked, "5th process should NOT be left in Blocked state after rollback" ); pids.iter().for_each(|&pid| { ptable.destroy(pid, &mut allocator); }); let _ = POOL.lock().dec_ref_phys(id, generation); } ); crate::kernel_test!( fn signal_zero_bits_preserves_word() { let (id, generation, cap) = alloc_notification_cap(); let mut ptable = PROCESSES.lock(); { let mut pool = POOL.lock(); let notif = pool .write_as::(id, generation) .expect("write notification"); notif.word = 0x0F; } notification::do_signal(&cap, 0x00, &mut ptable).expect("signal zero bits"); let pool = POOL.lock(); let notif = pool .read_as::(id, generation) .expect("read notification"); assert!( notif.word == 0x0F, "word should be preserved after signal(0)" ); drop(pool); drop(ptable); let _ = POOL.lock().dec_ref_phys(id, generation); } ); crate::kernel_test!( fn signal_u64_max_sets_all_bits() { let (id, generation, cap) = alloc_notification_cap(); let mut ptable = PROCESSES.lock(); notification::do_signal(&cap, u64::MAX, &mut ptable).expect("signal max"); let pool = POOL.lock(); let notif = pool .read_as::(id, generation) .expect("read notification"); assert!( notif.word == u64::MAX, "word should be u64::MAX after signal(u64::MAX)" ); drop(pool); drop(ptable); let _ = POOL.lock().dec_ref_phys(id, generation); } ); crate::kernel_test!( fn revoke_unblocks_mixed_caller_and_sender() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); let sender_created = ptable.allocate(&mut allocator).expect("alloc sender"); let caller_created = ptable.allocate(&mut allocator).expect("alloc caller"); ptable.start(owner_created).expect("start owner"); ptable.start(sender_created).expect("start sender"); ptable.start(caller_created).expect("start caller"); let owner_pid = owner_created.pid(); let sender_pid = sender_created.pid(); let caller_pid = caller_created.pid(); crate::tests::helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("cnode coords"); let (ep_id, ep_gen) = { let mut pool = POOL.lock_after(&ptable); let (ep_id, ep_gen) = crate::tests::helpers::alloc_endpoint(&mut pool).expect("alloc ep"); let cap = CapRef::new(ObjectTag::Endpoint, ep_id, Rights::ALL, ep_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); (ep_id, ep_gen) }; ptable.simulate_dispatch(sender_pid); ptable.exec_mut(sender_pid).unwrap().ipc_message = IpcMessage::from_regs([0x5E, 0, 0, 0, 0, 0]); let sender_blocked = ptable[sender_pid] .block_on(BlockedReason::Sending(ep_id, ep_gen)) .expect("block sender"); ptable.simulate_dispatch(caller_pid); ptable.exec_mut(caller_pid).unwrap().ipc_message = IpcMessage::from_regs([0xCA, 0, 0, 0, 0, 0]); let caller_blocked = ptable[caller_pid] .block_on(BlockedReason::Calling(ep_id, ep_gen)) .expect("block caller"); { let mut pool = POOL.lock_after(&ptable); let ep = pool .write_as::(ep_id, ep_gen) .expect("get ep"); let mut senders = endpoint::load_senders(ep); endpoint::enqueue(&mut senders, sender_blocked, &mut ptable).expect("enqueue sender"); endpoint::enqueue(&mut senders, caller_blocked, &mut ptable).expect("enqueue caller"); let ep = pool .write_as::(ep_id, ep_gen) .expect("get ep for store"); endpoint::store_senders(ep, &senders); } ops::revoke_via_cnode(owner_pid, address, &mut ptable).expect("revoke"); assert!( ptable[sender_pid].state() == ProcessState::Ready, "sender should be unblocked after revoke" ); assert!( ptable.exec(sender_pid).unwrap().saved_context.rax == KernelError::InvalidObject.to_errno() as u64, "sender rax should be InvalidObject errno" ); assert!( ptable[caller_pid].state() == ProcessState::Ready, "caller should be unblocked after revoke" ); assert!( ptable.exec(caller_pid).unwrap().saved_context.rax == KernelError::InvalidObject.to_errno() as u64, "caller rax should be InvalidObject errno" ); ptable.destroy(owner_pid, &mut allocator); ptable.destroy(sender_pid, &mut allocator); ptable.destroy(caller_pid, &mut allocator); } ); crate::kernel_test!( fn revoke_empty_slot_returns_error() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let result = ops::revoke_via_cnode(pid, 100, &mut ptable); assert!( matches!(result, Err(KernelError::SlotEmpty)), "revoke on empty slot should return SlotEmpty" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn double_destroy_is_idempotent() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); ptable.destroy(pid, &mut allocator); ptable.destroy(pid, &mut allocator); assert!( ptable.get(pid).is_none(), "slot should be freed after double destroy" ); } ); crate::kernel_test!( fn destroy_cleans_up_endpoint_queues() { let (ep_id, ep_gen, cap) = alloc_endpoint_cap(); let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let sender_created = ptable.allocate(&mut allocator).expect("alloc sender"); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); ptable.start(sender_created).expect("start sender"); ptable.start(owner_created).expect("start owner"); let sender_pid = sender_created.pid(); let owner_pid = owner_created.pid(); crate::tests::helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("cnode coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 0, depth, gv, gb, cap) .expect("insert"); } ptable.simulate_dispatch(sender_pid); ptable.exec_mut(sender_pid).unwrap().ipc_message = IpcMessage::from_regs([0xDE, 0, 0, 0, 0, 0]); let blocked = ptable[sender_pid] .block_on(BlockedReason::Sending(ep_id, ep_gen)) .expect("block sender"); { let mut pool = POOL.lock_after(&ptable); let ep = pool .write_as::(ep_id, ep_gen) .expect("get ep"); let mut senders = endpoint::load_senders(ep); endpoint::enqueue(&mut senders, blocked, &mut ptable).expect("enqueue"); let ep = pool .write_as::(ep_id, ep_gen) .expect("get ep for store"); endpoint::store_senders(ep, &senders); } ptable.destroy(sender_pid, &mut allocator); { let pool = POOL.lock_after(&ptable); let ep = pool .read_as::(ep_id, ep_gen) .expect("read ep"); let senders = endpoint::load_senders(ep); assert!( senders.is_empty(), "endpoint sender queue should be empty after destroying the sender" ); } ptable.destroy(owner_pid, &mut allocator); } ); crate::kernel_test!( fn process_table_exhaustion() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let mut pids = crate::static_vec::StaticVec::::new(); let mut count = 0usize; (0..crate::types::MAX_PIDS).for_each(|_| { if let Some(created) = ptable.allocate(&mut allocator) { ptable.start(created).expect("start"); let _ = pids.push(created.pid()); count += 1; } }); assert!(count > 0, "should have allocated at least 1 process"); let overflow = ptable.allocate(&mut allocator); assert!( overflow.is_none(), "should return None when process table is full" ); pids.as_slice().iter().for_each(|&pid| { ptable.destroy(pid, &mut allocator); }); } ); crate::kernel_test!( fn block_already_blocked_fails() { let (ep_id, ep_gen, _cap) = alloc_endpoint_cap(); let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); ptable.simulate_dispatch(pid); let _blocked = ptable[pid] .block_on(BlockedReason::Sending(ep_id, ep_gen)) .expect("first block"); let second = ptable[pid].block_on(BlockedReason::Receiving(ep_id, ep_gen)); assert!( matches!(second, Err(KernelError::BadState)), "blocking an already-blocked process should fail with BadState" ); ptable.destroy(pid, &mut allocator); let _ = POOL.lock().dec_ref_phys(ep_id, ep_gen); } ); crate::kernel_test!( fn revoke_without_revoke_right_fails() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 50u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("cnode coords"); { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, address, depth, gv, gb, ObjectTag::Endpoint, ) .expect("create endpoint"); } { let pool = POOL.lock_after(&ptable); let old_cap = cnode::resolve_and_clear(&pool, cnode_id, cnode_gen, address, depth, gv, gb) .expect("clear slot"); let modified = old_cap.with_rights(Rights::READ | Rights::WRITE); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, modified) .expect("re-insert"); } let result = ops::revoke_via_cnode(pid, address, &mut ptable); assert!( matches!(result, Err(KernelError::InsufficientRights)), "revoke without REVOKE right should fail" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn derive_self_slot_returns_occupied() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 60u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("cnode coords"); { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, address, depth, gv, gb, ObjectTag::Endpoint, ) .expect("create source"); } { let mut pool = POOL.lock_after(&ptable); let result = ops::derive_via_cnode( &mut pool, cnode_id, cnode_gen, address, address, depth, gv, gb, Rights::READ, ); assert!( matches!(result, Err(KernelError::SlotOccupied)), "derive into same slot should return SlotOccupied, got {:?}", result ); } { let pool = POOL.lock_after(&ptable); let cap = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, address, depth, gv, gb) .expect("read slot"); assert!( cap.tag() == ObjectTag::Endpoint, "original cap should still be intact" ); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn identify_after_revoke_fails() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 70u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("cnode coords"); { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, address, depth, gv, gb, ObjectTag::Notification, ) .expect("create notification"); } ops::revoke_via_cnode(pid, address, &mut ptable).expect("revoke"); { let pool = POOL.lock_after(&ptable); let result = ops::identify_via_cnode(&pool, cnode_id, cnode_gen, address, depth, gv, gb); assert!( matches!(result, Err(KernelError::SlotEmpty)), "identify after revoke should return SlotEmpty" ); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn priority_boost_only_increases() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); ptable[pid].boost_effective_priority(Priority::new(200)); assert!( ptable[pid].effective_priority() == Priority::new(200), "priority should be boosted to 200" ); ptable[pid].boost_effective_priority(Priority::new(150)); assert!( ptable[pid].effective_priority() == Priority::new(200), "lower boost should not decrease effective priority" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn reset_effective_restores_base() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let created = ptable.allocate(&mut allocator).expect("alloc"); ptable.start(created).expect("start"); let pid = created.pid(); let base = ptable[pid].effective_priority(); ptable[pid].boost_effective_priority(Priority::new(200)); assert!( ptable[pid].effective_priority() == Priority::new(200), "should be boosted" ); ptable[pid].reset_effective_priority(); assert!( ptable[pid].effective_priority() == base, "reset should restore base priority" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn reply_target_cleared_on_destroy() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let a_created = ptable.allocate(&mut allocator).expect("alloc A"); let b_created = ptable.allocate(&mut allocator).expect("alloc B"); ptable.start(a_created).expect("start A"); ptable.start(b_created).expect("start B"); let a_pid = a_created.pid(); let b_pid = b_created.pid(); ptable.exec_mut(a_pid).unwrap().reply_target = Some(b_pid); ptable.destroy(b_pid, &mut allocator); assert!( ptable.exec(a_pid).unwrap().reply_target.is_none(), "reply_target for pid {} should be None after target pid {} was destroyed", a_pid.raw(), b_pid.raw() ); ptable.destroy(a_pid, &mut allocator); } );