use crate::cap::cnode; use crate::cap::pool::POOL; use crate::cap::table::{CapRef, Rights}; use crate::error::KernelError; use crate::mem::phys::BitmapFrameAllocator; use crate::proc::PROCESSES; use lancer_core::object_layout::KernelObject; use lancer_core::object_tag::ObjectTag; crate::kernel_test!( fn scale_10k_pids() { let mut allocator = BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let mut allocated = crate::static_vec::StaticVec::::new(); let target = 256; (0..target).for_each(|_| { if let Some(created) = ptable.allocate(&mut allocator) { let _ = allocated.push(created.pid()); } }); let count = allocated.len(); assert!(count >= 200, "expected at least 200 PIDs, got {}", count); allocated.as_slice().iter().for_each(|&pid| { assert!( ptable.get(pid).is_some(), "allocated PID {} should be accessible", pid.raw() ); }); (1..count).for_each(|i| { (0..i).for_each(|j| { assert!( allocated.as_slice()[i] != allocated.as_slice()[j], "duplicate PID at indices {} and {}", i, j ); }); }); allocated.as_slice().iter().for_each(|&pid| { ptable.destroy(pid, &mut allocator); }); let mut reallocated = crate::static_vec::StaticVec::::new(); (0..target).for_each(|_| { if let Some(created) = ptable.allocate(&mut allocator) { let _ = reallocated.push(created.pid()); } }); assert!( reallocated.len() >= 200, "after free, expected at least 200 re-allocated PIDs, got {}", reallocated.len() ); reallocated.as_slice().iter().for_each(|&pid| { ptable.destroy(pid, &mut allocator); }); } ); crate::kernel_test!( fn scale_create_destroy_loop() { let ptable = PROCESSES.lock(); let mut pool = POOL.lock_after(&ptable); let baseline = pool.active_count(); let iterations = 50; (0..iterations).for_each(|round| { let (ep_id, ep_gen) = crate::tests::helpers::alloc_endpoint(&mut pool) .unwrap_or_else(|e| panic!("alloc endpoint round {}: {:?}", round, e)); if let Some((phys, _)) = pool.dec_ref_phys(ep_id, ep_gen) { crate::cap::kernel_objects::free_slot(phys); } }); let after = pool.active_count(); assert!( after == baseline, "object leak: baseline={}, after={}, delta={}", baseline, after, after as i64 - baseline as i64 ); } ); crate::kernel_test!( fn exhaust_retype() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_small_untyped(&ptable, 12); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); let mut pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); let mut dest = 100u64; let mut created_count = 0u32; let mut hit_exhaustion = false; (0..200u32).for_each(|_| { match crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, dest, depth, gv, gb, 1, ) { Ok(()) => { created_count += 1; dest += 1; } Err(KernelError::ResourceExhausted) => { hit_exhaustion = true; } Err(_) => { hit_exhaustion = true; } } }); assert!(hit_exhaustion, "should have hit resource exhaustion"); assert!(created_count > 0, "should have created at least one object"); drop(pool); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn interleaved_retype_revoke() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_untyped(&ptable, false); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); } let rounds = 10u32; (0..rounds).for_each(|round| { let dest_base = 100u64; let obj_count = 5u32; { let mut pool = POOL.lock_after(&ptable); let result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, dest_base, depth, gv, gb, obj_count, ); assert!( result.is_ok(), "retype round {} failed: {:?}", round, result.err() ); } { let result = crate::cap::ops::revoke_via_cnode(pid, 900, &mut ptable); assert!( result.is_ok(), "revoke round {} failed: {:?}", round, result.err() ); } }); let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn scale_10k_pid_cycles() { let mut allocator = BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let rounds = 40u32; let batch = 256usize; (0..rounds).for_each(|round| { let mut allocated = crate::static_vec::StaticVec::::new(); (0..batch).for_each(|_| { if let Some(created) = ptable.allocate(&mut allocator) { let _ = allocated.push(created.pid()); } }); let count = allocated.len(); assert!( count >= 200, "round {}: expected >= 200 PIDs, got {}", round, count ); allocated.as_slice().iter().for_each(|&pid| { assert!( ptable.get(pid).is_some(), "round {}: PID {} inaccessible", round, pid.raw() ); }); allocated.as_slice().iter().for_each(|&pid| { ptable.destroy(pid, &mut allocator); }); }); match ptable.allocate(&mut allocator) { Some(created) => { ptable.destroy(created.pid(), &mut allocator); } None => panic!("fresh allocation after 10K+ cycles failed"), } } ); crate::kernel_test!( fn scale_10k_object_cycles() { let ptable = PROCESSES.lock(); let mut pool = POOL.lock_after(&ptable); let baseline = pool.active_count(); let rounds = 40u32; let batch = 256u32; (0..rounds).for_each(|round| { let mut ids = crate::static_vec::StaticVec::< (crate::types::ObjPhys, crate::types::Generation), 256, >::new(); (0..batch).for_each(|i| match crate::tests::helpers::alloc_endpoint(&mut pool) { Ok((eid, egen)) => { let _ = ids.push((eid, egen)); } Err(e) => panic!("round {} alloc {}: {:?}", round, i, e), }); ids.as_slice().iter().for_each(|&(eid, egen)| { if let Some((phys, _)) = pool.dec_ref_phys(eid, egen) { crate::cap::kernel_objects::free_slot(phys); } }); }); let after = pool.active_count(); assert!( after == baseline, "object leak after 10K+ cycles: baseline={}, after={}", baseline, after ); } ); crate::kernel_test!( fn scale_10k_retype_cycles() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_small_untyped(&ptable, 16); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); } let rounds = 640u32; let batch_size = 16u32; let mut total_retyped = 0u64; (0..rounds).for_each(|round| { { let mut pool = POOL.lock_after(&ptable); let result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, 100, depth, gv, gb, batch_size, ); match result { Ok(()) => { total_retyped += batch_size as u64; } Err(KernelError::ResourceExhausted) => {} Err(e) => panic!("retype round {} failed: {:?}", round, e), } } { let result = crate::cap::ops::revoke_via_cnode(pid, 900, &mut ptable); assert!( result.is_ok(), "revoke round {} failed: {:?}", round, result.err() ); } }); assert!( total_retyped >= 10_000, "expected 10K+ cumulative retypes, got {}", total_retyped ); let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn exhaust_kernel_object_slots() { let ptable = PROCESSES.lock(); let mut pool = POOL.lock_after(&ptable); let mut allocated = crate::static_vec::StaticVec::< (crate::types::ObjPhys, crate::types::Generation, u64), 512, >::new(); let mut count = 0u32; while let Ok((eid, egen)) = crate::tests::helpers::alloc_endpoint(&mut pool) { if allocated.push((eid, egen, eid.raw())).is_err() { if let Some((phys, _)) = pool.dec_ref_phys(eid, egen) { crate::cap::kernel_objects::free_slot(phys); } break; } count += 1; } assert!( count >= 100, "expected at least 100 kernel object slots, got {}", count ); allocated.as_slice().iter().for_each(|&(eid, egen, _)| { if let Some((phys, _)) = pool.dec_ref_phys(eid, egen) { crate::cap::kernel_objects::free_slot(phys); } }); let (eid, egen) = crate::tests::helpers::alloc_endpoint(&mut pool) .unwrap_or_else(|e| panic!("alloc after free-all failed: {:?}", e)); if let Some((phys, _)) = pool.dec_ref_phys(eid, egen) { crate::cap::kernel_objects::free_slot(phys); } } ); crate::kernel_test!( fn large_child_list_revoke() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_small_untyped(&ptable, 20); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); } let mut total_children = 0u32; let mut dest = 100u64; let batch = 64u32; loop { let mut pool = POOL.lock_after(&ptable); match crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, dest, depth, gv, gb, batch, ) { Ok(()) => { total_children += batch; dest += batch as u64; } Err(KernelError::ResourceExhausted) => break, Err(KernelError::SlotOccupied) => break, Err(e) => panic!("retype failed at dest {}: {:?}", dest, e), } } assert!( total_children > 100, "expected > 100 children from 1MB untyped, got {}", total_children ); { let result = crate::cap::ops::revoke_via_cnode(pid, 900, &mut ptable); assert!(result.is_ok(), "revoke failed: {:?}", result.err()); } { let pool = POOL.lock_after(&ptable); let ut = pool .read_as::(ut_id, ut_gen) .expect("untyped still valid"); assert!( ut.watermark == 0, "watermark must reset after revoke, got {}", ut.watermark ); assert!( ut.child_count == 0, "child_count must be 0 after revoke, got {}", ut.child_count ); } let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn object_id_recycling_generation_increases() { let ptable = PROCESSES.lock(); let mut pool = POOL.lock_after(&ptable); let mut last_gen_by_slot = crate::static_vec::StaticVec::<(u64, u32), 256>::new(); let cycles = 100u32; (0..cycles).for_each(|round| { let (eid, egen) = crate::tests::helpers::alloc_endpoint(&mut pool) .unwrap_or_else(|e| panic!("alloc round {}: {:?}", round, e)); let raw_id = eid.raw(); let gen_val = egen.raw(); let existing = last_gen_by_slot .as_mut_slice() .iter_mut() .find(|(slot_id, _)| *slot_id == raw_id); match existing { Some(entry) => { assert!( gen_val > entry.1, "round {}: slot {} gen {} not > prev gen {}", round, raw_id, gen_val, entry.1 ); entry.1 = gen_val; } None => { let _ = last_gen_by_slot.push((raw_id, gen_val)); } } if let Some((phys, _)) = pool.dec_ref_phys(eid, egen) { crate::cap::kernel_objects::free_slot(phys); } }); } ); crate::kernel_test!( fn interleaved_retype_revoke_heavy() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_untyped(&ptable, false); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); } let rounds = 100u32; (0..rounds).for_each(|round| { { let mut pool = POOL.lock_after(&ptable); let result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, 100, depth, gv, gb, 5, ); assert!( result.is_ok(), "retype round {} failed: {:?}", round, result.err() ); } { let result = crate::cap::ops::revoke_via_cnode(pid, 900, &mut ptable); assert!( result.is_ok(), "revoke round {} failed: {:?}", round, result.err() ); } }); { let pool = POOL.lock_after(&ptable); let ut = pool .read_as::(ut_id, ut_gen) .expect("untyped valid"); assert!(ut.watermark == 0, "watermark leak after 100 rounds"); assert!(ut.child_count == 0, "child leak after 100 rounds"); } let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn mixed_tag_retype_revoke() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (ut_id, ut_gen, _) = crate::tests::helpers::allocate_untyped(&ptable, false); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); let ut_cap = CapRef::new(ObjectTag::Untyped, ut_id, Rights::ALL, ut_gen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 900, depth, gv, gb, ut_cap) .expect("insert ut"); } { let mut pool = POOL.lock_after(&ptable); let ep_result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, 100, depth, gv, gb, 10, ); assert!( ep_result.is_ok(), "retype endpoints failed: {:?}", ep_result.err() ); let notif_result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::Notification, 0, cnode_id, cnode_gen, 110, depth, gv, gb, 10, ); assert!( notif_result.is_ok(), "retype notifications failed: {:?}", notif_result.err() ); let sched_result = crate::cap::retype::kernel_retype( &mut pool, None, ut_id, ut_gen, ObjectTag::SchedContext, 0, cnode_id, cnode_gen, 120, depth, gv, gb, 5, ); assert!( sched_result.is_ok(), "retype sched contexts failed: {:?}", sched_result.err() ); } { let pool = POOL.lock_after(&ptable); let ut = pool .read_as::(ut_id, ut_gen) .expect("untyped valid"); assert!( ut.child_count == 25, "expected 25 children, got {}", ut.child_count ); } { let result = crate::cap::ops::revoke_via_cnode(pid, 900, &mut ptable); assert!(result.is_ok(), "revoke failed: {:?}", result.err()); } { let pool = POOL.lock_after(&ptable); let ut = pool .read_as::(ut_id, ut_gen) .expect("untyped valid after revoke"); assert!(ut.watermark == 0, "watermark must reset"); assert!(ut.child_count == 0, "child_count must be 0"); } let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn nested_untyped_retype_then_revoke_parent() { let mut ptable = PROCESSES.lock(); let mut allocator = BitmapFrameAllocator; let created = ptable.allocate(&mut allocator).expect("alloc proc"); ptable.start(created).expect("start"); let pid = created.pid(); crate::tests::helpers::bootstrap_test_cnode(pid, &mut ptable); let (parent_ut_id, parent_ut_gen, _) = crate::tests::helpers::allocate_untyped(&ptable, false); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); let child_size_bits = 14u8; let child_frames = 1usize << (child_size_bits - 12); let child_phys = BitmapFrameAllocator .allocate_contiguous(child_frames) .expect("alloc child untyped backing"); let child_virt = crate::mem::addr::phys_to_virt(child_phys); unsafe { core::ptr::write_bytes(child_virt.as_mut_ptr::(), 0, child_frames * 4096); } let header = lancer_core::header::KernelObjectHeader::new(ObjectTag::Untyped, 0, 64); let mut child_ut = lancer_core::object_layout::UntypedObject::init_default(header); child_ut.phys_base = child_phys.as_u64(); child_ut.size_bits = child_size_bits; child_ut.is_device = 0; let child_slot_phys = crate::cap::kernel_objects::alloc_slot().expect("alloc child ut slot"); crate::cap::kernel_objects::write_at(child_slot_phys, child_ut); let (child_ut_id, child_ut_gen) = { let mut pool = POOL.lock_after(&ptable); let (cid, cgen) = pool .register_object(child_slot_phys, ObjectTag::Untyped) .expect("register child ut"); crate::cap::derivation::link_child(&mut pool, parent_ut_id, parent_ut_gen, cid) .expect("link child untyped to parent"); let cap = CapRef::new(ObjectTag::Untyped, cid, Rights::ALL, cgen); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, 80, depth, gv, gb, cap) .expect("insert child untyped cap"); (cid, cgen) }; { let mut pool = POOL.lock_after(&ptable); crate::cap::retype::kernel_retype( &mut pool, None, child_ut_id, child_ut_gen, ObjectTag::Endpoint, 0, cnode_id, cnode_gen, 81, depth, gv, gb, 3, ) .expect("retype endpoints from child untyped"); } let ep_ids: [(crate::types::ObjPhys, crate::types::Generation); 3] = { let pool = POOL.lock_after(&ptable); let c0 = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, 81, depth, gv, gb) .expect("ep0"); let c1 = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, 82, depth, gv, gb) .expect("ep1"); let c2 = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, 83, depth, gv, gb) .expect("ep2"); [ (c0.phys(), c0.generation()), (c1.phys(), c1.generation()), (c2.phys(), c2.generation()), ] }; { let mut pool = POOL.lock_after(&ptable); crate::cap::derivation::destroy_children( &mut pool, &mut ptable, parent_ut_id, parent_ut_gen, ) .expect("destroy_children on parent"); } { let pool = POOL.lock_after(&ptable); let ut = pool .read_as::(parent_ut_id, parent_ut_gen) .expect("parent untyped valid"); assert!(ut.watermark == 0, "parent watermark must reset"); assert!(ut.child_count == 0, "parent child_count must be 0"); assert!( pool.get_tag(child_ut_id, child_ut_gen).is_err(), "child untyped must be destroyed" ); ep_ids.iter().for_each(|&(eid, egen)| { assert!( pool.get_tag(eid, egen).is_err(), "grandchild endpoint must be destroyed" ); }); } let mut allocator = BitmapFrameAllocator; ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn retype_after_revoke_with_stale_gen() { let ptable = PROCESSES.lock(); let mut pool = POOL.lock_after(&ptable); let (ep_id, ep_gen) = crate::tests::helpers::alloc_endpoint(&mut pool).expect("alloc endpoint"); let stale_gen = ep_gen; let (_new_gen, _old_phys_tag) = pool.revoke_phys(ep_id, ep_gen).expect("revoke endpoint"); let result = pool.get_tag(ep_id, stale_gen); assert!( matches!(result, Err(KernelError::StaleGeneration)), "accessing revoked object with stale gen should return StaleGeneration, got {:?}", result ); let new_gen_val = pool.generation_of(ep_id); assert!( new_gen_val.is_none(), "revoked slot should not report as active" ); } );