use crate::cap::cnode; use crate::cap::ops; use crate::cap::pool::POOL; use crate::cap::table::{CapRef, Rights}; use crate::error::KernelError; use crate::ipc::endpoint; use crate::proc::{BlockedReason, PROCESSES, ProcessState}; use crate::tests::helpers; use crate::types::Priority; use lancer_core::header::KernelObjectHeader; use lancer_core::object_layout::{ EndpointObject, KernelObject, NotificationObject, ProcessObject, SchedContextObject, }; use lancer_core::object_tag::ObjectTag; crate::kernel_test!( fn create_endpoint_inserts_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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 10u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); let oid = { 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 cap = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, address, depth, gv, gb) .expect("read slot"); assert!(cap.tag() == ObjectTag::Endpoint); assert!(cap.rights().contains(Rights::ALL)); assert!(cap.phys() == oid); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn create_notification_inserts_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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 11u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); let oid = { 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") }; { 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::Notification); assert!(cap.phys() == oid); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn create_invalid_tag_rejected() { 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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 13u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); let result = { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, address, depth, gv, gb, ObjectTag::Process, ) }; assert!( matches!(result, Err(KernelError::InvalidType)), "creating a Process cap via ops::create must fail" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn create_occupied_slot_rolls_back() { 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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 14u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("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("first create"); } let result = { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, address, depth, gv, gb, ObjectTag::Notification, ) }; assert!( matches!(result, Err(KernelError::SlotOccupied)), "second create into occupied slot must fail" ); { 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 in the slot" ); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn derive_attenuates_rights() { 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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let src_addr = 20u64; let dest_addr = 21u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, src_addr, depth, gv, gb, ObjectTag::Endpoint, ) .expect("create source"); } { let mut pool = POOL.lock_after(&ptable); ops::derive_via_cnode( &mut pool, cnode_id, cnode_gen, src_addr, dest_addr, depth, gv, gb, Rights::READ, ) .expect("derive"); } { let pool = POOL.lock_after(&ptable); let cap = cnode::resolve_and_read(&pool, cnode_id, cnode_gen, dest_addr, depth, gv, gb) .expect("read derived slot"); assert!(cap.rights().contains(Rights::READ)); assert!(!cap.rights().contains(Rights::WRITE)); assert!(!cap.rights().contains(Rights::REVOKE)); } ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn derive_without_grant_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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let src_addr = 30u64; let dest_addr = 31u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("coords"); { let mut pool = POOL.lock_after(&ptable); ops::create_via_cnode( &mut pool, cnode_id, cnode_gen, src_addr, depth, gv, gb, ObjectTag::Endpoint, ) .expect("create source"); } { let pool = POOL.lock_after(&ptable); let old_cap = cnode::resolve_and_clear(&pool, cnode_id, cnode_gen, src_addr, depth, gv, gb) .expect("clear slot"); let new_cap = old_cap.with_rights(Rights::READ | Rights::WRITE); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, src_addr, depth, gv, gb, new_cap) .expect("reinsert cap"); } let result = { let mut pool = POOL.lock_after(&ptable); ops::derive_via_cnode( &mut pool, cnode_id, cnode_gen, src_addr, dest_addr, depth, gv, gb, Rights::READ, ) }; assert!( matches!(result, Err(KernelError::InsufficientRights)), "derive without GRANT right must fail" ); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn identify_returns_tag_and_rights() { 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(); helpers::bootstrap_test_cnode(pid, &mut ptable); let address = 40u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(pid, &ptable).expect("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"); } let (tag, rights) = { let pool = POOL.lock_after(&ptable); ops::identify_via_cnode(&pool, cnode_id, cnode_gen, address, depth, gv, gb) .expect("identify") }; assert!(tag == ObjectTag::Notification); assert!(rights.contains(Rights::ALL)); ptable.destroy(pid, &mut allocator); } ); crate::kernel_test!( fn endpoint_revoke_unblocks_senders() { 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"); ptable.start(owner_created).expect("start owner"); ptable.start(sender_created).expect("start sender"); let owner_pid = owner_created.pid(); let sender_pid = sender_created.pid(); helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let (ep_id, ep_gen) = crate::tests::helpers::alloc_endpoint(&mut POOL.lock_after(&ptable)).expect("alloc ep"); let cap = CapRef::new(ObjectTag::Endpoint, ep_id, Rights::ALL, ep_gen); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ptable.simulate_dispatch(sender_pid); 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 again"); 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 endpoint revoke" ); assert!( ptable.exec(sender_pid).unwrap().saved_context.rax == KernelError::InvalidObject.to_errno() as u64, "sender's rax should contain InvalidObject errno after endpoint revoke" ); ptable.destroy(owner_pid, &mut allocator); ptable.destroy(sender_pid, &mut allocator); } ); crate::kernel_test!( fn notification_revoke_unblocks_waiters() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); let waiter_created = ptable.allocate(&mut allocator).expect("alloc waiter"); ptable.start(owner_created).expect("start owner"); ptable.start(waiter_created).expect("start waiter"); let owner_pid = owner_created.pid(); let waiter_pid = waiter_created.pid(); helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let (notif_id, notif_gen) = crate::tests::helpers::alloc_notification(&mut POOL.lock_after(&ptable)) .expect("alloc notif"); let cap = CapRef::new(ObjectTag::Notification, notif_id, Rights::ALL, notif_gen); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ptable.simulate_dispatch(waiter_pid); let _ = ptable[waiter_pid] .block_on(BlockedReason::WaitingNotification(notif_id, notif_gen)) .expect("block waiter"); { let mut pool = POOL.lock_after(&ptable); let notif = pool .write_as::(notif_id, notif_gen) .expect("get notif"); notif.waiters[notif.waiter_count as usize] = waiter_pid.raw(); notif.waiter_count += 1; } ops::revoke_via_cnode(owner_pid, address, &mut ptable).expect("revoke"); assert!( ptable[waiter_pid].state() == ProcessState::Ready, "waiter should be unblocked after notification revoke" ); assert!( ptable.exec(waiter_pid).unwrap().saved_context.rax == KernelError::InvalidObject.to_errno() as u64, "waiter's rax should contain InvalidObject errno after notification revoke" ); ptable.destroy(owner_pid, &mut allocator); ptable.destroy(waiter_pid, &mut allocator); } ); crate::kernel_test!( fn proc_destroy_decrements_pool_refcount() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let parent_created = ptable.allocate(&mut allocator).expect("alloc parent"); let child_created = ptable.allocate(&mut allocator).expect("alloc child"); ptable.start(parent_created).expect("start parent"); ptable.start(child_created).expect("start child"); let parent_pid = parent_created.pid(); let child_pid = child_created.pid(); helpers::bootstrap_test_cnode(parent_pid, &mut ptable); let header = KernelObjectHeader::new(ObjectTag::Process, 0, 64); let mut proc_obj = ProcessObject::init_default(header); proc_obj.pid = child_pid.raw(); let (obj_id, generation) = crate::tests::helpers::alloc_typed( &mut POOL.lock_after(&ptable), ObjectTag::Process, proc_obj, ) .expect("alloc process object"); let cap = CapRef::new(ObjectTag::Process, obj_id, Rights::ALL, generation); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(parent_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } POOL.lock_after(&ptable) .inc_ref(obj_id, generation) .expect("inc_ref"); let refcount_before = POOL .lock_after(&ptable) .get_tag(obj_id, generation) .is_ok(); assert!(refcount_before, "object should be alive before destroy"); ptable.destroy(child_pid, &mut allocator); POOL.lock_after(&ptable).dec_ref_phys(obj_id, generation); { let pool = POOL.lock_after(&ptable); let _ = cnode::resolve_and_clear(&pool, cnode_id, cnode_gen, address, depth, gv, gb); } ptable.destroy(parent_pid, &mut allocator); } ); crate::kernel_test!( fn sched_context_revoke_detaches_process() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); ptable.start(owner_created).expect("start owner"); let owner_pid = owner_created.pid(); helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64); let mut sc_obj = SchedContextObject::init_default(header); sc_obj.budget_us = 1000; sc_obj.period_us = 10000; sc_obj.priority = 100; let (sc_id, sc_gen) = crate::tests::helpers::alloc_typed( &mut POOL.lock_after(&ptable), ObjectTag::SchedContext, sc_obj, ) .expect("alloc sched context"); { let mut pool = POOL.lock_after(&ptable); let sc = pool .write_as::(sc_id, sc_gen) .expect("get sc"); sc.attached_pid = owner_pid.raw(); } let cap = CapRef::new(ObjectTag::SchedContext, sc_id, Rights::ALL, sc_gen); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ptable[owner_pid].attach_sched_context(sc_id, sc_gen, Priority::new(100)); assert!( ptable[owner_pid].sched_context().is_some(), "sched_context should be attached before revoke" ); ops::revoke_via_cnode(owner_pid, address, &mut ptable).expect("revoke sched context"); assert!( ptable[owner_pid].sched_context().is_none(), "sched_context must be detached after SchedContext revoke" ); ptable.destroy(owner_pid, &mut allocator); } ); crate::kernel_test!( fn sched_context_revoke_prevents_scheduling() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); ptable.start(owner_created).expect("start owner"); let owner_pid = owner_created.pid(); helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64); let mut sc_obj = SchedContextObject::init_default(header); sc_obj.budget_us = 1000; sc_obj.period_us = 10000; sc_obj.priority = 200; let address = 0u64; let (sc_id, sc_gen) = crate::tests::helpers::alloc_typed( &mut POOL.lock_after(&ptable), ObjectTag::SchedContext, sc_obj, ) .expect("alloc sc"); { let mut pool = POOL.lock_after(&ptable); let sc = pool .write_as::(sc_id, sc_gen) .expect("get sc"); sc.attached_pid = owner_pid.raw(); } let cap = CapRef::new(ObjectTag::SchedContext, sc_id, Rights::ALL, sc_gen); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ptable[owner_pid].attach_sched_context(sc_id, sc_gen, Priority::new(200)); assert!( ptable[owner_pid].is_runnable(), "process should be runnable before revoke" ); ops::revoke_via_cnode(owner_pid, address, &mut ptable).expect("revoke"); assert!( ptable[owner_pid].sched_context().is_none(), "sched_context detached after revoke" ); ptable.destroy(owner_pid, &mut allocator); } ); crate::kernel_test!( fn cleanup_sched_context_with_no_attached_process() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let owner_created = ptable.allocate(&mut allocator).expect("alloc owner"); ptable.start(owner_created).expect("start owner"); let owner_pid = owner_created.pid(); helpers::bootstrap_test_cnode(owner_pid, &mut ptable); let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64); let mut sc_obj = SchedContextObject::init_default(header); sc_obj.budget_us = 500; sc_obj.period_us = 5000; sc_obj.priority = 50; let address = 0u64; let (sc_id, sc_gen) = crate::tests::helpers::alloc_typed( &mut POOL.lock_after(&ptable), ObjectTag::SchedContext, sc_obj, ) .expect("alloc sc"); let cap = CapRef::new(ObjectTag::SchedContext, sc_id, Rights::ALL, sc_gen); let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(owner_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ops::revoke_via_cnode(owner_pid, address, &mut ptable).expect("revoke unattached sc"); ptable.destroy(owner_pid, &mut allocator); } ); crate::kernel_test!( fn sched_context_cleanup_different_holder_and_attached() { let mut allocator = crate::mem::phys::BitmapFrameAllocator; let mut ptable = PROCESSES.lock(); let holder_created = ptable.allocate(&mut allocator).expect("alloc holder"); let target_created = ptable.allocate(&mut allocator).expect("alloc target"); ptable.start(holder_created).expect("start holder"); ptable.start(target_created).expect("start target"); let holder_pid = holder_created.pid(); let target_pid = target_created.pid(); helpers::bootstrap_test_cnode(holder_pid, &mut ptable); let header = KernelObjectHeader::new(ObjectTag::SchedContext, 0, 64); let mut sc_obj = SchedContextObject::init_default(header); sc_obj.budget_us = 1000; sc_obj.period_us = 10000; sc_obj.priority = 100; let (sc_id, sc_gen) = crate::tests::helpers::alloc_typed( &mut POOL.lock_after(&ptable), ObjectTag::SchedContext, sc_obj, ) .expect("alloc sc"); { let mut pool = POOL.lock_after(&ptable); let sc = pool .write_as::(sc_id, sc_gen) .expect("get sc"); sc.attached_pid = target_pid.raw(); } ptable[target_pid].attach_sched_context(sc_id, sc_gen, Priority::new(100)); assert!( ptable[target_pid].sched_context().is_some(), "target should have sched context" ); let cap = CapRef::new(ObjectTag::SchedContext, sc_id, Rights::ALL, sc_gen); let address = 0u64; let (cnode_id, cnode_gen, depth, gv, gb) = cnode::cnode_coords(holder_pid, &ptable).expect("coords"); { let pool = POOL.lock_after(&ptable); cnode::resolve_and_insert(&pool, cnode_id, cnode_gen, address, depth, gv, gb, cap) .expect("insert cap"); } ops::revoke_via_cnode(holder_pid, address, &mut ptable).expect("revoke"); assert!( ptable[target_pid].sched_context().is_none(), "target's sched_context must be cleared when holder revokes the SchedContext" ); ptable.destroy(holder_pid, &mut allocator); ptable.destroy(target_pid, &mut allocator); } );