use crate::cap::cnode; use crate::cap::object::{ EndpointData, MAX_NOTIFICATION_WAITERS, NotificationData, ObjectData, 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::types::{Pid, Priority}; fn bootstrap_test_cnode(pid: Pid, ptable: &mut crate::proc::ProcessManager) { let size_bits = crate::proc::ROOT_CNODE_SIZE_BITS; let allocator = &crate::mem::phys::BitmapFrameAllocator; let cnode_data = crate::cap::cnode::create_cnode(size_bits, allocator).expect("create cnode"); let frame_count = cnode_data.frame_count; let (cnode_id, cnode_gen) = POOL.lock().allocate(ObjectData::CNode(cnode_data)).expect("alloc cnode"); let proc = ptable.get_mut(pid).expect("get proc"); proc.root_cnode = Some((cnode_id, cnode_gen)); proc.cnode_depth = size_bits; proc.charge_frames(frame_count as u16).expect("charge frames"); } fn alloc_endpoint() -> (crate::types::ObjectId, crate::types::Generation, CapRef) { let (id, generation) = POOL .lock() .allocate(ObjectData::Endpoint(EndpointData::new())) .expect("alloc endpoint"); let cap = CapRef::new(ObjectTag::Endpoint, id, Rights::ALL, generation); (id, generation, cap) } fn alloc_notification() -> (crate::types::ObjectId, crate::types::Generation, CapRef) { let (id, generation) = POOL .lock() .allocate(ObjectData::Notification(NotificationData::new())) .expect("alloc notification"); let cap = CapRef::new(ObjectTag::Notification, id, Rights::ALL, generation); (id, generation, cap) } crate::kernel_test!( fn notification_waiter_overflow() { let (id, generation, cap) = alloc_notification(); 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[pid] .transition_to(ProcessState::Running) .expect("-> Running"); 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(id, generation); } ); crate::kernel_test!( fn signal_zero_bits_preserves_word() { let (id, generation, cap) = alloc_notification(); let mut ptable = PROCESSES.lock(); { let mut pool = POOL.lock(); pool.get_mut(id, generation) .unwrap() .as_notification_mut() .unwrap() .word = 0x0F; } notification::do_signal(&cap, 0x00, &mut ptable).expect("signal zero bits"); let pool = POOL.lock(); let notif = pool.get(id, generation).unwrap().as_notification().unwrap(); assert!( notif.word == 0x0F, "word should be preserved after signal(0)" ); drop(pool); drop(ptable); let _ = POOL.lock().dec_ref(id, generation); } ); crate::kernel_test!( fn signal_u64_max_sets_all_bits() { let (id, generation, cap) = alloc_notification(); let mut ptable = PROCESSES.lock(); notification::do_signal(&cap, u64::MAX, &mut ptable).expect("signal max"); let pool = POOL.lock(); let notif = pool.get(id, generation).unwrap().as_notification().unwrap(); assert!( notif.word == u64::MAX, "word should be u64::MAX after signal(u64::MAX)" ); drop(pool); drop(ptable); let _ = POOL.lock().dec_ref(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(); bootstrap_test_cnode(owner_pid, &mut ptable); let address = 0u64; let (cnode_id, cnode_gen, depth) = 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) = pool .allocate(ObjectData::Endpoint(EndpointData::new())) .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, cap) .expect("insert cap"); (ep_id, ep_gen) }; ptable[sender_pid] .transition_to(ProcessState::Running) .expect("sender -> Running"); ptable[sender_pid].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[caller_pid] .transition_to(ProcessState::Running) .expect("caller -> Running"); ptable[caller_pid].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 .get_mut(ep_id, ep_gen) .and_then(|d| d.as_endpoint_mut()) .expect("get ep"); endpoint::enqueue(&mut ep.senders, sender_blocked, &mut ptable) .expect("enqueue sender"); endpoint::enqueue(&mut ep.senders, caller_blocked, &mut ptable) .expect("enqueue caller"); } 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[sender_pid].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[caller_pid].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(); 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(); 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(); bootstrap_test_cnode(owner_pid, &mut ptable); let (cnode_id, cnode_gen, depth) = 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, cap) .expect("insert"); } ptable[sender_pid] .transition_to(ProcessState::Running) .expect("sender -> Running"); ptable[sender_pid].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 .get_mut(ep_id, ep_gen) .and_then(|d| d.as_endpoint_mut()) .expect("get ep"); endpoint::enqueue(&mut ep.senders, blocked, &mut ptable).expect("enqueue"); } ptable.destroy(sender_pid, &mut allocator); { let pool = POOL.lock_after(&ptable); let ep = pool.get(ep_id, ep_gen).unwrap().as_endpoint().unwrap(); assert!( ep.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(); 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] .transition_to(ProcessState::Running) .expect("-> Running"); 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(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(); bootstrap_test_cnode(pid, &mut ptable); let address = 50u64; let (cnode_id, cnode_gen, depth) = 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, 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) .expect("clear slot"); let modified = old_cap.with_rights(Rights::READ | Rights::WRITE); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, 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(); bootstrap_test_cnode(pid, &mut ptable); let address = 60u64; let (cnode_id, cnode_gen, depth) = 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, 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, 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) .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(); bootstrap_test_cnode(pid, &mut ptable); let address = 70u64; let (cnode_id, cnode_gen, depth) = 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, 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); 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[a_pid].reply_target = Some(b_pid); ptable.destroy(b_pid, &mut allocator); assert!( ptable[a_pid].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); } );