kernel: Complete domain teardown and parenting
5e7ead57981261d3095812ae7f65daf329fb70fd5cf0666c09501552b7e6c22c
1 parent
92262c08
Makefile
+10 -1
| 21 | 21 | ||
| 22 | 22 | # Verify the emulator binary exists. |
|
| 23 | 23 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 24 | 24 | ||
| 25 | 25 | default: emulator $(RAD_BIN) |
|
| 26 | - | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test smp-test |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test module-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test smp-test termination-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | + | module-test: emulator $(RAD_BIN) |
|
| 32 | + | @RAD_EMULATOR="$(EMU)" sh test/modules/run |
|
| 33 | + | ||
| 31 | 34 | # Emulator command check |
|
| 32 | 35 | ||
| 33 | 36 | emulator: |
|
| 34 | 37 | ifeq ($(EMU_PATH),) |
|
| 35 | 38 | $(error Emulator not found. Install it or set RAD_EMULATOR to its path) |
| 211 | 214 | ||
| 212 | 215 | # Concurrent user contexts and metadata transactions on every online hart. |
|
| 213 | 216 | .PHONY: smp-test |
|
| 214 | 217 | smp-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64 |
|
| 215 | 218 | @RAD_EMULATOR="$(EMU)" sh test/dispatch/run smp "1 2 8" 7 |
|
| 219 | + | ||
| 220 | + | # User exit, abort, and CPU faults preserve the surviving parent's execution. |
|
| 221 | + | termination-test: emulator $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64 |
|
| 222 | + | @RAD_EMULATOR="$(EMU)" sh test/dispatch/run termination |
|
| 223 | + | ||
| 224 | + | .PHONY: termination-test |
compiler/radiance.rad
+2 -4
| 26 | 26 | use std::sys; |
|
| 27 | 27 | use std::sys::unix; |
|
| 28 | 28 | use std::collections::dict; |
|
| 29 | 29 | ||
| 30 | 30 | /// Maximum number of modules we can load per package. |
|
| 31 | - | constant MAX_LOADED_MODULES: u32 = module::MAX_MODULES; |
|
| 31 | + | constant MAX_LOADED_MODULES: u32 = 128; |
|
| 32 | 32 | /// Maximum number of packages we can compile. |
|
| 33 | 33 | constant MAX_PACKAGES: u32 = 4; |
|
| 34 | - | /// Total module entries across all packages. |
|
| 35 | - | constant MAX_TOTAL_MODULES: u32 = 192; |
|
| 36 | 34 | /// Source code buffer arena (4 MB). |
|
| 37 | 35 | constant MAX_SOURCES_SIZE: u32 = 4194304; |
|
| 38 | 36 | /// Maximum number of test functions we can discover. |
|
| 39 | 37 | constant MAX_TESTS: u32 = 1024; |
|
| 40 | 38 | /// Maximum number of assembly source paths we can load per package. |
| 56 | 54 | static MAIN_ARENA: [u8; MAIN_ARENA_SIZE] = [0; MAIN_ARENA_SIZE]; |
|
| 57 | 55 | ||
| 58 | 56 | /// Module source code. |
|
| 59 | 57 | static MODULE_SOURCES: [u8; MAX_SOURCES_SIZE] = [0; MAX_SOURCES_SIZE]; |
|
| 60 | 58 | /// Module entries for all packages. |
|
| 61 | - | unsafe static MODULE_ENTRIES: [module::ModuleEntry; MAX_TOTAL_MODULES] = undefined; |
|
| 59 | + | unsafe static MODULE_ENTRIES: [module::ModuleEntry; module::MAX_MODULES] = undefined; |
|
| 62 | 60 | /// String pool. |
|
| 63 | 61 | unsafe static STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 64 | 62 | ||
| 65 | 63 | /// Package scope. |
|
| 66 | 64 | unsafe static RESOLVER_PKG_SCOPE: resolver::Scope = undefined; |
kernel/kernel.rad
+1 -0
| 20 | 20 | export mod backing; |
|
| 21 | 21 | export mod pages; |
|
| 22 | 22 | export mod loader; |
|
| 23 | 23 | export mod instances; |
|
| 24 | 24 | export mod domains; |
|
| 25 | + | export mod lifecycle; |
|
| 25 | 26 | export mod budgets; |
|
| 26 | 27 | export mod dispatch; |
|
| 27 | 28 | export mod timers; |
|
| 28 | 29 | export mod remote; |
|
| 29 | 30 | export mod calls; |
kernel/kernel/abi.rad
+2 -0
| 133 | 133 | export constant DESTROY: u16 = 256; |
|
| 134 | 134 | /// All defined right bits. |
|
| 135 | 135 | export constant RIGHTS_MASK: u16 = 511; |
|
| 136 | 136 | /// Cascade destruction through creation ancestry. |
|
| 137 | 137 | export constant CASCADE: u64 = 1; |
|
| 138 | + | /// Fault code for explicit user Abort, separate from CPU exception codes. |
|
| 139 | + | export constant FAULT_ABORT: u32 = 256; |
|
| 138 | 140 | ||
| 139 | 141 | /// Object kind stored in the high four handle bits. |
|
| 140 | 142 | export union Kind: Copy { |
|
| 141 | 143 | /// Unoccupied table entry. |
|
| 142 | 144 | Empty = 0, |
kernel/kernel/backing.rad
+43 -10
| 27 | 27 | pool: frames::Pool, |
|
| 28 | 28 | /// Backing-record lifetimes. |
|
| 29 | 29 | slots: [slots::Slot; limits::ALLOCATIONS], |
|
| 30 | 30 | /// Payloads for live backing records. |
|
| 31 | 31 | records: [Allocation; limits::ALLOCATIONS], |
|
| 32 | + | /// Exclusive upper bound of backing slots that have been published. |
|
| 33 | + | used: u32, |
|
| 32 | 34 | /// Live domain generation, or zero after all of that domain's exposures end. |
|
| 33 | 35 | domains: [u32; limits::DOMAINS], |
|
| 36 | + | /// Zero while open; otherwise one plus the next exposure slot to inspect. |
|
| 37 | + | ending: [u32; limits::DOMAINS], |
|
| 34 | 38 | } |
|
| 35 | 39 | ||
| 36 | 40 | /// Initialize fresh backing and exposure-domain tables. |
|
| 37 | 41 | export fn initialize(store: &mut Store) { |
|
| 42 | + | set store.used = 0; |
|
| 38 | 43 | set store.pool.count = 0; |
|
| 44 | + | set store.pool.retiredHead = 0; |
|
| 45 | + | set store.pool.retiredCount = 0; |
|
| 39 | 46 | slots::initialize(&mut store.slots[..]); |
|
| 40 | - | for i in 0..limits::DOMAINS { set store.domains[i] = 0; } |
|
| 47 | + | for i in 0..limits::DOMAINS { set store.domains[i] = 0; set store.ending[i] = 0; } |
|
| 41 | 48 | } |
|
| 42 | 49 | ||
| 43 | 50 | /// Register a domain before it can receive allocation exposure. |
|
| 44 | 51 | export fn registerDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) { |
|
| 45 | 52 | if domain.index >= limits::DOMAINS or domain.generation == 0 { throw abi::Error::InvalidArg; } |
| 47 | 54 | set store.domains[domain.index] = domain.generation; |
|
| 48 | 55 | } |
|
| 49 | 56 | ||
| 50 | 57 | /// Test exposure-domain generation without reading beyond the fixed table. |
|
| 51 | 58 | export fn domainLive(store: &Store, domain: abi::Ref) -> bool { |
|
| 52 | - | return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation; |
|
| 59 | + | return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation and store.ending[domain.index] == 0; |
|
| 53 | 60 | } |
|
| 54 | 61 | ||
| 55 | 62 | /// Publish reserved metadata with one page object and its live origin exposure. |
|
| 56 | 63 | /// The caller owns the committed run and has validated the origin domain. |
|
| 57 | 64 | export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, run: frames::Run, origin: abi::Ref) -> abi::Ref { |
|
| 58 | 65 | assert domainLive(store, origin); |
|
| 59 | 66 | let object = slots::reference(&reservation); |
|
| 60 | 67 | assert slots::matches(&store.slots[..], object, slots::State::Reserved); |
|
| 68 | + | if object.index >= store.used { set store.used = object.index + 1; } |
|
| 61 | 69 | set store.records[object.index].run = run; |
|
| 62 | 70 | set store.records[object.index].pages = 1; |
|
| 63 | 71 | set store.records[object.index].exposed = 1; |
|
| 64 | 72 | let mut bits = bitset::init(&mut store.records[object.index].exposures[..]); |
|
| 65 | 73 | bitset::put(&mut bits, origin.index); |
| 88 | 96 | bitset::put(&mut bits, domain.index); |
|
| 89 | 97 | set store.records[object.index].exposed += 1; |
|
| 90 | 98 | } |
|
| 91 | 99 | ||
| 92 | 100 | /// Reclaim only when no page object and no domain exposure remains. |
|
| 93 | - | fn reclaim(store: &mut Store, object: abi::Ref) { |
|
| 101 | + | fn reclaim(store: &mut Store, object: abi::Ref, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) { |
|
| 94 | 102 | if store.records[object.index].pages <> 0 or store.records[object.index].exposed <> 0 { return; } |
|
| 95 | 103 | let run = store.records[object.index].run; |
|
| 96 | - | try! frames::release(&mut store.pool, run); |
|
| 104 | + | try! release(&mut store.pool, run); |
|
| 97 | 105 | try! slots::release(&mut store.slots[..], object); |
|
| 98 | 106 | } |
|
| 99 | 107 | ||
| 100 | 108 | /// Release one page object after its final handle disappears. |
|
| 101 | 109 | export fn releasePage(store: &mut Store, object: abi::Ref) throws (abi::Error) { |
|
| 102 | 110 | try require(store, object); |
|
| 103 | 111 | if store.records[object.index].pages == 0 { throw abi::Error::BadHandle; } |
|
| 104 | 112 | set store.records[object.index].pages -= 1; |
|
| 105 | - | reclaim(store, object); |
|
| 113 | + | reclaim(store, object, frames::release); |
|
| 106 | 114 | } |
|
| 107 | 115 | ||
| 108 | - | /// End all exposure for one domain before its slot can be registered again. |
|
| 109 | - | /// Callers serialize this bounded scan with allocation and capability changes. |
|
| 116 | + | /// End all exposure while the caller has exclusive access to metadata. |
|
| 110 | 117 | export unsafe fn endDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) { |
|
| 111 | - | if not domainLive(store, domain) { throw abi::Error::BadHandle; } |
|
| 112 | - | for i in 0..limits::ALLOCATIONS { |
|
| 118 | + | assert try end(store, domain, limits::ALLOCATIONS, frames::release); |
|
| 119 | + | } |
|
| 120 | + | ||
| 121 | + | /// Inspect at most limit backing slots and retire allocations whose lifetimes end. |
|
| 122 | + | /// Returns true after all exposure is clear and the domain slot can be registered again. |
|
| 123 | + | export unsafe fn endDomainStep(store: &mut Store, domain: abi::Ref, limit: u32) -> bool throws (abi::Error) { |
|
| 124 | + | return try end(store, domain, limit, frames::retire); |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Close exposure admission and advance one generation's retained scan cursor. |
|
| 128 | + | unsafe fn end(store: &mut Store, domain: abi::Ref, limit: u32, |
|
| 129 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) -> bool throws (abi::Error) |
|
| 130 | + | { |
|
| 131 | + | if domain.index >= limits::DOMAINS or domain.generation == 0 |
|
| 132 | + | or store.domains[domain.index] <> domain.generation { throw abi::Error::BadHandle; } |
|
| 133 | + | if limit == 0 { throw abi::Error::InvalidArg; } |
|
| 134 | + | if store.ending[domain.index] == 0 { set store.ending[domain.index] = 1; } |
|
| 135 | + | let first = store.ending[domain.index] - 1; |
|
| 136 | + | let mut count = store.used - first; |
|
| 137 | + | if count > limit { set count = limit; } |
|
| 138 | + | let next = first + count; |
|
| 139 | + | for i in first..next { |
|
| 113 | 140 | if store.slots[i].state <> slots::State::Live { continue; } |
|
| 114 | 141 | let mut bits = bitset::new(&mut store.records[i].exposures[..]); |
|
| 115 | 142 | if not bitset::contains(&bits, domain.index) { continue; } |
|
| 116 | 143 | bitset::clear(&mut bits, domain.index); |
|
| 117 | 144 | set store.records[i].exposed -= 1; |
|
| 118 | - | reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation }); |
|
| 145 | + | reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation }, release); |
|
| 146 | + | } |
|
| 147 | + | if next < store.used { |
|
| 148 | + | set store.ending[domain.index] = next + 1; |
|
| 149 | + | return false; |
|
| 119 | 150 | } |
|
| 120 | 151 | set store.domains[domain.index] = 0; |
|
| 152 | + | set store.ending[domain.index] = 0; |
|
| 153 | + | return true; |
|
| 121 | 154 | } |
kernel/kernel/boot.rad
+6 -3
| 7 | 7 | use super::sync; |
|
| 8 | 8 | use super::trap; |
|
| 9 | 9 | use super::pages; |
|
| 10 | 10 | use super::registry; |
|
| 11 | 11 | use super::domains; |
|
| 12 | + | use super::lifecycle; |
|
| 12 | 13 | use super::budgets; |
|
| 13 | 14 | use super::dispatch; |
|
| 14 | 15 | use super::timers; |
|
| 15 | 16 | use super::remote; |
|
| 16 | 17 | use super::interrupts; |
| 24 | 25 | /// Number of harts that validated their machine stack. |
|
| 25 | 26 | static ARRIVED: u64 = 0; |
|
| 26 | 27 | /// Trap anchors owned by their physical harts. |
|
| 27 | 28 | unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined; |
|
| 28 | 29 | ||
| 29 | - | /// Handle timer boundaries and user calls, and stop on unsupported traps. |
|
| 30 | + | /// Dispatch interrupts, user calls, and user faults; halt on kernel faults. |
|
| 30 | 31 | unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) { |
|
| 31 | 32 | let cause = trap::classify(frame.cause); |
|
| 32 | 33 | if cause == trap::Cause::Timer or cause == trap::Cause::Software or cause == trap::Cause::External { |
|
| 33 | 34 | dispatch::interrupt(frame, hart); |
|
| 34 | - | return; |
|
| 35 | 35 | } |
|
| 36 | 36 | if cause == trap::Cause::UserCall and trap::fromUser(frame) { |
|
| 37 | 37 | dispatch::call(frame, hart); |
|
| 38 | - | return; |
|
| 38 | + | } |
|
| 39 | + | if trap::fromUser(frame) { |
|
| 40 | + | dispatch::fault(frame, hart); |
|
| 39 | 41 | } |
|
| 40 | 42 | print("kernel: unexpected trap\n"); |
|
| 41 | 43 | trap::halt(); |
|
| 42 | 44 | } |
|
| 43 | 45 |
| 79 | 81 | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 80 | 82 | assert platform::inRam(&PLATFORM, treeRange); |
|
| 81 | 83 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 82 | 84 | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 83 | 85 | domains::initialize(&mut domains::STORE); |
|
| 86 | + | lifecycle::initialize(&mut lifecycle::CALLS); |
|
| 84 | 87 | budgets::initialize(&mut budgets::STORE); |
|
| 85 | 88 | timers::initialize(&mut timers::STORE); |
|
| 86 | 89 | remote::initialize(&mut remote::STORE, PLATFORM.harts); |
|
| 87 | 90 | interrupts::initialize(&mut interrupts::STORE, PLATFORM.irqSources); |
|
| 88 | 91 | try! plic::initialize(&PLATFORM); |
kernel/kernel/budgets.rad
+2 -1
| 234 | 234 | let object = try owned(store, table, handle, abi::TRANSFER); |
|
| 235 | 235 | let window = try get(store, object); |
|
| 236 | 236 | if window.hart as u64 <> current.hart or window.context <> abi::reference(current.context) |
|
| 237 | 237 | or now < window.start or now >= window.end { throw abi::Error::Busy; } |
|
| 238 | 238 | let permit = try capability::lookup(table, target, abi::Kind::Domain, abi::Rights(abi::WAKE)); |
|
| 239 | - | let receiver = try domains::get(domainStore, permit.object) catch { throw abi::Error::Busy; }; |
|
| 239 | + | let receiver = try domains::get(domainStore, permit.object); |
|
| 240 | + | if receiver.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 240 | 241 | if receiver.state <> domains::Lifecycle::Active or abi::id(receiver.initial) == current.context { throw abi::Error::Busy; } |
|
| 241 | 242 | let context = try domains::context(domainStore, permit.object, receiver.initial) catch { throw abi::Error::Busy; }; |
|
| 242 | 243 | if context.state <> domains::ContextState::Ready or context.hart <> nil { throw abi::Error::Busy; } |
|
| 243 | 244 | let result = try bind(store, domainStore, table, Binding { budget: handle, domain: target, context: receiver.initial }, now) catch error { |
|
| 244 | 245 | if error == abi::Error::InvalidArg { throw abi::Error::Busy; } |
kernel/kernel/calls.rad
+114 -32
| 16 | 16 | use super::timers; |
|
| 17 | 17 | use super::sync; |
|
| 18 | 18 | use super::remote; |
|
| 19 | 19 | use super::interrupts; |
|
| 20 | 20 | use super::plic; |
|
| 21 | + | use super::lifecycle; |
|
| 21 | 22 | ||
| 22 | 23 | /// Resolve the owning hart for an operation that can change CPU authority. |
|
| 23 | 24 | unsafe fn budgetHart(table: &capability::Table, operation: u64, arguments: &[u64]) -> ?u32 throws (abi::Error) { |
|
| 24 | 25 | match operation { |
|
| 25 | 26 | case 11, 12, 70, 71, 73 => {}, |
| 45 | 46 | } |
|
| 46 | 47 | ||
| 47 | 48 | /// Validate resident object generations for capabilities without page ownership. |
|
| 48 | 49 | unsafe fn resident(entry: capability::Entry) throws (abi::Error) { |
|
| 49 | 50 | match entry.kind { |
|
| 50 | - | case abi::Kind::Domain, abi::Kind::Events => { let object = try domains::get(&domains::STORE, entry.object); }, |
|
| 51 | + | case abi::Kind::Domain, abi::Kind::Events => { |
|
| 52 | + | let object = try domains::get(&domains::STORE, entry.object); |
|
| 53 | + | if object.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 54 | + | }, |
|
| 51 | 55 | case abi::Kind::Image => { let object = try registry::get(®istry::STORE, entry.object); }, |
|
| 52 | 56 | else => throw abi::Error::BadHandle, |
|
| 53 | 57 | } |
|
| 54 | 58 | } |
|
| 55 | 59 | ||
| 60 | + | /// Reject expired domain authority before an allocation can reserve resources. |
|
| 61 | + | unsafe fn allocationAuthority(table: &capability::Table, operation: u64, handle: abi::Handle) throws (abi::Error) { |
|
| 62 | + | if operation <> 20 and operation <> 30 and operation <> 51 { return; } |
|
| 63 | + | let rights = abi::CREATE if operation == 20 else abi::ALLOCATE; |
|
| 64 | + | try resident(try capability::authority(table, handle, abi::Rights(rights))); |
|
| 65 | + | } |
|
| 66 | + | ||
| 56 | 67 | /// Grant or transfer authority while both domain tables remain serialized. |
|
| 57 | 68 | unsafe fn delegate(domain: &mut domains::Domain, handle: abi::Handle, target: abi::Handle, rights: u64, moving: bool, now: u64) |
|
| 58 | 69 | -> abi::Handle throws (abi::Error) |
|
| 59 | 70 | { |
|
| 60 | 71 | let owner = domain.memory.table.owner; |
| 140 | 151 | } |
|
| 141 | 152 | let output = @sliceOf(memory(arguments[1]), words.len); |
|
| 142 | 153 | for i in 0..words.len { set output[i] = words[i]; } |
|
| 143 | 154 | } |
|
| 144 | 155 | ||
| 156 | + | /// Validate a termination call and return harts that must release stopped contexts. |
|
| 157 | + | export unsafe fn termination(owner: abi::Ref, operation: u64, arguments: &[u64]) -> u32 throws (abi::Error) { |
|
| 158 | + | assert arguments.len == 4; |
|
| 159 | + | let domain = try domains::get(&domains::STORE, owner); |
|
| 160 | + | if domain.state <> domains::Lifecycle::Active { throw abi::Error::BadHandle; } |
|
| 161 | + | if arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; } |
|
| 162 | + | match operation { |
|
| 163 | + | case 49 => { |
|
| 164 | + | if arguments[0] > 0xffffffff or arguments[1] <> 0 { throw abi::Error::InvalidArg; } |
|
| 165 | + | return try domains::terminate(&mut domains::STORE, owner, events::CHILD_EXIT, arguments[0] as u32); |
|
| 166 | + | }, |
|
| 167 | + | case 43 => { |
|
| 168 | + | if arguments[0] <> 0 or arguments[1] <> 0 { throw abi::Error::InvalidArg; } |
|
| 169 | + | return try domains::terminate(&mut domains::STORE, owner, events::FAULT, abi::FAULT_ABORT); |
|
| 170 | + | }, |
|
| 171 | + | case 22 => { |
|
| 172 | + | let flags = arguments[1]; |
|
| 173 | + | if (flags & ~abi::CASCADE) <> 0 or (arguments[0] == 0 and flags <> 0) { throw abi::Error::InvalidArg; } |
|
| 174 | + | let mut target = owner; |
|
| 175 | + | if arguments[0] <> 0 { |
|
| 176 | + | let entry = try capability::lookup(&domain.memory.table, abi::Handle(arguments[0]), abi::Kind::Domain, abi::Rights(abi::DESTROY)); |
|
| 177 | + | set target = entry.object; |
|
| 178 | + | if (try domains::get(&domains::STORE, target)).state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 179 | + | } |
|
| 180 | + | if flags == abi::CASCADE { return try domains::terminateTree(&mut domains::STORE, target, events::CHILD_EXIT, 0); } |
|
| 181 | + | return try domains::terminate(&mut domains::STORE, target, events::CHILD_EXIT, 0); |
|
| 182 | + | }, |
|
| 183 | + | else => throw abi::Error::InvalidArg, |
|
| 184 | + | } |
|
| 185 | + | } |
|
| 186 | + | ||
| 145 | 187 | /// Execute one operation with domain and resource metadata serialized. |
|
| 146 | 188 | export unsafe fn invoke(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 147 | 189 | assert arguments.len == 4; |
|
| 148 | 190 | let mut domain = try domains::get(&domains::STORE, owner); |
|
| 149 | 191 | let handle = abi::Handle(arguments[0]); |
|
| 192 | + | try allocationAuthority(&domain.memory.table, operation, handle); |
|
| 150 | 193 | match operation { |
|
| 194 | + | case 22, 43, 49 => { let stopped = try termination(owner, operation, arguments); return 0; }, |
|
| 151 | 195 | case 10, 11 => return *try delegate(&mut domain, handle, abi::Handle(arguments[1]), arguments[2], operation == 11, now), |
|
| 152 | 196 | case 12 => { try drop(&mut domain, handle); return 0; }, |
|
| 153 | 197 | case 20 => return *try domains::create(&mut domains::STORE, &mut pages::STORE.backings, |
|
| 154 | 198 | ®istry::STORE, &mut domain.memory.table, handle, abi::Handle(arguments[1])), |
|
| 155 | 199 | case 21 => { |
|
| 156 | 200 | try domains::activate(&mut domains::STORE, &pages::STORE, &domain.memory.table, |
|
| 157 | 201 | handle, arguments[1], arguments[2], arguments[3]); |
|
| 158 | 202 | return 0; |
|
| 159 | 203 | }, |
|
| 204 | + | case 23 => { |
|
| 205 | + | if arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; } |
|
| 206 | + | try domains::reparent(&mut domains::STORE, &domain.memory.table, handle, abi::Handle(arguments[1])); |
|
| 207 | + | return 0; |
|
| 208 | + | }, |
|
| 160 | 209 | case 30 => return *try pages::allocate(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]), |
|
| 161 | 210 | case 31 => return *try pages::split(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]), |
|
| 162 | 211 | case 40 => { |
|
| 163 | 212 | if arguments[1] <> 0 or arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; } |
|
| 164 | 213 | try budgets::handoff(&mut budgets::STORE, &domains::STORE, &mut domain.memory.table, dispatch::current(), handle, now); |
| 190 | 239 | return 0; |
|
| 191 | 240 | }, |
|
| 192 | 241 | case 47 => { |
|
| 193 | 242 | let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(0)); |
|
| 194 | 243 | let target = try domains::get(&domains::STORE, entry.object); |
|
| 244 | + | if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 195 | 245 | let mut parent: u64 = 0; |
|
| 196 | 246 | if let object = target.parent { set parent = abi::id(object); } |
|
| 197 | 247 | try reply(&domain, arguments, &[abi::id(entry.object), abi::id(target.creator), parent, target.state as u64]); |
|
| 198 | 248 | return 0; |
|
| 199 | 249 | }, |
|
| 200 | 250 | case 48 => { |
|
| 201 | 251 | let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Events, abi::Rights(0)); |
|
| 202 | 252 | let target = try domains::get(&domains::STORE, entry.object); |
|
| 253 | + | if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 203 | 254 | try reply(&domain, arguments, &[events::CAPACITY as u64]); |
|
| 204 | 255 | return 0; |
|
| 205 | 256 | }, |
|
| 206 | 257 | case 51 => return *try loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut domain.memory.table, |
|
| 207 | 258 | loader::Request { authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3] }), |
| 235 | 286 | return 0; |
|
| 236 | 287 | }, |
|
| 237 | 288 | case 62 => { |
|
| 238 | 289 | let permission = try capability::authority(&domain.memory.table, handle, abi::Rights(0)); |
|
| 239 | 290 | let target = try domains::get(&domains::STORE, permission.object); |
|
| 291 | + | if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 240 | 292 | let mut object = target.initial; |
|
| 241 | 293 | if arguments[1] <> 0 { set object = abi::reference(arguments[1]); } |
|
| 242 | 294 | let context = try domains::context(&domains::STORE, permission.object, object); |
|
| 243 | 295 | let mut hart: u64 = 0xffffffffffffffff; |
|
| 244 | 296 | if let id = context.hart { set hart = id as u64; } |
| 276 | 328 | /// Load a package with metadata locked only around resource transactions. |
|
| 277 | 329 | unsafe fn runtimeLoad(domain: &mut domains::Domain, request: loader::Request, guard: sync::Guard) -> u64 throws (abi::Error) { |
|
| 278 | 330 | let owner = domain.memory.table.owner; |
|
| 279 | 331 | let pending = try loader::reserve(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, request) |
|
| 280 | 332 | catch error { sync::leave(guard); throw error; }; |
|
| 333 | + | let context = abi::reference(dispatch::current().context); |
|
| 334 | + | let lease = lifecycle::holdLoad(&mut lifecycle::CALLS, &mut domains::STORE, context, pending); |
|
| 335 | + | let retained = lifecycle::load(&lifecycle::CALLS, &lease); |
|
| 281 | 336 | sync::leave(guard); |
|
| 282 | - | let input = try loader::decode(&pending) catch error { |
|
| 337 | + | let input = try loader::decodeInput(&retained.input) catch error { |
|
| 283 | 338 | let guard = sync::enter(); |
|
| 284 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 339 | + | lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 340 | + | &mut loader::STATE, &mut registry::STORE, lease); |
|
| 285 | 341 | sync::leave(guard); throw error; |
|
| 286 | 342 | }; |
|
| 287 | - | let existing = try loader::identify(®istry::STORE, &pending, &input) catch error { |
|
| 343 | + | let existing = try loader::identifyInput(®istry::STORE, &retained.input, &input) catch error { |
|
| 288 | 344 | let guard = sync::enter(); |
|
| 289 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 345 | + | lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 346 | + | &mut loader::STATE, &mut registry::STORE, lease); |
|
| 290 | 347 | sync::leave(guard); throw error; |
|
| 291 | 348 | }; |
|
| 292 | 349 | let allocationGuard = sync::enter(); |
|
| 293 | 350 | if not live(owner) { |
|
| 294 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 351 | + | lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 352 | + | &mut loader::STATE, &mut registry::STORE, lease); |
|
| 295 | 353 | sync::leave(allocationGuard); throw abi::Error::BadHandle; |
|
| 296 | 354 | } |
|
| 297 | 355 | if let object = existing { |
|
| 298 | - | let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object) |
|
| 356 | + | let result = try lifecycle::finishLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 357 | + | &mut loader::STATE, &mut registry::STORE, lease, lifecycle::LoadResult::Existing(object)) |
|
| 299 | 358 | catch error { sync::leave(allocationGuard); throw error; }; |
|
| 300 | 359 | sync::leave(allocationGuard); return *result; |
|
| 301 | 360 | } |
|
| 302 | - | let output = try loader::reserveOutput(&mut pages::STORE.backings.pool, &mut registry::STORE) catch error { |
|
| 303 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 361 | + | try lifecycle::reserveOutput(&mut lifecycle::CALLS, &lease, &mut pages::STORE, &mut registry::STORE) catch error { |
|
| 362 | + | lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 363 | + | &mut loader::STATE, &mut registry::STORE, lease); |
|
| 304 | 364 | sync::leave(allocationGuard); throw error; |
|
| 305 | 365 | }; |
|
| 366 | + | let compiling = lifecycle::load(&lifecycle::CALLS, &lease); |
|
| 367 | + | let output = compiling.output else panic "missing loader output"; |
|
| 306 | 368 | sync::leave(allocationGuard); |
|
| 307 | - | let compiled = try loader::compile(®istry::STORE, &pending, &input, &output) catch error { |
|
| 369 | + | let compiled = try loader::compileInput(®istry::STORE, &compiling.input, &input, &output) catch error { |
|
| 308 | 370 | let guard = sync::enter(); |
|
| 309 | - | loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output); |
|
| 310 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 371 | + | lifecycle::cancelLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 372 | + | &mut loader::STATE, &mut registry::STORE, lease); |
|
| 311 | 373 | sync::leave(guard); throw error; |
|
| 312 | 374 | }; |
|
| 313 | 375 | let publicationGuard = sync::enter(); |
|
| 314 | - | if not live(owner) { |
|
| 315 | - | loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output); |
|
| 316 | - | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 317 | - | sync::leave(publicationGuard); throw abi::Error::BadHandle; |
|
| 318 | - | } |
|
| 319 | - | let object = loader::publishOutput(&mut loader::STATE, &mut pages::STORE.backings.pool, &mut registry::STORE, output, compiled); |
|
| 320 | - | let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object) |
|
| 376 | + | let result = try lifecycle::finishLoad(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, |
|
| 377 | + | &mut loader::STATE, &mut registry::STORE, lease, lifecycle::LoadResult::Compiled(compiled)) |
|
| 321 | 378 | catch error { sync::leave(publicationGuard); throw error; }; |
|
| 322 | 379 | sync::leave(publicationGuard); |
|
| 323 | 380 | return *result; |
|
| 324 | 381 | } |
|
| 325 | 382 | ||
| 326 | 383 | /// Execute a call with short metadata transactions and private bulk initialization. |
|
| 327 | 384 | /// Context ownership and pending calls retain storage across dispatch boundaries. |
|
| 328 | 385 | export unsafe fn synchronized(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 329 | 386 | assert arguments.len == 4; |
|
| 330 | - | if operation == 21 or operation == 50 { return try runtimeReady(owner, operation, arguments, now); } |
|
| 387 | + | if operation == 21 or operation == 50 { return try runtimeReady(owner, operation, arguments); } |
|
| 331 | 388 | let guard = sync::enter(); |
|
| 332 | 389 | let mut domain = try domains::get(&domains::STORE, owner) catch error { sync::leave(guard); throw error; }; |
|
| 333 | 390 | if domain.state <> domains::Lifecycle::Active { sync::leave(guard); throw abi::Error::BadHandle; } |
|
| 391 | + | try allocationAuthority(&domain.memory.table, operation, abi::Handle(arguments[0])) catch error { |
|
| 392 | + | sync::leave(guard); throw error; |
|
| 393 | + | }; |
|
| 394 | + | if operation == 22 or operation == 43 or operation == 49 { |
|
| 395 | + | let stopped = try termination(owner, operation, arguments) catch error { sync::leave(guard); throw error; }; |
|
| 396 | + | dispatch::reschedule(stopped); |
|
| 397 | + | sync::leave(guard); return 0; |
|
| 398 | + | } |
|
| 334 | 399 | let target = try budgetHart(&domain.memory.table, operation, arguments) catch error { sync::leave(guard); throw error; }; |
|
| 335 | 400 | if let destination = target { |
|
| 336 | 401 | let source = dispatch::hart(); |
|
| 337 | 402 | if destination <> source { |
|
| 338 | 403 | let call = remote::Call { owner, context: abi::reference(dispatch::current().context), operation, |
| 349 | 414 | let handle = abi::Handle(arguments[0]); |
|
| 350 | 415 | match operation { |
|
| 351 | 416 | case 20 => { |
|
| 352 | 417 | let pending = try domains::reserve(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, |
|
| 353 | 418 | &mut domain.memory.table, handle, abi::Handle(arguments[1])) catch error { sync::leave(guard); throw error; }; |
|
| 419 | + | let context = abi::reference(dispatch::current().context); |
|
| 420 | + | let lease = lifecycle::holdDomain(&mut lifecycle::CALLS, &mut domains::STORE, context, pending); |
|
| 354 | 421 | sync::leave(guard); |
|
| 355 | - | try domains::prepare(®istry::STORE, &pending) catch error { |
|
| 422 | + | try lifecycle::prepareDomain(&lifecycle::CALLS, ®istry::STORE, &lease) catch error { |
|
| 356 | 423 | let guard = sync::enter(); |
|
| 357 | - | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending); |
|
| 424 | + | let failed = lifecycle::takeDomain(&mut lifecycle::CALLS, &mut domains::STORE, lease); |
|
| 425 | + | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, failed); |
|
| 358 | 426 | sync::leave(guard); throw error; |
|
| 359 | 427 | }; |
|
| 360 | 428 | let guard = sync::enter(); |
|
| 429 | + | let ready = lifecycle::takeDomain(&mut lifecycle::CALLS, &mut domains::STORE, lease); |
|
| 361 | 430 | if not live(owner) { |
|
| 362 | - | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending); |
|
| 431 | + | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, ready); |
|
| 363 | 432 | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 364 | 433 | } |
|
| 365 | - | let result = try domains::publish(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending) |
|
| 434 | + | let result = try domains::publish(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, ready) |
|
| 366 | 435 | catch error { sync::leave(guard); throw error; }; |
|
| 367 | 436 | sync::leave(guard); return *result; |
|
| 368 | 437 | }, |
|
| 369 | 438 | case 30 => { |
|
| 370 | 439 | let pending = try pages::reserve(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]) |
|
| 371 | 440 | catch error { sync::leave(guard); throw error; }; |
|
| 441 | + | let context = abi::reference(dispatch::current().context); |
|
| 442 | + | let lease = lifecycle::holdPage(&mut lifecycle::CALLS, &mut domains::STORE, context, pending); |
|
| 372 | 443 | sync::leave(guard); |
|
| 373 | - | pages::clear(&pending); |
|
| 444 | + | lifecycle::clearPage(&lifecycle::CALLS, &lease); |
|
| 374 | 445 | let guard = sync::enter(); |
|
| 446 | + | let ready = lifecycle::takePage(&mut lifecycle::CALLS, &mut domains::STORE, lease); |
|
| 375 | 447 | if not live(owner) { |
|
| 376 | - | pages::cancel(&mut pages::STORE, &mut domain.memory.table, pending); |
|
| 448 | + | pages::cancel(&mut pages::STORE, &mut domain.memory.table, ready); |
|
| 377 | 449 | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 378 | 450 | } |
|
| 379 | - | let result = pages::publish(&mut pages::STORE, &mut domain.memory.table, pending); |
|
| 451 | + | let result = pages::publish(&mut pages::STORE, &mut domain.memory.table, ready); |
|
| 380 | 452 | sync::leave(guard); return *result; |
|
| 381 | 453 | }, |
|
| 382 | 454 | case 51 => return try runtimeLoad(&mut domain, loader::Request { |
|
| 383 | 455 | authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3], |
|
| 384 | 456 | }, guard), |
| 391 | 463 | let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] }; |
|
| 392 | 464 | let pending = try domains::contextReserve(&mut domains::STORE, &mut pages::STORE, ®istry::STORE, |
|
| 393 | 465 | &domain.memory.table, handle, start) catch error { sync::leave(guard); throw error; }; |
|
| 394 | 466 | let mut target: abi::Ref = undefined; |
|
| 395 | 467 | match &pending { case domains::ContextReservation::Held(allocation) => { set target = allocation.owner; }, } |
|
| 468 | + | let context = abi::reference(dispatch::current().context); |
|
| 469 | + | let lease = lifecycle::holdContext(&mut lifecycle::CALLS, &mut domains::STORE, context, pending); |
|
| 396 | 470 | sync::leave(guard); |
|
| 397 | - | domains::contextClear(&pending); |
|
| 471 | + | lifecycle::clearContext(&lifecycle::CALLS, &lease); |
|
| 398 | 472 | let guard = sync::enter(); |
|
| 473 | + | let ready = lifecycle::takeContext(&mut lifecycle::CALLS, &mut domains::STORE, lease); |
|
| 399 | 474 | if not live(owner) or not live(target) { |
|
| 400 | - | domains::contextCancel(&mut domains::STORE, &mut pages::STORE, pending); |
|
| 475 | + | domains::contextCancel(&mut domains::STORE, &mut pages::STORE, ready); |
|
| 401 | 476 | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 402 | 477 | } |
|
| 403 | - | let result = domains::contextPublish(&mut domains::STORE, pending); |
|
| 478 | + | let result = domains::contextPublish(&mut domains::STORE, ready); |
|
| 404 | 479 | sync::leave(guard); return abi::id(result); |
|
| 405 | 480 | }, |
|
| 406 | 481 | else => { |
|
| 407 | - | let result = try invoke(owner, operation, arguments, now) catch error { sync::leave(guard); throw error; }; |
|
| 482 | + | let result = try invoke(owner, operation, arguments, dispatch::clock()) catch error { sync::leave(guard); throw error; }; |
|
| 408 | 483 | sync::leave(guard); return result; |
|
| 409 | 484 | }, |
|
| 410 | 485 | } |
|
| 411 | 486 | } |
|
| 412 | 487 | ||
| 413 | 488 | /// Reserve remote dispatch capacity before activation or event publication. |
|
| 414 | - | unsafe fn runtimeReady(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 489 | + | unsafe fn runtimeReady(owner: abi::Ref, operation: u64, arguments: &[u64]) -> u64 throws (abi::Error) { |
|
| 415 | 490 | if operation == 50 and (arguments[1] > 0xffffffff or arguments[2] <> 0 or arguments[3] <> 0) { throw abi::Error::InvalidArg; } |
|
| 416 | 491 | let guard = sync::enter(); |
|
| 417 | 492 | let domain = try domains::get(&domains::STORE, owner) catch error { sync::leave(guard); throw error; }; |
|
| 418 | 493 | if domain.state <> domains::Lifecycle::Active { sync::leave(guard); throw abi::Error::BadHandle; } |
|
| 419 | 494 | let handle = abi::Handle(arguments[0]); |
| 421 | 496 | let permit = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(rights)) catch error { |
|
| 422 | 497 | sync::leave(guard); throw error; |
|
| 423 | 498 | }; |
|
| 424 | 499 | let source = dispatch::hart(); |
|
| 425 | 500 | let context = abi::reference(dispatch::current().context); |
|
| 501 | + | let now = dispatch::clock(); |
|
| 426 | 502 | let targets = budgets::wakeTargets(&budgets::STORE, &domains::STORE, permit.object, now) & ~(1 << source); |
|
| 427 | 503 | let pending = try remote::reserve(&mut remote::STORE, source, targets) catch error { sync::leave(guard); throw error; }; |
|
| 428 | 504 | let result = try invoke(owner, operation, arguments, now) catch error { |
|
| 429 | 505 | remote::cancelReservation(&mut remote::STORE, pending); |
|
| 430 | 506 | sync::leave(guard); throw error; |
| 442 | 518 | { |
|
| 443 | 519 | assert trap::fromUser(frame) and frame.cause == 8; |
|
| 444 | 520 | assert trap::advanceCall(frame); |
|
| 445 | 521 | let args = [frame.registers[10], frame.registers[11], frame.registers[12], frame.registers[13]]; |
|
| 446 | 522 | let result = try execute(owner, frame.registers[17], &args[..], now) catch error { |
|
| 523 | + | let operation = frame.registers[17]; |
|
| 524 | + | if operation >= 44 and operation <= 48 and error == abi::Error::BadHandle { |
|
| 525 | + | try execute(owner, 43, &[0, 0, 0, 0], now) catch stopped { |
|
| 526 | + | assert stopped == abi::Error::BadHandle; |
|
| 527 | + | }; |
|
| 528 | + | } |
|
| 447 | 529 | set frame.registers[10] = (0 as u64) - error as u64; |
|
| 448 | 530 | return; |
|
| 449 | 531 | }; |
|
| 450 | 532 | set frame.registers[10] = result; |
|
| 451 | 533 | } |
kernel/kernel/dispatch.rad
+59 -7
| 3 | 3 | use super::abi; |
|
| 4 | 4 | use super::limits; |
|
| 5 | 5 | use super::slots; |
|
| 6 | 6 | use super::budgets; |
|
| 7 | 7 | use super::domains; |
|
| 8 | + | use super::lifecycle; |
|
| 9 | + | use super::loader; |
|
| 10 | + | use super::registry; |
|
| 11 | + | use super::frames; |
|
| 12 | + | use super::interrupts; |
|
| 13 | + | use super::events; |
|
| 14 | + | use super::pages; |
|
| 8 | 15 | use super::platform; |
|
| 9 | 16 | use super::trap; |
|
| 10 | 17 | use super::range; |
|
| 11 | 18 | use super::calls; |
|
| 12 | 19 | use super::timers; |
| 42 | 49 | record Runtime: Copy { |
|
| 43 | 50 | /// Current context and retained idle frame. |
|
| 44 | 51 | state: State, |
|
| 45 | 52 | /// Validated timer registers for this hart. |
|
| 46 | 53 | timer: Timer, |
|
| 54 | + | /// Minimum ticks between scheduled maintenance passes. |
|
| 55 | + | maintenance: u64, |
|
| 56 | + | /// Earliest tick for another background maintenance pass. |
|
| 57 | + | nextMaintenance: u64, |
|
| 47 | 58 | } |
|
| 48 | 59 | ||
| 49 | 60 | /// Runtime slots initialized before each hart enables timer interrupts. |
|
| 50 | 61 | unsafe static HARTS: [Runtime; limits::HARTS] = undefined; |
|
| 51 | 62 |
| 65 | 76 | let device = try timer(machine, id); |
|
| 66 | 77 | let stack = machine.stacks[id]; |
|
| 67 | 78 | let mut idle = trap::Frame { registers: [0; 32], pc: idleAddress(), status: 0x1880, cause: 0, value: 0 }; |
|
| 68 | 79 | set idle.registers[2] = stack.end; |
|
| 69 | 80 | set idle.registers[3] = kernelGp; |
|
| 81 | + | let mut maintenance = machine.timebase as u64 / 100; |
|
| 82 | + | if maintenance == 0 { set maintenance = 1; } |
|
| 70 | 83 | set HARTS[id] = Runtime { |
|
| 71 | - | state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device, |
|
| 84 | + | state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device, maintenance, nextMaintenance: 0, |
|
| 72 | 85 | }; |
|
| 73 | 86 | arm(device, 0xffffffffffffffff); |
|
| 74 | 87 | } |
|
| 75 | 88 | ||
| 76 | 89 | /// Tail-enter dispatch on the firmware stack and resume the selected frame. |
| 103 | 116 | set deliveries[count] = delivery; |
|
| 104 | 117 | set count += 1; |
|
| 105 | 118 | } |
|
| 106 | 119 | let clock = now(HARTS[id].timer); |
|
| 107 | 120 | let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, &budgets::STORE, id, clock); |
|
| 108 | - | let notified = timeout.harts | plic::service(clock); |
|
| 121 | + | let notified = timeout.harts | plic::service(clock) | lifecycle::service(&mut domains::STORE, &budgets::STORE, clock); |
|
| 109 | 122 | let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock); |
|
| 110 | 123 | let mut deadline = choice.deadline; |
|
| 111 | 124 | if timeout.deadline < deadline { set deadline = timeout.deadline; } |
|
| 112 | - | for target in 0..limits::HARTS { |
|
| 113 | - | if target <> id and (notified & (1 << target)) <> 0 { signal(target); } |
|
| 114 | - | } |
|
| 115 | - | arm(HARTS[id].timer, deadline); |
|
| 125 | + | reschedule(notified); |
|
| 116 | 126 | if previous <> choice.context { |
|
| 117 | 127 | if let context = previous { signalContext(context, clock); } |
|
| 118 | 128 | } |
|
| 119 | 129 | for delivery in &deliveries[..count] { |
|
| 120 | 130 | assert remote::acknowledge(&mut remote::STORE, id, delivery.id); |
|
| 121 | 131 | if delivery.request.source <> id { signal(delivery.request.source); } |
|
| 122 | 132 | if let case remote::Action::Budget(call) = delivery.request.action { signalContext(call.context, clock); } |
|
| 123 | 133 | if let case remote::Action::Ready(context) = delivery.request.action { signalContext(context, clock); } |
|
| 124 | 134 | } |
|
| 135 | + | lifecycle::cancelRequests(&mut domains::STORE, &mut remote::STORE); |
|
| 136 | + | let cancelled = lifecycle::cancelAllocations(&mut lifecycle::CALLS, &mut domains::STORE, &mut pages::STORE, &mut loader::STATE, &mut registry::STORE); |
|
| 137 | + | let maintenanceClock = now(HARTS[id].timer); |
|
| 138 | + | let mut maintenance = cancelled or domains::STORE.dead > 0 or pages::STORE.backings.pool.retiredCount > 0; |
|
| 139 | + | if maintenance and maintenanceClock >= HARTS[id].nextMaintenance { |
|
| 140 | + | let reaped = lifecycle::reapNext(&mut domains::STORE, &mut pages::STORE, &mut budgets::STORE, &mut interrupts::STORE); |
|
| 141 | + | let reclaimed = frames::reclaim(&mut pages::STORE.backings.pool, 256); |
|
| 142 | + | set maintenance = cancelled or reaped or pages::STORE.backings.pool.retiredCount > 0; |
|
| 143 | + | let after = now(HARTS[id].timer); |
|
| 144 | + | let interval = HARTS[id].maintenance; |
|
| 145 | + | set HARTS[id].nextMaintenance = 0xffffffffffffffff; |
|
| 146 | + | if interval < 0xffffffffffffffff - after { set HARTS[id].nextMaintenance = after + interval; } |
|
| 147 | + | } |
|
| 148 | + | if maintenance and HARTS[id].nextMaintenance < deadline { set deadline = HARTS[id].nextMaintenance; } |
|
| 149 | + | arm(HARTS[id].timer, deadline); |
|
| 125 | 150 | sync::leave(guard); |
|
| 126 | 151 | } |
|
| 127 | 152 | ||
| 128 | 153 | /// Signal an initialized online hart while request metadata is serialized. |
|
| 129 | 154 | unsafe fn signal(target: u32) { writeSoftware(HARTS[target].timer.software, 1); } |
|
| 130 | 155 | ||
| 156 | + | /// Signal other harts that retain stopped contexts under the metadata lock. |
|
| 157 | + | export unsafe fn reschedule(harts: u32) { |
|
| 158 | + | let source = hart(); |
|
| 159 | + | for target in 0..limits::HARTS { |
|
| 160 | + | if target <> source and (harts & (1 << target)) <> 0 { signal(target); } |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | ||
| 131 | 164 | /// Notify the current owner or the eligible hart after a context is released. |
|
| 132 | 165 | unsafe fn signalContext(context: abi::Ref, now: u64) { |
|
| 133 | 166 | if not slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live) { return; } |
|
| 134 | 167 | if let owner = domains::STORE.contexts[context.index].hart { signal(owner); return; } |
|
| 135 | 168 | for i in 0..limits::BUDGETS { |
| 215 | 248 | }; |
|
| 216 | 249 | releaseRequest(source, request); |
|
| 217 | 250 | sync::leave(guard); return result; |
|
| 218 | 251 | } |
|
| 219 | 252 | ||
| 253 | + | /// Read mtime through the executing hart's validated timer mapping. |
|
| 254 | + | export unsafe fn clock() -> u64 { return now(HARTS[hart()].timer); } |
|
| 255 | + | ||
| 220 | 256 | /// Read the running context's identity and domain-relative CPU authority. |
|
| 221 | 257 | export unsafe fn current() -> abi::CurrentContextInfo { |
|
| 222 | 258 | let id = hart(); |
|
| 223 | 259 | assert id < limits::HARTS; |
|
| 224 | 260 | let context = HARTS[id].state.current else panic "no current context"; |
| 235 | 271 | let guard = sync::enter(); |
|
| 236 | 272 | assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live); |
|
| 237 | 273 | let owner = domains::STORE.contexts[context.index].owner; |
|
| 238 | 274 | if frame.registers[17] == abi::Operation::ContextReturn as u64 { |
|
| 239 | 275 | assert trap::advanceCall(frame); |
|
| 240 | - | try! domains::returned(&mut domains::STORE, owner, context); |
|
| 276 | + | let stopped = try! domains::returned(&mut domains::STORE, owner, context); |
|
| 277 | + | reschedule(stopped); |
|
| 241 | 278 | sync::leave(guard); |
|
| 242 | 279 | } else { |
|
| 243 | 280 | sync::leave(guard); |
|
| 281 | + | sync::restoreInterrupts(8); |
|
| 244 | 282 | calls::handle(owner, frame, now(HARTS[id].timer), calls::synchronized); |
|
| 283 | + | let interrupts = sync::maskInterrupts(); |
|
| 245 | 284 | } |
|
| 246 | 285 | interrupt(frame, &mut *localAnchor()); |
|
| 247 | 286 | } |
|
| 248 | 287 | ||
| 288 | + | /// Stop a faulting user domain and resume another authorized context. |
|
| 289 | + | export unsafe fn fault(frame: &mut trap::Frame, anchor: &mut trap::Hart) -> ! { |
|
| 290 | + | assert trap::fromUser(frame); |
|
| 291 | + | let guard = sync::enter(); |
|
| 292 | + | let context = HARTS[hart()].state.current else panic "fault without current context"; |
|
| 293 | + | assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live); |
|
| 294 | + | let owner = domains::STORE.contexts[context.index].owner; |
|
| 295 | + | let stopped = try! domains::terminate(&mut domains::STORE, owner, events::FAULT, frame.cause as u32); |
|
| 296 | + | reschedule(stopped); |
|
| 297 | + | sync::leave(guard); |
|
| 298 | + | interrupt(frame, &mut *localAnchor()); |
|
| 299 | + | } |
|
| 300 | + | ||
| 249 | 301 | /// Enter the first authorized context or the hart's retained idle frame. |
|
| 250 | 302 | export unsafe fn start(anchor: &mut trap::Hart) -> ! { |
|
| 251 | 303 | let id = hart(); |
|
| 252 | 304 | assert id < limits::HARTS; |
|
| 253 | 305 | let mut frame = HARTS[id].state.idle; |
kernel/kernel/domains.rad
+191 -32
| 18 | 18 | export constant KERNEL_STACK_PAGES: u32 = 64; |
|
| 19 | 19 | ||
| 20 | 20 | /// Management rights returned to a domain's creator. |
|
| 21 | 21 | export constant MANAGEMENT_RIGHTS: u16 = abi::DESTROY | abi::EXECUTE | abi::GRANT | abi::TRANSFER | abi::WAKE; |
|
| 22 | 22 | ||
| 23 | + | /// Words needed to retain one ancestry bit per domain slot. |
|
| 24 | + | constant ANCESTRY_WORDS: u32 = (limits::DOMAINS + 31) / 32; |
|
| 25 | + | ||
| 23 | 26 | /// Protection-domain lifetime independent of execution budgets. |
|
| 24 | 27 | export union Lifecycle: Copy { |
|
| 25 | 28 | /// Resources can be installed before one-time activation. |
|
| 26 | 29 | Pending, |
|
| 27 | 30 | /// The domain can execute when a context has CPU authority. |
|
| 28 | 31 | Active, |
|
| 29 | - | /// Execution has stopped and the identity can remain as a tombstone. |
|
| 32 | + | /// Contexts cannot be selected; hart release and resource cleanup can remain. |
|
| 30 | 33 | Dead, |
|
| 31 | 34 | } |
|
| 32 | 35 | ||
| 33 | 36 | /// Private domain metadata and its shared event ring. |
|
| 34 | 37 | export record Memory: Copy { |
| 58 | 61 | state: ContextState, |
|
| 59 | 62 | /// Hart retaining this context as its current execution owner. |
|
| 60 | 63 | hart: ?u32, |
|
| 61 | 64 | /// Remote call whose retained continuation requires this context's storage. |
|
| 62 | 65 | pending: ?abi::Ref, |
|
| 66 | + | /// A kernel call retains unpublished resources and requires this context's stack. |
|
| 67 | + | reservation: bool, |
|
| 63 | 68 | /// Physical frames retained until this context stops. |
|
| 64 | 69 | kernelFrames: frames::Run, |
|
| 65 | 70 | /// Exclusive stack bounds used by trap entry and suspended M-mode calls. |
|
| 66 | 71 | kernelStack: range::Range, |
|
| 67 | 72 | /// Exclusive user stack extent; empty until initial activation. |
| 96 | 101 | export record Store: Copy { |
|
| 97 | 102 | /// Domain generations, including retained dead identities. |
|
| 98 | 103 | slots: [slots::Slot; limits::DOMAINS], |
|
| 99 | 104 | /// Payload for each published domain. |
|
| 100 | 105 | records: [Domain; limits::DOMAINS], |
|
| 106 | + | /// True after private storage is retired, while terminal identity can remain. |
|
| 107 | + | reclaimed: [bool; limits::DOMAINS], |
|
| 108 | + | /// True after quiescence admits a domain to resource teardown. |
|
| 109 | + | reaping: [bool; limits::DOMAINS], |
|
| 110 | + | /// Number of dead domain identities retained by the store. |
|
| 111 | + | dead: u32, |
|
| 112 | + | /// First domain slot considered by the next reaper pass. |
|
| 113 | + | reapCursor: u32, |
|
| 114 | + | /// Creation ancestors by domain slot. Clear a column before its ancestor slot is reused. |
|
| 115 | + | ancestors: [[u32; ANCESTRY_WORDS]; limits::DOMAINS], |
|
| 101 | 116 | /// Context generations shared by initial and additional contexts. |
|
| 102 | 117 | contextSlots: [slots::Slot; limits::CONTEXTS], |
|
| 103 | 118 | /// Payload for each published execution context. |
|
| 104 | 119 | contexts: [Context; limits::CONTEXTS], |
|
| 105 | 120 | /// Trusted event producers and terminal records. |
| 116 | 131 | /// Address of the U-mode normal-return call sequence. |
|
| 117 | 132 | fn returnAddress() -> u64; |
|
| 118 | 133 | ||
| 119 | 134 | /// Initialize fresh metadata before boot-domain publication. |
|
| 120 | 135 | export fn initialize(store: &mut Store) { |
|
| 136 | + | set store.dead = 0; |
|
| 137 | + | set store.reapCursor = 0; |
|
| 121 | 138 | slots::initialize(&mut store.slots[..]); |
|
| 122 | 139 | slots::initialize(&mut store.contextSlots[..]); |
|
| 140 | + | for i in 0..limits::CONTEXTS { set store.contexts[i].reservation = false; } |
|
| 141 | + | for i in 0..limits::DOMAINS { set store.ancestors[i] = [0; ANCESTRY_WORDS]; set store.reclaimed[i] = false; set store.reaping[i] = false; } |
|
| 123 | 142 | events::initialize(&mut store.events); |
|
| 124 | 143 | } |
|
| 125 | 144 | ||
| 126 | 145 | /// Read a domain record after validating its retained generation. |
|
| 127 | 146 | export fn get(store: &Store, object: abi::Ref) -> Domain throws (abi::Error) { |
|
| 128 | - | if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; } |
|
| 147 | + | if not slots::matches(&store.slots[..], object, slots::State::Live) or store.reclaimed[object.index] { throw abi::Error::BadHandle; } |
|
| 129 | 148 | return store.records[object.index]; |
|
| 130 | 149 | } |
|
| 131 | 150 | ||
| 151 | + | /// Test whether all published and unpublished execution ownership has ended. |
|
| 152 | + | export fn quiescent(store: &Store, owner: abi::Ref) -> bool throws (abi::Error) { |
|
| 153 | + | let domain = try get(store, owner); |
|
| 154 | + | if domain.state <> Lifecycle::Dead { return false; } |
|
| 155 | + | for i in 0..limits::CONTEXTS { |
|
| 156 | + | let state = store.contextSlots[i].state; |
|
| 157 | + | if state <> slots::State::Live and state <> slots::State::Reserved { continue; } |
|
| 158 | + | if store.contexts[i].owner <> owner { continue; } |
|
| 159 | + | if state == slots::State::Reserved or store.contexts[i].state <> ContextState::Stopped |
|
| 160 | + | or store.contexts[i].hart <> nil or store.contexts[i].pending <> nil or store.contexts[i].reservation { return false; } |
|
| 161 | + | } |
|
| 162 | + | return true; |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | /// Retire at most one stopped context after all domain execution ownership ends. |
|
| 166 | + | export fn retireContext(store: &mut Store, pool: &mut frames::Pool, owner: abi::Ref) -> bool throws (abi::Error) { |
|
| 167 | + | if not try quiescent(store, owner) { throw abi::Error::Busy; } |
|
| 168 | + | for i in 0..limits::CONTEXTS { |
|
| 169 | + | if store.contextSlots[i].state <> slots::State::Live or store.contexts[i].owner <> owner { continue; } |
|
| 170 | + | let object = abi::Ref { index: i, generation: store.contextSlots[i].generation }; |
|
| 171 | + | try! frames::retire(pool, store.contexts[i].kernelFrames); |
|
| 172 | + | try! slots::release(&mut store.contextSlots[..], object); |
|
| 173 | + | return true; |
|
| 174 | + | } |
|
| 175 | + | return false; |
|
| 176 | + | } |
|
| 177 | + | ||
| 178 | + | /// Retire private domain storage after contexts, handles, and exposures are gone. |
|
| 179 | + | export unsafe fn retireStorage(store: &mut Store, backings: &mut backing::Store, owner: abi::Ref) throws (abi::Error) { |
|
| 180 | + | let domain = try get(store, owner); |
|
| 181 | + | if not try quiescent(store, owner) { throw abi::Error::Busy; } |
|
| 182 | + | for i in 0..limits::CONTEXTS { |
|
| 183 | + | if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == owner { throw abi::Error::Busy; } |
|
| 184 | + | } |
|
| 185 | + | for i in 0..limits::HANDLES { |
|
| 186 | + | let state = domain.memory.table.slots[i].state; |
|
| 187 | + | if state == slots::State::Live or state == slots::State::Reserved { throw abi::Error::Busy; } |
|
| 188 | + | } |
|
| 189 | + | if backings.domains[owner.index] <> 0 or store.events.queues[owner.index].generation <> 0 { throw abi::Error::Busy; } |
|
| 190 | + | try! frames::retire(&mut backings.pool, domain.graph.frames); |
|
| 191 | + | try! frames::retire(&mut backings.pool, domain.allocation); |
|
| 192 | + | set store.reclaimed[owner.index] = true; |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | /// Release an acknowledged terminal identity and clear its creation-ancestry column. |
|
| 196 | + | export fn releaseIdentity(store: &mut Store, owner: abi::Ref) throws (abi::Error) { |
|
| 197 | + | if not slots::matches(&store.slots[..], owner, slots::State::Live) { throw abi::Error::BadHandle; } |
|
| 198 | + | if not store.reclaimed[owner.index] or not try events::acknowledged(&store.events, owner) { throw abi::Error::Busy; } |
|
| 199 | + | let word = owner.index / 32; |
|
| 200 | + | let bit = 1 << (owner.index % 32); |
|
| 201 | + | for i in 0..limits::DOMAINS { set store.ancestors[i][word] &= ~bit; } |
|
| 202 | + | try! events::release(&mut store.events, owner); |
|
| 203 | + | try! slots::release(&mut store.slots[..], owner); |
|
| 204 | + | assert store.dead > 0; |
|
| 205 | + | set store.dead -= 1; |
|
| 206 | + | } |
|
| 207 | + | ||
| 132 | 208 | /// Read a live context after checking its generation and owning domain. |
|
| 133 | 209 | export fn context(store: &Store, owner: abi::Ref, object: abi::Ref) -> Context throws (abi::Error) { |
|
| 134 | 210 | let domain = try get(store, owner); |
|
| 135 | 211 | if not slots::matches(&store.contextSlots[..], object, slots::State::Live) |
|
| 136 | 212 | or store.contexts[object.index].owner <> owner { throw abi::Error::BadHandle; } |
|
| 137 | 213 | return store.contexts[object.index]; |
|
| 138 | 214 | } |
|
| 139 | 215 | ||
| 140 | - | /// Stop a returned context; initial return ends execution of its whole domain. |
|
| 141 | - | export fn returned(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) { |
|
| 142 | - | let current = try context(store, owner, object); |
|
| 143 | - | if store.records[owner.index].initial == object { |
|
| 144 | - | set store.records[owner.index].state = Lifecycle::Dead; |
|
| 145 | - | for i in 0..limits::CONTEXTS { |
|
| 146 | - | if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == owner { |
|
| 147 | - | set store.contexts[i].state = ContextState::Stopped; |
|
| 148 | - | } |
|
| 216 | + | /// Change the live lifecycle receiver under shared metadata serialization. |
|
| 217 | + | export fn reparent(store: &mut Store, table: &capability::Table, child: abi::Handle, parent: abi::Handle) |
|
| 218 | + | throws (abi::Error) |
|
| 219 | + | { |
|
| 220 | + | let target = try capability::lookup(table, child, abi::Kind::Domain, abi::Rights(abi::DESTROY)); |
|
| 221 | + | let receiver = try capability::lookup(table, parent, abi::Kind::Domain, abi::Rights(abi::WAKE)); |
|
| 222 | + | let domain = try get(store, target.object); |
|
| 223 | + | let destination = try get(store, receiver.object); |
|
| 224 | + | if domain.state == Lifecycle::Dead or destination.state == Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 225 | + | set store.records[target.object.index].parent = receiver.object; |
|
| 226 | + | } |
|
| 227 | + | ||
| 228 | + | /// Retain a terminal event, stop execution, and report harts that must release contexts. |
|
| 229 | + | export fn terminate(store: &mut Store, owner: abi::Ref, kind: u16, code: u32) -> u32 throws (abi::Error) { |
|
| 230 | + | return try stop(store, owner, false, kind, code); |
|
| 231 | + | } |
|
| 232 | + | ||
| 233 | + | /// Stop a domain and its creation descendants without changing creation ancestry. |
|
| 234 | + | export fn terminateTree(store: &mut Store, owner: abi::Ref, kind: u16, code: u32) -> u32 throws (abi::Error) { |
|
| 235 | + | return try stop(store, owner, true, kind, code); |
|
| 236 | + | } |
|
| 237 | + | ||
| 238 | + | /// Commit terminal state for a selected set before releasing the metadata lock. |
|
| 239 | + | fn stop(store: &mut Store, owner: abi::Ref, tree: bool, kind: u16, code: u32) -> u32 throws (abi::Error) { |
|
| 240 | + | if kind <> events::CHILD_EXIT and kind <> events::FAULT { throw abi::Error::InvalidArg; } |
|
| 241 | + | let domain = try get(store, owner); |
|
| 242 | + | let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS]; |
|
| 243 | + | for i in 0..limits::DOMAINS { |
|
| 244 | + | if store.slots[i].state <> slots::State::Live or store.records[i].state == Lifecycle::Dead { continue; } |
|
| 245 | + | if i <> owner.index and (not tree or (store.ancestors[i][owner.index / 32] & (1 << (owner.index % 32))) == 0) { continue; } |
|
| 246 | + | if store.events.terminals[i].state <> events::State::Free { throw abi::Error::Busy; } |
|
| 247 | + | if store.events.queues[i].generation <> store.slots[i].generation { throw abi::Error::BadHandle; } |
|
| 248 | + | set generations[i] = store.slots[i].generation; |
|
| 249 | + | } |
|
| 250 | + | for i in 0..limits::DOMAINS { |
|
| 251 | + | if generations[i] == 0 { continue; } |
|
| 252 | + | let object = abi::Ref { index: i, generation: generations[i] }; |
|
| 253 | + | let terminal = try! events::reserve(&mut store.events, object); |
|
| 254 | + | let mut receiver = abi::Ref { index: 0, generation: 0 }; |
|
| 255 | + | if let parent = store.records[i].parent { set receiver = parent; } |
|
| 256 | + | events::finish(&mut store.events, terminal, receiver, kind, code); |
|
| 257 | + | set store.records[i].state = Lifecycle::Dead; |
|
| 258 | + | set store.dead += 1; |
|
| 259 | + | } |
|
| 260 | + | try! events::closeSet(&mut store.events, &generations[..]); |
|
| 261 | + | let mut harts: u32 = 0; |
|
| 262 | + | for i in 0..limits::CONTEXTS { |
|
| 263 | + | if store.contextSlots[i].state <> slots::State::Live { continue; } |
|
| 264 | + | let target = store.contexts[i].owner; |
|
| 265 | + | if generations[target.index] == target.generation { |
|
| 266 | + | set store.contexts[i].state = ContextState::Stopped; |
|
| 267 | + | if let hart = store.contexts[i].hart { set harts |= 1 << hart; } |
|
| 149 | 268 | } |
|
| 150 | - | } else { |
|
| 151 | - | set store.contexts[object.index].state = ContextState::Stopped; |
|
| 152 | 269 | } |
|
| 270 | + | for i in 0..limits::DOMAINS { |
|
| 271 | + | if store.slots[i].state <> slots::State::Live { continue; } |
|
| 272 | + | if let parent = store.records[i].parent { |
|
| 273 | + | if generations[parent.index] == parent.generation { set store.records[i].parent = nil; } |
|
| 274 | + | } |
|
| 275 | + | } |
|
| 276 | + | return harts; |
|
| 277 | + | } |
|
| 278 | + | ||
| 279 | + | /// Stop a returned context; initial return ends execution of its whole domain. |
|
| 280 | + | export fn returned(store: &mut Store, owner: abi::Ref, object: abi::Ref) -> u32 throws (abi::Error) { |
|
| 281 | + | let current = try context(store, owner, object); |
|
| 282 | + | if store.records[owner.index].initial == object { return try terminate(store, owner, events::CHILD_EXIT, 0); } |
|
| 283 | + | set store.contexts[object.index].state = ContextState::Stopped; |
|
| 284 | + | return 0; |
|
| 153 | 285 | } |
|
| 154 | 286 | ||
| 155 | 287 | /// Atomically check notifications and suspend the caller if its queue is empty. |
|
| 156 | 288 | /// The caller serializes this check with notification publication. |
|
| 157 | 289 | export unsafe fn wait(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) { |
| 191 | 323 | export fn contextDestroy(store: &mut Store, memory: &mut pages::Store, table: &capability::Table, |
|
| 192 | 324 | authority: abi::Handle, object: abi::Ref) throws (abi::Error) |
|
| 193 | 325 | { |
|
| 194 | 326 | let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY)); |
|
| 195 | 327 | let target = try context(store, permit.object, object); |
|
| 328 | + | if store.records[permit.object.index].state == Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 196 | 329 | if store.records[permit.object.index].initial == object { throw abi::Error::Denied; } |
|
| 197 | - | if target.hart <> nil or target.pending <> nil { throw abi::Error::Busy; } |
|
| 330 | + | if target.hart <> nil or target.pending <> nil or target.reservation { throw abi::Error::Busy; } |
|
| 198 | 331 | try! frames::release(&mut memory.backings.pool, target.kernelFrames); |
|
| 199 | 332 | try! slots::release(&mut store.contextSlots[..], object); |
|
| 200 | 333 | } |
|
| 201 | 334 | ||
| 202 | 335 | /// Derive a descending stack extent from one writable Page capability. |
| 230 | 363 | export unsafe fn activate(store: &mut Store, memory: &pages::Store, table: &capability::Table, |
|
| 231 | 364 | handle: abi::Handle, stack: u64, args: u64, size: u64) throws (abi::Error) |
|
| 232 | 365 | { |
|
| 233 | 366 | let entry = try capability::lookup(table, handle, abi::Kind::Domain, abi::Rights(abi::EXECUTE)); |
|
| 234 | 367 | let mut domain = try get(store, entry.object); |
|
| 368 | + | if domain.state == Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 235 | 369 | if domain.state <> Lifecycle::Pending { throw abi::Error::NotPending; } |
|
| 236 | 370 | let userStack = try stackRange(memory, &domain.memory.table, stack); |
|
| 237 | 371 | if size <> 0 and not pages::accessible(memory, &domain.memory.table, args, size, abi::Rights(abi::READ)) { |
|
| 238 | 372 | throw abi::Error::InvalidArg; |
|
| 239 | 373 | } |
| 308 | 442 | set frame.registers[3] = domain.graph.table.ptr as u64; |
|
| 309 | 443 | set frame.registers[10] = start.args; |
|
| 310 | 444 | set frame.registers[11] = start.size; |
|
| 311 | 445 | let object = slots::reference(&slot); |
|
| 312 | 446 | set store.contexts[object.index] = Context { |
|
| 313 | - | owner: permission.object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames, kernelStack, userStack, frame, |
|
| 447 | + | owner: permission.object, state: ContextState::Ready, hart: nil, pending: nil, reservation: false, kernelFrames, kernelStack, userStack, frame, |
|
| 314 | 448 | }; |
|
| 315 | 449 | match slot { |
|
| 316 | 450 | case slots::Reservation::Held(object) => return ContextReservation::Held(ContextAllocation { |
|
| 317 | 451 | object, owner: permission.object, frames: kernelFrames, base: kernelStack.start, |
|
| 318 | 452 | }), |
|
| 319 | 453 | } |
|
| 320 | 454 | } |
|
| 321 | 455 | ||
| 322 | 456 | /// Clear an unpublished context's private kernel stack. |
|
| 323 | 457 | export fn contextClear(reservation: &ContextReservation) { |
|
| 324 | - | match reservation { case ContextReservation::Held(allocation) => zero(allocation.base, allocation.frames.count), } |
|
| 458 | + | match reservation { case ContextReservation::Held(allocation) => contextClearAllocation(allocation), } |
|
| 459 | + | } |
|
| 460 | + | ||
| 461 | + | /// Clear retained private stack frames outside the metadata lock. |
|
| 462 | + | export fn contextClearAllocation(allocation: &ContextAllocation) { |
|
| 463 | + | zero(allocation.base, allocation.frames.count); |
|
| 325 | 464 | } |
|
| 326 | 465 | ||
| 327 | 466 | /// Check the reserved context and its retained physical allocation. |
|
| 328 | 467 | fn contextRequired(store: &Store, allocation: &ContextAllocation) { |
|
| 329 | 468 | assert slots::matches(&store.contextSlots[..], allocation.object, slots::State::Reserved); |
| 343 | 482 | } |
|
| 344 | 483 | } |
|
| 345 | 484 | ||
| 346 | 485 | /// Return an unpublished context's stack and slot under metadata serialization. |
|
| 347 | 486 | export fn contextCancel(store: &mut Store, memory: &mut pages::Store, reservation: ContextReservation) { |
|
| 487 | + | contextCancelUsing(store, memory, reservation, frames::release); |
|
| 488 | + | } |
|
| 489 | + | ||
| 490 | + | /// Cancel a context slot and transfer its private stack to the release operation. |
|
| 491 | + | export fn contextCancelUsing(store: &mut Store, memory: &mut pages::Store, reservation: ContextReservation, |
|
| 492 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 493 | + | { |
|
| 348 | 494 | match reservation { |
|
| 349 | 495 | case ContextReservation::Held(allocation) => { |
|
| 350 | 496 | contextRequired(store, &allocation); |
|
| 351 | - | try! frames::release(&mut memory.backings.pool, allocation.frames); |
|
| 497 | + | try! release(&mut memory.backings.pool, allocation.frames); |
|
| 352 | 498 | try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(allocation.object)); |
|
| 353 | 499 | }, |
|
| 354 | 500 | } |
|
| 355 | 501 | } |
|
| 356 | 502 |
| 397 | 543 | }, |
|
| 398 | 544 | } |
|
| 399 | 545 | } |
|
| 400 | 546 | ||
| 401 | 547 | /// Return storage that has never been exposed to a published domain. |
|
| 402 | - | fn discard(pool: &mut frames::Pool, prepared: Prepared) { |
|
| 403 | - | try! frames::release(pool, prepared.kernelFrames); |
|
| 404 | - | try! frames::release(pool, prepared.graph.frames); |
|
| 405 | - | try! frames::release(pool, prepared.allocation); |
|
| 548 | + | fn discard(pool: &mut frames::Pool, prepared: Prepared, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) { |
|
| 549 | + | try! release(pool, prepared.kernelFrames); |
|
| 550 | + | try! release(pool, prepared.graph.frames); |
|
| 551 | + | try! release(pool, prepared.allocation); |
|
| 406 | 552 | } |
|
| 407 | 553 | ||
| 408 | 554 | /// Unpublished domain metadata retained during private initialization. |
|
| 409 | 555 | export record Creation: Copy { |
|
| 410 | 556 | /// Calling domain kept live until publication or cancellation. |
| 449 | 595 | let contextSlot = try slots::reserve(&mut store.contextSlots[..]) catch err { |
|
| 450 | 596 | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 451 | 597 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 452 | 598 | }; |
|
| 453 | 599 | let initialSlot = slots::reference(&contextSlot); |
|
| 600 | + | set store.contexts[initialSlot.index].owner = slots::reference(&domainSlot); |
|
| 454 | 601 | set store.contexts[initialSlot.index].userStack = range::Range { start: 0, end: 0 }; |
|
| 455 | 602 | let prepared = try allocate(&mut backings.pool, packages, root) catch err { |
|
| 456 | 603 | try! slots::cancel(&mut store.contextSlots[..], contextSlot); |
|
| 457 | 604 | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 458 | 605 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
| 470 | 617 | } |
|
| 471 | 618 | } |
|
| 472 | 619 | ||
| 473 | 620 | /// Initialize private storage from resident packages while metadata can change. |
|
| 474 | 621 | export unsafe fn prepare(packages: ®istry::Store, reservation: &Reservation) throws (abi::Error) { |
|
| 475 | - | match reservation { |
|
| 476 | - | case Reservation::Held(creation) => { |
|
| 477 | - | let mut storage = creation.storage; |
|
| 478 | - | zero(storage.memory as u64, storage.allocation.count); |
|
| 479 | - | zero(storage.kernelStack.start, storage.kernelFrames.count); |
|
| 480 | - | try instances::fill(packages, &storage.graph.graph, storage.graph.base); |
|
| 481 | - | capability::initialize(&mut storage.memory.table, creation.domain); |
|
| 482 | - | }, |
|
| 483 | - | } |
|
| 622 | + | match reservation { case Reservation::Held(creation) => try prepareCreation(packages, creation), } |
|
| 623 | + | } |
|
| 624 | + | ||
| 625 | + | /// Initialize retained domain storage outside the metadata lock. |
|
| 626 | + | export unsafe fn prepareCreation(packages: ®istry::Store, creation: &Creation) throws (abi::Error) { |
|
| 627 | + | let mut storage = creation.storage; |
|
| 628 | + | zero(storage.memory as u64, storage.allocation.count); |
|
| 629 | + | zero(storage.kernelStack.start, storage.kernelFrames.count); |
|
| 630 | + | try instances::fill(packages, &storage.graph.graph, storage.graph.base); |
|
| 631 | + | capability::initialize(&mut storage.memory.table, creation.domain); |
|
| 484 | 632 | } |
|
| 485 | 633 | ||
| 486 | 634 | /// Check the unpublished capacity retained by one creation transaction. |
|
| 487 | 635 | fn require(store: &Store, table: &capability::Table, creation: &Creation) { |
|
| 488 | 636 | assert table.owner == creation.owner; |
| 491 | 639 | assert slots::matches(&store.contextSlots[..], creation.context, slots::State::Reserved); |
|
| 492 | 640 | } |
|
| 493 | 641 | ||
| 494 | 642 | /// Return private domain storage and reserved slots under metadata serialization. |
|
| 495 | 643 | export fn cancel(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 644 | + | cancelUsing(store, backings, table, reservation, frames::release); |
|
| 645 | + | } |
|
| 646 | + | ||
| 647 | + | /// Cancel private domain slots and transfer their storage to the release operation. |
|
| 648 | + | export fn cancelUsing(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation, |
|
| 649 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 650 | + | { |
|
| 496 | 651 | match reservation { |
|
| 497 | 652 | case Reservation::Held(creation) => { |
|
| 498 | 653 | require(store, table, &creation); |
|
| 499 | - | discard(&mut backings.pool, creation.storage); |
|
| 654 | + | discard(&mut backings.pool, creation.storage, release); |
|
| 500 | 655 | try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(creation.context)); |
|
| 501 | 656 | try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(creation.domain)); |
|
| 502 | 657 | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(creation.handle)); |
|
| 503 | 658 | }, |
|
| 504 | 659 | } |
| 532 | 687 | // MPIE enables interrupts after the first return into U-mode. |
|
| 533 | 688 | let mut frame = trap::Frame { registers: [0; 32], pc: creation.entry, status: 0x80, cause: 0, value: 0 }; |
|
| 534 | 689 | set frame.registers[1] = returnAddress(); |
|
| 535 | 690 | set frame.registers[3] = graph.table.ptr as u64; |
|
| 536 | 691 | set store.contexts[initial.index] = Context { |
|
| 537 | - | owner: object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 692 | + | owner: object, state: ContextState::Ready, hart: nil, pending: nil, reservation: false, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 538 | 693 | userStack: range::Range { start: 0, end: 0 }, frame, |
|
| 539 | 694 | }; |
|
| 695 | + | set store.reclaimed[object.index] = false; |
|
| 696 | + | set store.reaping[object.index] = false; |
|
| 540 | 697 | set store.records[object.index] = Domain { |
|
| 541 | 698 | state: Lifecycle::Pending, creator: creation.owner, parent: creation.owner, image: creation.image, initial, |
|
| 542 | 699 | allocation: prepared.allocation, memory: prepared.memory, graph, events: queue, |
|
| 543 | 700 | }; |
|
| 701 | + | set store.ancestors[object.index] = store.ancestors[creation.owner.index]; |
|
| 702 | + | set store.ancestors[object.index][creation.owner.index / 32] |= 1 << (creation.owner.index % 32); |
|
| 544 | 703 | let context = try! slots::commit(&mut store.contextSlots[..], slots::Reservation::Held(initial)); |
|
| 545 | 704 | let domain = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(object)); |
|
| 546 | 705 | return capability::publish(table, slots::Reservation::Held(creation.handle), capability::Entry { |
|
| 547 | 706 | kind: abi::Kind::Domain, object: domain, rights: creation.rights, |
|
| 548 | 707 | }); |
kernel/kernel/events.rad
+41 -7
| 83 | 83 | state: State, |
|
| 84 | 84 | } |
|
| 85 | 85 | ||
| 86 | 86 | /// Fixed event metadata. Callers serialize all kernel-side mutations. |
|
| 87 | 87 | export record Store: Copy { |
|
| 88 | + | /// Terminal events retained for publication into receiver rings. |
|
| 89 | + | pending: u32, |
|
| 88 | 90 | /// Producer metadata indexed by receiver domain. |
|
| 89 | 91 | queues: [Queue; limits::DOMAINS], |
|
| 90 | 92 | /// Terminal storage indexed by the terminating domain. |
|
| 91 | 93 | terminals: [Terminal; limits::DOMAINS], |
|
| 92 | 94 | } |
| 105 | 107 | /// Advance a shared counter with explicit modulo arithmetic. |
|
| 106 | 108 | fn next(value: u32) -> u32 { return ((value as u64 + 1) & 0xffffffff) as u32; } |
|
| 107 | 109 | ||
| 108 | 110 | /// Initialize metadata before any queue or terminal record exists. |
|
| 109 | 111 | export fn initialize(store: &mut Store) { |
|
| 112 | + | set store.pending = 0; |
|
| 110 | 113 | for i in 0..limits::DOMAINS { |
|
| 111 | 114 | set store.queues[i].generation = 0; |
|
| 112 | 115 | set store.terminals[i].state = State::Free; |
|
| 113 | 116 | } |
|
| 114 | 117 | } |
| 227 | 230 | case Reservation::Held(subject) => { |
|
| 228 | 231 | let terminal = &mut store.terminals[subject.index]; |
|
| 229 | 232 | assert terminal.subject == subject and terminal.state == State::Reserved; |
|
| 230 | 233 | set terminal.receiver = receiver; |
|
| 231 | 234 | set terminal.event = Event { kind, reserved: 0, code, value: abi::id(subject) }; |
|
| 232 | - | if active { set terminal.state = State::Pending; } |
|
| 235 | + | if active { set terminal.state = State::Pending; set store.pending += 1; } |
|
| 233 | 236 | else { set terminal.state = State::Acknowledged; } |
|
| 234 | 237 | }, |
|
| 235 | 238 | } |
|
| 236 | 239 | } |
|
| 237 | 240 | ||
| 241 | + | /// Publish one retained terminal event when its receiver has ring capacity. |
|
| 242 | + | export fn deliver(store: &mut Store, subject: abi::Ref, ring: &mut Ring) -> bool throws (abi::Error) { |
|
| 243 | + | if subject.index >= limits::DOMAINS or store.terminals[subject.index].subject <> subject |
|
| 244 | + | or store.terminals[subject.index].state == State::Free { throw abi::Error::BadHandle; } |
|
| 245 | + | let terminal = store.terminals[subject.index]; |
|
| 246 | + | if terminal.state <> State::Pending { return false; } |
|
| 247 | + | try refresh(store, terminal.receiver, ring); |
|
| 248 | + | let queue = &mut store.queues[terminal.receiver.index]; |
|
| 249 | + | if distance(queue.tail, queue.head) == CAPACITY { return false; } |
|
| 250 | + | set store.terminals[subject.index].state = State::Queued; |
|
| 251 | + | assert store.pending > 0; |
|
| 252 | + | set store.pending -= 1; |
|
| 253 | + | publish(queue, ring, terminal.event, subject); |
|
| 254 | + | return true; |
|
| 255 | + | } |
|
| 256 | + | ||
| 238 | 257 | /// Publish retained terminal events in domain-index order as ring capacity permits. |
|
| 239 | 258 | export fn flush(store: &mut Store, receiver: abi::Ref, ring: &mut Ring) -> u32 throws (abi::Error) { |
|
| 240 | 259 | try refresh(store, receiver, ring); |
|
| 241 | 260 | let mut count: u32 = 0; |
|
| 242 | 261 | for i in 0..limits::DOMAINS { |
|
| 243 | 262 | if distance(store.queues[receiver.index].tail, store.queues[receiver.index].head) == CAPACITY { break; } |
|
| 244 | 263 | if store.terminals[i].state <> State::Pending or store.terminals[i].receiver <> receiver { continue; } |
|
| 245 | - | set store.terminals[i].state = State::Queued; |
|
| 246 | - | let event = store.terminals[i].event; |
|
| 247 | 264 | let subject = store.terminals[i].subject; |
|
| 248 | - | publish(&mut store.queues[receiver.index], ring, event, subject); |
|
| 249 | - | set count += 1; |
|
| 265 | + | if try deliver(store, subject, ring) { set count += 1; } |
|
| 250 | 266 | } |
|
| 251 | 267 | return count; |
|
| 252 | 268 | } |
|
| 253 | 269 | ||
| 254 | 270 | /// Report whether a retained terminal identity can be released. |
| 265 | 281 | } |
|
| 266 | 282 | ||
| 267 | 283 | /// Close a stopped domain's queue and end all terminal references held by that receiver. |
|
| 268 | 284 | export fn close(store: &mut Store, receiver: abi::Ref) throws (abi::Error) { |
|
| 269 | 285 | if not live(store, receiver) { throw abi::Error::BadHandle; } |
|
| 270 | - | set store.queues[receiver.index].generation = 0; |
|
| 286 | + | let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS]; |
|
| 287 | + | set generations[receiver.index] = receiver.generation; |
|
| 288 | + | try! closeSet(store, &generations[..]); |
|
| 289 | + | } |
|
| 290 | + | ||
| 291 | + | /// Close selected receiver generations and acknowledge their retained terminal events. |
|
| 292 | + | /// Zero entries leave a receiver open. Validate all selected generations before mutation. |
|
| 293 | + | export fn closeSet(store: &mut Store, generations: &[u32]) throws (abi::Error) { |
|
| 294 | + | if generations.len <> limits::DOMAINS { throw abi::Error::InvalidArg; } |
|
| 295 | + | for i in 0..limits::DOMAINS { |
|
| 296 | + | if generations[i] <> 0 and store.queues[i].generation <> generations[i] { throw abi::Error::BadHandle; } |
|
| 297 | + | } |
|
| 298 | + | for i in 0..limits::DOMAINS { |
|
| 299 | + | if generations[i] <> 0 { set store.queues[i].generation = 0; } |
|
| 300 | + | } |
|
| 271 | 301 | for i in 0..limits::DOMAINS { |
|
| 272 | - | if (store.terminals[i].state == State::Pending or store.terminals[i].state == State::Queued) and store.terminals[i].receiver == receiver { |
|
| 302 | + | let terminal = store.terminals[i]; |
|
| 303 | + | if terminal.state <> State::Pending and terminal.state <> State::Queued { continue; } |
|
| 304 | + | let receiver = terminal.receiver; |
|
| 305 | + | if generations[receiver.index] == receiver.generation { |
|
| 306 | + | if store.terminals[i].state == State::Pending { assert store.pending > 0; set store.pending -= 1; } |
|
| 273 | 307 | set store.terminals[i].state = State::Acknowledged; |
|
| 274 | 308 | } |
|
| 275 | 309 | } |
|
| 276 | 310 | } |
kernel/kernel/frames.rad
+37 -0
| 25 | 25 | addresses: [u64; limits::FRAMES], |
|
| 26 | 26 | /// True when the frame is available for reservation. |
|
| 27 | 27 | free: [bool; limits::FRAMES], |
|
| 28 | 28 | /// Number of valid frame-map entries. |
|
| 29 | 29 | count: u32, |
|
| 30 | + | /// Retired runs whose frames remain unavailable until reclamation. |
|
| 31 | + | retired: [Run; limits::FRAMES], |
|
| 32 | + | /// Index of the next retired run to reclaim. |
|
| 33 | + | retiredHead: u32, |
|
| 34 | + | /// Number of occupied retirement entries. |
|
| 35 | + | retiredCount: u32, |
|
| 30 | 36 | } |
|
| 31 | 37 | ||
| 32 | 38 | /// Insert one range into an address-sorted prefix of fixed storage. |
|
| 33 | 39 | fn insert(ranges: &mut [range::Range], count: u32, value: range::Range) { |
|
| 34 | 40 | let mut at = count; |
| 40 | 46 | ||
| 41 | 47 | /// Build a fresh frame map from complete RAM pages with no reserved byte. |
|
| 42 | 48 | /// The pool has zero valid entries if initialization fails. |
|
| 43 | 49 | export unsafe fn initialize(pool: &mut Pool, machine: &platform::Platform) throws (abi::Error) { |
|
| 44 | 50 | set pool.count = 0; |
|
| 51 | + | set pool.retiredHead = 0; |
|
| 52 | + | set pool.retiredCount = 0; |
|
| 45 | 53 | if machine.ramCount > platform::RAM_BANKS or machine.reservedCount > platform::RESERVATIONS { |
|
| 46 | 54 | throw abi::Error::InvalidArg; |
|
| 47 | 55 | } |
|
| 48 | 56 | let mut reserved: [range::Range; platform::RESERVATIONS] = undefined; |
|
| 49 | 57 | for i in 0..machine.reservedCount { |
| 123 | 131 | let memory = try extent(pool, run); |
|
| 124 | 132 | for i in run.first..run.first + run.count { if pool.free[i] { throw abi::Error::BadHandle; } } |
|
| 125 | 133 | for i in run.first..run.first + run.count { set pool.free[i] = true; } |
|
| 126 | 134 | } |
|
| 127 | 135 | ||
| 136 | + | /// Transfer exclusive ownership of a committed run to bounded reclamation. |
|
| 137 | + | /// The caller must not release or retire this run again. |
|
| 138 | + | export fn retire(pool: &mut Pool, run: Run) throws (abi::Error) { |
|
| 139 | + | let memory = try extent(pool, run); |
|
| 140 | + | assert pool.retiredCount < pool.count; |
|
| 141 | + | let tail = (pool.retiredHead + pool.retiredCount) % limits::FRAMES; |
|
| 142 | + | set pool.retired[tail] = run; |
|
| 143 | + | set pool.retiredCount += 1; |
|
| 144 | + | } |
|
| 145 | + | ||
| 146 | + | /// Return at most limit retired frames while metadata is serialized. |
|
| 147 | + | export fn reclaim(pool: &mut Pool, limit: u32) -> u32 { |
|
| 148 | + | let mut returned: u32 = 0; |
|
| 149 | + | while pool.retiredCount > 0 and returned < limit { |
|
| 150 | + | let mut count = pool.retired[pool.retiredHead].count; |
|
| 151 | + | if count > limit - returned { set count = limit - returned; } |
|
| 152 | + | let batch = Run { first: pool.retired[pool.retiredHead].first, count }; |
|
| 153 | + | try! release(pool, batch); |
|
| 154 | + | set pool.retired[pool.retiredHead].first += count; |
|
| 155 | + | set pool.retired[pool.retiredHead].count -= count; |
|
| 156 | + | set returned += count; |
|
| 157 | + | if pool.retired[pool.retiredHead].count == 0 { |
|
| 158 | + | set pool.retiredHead = (pool.retiredHead + 1) % limits::FRAMES; |
|
| 159 | + | set pool.retiredCount -= 1; |
|
| 160 | + | } |
|
| 161 | + | } |
|
| 162 | + | return returned; |
|
| 163 | + | } |
|
| 164 | + | ||
| 128 | 165 | /// Cancel unpublished frame ownership and restore its capacity. |
|
| 129 | 166 | export fn cancel(pool: &mut Pool, reservation: Reservation) throws (abi::Error) { |
|
| 130 | 167 | match reservation { case Reservation::Held(run) => try release(pool, run), } |
|
| 131 | 168 | } |
kernel/kernel/lifecycle.rad
added
+468 -0
| 1 | + | //! Terminal notification delivery and execution eligibility. |
|
| 2 | + | use super::limits; |
|
| 3 | + | use super::slots; |
|
| 4 | + | use super::events; |
|
| 5 | + | use super::domains; |
|
| 6 | + | use super::budgets; |
|
| 7 | + | use super::abi; |
|
| 8 | + | use super::remote; |
|
| 9 | + | use super::pages; |
|
| 10 | + | use super::registry; |
|
| 11 | + | use super::loader; |
|
| 12 | + | use super::capability; |
|
| 13 | + | use super::frames; |
|
| 14 | + | use super::backing; |
|
| 15 | + | use super::interrupts; |
|
| 16 | + | use super::plic; |
|
| 17 | + | ||
| 18 | + | /// Words needed to track request ownership for every context slot. |
|
| 19 | + | constant CONTEXT_WORDS: u32 = (limits::CONTEXTS + 31) / 32; |
|
| 20 | + | ||
| 21 | + | /// Private input and optional native output retained by a loader call. |
|
| 22 | + | export record Loading: Copy { |
|
| 23 | + | /// Workspace and reserved caller handle. |
|
| 24 | + | input: loader::Input, |
|
| 25 | + | /// Output frames and package slot, when compilation has been admitted. |
|
| 26 | + | output: ?loader::Output, |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Successful loader work ready for publication under the metadata lock. |
|
| 30 | + | export union LoadResult: Copy { |
|
| 31 | + | /// Matching immutable package already present in the registry. |
|
| 32 | + | Existing(abi::Ref), |
|
| 33 | + | /// Generated code and metadata in the retained output allocation. |
|
| 34 | + | Compiled(loader::Compiled), |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Private storage retained by an unfinished syscall. |
|
| 38 | + | export union Allocation: Copy { |
|
| 39 | + | /// Page frames and unpublished capability metadata. |
|
| 40 | + | Page(pages::Allocation), |
|
| 41 | + | /// Additional context slot and private kernel stack. |
|
| 42 | + | Context(domains::ContextAllocation), |
|
| 43 | + | /// Reserved domain generation with its retained creation payload. |
|
| 44 | + | Domain(abi::Ref), |
|
| 45 | + | /// Private compiler workspace and unpublished output. |
|
| 46 | + | Load(Loading), |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// One unfinished allocation retained by a caller generation. |
|
| 50 | + | export record HeldAllocation: Copy { |
|
| 51 | + | /// Context whose continuation owns this allocation. |
|
| 52 | + | context: abi::Ref, |
|
| 53 | + | /// Calling domain, which can differ from the allocation target. |
|
| 54 | + | owner: abi::Ref, |
|
| 55 | + | /// Frames and metadata slots excluded from allocation. |
|
| 56 | + | allocation: Allocation, |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Allocations retained while their contexts initialize private frames. |
|
| 60 | + | export record Calls: Copy { |
|
| 61 | + | /// One validity bit per occupied context slot. |
|
| 62 | + | active: [u32; CONTEXT_WORDS], |
|
| 63 | + | /// Reservation payloads indexed by context slot. |
|
| 64 | + | records: [HeldAllocation; limits::CONTEXTS], |
|
| 65 | + | /// Creation payloads valid while their domain slots are reserved. |
|
| 66 | + | creations: [domains::Creation; limits::DOMAINS], |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// A continuation's exclusive claim to its retained reservation. |
|
| 70 | + | export union Lease: Once { |
|
| 71 | + | /// Context generation that must take back the reservation before completion. |
|
| 72 | + | Held(abi::Ref), |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Reservations serialized with context and physical-memory metadata. |
|
| 76 | + | export unsafe static CALLS: Calls = undefined; |
|
| 77 | + | ||
| 78 | + | /// Clear allocation ownership before contexts start. |
|
| 79 | + | export fn initialize(calls: &mut Calls) { |
|
| 80 | + | for i in 0..CONTEXT_WORDS { set calls.active[i] = 0; } |
|
| 81 | + | } |
|
| 82 | + | ||
| 83 | + | /// Retain private storage and pin the caller's kernel stack under the lock. |
|
| 84 | + | fn hold(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, allocation: Allocation) -> Lease { |
|
| 85 | + | assert context.index < limits::CONTEXTS and context.generation <> 0; |
|
| 86 | + | assert slots::matches(&store.contextSlots[..], context, slots::State::Live); |
|
| 87 | + | assert not store.contexts[context.index].reservation; |
|
| 88 | + | let bit = 1 << (context.index % 32); |
|
| 89 | + | assert (calls.active[context.index / 32] & bit) == 0; |
|
| 90 | + | set calls.records[context.index] = HeldAllocation { context, owner: store.contexts[context.index].owner, allocation }; |
|
| 91 | + | set calls.active[context.index / 32] |= bit; |
|
| 92 | + | set store.contexts[context.index].reservation = true; |
|
| 93 | + | return Lease::Held(context); |
|
| 94 | + | } |
|
| 95 | + | ||
| 96 | + | /// Transfer a page reservation into context-owned storage under the metadata lock. |
|
| 97 | + | export fn holdPage(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: pages::Reservation) -> Lease { |
|
| 98 | + | match reservation { |
|
| 99 | + | case pages::Reservation::Held(allocation) => { |
|
| 100 | + | assert store.contexts[context.index].owner == allocation.owner; |
|
| 101 | + | return hold(calls, store, context, Allocation::Page(allocation)); |
|
| 102 | + | }, |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Transfer an unpublished context into its caller's retained storage. |
|
| 107 | + | export fn holdContext(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: domains::ContextReservation) -> Lease { |
|
| 108 | + | match reservation { |
|
| 109 | + | case domains::ContextReservation::Held(allocation) => return hold(calls, store, context, Allocation::Context(allocation)), |
|
| 110 | + | } |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | /// Retain unpublished domain storage under its reserved domain generation. |
|
| 114 | + | export fn holdDomain(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: domains::Reservation) -> Lease { |
|
| 115 | + | match reservation { |
|
| 116 | + | case domains::Reservation::Held(creation) => { |
|
| 117 | + | assert slots::matches(&store.slots[..], creation.domain, slots::State::Reserved); |
|
| 118 | + | assert store.contexts[context.index].owner == creation.owner; |
|
| 119 | + | set calls.creations[creation.domain.index] = creation; |
|
| 120 | + | return hold(calls, store, context, Allocation::Domain(creation.domain)); |
|
| 121 | + | }, |
|
| 122 | + | } |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Read the private payload for an exact reserved domain generation. |
|
| 126 | + | fn creation(calls: &Calls, object: abi::Ref) -> domains::Creation { |
|
| 127 | + | assert object.index < limits::DOMAINS; |
|
| 128 | + | assert calls.creations[object.index].domain == object; |
|
| 129 | + | return calls.creations[object.index]; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | /// Initialize domain storage while the caller retains execution ownership. |
|
| 133 | + | export unsafe fn prepareDomain(calls: &Calls, packages: ®istry::Store, lease: &Lease) throws (abi::Error) { |
|
| 134 | + | match lease { |
|
| 135 | + | case Lease::Held(context) => match held(calls, *context) { |
|
| 136 | + | case Allocation::Domain(object) => { |
|
| 137 | + | let retained = creation(calls, object); |
|
| 138 | + | try domains::prepareCreation(packages, &retained); |
|
| 139 | + | }, |
|
| 140 | + | else => panic "domain reservation required", |
|
| 141 | + | }, |
|
| 142 | + | } |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Retain private loader input and pin its caller's kernel stack. |
|
| 146 | + | export fn holdLoad(calls: &mut Calls, store: &mut domains::Store, context: abi::Ref, reservation: loader::Reservation) -> Lease { |
|
| 147 | + | match reservation { |
|
| 148 | + | case loader::Reservation::Held(input) => { |
|
| 149 | + | assert store.contexts[context.index].owner == input.owner; |
|
| 150 | + | return hold(calls, store, context, Allocation::Load(Loading { input, output: nil })); |
|
| 151 | + | }, |
|
| 152 | + | } |
|
| 153 | + | } |
|
| 154 | + | ||
| 155 | + | /// Read the private loader allocations through their caller's lease. |
|
| 156 | + | export fn load(calls: &Calls, lease: &Lease) -> Loading { |
|
| 157 | + | match lease { |
|
| 158 | + | case Lease::Held(context) => match held(calls, *context) { |
|
| 159 | + | case Allocation::Load(loading) => return loading, |
|
| 160 | + | else => panic "loader reservation required", |
|
| 161 | + | }, |
|
| 162 | + | } |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | /// Attach reserved native output to its caller before compilation can start. |
|
| 166 | + | export fn reserveOutput(calls: &mut Calls, lease: &Lease, memory: &mut pages::Store, packages: &mut registry::Store) throws (abi::Error) { |
|
| 167 | + | let mut loading = load(calls, lease); |
|
| 168 | + | assert loading.output == nil; |
|
| 169 | + | let pending = try loader::reserveOutput(&mut memory.backings.pool, packages); |
|
| 170 | + | match pending { case loader::OutputReservation::Held(output) => { set loading.output = output; }, } |
|
| 171 | + | match lease { case Lease::Held(context) => { set calls.records[context.index].allocation = Allocation::Load(loading); }, } |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | /// Return all unpublished loader capacity under the metadata lock. |
|
| 175 | + | fn discardLoad(memory: &mut pages::Store, state: &mut loader::State, packages: &mut registry::Store, |
|
| 176 | + | table: &mut capability::Table, loading: Loading, release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 177 | + | { |
|
| 178 | + | if let output = loading.output { loader::cancelOutputUsing(&mut memory.backings.pool, packages, loader::OutputReservation::Held(output), release); } |
|
| 179 | + | loader::cancelUsing(state, memory, table, loader::Reservation::Held(loading.input), release); |
|
| 180 | + | } |
|
| 181 | + | ||
| 182 | + | /// Cancel a live continuation's load and release its stack pin under the lock. |
|
| 183 | + | export unsafe fn cancelLoad(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store, |
|
| 184 | + | state: &mut loader::State, packages: &mut registry::Store, lease: Lease) |
|
| 185 | + | { |
|
| 186 | + | match take(calls, store, lease) { |
|
| 187 | + | case Allocation::Load(loading) => { |
|
| 188 | + | let mut owner = try! domains::get(store, loading.input.owner); |
|
| 189 | + | discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::release); |
|
| 190 | + | }, |
|
| 191 | + | else => panic "loader reservation required", |
|
| 192 | + | } |
|
| 193 | + | } |
|
| 194 | + | ||
| 195 | + | /// Publish successful loader work and release caller-owned temporary storage. |
|
| 196 | + | export unsafe fn finishLoad(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store, |
|
| 197 | + | state: &mut loader::State, packages: &mut registry::Store, lease: Lease, result: LoadResult) -> abi::Handle throws (abi::Error) |
|
| 198 | + | { |
|
| 199 | + | match take(calls, store, lease) { |
|
| 200 | + | case Allocation::Load(loading) => { |
|
| 201 | + | let mut owner = try! domains::get(store, loading.input.owner); |
|
| 202 | + | if owner.state == domains::Lifecycle::Dead { |
|
| 203 | + | discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::release); |
|
| 204 | + | throw abi::Error::BadHandle; |
|
| 205 | + | } |
|
| 206 | + | let mut object: abi::Ref = undefined; |
|
| 207 | + | match result { |
|
| 208 | + | case LoadResult::Existing(existing) => { assert loading.output == nil; set object = existing; }, |
|
| 209 | + | case LoadResult::Compiled(compiled) => { |
|
| 210 | + | let output = loading.output else panic "missing loader output"; |
|
| 211 | + | set object = loader::publishOutput(state, &mut memory.backings.pool, packages, |
|
| 212 | + | loader::OutputReservation::Held(output), compiled); |
|
| 213 | + | }, |
|
| 214 | + | } |
|
| 215 | + | return try loader::finish(state, memory, &mut owner.memory.table, loader::Reservation::Held(loading.input), object); |
|
| 216 | + | }, |
|
| 217 | + | else => panic "loader reservation required", |
|
| 218 | + | } |
|
| 219 | + | } |
|
| 220 | + | ||
| 221 | + | /// Read retained storage through its exact caller generation. |
|
| 222 | + | fn held(calls: &Calls, context: abi::Ref) -> Allocation { |
|
| 223 | + | assert context.index < limits::CONTEXTS; |
|
| 224 | + | assert (calls.active[context.index / 32] & (1 << (context.index % 32))) <> 0; |
|
| 225 | + | assert calls.records[context.index].context == context; |
|
| 226 | + | return calls.records[context.index].allocation; |
|
| 227 | + | } |
|
| 228 | + | ||
| 229 | + | /// Clear private page frames while the executing context retains hart ownership. |
|
| 230 | + | export fn clearPage(calls: &Calls, lease: &Lease) { |
|
| 231 | + | match lease { |
|
| 232 | + | case Lease::Held(context) => match held(calls, *context) { |
|
| 233 | + | case Allocation::Page(allocation) => pages::clearAllocation(&allocation), |
|
| 234 | + | else => panic "page reservation required", |
|
| 235 | + | }, |
|
| 236 | + | } |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Clear a private kernel stack while the caller retains hart ownership. |
|
| 240 | + | export fn clearContext(calls: &Calls, lease: &Lease) { |
|
| 241 | + | match lease { |
|
| 242 | + | case Lease::Held(context) => match held(calls, *context) { |
|
| 243 | + | case Allocation::Context(allocation) => domains::contextClearAllocation(&allocation), |
|
| 244 | + | else => panic "context reservation required", |
|
| 245 | + | }, |
|
| 246 | + | } |
|
| 247 | + | } |
|
| 248 | + | ||
| 249 | + | /// Remove retained ownership before serialized publication or cancellation. |
|
| 250 | + | fn take(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> Allocation { |
|
| 251 | + | match lease { |
|
| 252 | + | case Lease::Held(context) => { |
|
| 253 | + | let allocation = held(calls, context); |
|
| 254 | + | set calls.active[context.index / 32] &= ~(1 << (context.index % 32)); |
|
| 255 | + | assert slots::matches(&store.contextSlots[..], context, slots::State::Live); |
|
| 256 | + | set store.contexts[context.index].reservation = false; |
|
| 257 | + | return allocation; |
|
| 258 | + | }, |
|
| 259 | + | } |
|
| 260 | + | } |
|
| 261 | + | ||
| 262 | + | /// Return a page reservation for publication or cancellation under the lock. |
|
| 263 | + | export fn takePage(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> pages::Reservation { |
|
| 264 | + | match take(calls, store, lease) { |
|
| 265 | + | case Allocation::Page(allocation) => return pages::Reservation::Held(allocation), |
|
| 266 | + | else => panic "page reservation required", |
|
| 267 | + | } |
|
| 268 | + | } |
|
| 269 | + | ||
| 270 | + | /// Return a context reservation for publication or cancellation under the lock. |
|
| 271 | + | export fn takeContext(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> domains::ContextReservation { |
|
| 272 | + | match take(calls, store, lease) { |
|
| 273 | + | case Allocation::Context(allocation) => return domains::ContextReservation::Held(allocation), |
|
| 274 | + | else => panic "context reservation required", |
|
| 275 | + | } |
|
| 276 | + | } |
|
| 277 | + | ||
| 278 | + | /// Return domain storage for publication or cancellation under the lock. |
|
| 279 | + | export fn takeDomain(calls: &mut Calls, store: &mut domains::Store, lease: Lease) -> domains::Reservation { |
|
| 280 | + | match take(calls, store, lease) { |
|
| 281 | + | case Allocation::Domain(object) => return domains::Reservation::Held(creation(calls, object)), |
|
| 282 | + | else => panic "domain reservation required", |
|
| 283 | + | } |
|
| 284 | + | } |
|
| 285 | + | ||
| 286 | + | /// Retire one dead caller's private storage after execution ownership ends. |
|
| 287 | + | export unsafe fn cancelAllocations(calls: &mut Calls, store: &mut domains::Store, memory: &mut pages::Store, |
|
| 288 | + | state: &mut loader::State, packages: &mut registry::Store) -> bool |
|
| 289 | + | { |
|
| 290 | + | for word in 0..CONTEXT_WORDS { |
|
| 291 | + | if calls.active[word] == 0 { continue; } |
|
| 292 | + | for offset in 0..32 as u32 { |
|
| 293 | + | let bit: u32 = 1 << offset; |
|
| 294 | + | if (calls.active[word] & bit) == 0 { continue; } |
|
| 295 | + | let index = word * 32 + offset; |
|
| 296 | + | let retained = calls.records[index]; |
|
| 297 | + | let context = try domains::context(store, retained.owner, retained.context) catch { continue; }; |
|
| 298 | + | if context.hart <> nil or context.state <> domains::ContextState::Stopped { continue; } |
|
| 299 | + | let mut owner = try! domains::get(store, retained.owner); |
|
| 300 | + | if owner.state <> domains::Lifecycle::Dead { continue; } |
|
| 301 | + | set calls.active[word] &= ~bit; |
|
| 302 | + | match retained.allocation { |
|
| 303 | + | case Allocation::Load(loading) => discardLoad(memory, state, packages, &mut owner.memory.table, loading, frames::retire), |
|
| 304 | + | case Allocation::Page(allocation) => pages::cancelUsing(memory, &mut owner.memory.table, pages::Reservation::Held(allocation), frames::retire), |
|
| 305 | + | case Allocation::Context(allocation) => domains::contextCancelUsing(store, memory, domains::ContextReservation::Held(allocation), frames::retire), |
|
| 306 | + | case Allocation::Domain(object) => domains::cancelUsing(store, &mut memory.backings, |
|
| 307 | + | &mut owner.memory.table, domains::Reservation::Held(creation(calls, object)), frames::retire), |
|
| 308 | + | } |
|
| 309 | + | set store.contexts[index].reservation = false; |
|
| 310 | + | return true; |
|
| 311 | + | } |
|
| 312 | + | } |
|
| 313 | + | return false; |
|
| 314 | + | } |
|
| 315 | + | ||
| 316 | + | /// Release stopped callers' remote work after hart ownership ends. |
|
| 317 | + | /// Taken requests keep the context pinned until the target acknowledges them. |
|
| 318 | + | export fn cancelRequests(store: &mut domains::Store, requests: &mut remote::Store) { |
|
| 319 | + | let mut touched: [u32; CONTEXT_WORDS] = [0; CONTEXT_WORDS]; |
|
| 320 | + | let mut retained: [u32; CONTEXT_WORDS] = [0; CONTEXT_WORDS]; |
|
| 321 | + | let mut cancelled = false; |
|
| 322 | + | for i in 0..limits::REMOTE_REQUESTS { |
|
| 323 | + | if requests.slots[i].state <> slots::State::Live { continue; } |
|
| 324 | + | let request = requests.requests[i]; |
|
| 325 | + | let mut caller: ?abi::Ref = nil; |
|
| 326 | + | match request.action { |
|
| 327 | + | case remote::Action::Budget(call) => { set caller = call.context; }, |
|
| 328 | + | case remote::Action::Ready(context) => { set caller = context; }, |
|
| 329 | + | else => continue, |
|
| 330 | + | } |
|
| 331 | + | let object = caller else { continue; }; |
|
| 332 | + | if not slots::matches(&store.contextSlots[..], object, slots::State::Live) { continue; } |
|
| 333 | + | if store.contexts[object.index].hart <> nil or store.contexts[object.index].state <> domains::ContextState::Stopped { continue; } |
|
| 334 | + | if not slots::matches(&store.slots[..], store.contexts[object.index].owner, slots::State::Live) |
|
| 335 | + | or store.records[store.contexts[object.index].owner.index].state <> domains::Lifecycle::Dead { continue; } |
|
| 336 | + | let word = object.index / 32; |
|
| 337 | + | let bit = 1 << (object.index % 32); |
|
| 338 | + | set touched[word] |= bit; |
|
| 339 | + | if request.state == remote::State::Delivered { |
|
| 340 | + | set retained[word] |= bit; |
|
| 341 | + | set store.contexts[object.index].pending = abi::Ref { index: i, generation: requests.slots[i].generation }; |
|
| 342 | + | continue; |
|
| 343 | + | } |
|
| 344 | + | try! remote::cancel(requests, request.source, abi::Ref { index: i, generation: requests.slots[i].generation }); |
|
| 345 | + | set cancelled = true; |
|
| 346 | + | } |
|
| 347 | + | if not cancelled { return; } |
|
| 348 | + | for i in 0..limits::CONTEXTS { |
|
| 349 | + | let bit = 1 << (i % 32); |
|
| 350 | + | if (touched[i / 32] & bit) <> 0 and (retained[i / 32] & bit) == 0 { |
|
| 351 | + | set store.contexts[i].pending = nil; |
|
| 352 | + | } |
|
| 353 | + | } |
|
| 354 | + | } |
|
| 355 | + | ||
| 356 | + | /// Advance one eligible domain from a rotating slot cursor. |
|
| 357 | + | export unsafe fn reapNext(store: &mut domains::Store, memory: &mut pages::Store, windows: &mut budgets::Store, |
|
| 358 | + | irqs: &mut interrupts::Store) -> bool |
|
| 359 | + | { |
|
| 360 | + | if store.dead == 0 { return false; } |
|
| 361 | + | let mut checked = false; |
|
| 362 | + | let mut blocked: [bool; limits::DOMAINS] = [false; limits::DOMAINS]; |
|
| 363 | + | for offset in 0..limits::DOMAINS { |
|
| 364 | + | let index = (store.reapCursor + offset) % limits::DOMAINS; |
|
| 365 | + | if store.slots[index].state <> slots::State::Live or store.records[index].state <> domains::Lifecycle::Dead { continue; } |
|
| 366 | + | let owner = abi::Ref { index, generation: store.slots[index].generation }; |
|
| 367 | + | if store.reclaimed[index] { |
|
| 368 | + | if not try! events::acknowledged(&store.events, owner) { continue; } |
|
| 369 | + | } else if not store.reaping[index] { |
|
| 370 | + | if not checked { |
|
| 371 | + | for i in 0..limits::CONTEXTS { |
|
| 372 | + | let state = store.contextSlots[i].state; |
|
| 373 | + | if state <> slots::State::Live and state <> slots::State::Reserved { continue; } |
|
| 374 | + | if state == slots::State::Reserved or store.contexts[i].state <> domains::ContextState::Stopped |
|
| 375 | + | or store.contexts[i].hart <> nil or store.contexts[i].pending <> nil or store.contexts[i].reservation { |
|
| 376 | + | set blocked[store.contexts[i].owner.index] = true; |
|
| 377 | + | } |
|
| 378 | + | } |
|
| 379 | + | set checked = true; |
|
| 380 | + | } |
|
| 381 | + | if blocked[index] { continue; } |
|
| 382 | + | } |
|
| 383 | + | if try! reap(store, memory, windows, irqs, owner) { |
|
| 384 | + | set store.reapCursor = (index + 1) % limits::DOMAINS; |
|
| 385 | + | return true; |
|
| 386 | + | } |
|
| 387 | + | } |
|
| 388 | + | return false; |
|
| 389 | + | } |
|
| 390 | + | ||
| 391 | + | /// Advance one dead domain's resource teardown under the metadata lock. |
|
| 392 | + | /// Returns false while execution ownership or terminal acknowledgement is pending. |
|
| 393 | + | export unsafe fn reap(store: &mut domains::Store, memory: &mut pages::Store, windows: &mut budgets::Store, |
|
| 394 | + | irqs: &mut interrupts::Store, owner: abi::Ref) -> bool throws (abi::Error) |
|
| 395 | + | { |
|
| 396 | + | if not slots::matches(&store.slots[..], owner, slots::State::Live) { throw abi::Error::BadHandle; } |
|
| 397 | + | if store.reclaimed[owner.index] { |
|
| 398 | + | if not try events::acknowledged(&store.events, owner) { return false; } |
|
| 399 | + | try! domains::releaseIdentity(store, owner); |
|
| 400 | + | return true; |
|
| 401 | + | } |
|
| 402 | + | if not store.reaping[owner.index] { |
|
| 403 | + | if not try domains::quiescent(store, owner) { return false; } |
|
| 404 | + | set store.reaping[owner.index] = true; |
|
| 405 | + | } |
|
| 406 | + | let mut domain = try! domains::get(store, owner); |
|
| 407 | + | for i in 0..limits::HANDLES { |
|
| 408 | + | let slot = domain.memory.table.slots[i]; |
|
| 409 | + | if slot.state == slots::State::Reserved { return false; } |
|
| 410 | + | if slot.state <> slots::State::Live { continue; } |
|
| 411 | + | let entry = domain.memory.table.entries[i]; |
|
| 412 | + | let handle = try! abi::handle(entry.kind, abi::Ref { index: i, generation: slot.generation }); |
|
| 413 | + | match entry.kind { |
|
| 414 | + | case abi::Kind::Page => try! pages::drop(memory, &mut domain.memory.table, handle), |
|
| 415 | + | case abi::Kind::Budget => try! budgets::drop(windows, &mut domain.memory.table, handle), |
|
| 416 | + | case abi::Kind::Interrupt => { |
|
| 417 | + | let number = try! interrupts::query(irqs, &domain.memory.table, handle); |
|
| 418 | + | plic::mask(number); |
|
| 419 | + | try! interrupts::drop(irqs, &mut domain.memory.table, handle); |
|
| 420 | + | }, |
|
| 421 | + | else => { let removed = try! capability::invalidate(&mut domain.memory.table, handle); }, |
|
| 422 | + | } |
|
| 423 | + | return true; |
|
| 424 | + | } |
|
| 425 | + | if memory.backings.domains[owner.index] <> 0 { |
|
| 426 | + | let complete = try! backing::endDomainStep(&mut memory.backings, owner, 64); |
|
| 427 | + | return true; |
|
| 428 | + | } |
|
| 429 | + | if try! domains::retireContext(store, &mut memory.backings.pool, owner) { return true; } |
|
| 430 | + | try! domains::retireStorage(store, &mut memory.backings, owner); |
|
| 431 | + | return true; |
|
| 432 | + | } |
|
| 433 | + | ||
| 434 | + | /// Publish retained terminal events and report receiver harts under the metadata lock. |
|
| 435 | + | export unsafe fn service(store: &mut domains::Store, windows: &budgets::Store, now: u64) -> u32 { |
|
| 436 | + | if store.events.pending == 0 { return 0; } |
|
| 437 | + | let mut notified: [bool; limits::DOMAINS] = [false; limits::DOMAINS]; |
|
| 438 | + | let mut changed = false; |
|
| 439 | + | for i in 0..limits::DOMAINS { |
|
| 440 | + | let terminal = store.events.terminals[i]; |
|
| 441 | + | if terminal.state <> events::State::Pending { continue; } |
|
| 442 | + | let mut receiver = try domains::get(store, terminal.receiver) catch { continue; }; |
|
| 443 | + | if receiver.state == domains::Lifecycle::Dead { continue; } |
|
| 444 | + | let delivered = try events::deliver(&mut store.events, terminal.subject, &mut receiver.memory.ring) catch { false }; |
|
| 445 | + | if delivered { set notified[terminal.receiver.index] = true; set changed = true; } |
|
| 446 | + | } |
|
| 447 | + | if not changed { return 0; } |
|
| 448 | + | for i in 0..limits::CONTEXTS { |
|
| 449 | + | if store.contextSlots[i].state <> slots::State::Live { continue; } |
|
| 450 | + | if notified[store.contexts[i].owner.index] and store.contexts[i].state == domains::ContextState::Waiting { |
|
| 451 | + | set store.contexts[i].state = domains::ContextState::Ready; |
|
| 452 | + | } |
|
| 453 | + | } |
|
| 454 | + | let mut harts: u32 = 0; |
|
| 455 | + | for i in 0..limits::BUDGETS { |
|
| 456 | + | if windows.slots[i].state <> slots::State::Live { continue; } |
|
| 457 | + | let window = windows.windows[i]; |
|
| 458 | + | if not slots::matches(&store.slots[..], window.owner, slots::State::Live) |
|
| 459 | + | or not notified[window.owner.index] or window.end <= now { continue; } |
|
| 460 | + | let context = window.context else { continue; }; |
|
| 461 | + | if not slots::matches(&store.contextSlots[..], context, slots::State::Live) { continue; } |
|
| 462 | + | if store.contexts[context.index].owner == window.owner |
|
| 463 | + | and store.contexts[context.index].state <> domains::ContextState::Stopped { |
|
| 464 | + | set harts |= 1 << window.hart; |
|
| 465 | + | } |
|
| 466 | + | } |
|
| 467 | + | return harts; |
|
| 468 | + | } |
kernel/kernel/loader.rad
+52 -18
| 140 | 140 | return Lease::Held(frames::commit(reservation)); |
|
| 141 | 141 | } |
|
| 142 | 142 | ||
| 143 | 143 | /// Return temporary frames and release the compiler workspace under metadata serialization. |
|
| 144 | 144 | export fn release(state: &mut State, pool: &mut frames::Pool, lease: Lease) { |
|
| 145 | + | releaseUsing(state, pool, lease, frames::release); |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Transfer temporary frames to their release operation and clear workspace ownership. |
|
| 149 | + | fn releaseUsing(state: &mut State, pool: &mut frames::Pool, lease: Lease, |
|
| 150 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 151 | + | { |
|
| 145 | 152 | match lease { |
|
| 146 | - | case Lease::Held(run) => { try! frames::release(pool, run); }, |
|
| 153 | + | case Lease::Held(run) => { try! release(pool, run); }, |
|
| 147 | 154 | } |
|
| 148 | 155 | set state.owner = abi::Ref { index: 0, generation: 0 }; |
|
| 149 | 156 | set state.busy = false; |
|
| 150 | 157 | } |
|
| 151 | 158 |
| 229 | 236 | } |
|
| 230 | 237 | ||
| 231 | 238 | /// Snapshot and decode trusted input into the held private workspace. |
|
| 232 | 239 | /// The decoded package stays valid until this reservation is completed or cancelled. |
|
| 233 | 240 | export unsafe fn decode(reservation: &Reservation) -> binary::Package throws (abi::Error) { |
|
| 234 | - | match reservation { |
|
| 235 | - | case Reservation::Held(input) => { |
|
| 236 | - | let mut work = workspace(input.work); |
|
| 237 | - | try! mem::copy(&mut work.input[..input.length], @sliceOf(memory(input.address), input.length)); |
|
| 238 | - | let mut decoder = alloc::new(&mut work.decoded[..]); |
|
| 239 | - | return try program::decode(&work.input[..input.length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err { |
|
| 240 | - | if err == binary::Error::Storage { throw abi::Error::Exhausted; } |
|
| 241 | - | throw abi::Error::VerifyFailed; |
|
| 242 | - | }; |
|
| 243 | - | }, |
|
| 244 | - | } |
|
| 241 | + | match reservation { case Reservation::Held(input) => return try decodeInput(input), } |
|
| 242 | + | } |
|
| 243 | + | ||
| 244 | + | /// Snapshot retained input and decode it in its private workspace. |
|
| 245 | + | export unsafe fn decodeInput(input: &Input) -> binary::Package throws (abi::Error) { |
|
| 246 | + | let mut work = workspace(input.work); |
|
| 247 | + | try! mem::copy(&mut work.input[..input.length], @sliceOf(memory(input.address), input.length)); |
|
| 248 | + | let mut decoder = alloc::new(&mut work.decoded[..]); |
|
| 249 | + | return try program::decode(&work.input[..input.length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err { |
|
| 250 | + | if err == binary::Error::Storage { throw abi::Error::Exhausted; } |
|
| 251 | + | throw abi::Error::VerifyFailed; |
|
| 252 | + | }; |
|
| 245 | 253 | } |
|
| 246 | 254 | ||
| 247 | 255 | /// Reserved native output and its immutable physical extents. |
|
| 248 | 256 | export record Output: Copy { |
|
| 249 | 257 | /// Reserved package slot selected for generated state references. |
| 288 | 296 | -> Compiled throws (abi::Error) |
|
| 289 | 297 | { |
|
| 290 | 298 | match reservation { |
|
| 291 | 299 | case Reservation::Held(source) => match output { |
|
| 292 | 300 | case OutputReservation::Held(target) => { |
|
| 293 | - | let work = workspace(source.work); |
|
| 294 | - | return try generate(work, input, packages, target.object, target.codeBase, target.metadataBase, source.length); |
|
| 301 | + | return try compileInput(packages, source, input, target); |
|
| 295 | 302 | }, |
|
| 296 | 303 | }, |
|
| 297 | 304 | } |
|
| 298 | 305 | } |
|
| 299 | 306 | ||
| 307 | + | /// Generate native code into retained output while the caller owns its workspace. |
|
| 308 | + | export unsafe fn compileInput(packages: ®istry::Store, source: &Input, input: &binary::Package, target: &Output) |
|
| 309 | + | -> Compiled throws (abi::Error) |
|
| 310 | + | { |
|
| 311 | + | let work = workspace(source.work); |
|
| 312 | + | return try generate(work, input, packages, target.object, target.codeBase, target.metadataBase, source.length); |
|
| 313 | + | } |
|
| 314 | + | ||
| 300 | 315 | /// Return unpublished output frames and its package slot under serialization. |
|
| 301 | 316 | export fn cancelOutput(pool: &mut frames::Pool, packages: &mut registry::Store, reservation: OutputReservation) { |
|
| 317 | + | cancelOutputUsing(pool, packages, reservation, frames::release); |
|
| 318 | + | } |
|
| 319 | + | ||
| 320 | + | /// Cancel a package slot and transfer private output to the release operation. |
|
| 321 | + | export fn cancelOutputUsing(pool: &mut frames::Pool, packages: &mut registry::Store, reservation: OutputReservation, |
|
| 322 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 323 | + | { |
|
| 302 | 324 | match reservation { |
|
| 303 | 325 | case OutputReservation::Held(output) => { |
|
| 304 | 326 | assert slots::matches(&packages.slots[..], output.object, slots::State::Reserved); |
|
| 305 | - | try! frames::release(pool, output.metadata); |
|
| 306 | - | try! frames::release(pool, output.code); |
|
| 327 | + | try! release(pool, output.metadata); |
|
| 328 | + | try! release(pool, output.code); |
|
| 307 | 329 | registry::cancel(packages, slots::Reservation::Held(output.object)); |
|
| 308 | 330 | }, |
|
| 309 | 331 | } |
|
| 310 | 332 | } |
|
| 311 | 333 |
| 329 | 351 | } |
|
| 330 | 352 | ||
| 331 | 353 | /// Compare the snapshot with immutable resident content while the workspace is held. |
|
| 332 | 354 | export unsafe fn identify(packages: ®istry::Store, reservation: &Reservation, input: &binary::Package) -> ?abi::Ref throws (abi::Error) { |
|
| 333 | 355 | match reservation { |
|
| 334 | - | case Reservation::Held(source) => return try registry::identify(packages, &input.name[..], &workspace(source.work).input[..source.length]), |
|
| 356 | + | case Reservation::Held(source) => return try identifyInput(packages, source, input), |
|
| 335 | 357 | } |
|
| 336 | 358 | } |
|
| 337 | 359 | ||
| 360 | + | /// Compare retained source bytes with immutable resident package content. |
|
| 361 | + | export unsafe fn identifyInput(packages: ®istry::Store, source: &Input, input: &binary::Package) -> ?abi::Ref throws (abi::Error) { |
|
| 362 | + | return try registry::identify(packages, &input.name[..], &workspace(source.work).input[..source.length]); |
|
| 363 | + | } |
|
| 364 | + | ||
| 338 | 365 | /// Reserve an Image handle and exclusive workspace under metadata serialization. |
|
| 339 | 366 | export unsafe fn reserve(state: &mut State, store: &mut pages::Store, |
|
| 340 | 367 | table: &mut capability::Table, request: Request) -> Reservation throws (abi::Error) |
|
| 341 | 368 | { |
|
| 342 | 369 | let address = try source(store, table, request); |
| 360 | 387 | assert slots::matches(&table.slots[..], input.handle, slots::State::Reserved); |
|
| 361 | 388 | } |
|
| 362 | 389 | ||
| 363 | 390 | /// Return the workspace and reserved handle under metadata serialization. |
|
| 364 | 391 | export fn cancel(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 392 | + | cancelUsing(state, store, table, reservation, frames::release); |
|
| 393 | + | } |
|
| 394 | + | ||
| 395 | + | /// Cancel a reserved Image handle and transfer its workspace to the release operation. |
|
| 396 | + | export fn cancelUsing(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, reservation: Reservation, |
|
| 397 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 398 | + | { |
|
| 365 | 399 | match reservation { |
|
| 366 | 400 | case Reservation::Held(input) => { |
|
| 367 | 401 | require(state, table, &input); |
|
| 368 | - | release(state, &mut store.backings.pool, Lease::Held(input.frames)); |
|
| 402 | + | releaseUsing(state, &mut store.backings.pool, Lease::Held(input.frames), release); |
|
| 369 | 403 | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(input.handle)); |
|
| 370 | 404 | }, |
|
| 371 | 405 | } |
|
| 372 | 406 | } |
|
| 373 | 407 |
kernel/kernel/pages.rad
+12 -2
| 140 | 140 | } |
|
| 141 | 141 | } |
|
| 142 | 142 | ||
| 143 | 143 | /// Clear private frames while shared metadata can be used by other harts. |
|
| 144 | 144 | export fn clear(reservation: &Reservation) { |
|
| 145 | - | match reservation { case Reservation::Held(allocation) => zero(allocation.base, allocation.run.count), } |
|
| 145 | + | match reservation { case Reservation::Held(allocation) => clearAllocation(allocation), } |
|
| 146 | 146 | } |
|
| 147 | 147 | ||
| 148 | + | /// Clear frames held by an unpublished allocation's exclusive owner. |
|
| 149 | + | export fn clearAllocation(allocation: &Allocation) { zero(allocation.base, allocation.run.count); } |
|
| 150 | + | ||
| 148 | 151 | /// Check reserved generations before publishing or cancelling an allocation. |
|
| 149 | 152 | fn require(store: &Store, table: &capability::Table, allocation: &Allocation) { |
|
| 150 | 153 | assert table.owner == allocation.owner; |
|
| 151 | 154 | assert slots::matches(&table.slots[..], allocation.handle, slots::State::Reserved); |
|
| 152 | 155 | assert slots::matches(&store.slots[..], allocation.page, slots::State::Reserved); |
| 171 | 174 | } |
|
| 172 | 175 | } |
|
| 173 | 176 | ||
| 174 | 177 | /// Return private allocation capacity under metadata serialization. |
|
| 175 | 178 | export fn cancel(store: &mut Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 179 | + | cancelUsing(store, table, reservation, frames::release); |
|
| 180 | + | } |
|
| 181 | + | ||
| 182 | + | /// Cancel unpublished slots and transfer their frames to the selected release operation. |
|
| 183 | + | export fn cancelUsing(store: &mut Store, table: &mut capability::Table, reservation: Reservation, |
|
| 184 | + | release: fn(&mut frames::Pool, frames::Run) throws (abi::Error)) |
|
| 185 | + | { |
|
| 176 | 186 | match reservation { |
|
| 177 | 187 | case Reservation::Held(allocation) => { |
|
| 178 | 188 | require(store, table, &allocation); |
|
| 179 | - | try! frames::release(&mut store.backings.pool, allocation.run); |
|
| 189 | + | try! release(&mut store.backings.pool, allocation.run); |
|
| 180 | 190 | try! slots::cancel(&mut store.backings.slots[..], slots::Reservation::Held(allocation.backing)); |
|
| 181 | 191 | try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(allocation.page)); |
|
| 182 | 192 | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(allocation.handle)); |
|
| 183 | 193 | }, |
|
| 184 | 194 | } |
kernel/kernel/sys.rad
+22 -0
| 24 | 24 | /// Copy authority to a domain with optional rights narrowing. |
|
| 25 | 25 | export fn capabilityGrant(handle: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) { |
|
| 26 | 26 | return abi::Handle(try result(ecall(abi::Operation::CapabilityGrant as u32, *handle as i64, *target as i64, rights as i64, 0))); |
|
| 27 | 27 | } |
|
| 28 | 28 | ||
| 29 | + | /// Set a child's lifecycle receiver with Destroy and Wake authority. |
|
| 30 | + | export fn domainReparent(domain: abi::Handle, parent: abi::Handle) throws (abi::Error) { |
|
| 31 | + | try result(ecall(abi::Operation::DomainReparent as u32, *domain as i64, *parent as i64, 0, 0)); |
|
| 32 | + | } |
|
| 33 | + | ||
| 29 | 34 | /// Load a trusted binary package from a readable Page range. |
|
| 30 | 35 | export fn imageLoad(authority: abi::Handle, source: abi::Handle, offset: u64, length: u64) -> abi::Handle throws (abi::Error) { |
|
| 31 | 36 | return abi::Handle(try result(ecall(abi::Operation::ImageLoad as u32, *authority as i64, *source as i64, offset as i64, length as i64))); |
|
| 32 | 37 | } |
|
| 33 | 38 | ||
| 39 | + | /// Destroy the named domain or the calling domain through the zero sentinel. |
|
| 40 | + | export fn domainDestroy(handle: abi::Handle, flags: u64) throws (abi::Error) { |
|
| 41 | + | try result(ecall(abi::Operation::DomainDestroy as u32, *handle as i64, flags as i64, 0, 0)); |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// End the calling domain and report its normal exit status to its parent. |
|
| 45 | + | export fn exit(status: u32) -> ! { |
|
| 46 | + | ecall(abi::Operation::Exit as u32, status as i64, 0, 0, 0); |
|
| 47 | + | panic "exit returned"; |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// End the calling domain and report an explicit-abort fault to its parent. |
|
| 51 | + | export fn abort() -> ! { |
|
| 52 | + | ecall(abi::Operation::Abort as u32, 0, 0, 0, 0); |
|
| 53 | + | panic "abort returned"; |
|
| 54 | + | } |
|
| 55 | + | ||
| 34 | 56 | /// Read resident package metadata through a readable Image capability. |
|
| 35 | 57 | export fn queryImage(handle: abi::Handle) -> abi::ImageInfo throws (abi::Error) { |
|
| 36 | 58 | unsafe { |
|
| 37 | 59 | let mut info: abi::ImageInfo = undefined; |
|
| 38 | 60 | try result(ecall(abi::Operation::QueryImage as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::ImageInfo) as i64, 0)); |
kernel/kernel/tests.rad
+1 -0
| 15 | 15 | export mod interrupts; |
|
| 16 | 16 | export mod registry; |
|
| 17 | 17 | export mod loader; |
|
| 18 | 18 | export mod instances; |
|
| 19 | 19 | export mod domains; |
|
| 20 | + | export mod lifecycle; |
|
| 20 | 21 | export mod budgets; |
|
| 21 | 22 | export mod dispatch; |
|
| 22 | 23 | export mod calls; |
|
| 23 | 24 | export mod timers; |
|
| 24 | 25 | export mod remote; |
kernel/kernel/tests/backing.rad
+58 -0
| 24 | 24 | let reservation = try! frames::reserve(&mut STORE.pool, 4); |
|
| 25 | 25 | let run = frames::commit(reservation); |
|
| 26 | 26 | return backing::publish(&mut STORE, slot, run, abi::Ref { index: 0, generation: 1 }); |
|
| 27 | 27 | } |
|
| 28 | 28 | ||
| 29 | + | /// Exposure teardown advances in bounded steps and retains shared allocations. |
|
| 30 | + | @test unsafe fn boundedEnd() throws (testing::TestError) { |
|
| 31 | + | initialize(); |
|
| 32 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 33 | + | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 34 | + | let mut objects: [abi::Ref; 3] = undefined; |
|
| 35 | + | for i in 0..3 { |
|
| 36 | + | let slot = try! slots::reserve(&mut STORE.slots[..]); |
|
| 37 | + | let run = frames::commit(try! frames::reserve(&mut STORE.pool, 1)); |
|
| 38 | + | set objects[i] = backing::publish(&mut STORE, slot, run, owner); |
|
| 39 | + | } |
|
| 40 | + | assert STORE.used == 3; |
|
| 41 | + | try! backing::expose(&mut STORE, objects[1], receiver); |
|
| 42 | + | try! backing::releasePage(&mut STORE, objects[0]); |
|
| 43 | + | try! backing::releasePage(&mut STORE, objects[1]); |
|
| 44 | + | let mut invalid = false; |
|
| 45 | + | try backing::endDomainStep(&mut STORE, owner, 0) catch error { |
|
| 46 | + | assert error == abi::Error::InvalidArg; set invalid = true; |
|
| 47 | + | }; |
|
| 48 | + | assert invalid and backing::domainLive(&STORE, owner); |
|
| 49 | + | assert not try! backing::endDomainStep(&mut STORE, owner, 1); |
|
| 50 | + | assert not backing::domainLive(&STORE, owner); |
|
| 51 | + | assert STORE.slots[objects[0].index].state == slots::State::Free; |
|
| 52 | + | assert STORE.pool.retiredCount == 1 and not STORE.pool.free[0]; |
|
| 53 | + | assert STORE.records[objects[1].index].exposed == 2; |
|
| 54 | + | let mut busy = false; |
|
| 55 | + | try backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 2 }) catch error { |
|
| 56 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 57 | + | }; |
|
| 58 | + | assert busy; |
|
| 59 | + | let mut denied = false; |
|
| 60 | + | try backing::expose(&mut STORE, objects[2], owner) catch error { |
|
| 61 | + | assert error == abi::Error::BadHandle; set denied = true; |
|
| 62 | + | }; |
|
| 63 | + | assert denied; |
|
| 64 | + | assert not try! backing::endDomainStep(&mut STORE, owner, 1); |
|
| 65 | + | assert STORE.records[objects[1].index].exposed == 1; |
|
| 66 | + | assert STORE.records[objects[2].index].exposed == 1; |
|
| 67 | + | while not try! backing::endDomainStep(&mut STORE, owner, 64) {} |
|
| 68 | + | assert STORE.records[objects[2].index].exposed == 0 and STORE.records[objects[2].index].pages == 1; |
|
| 69 | + | assert STORE.pool.retiredCount == 1; |
|
| 70 | + | assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[0]; |
|
| 71 | + | assert not STORE.pool.free[1] and not STORE.pool.free[2]; |
|
| 72 | + | let next = abi::Ref { index: 0, generation: 2 }; |
|
| 73 | + | try! backing::registerDomain(&mut STORE, next); |
|
| 74 | + | assert backing::domainLive(&STORE, next); |
|
| 75 | + | let mut stale = false; |
|
| 76 | + | try backing::endDomainStep(&mut STORE, owner, 64) catch error { |
|
| 77 | + | assert error == abi::Error::BadHandle; set stale = true; |
|
| 78 | + | }; |
|
| 79 | + | assert stale and backing::domainLive(&STORE, next); |
|
| 80 | + | try! backing::releasePage(&mut STORE, objects[2]); |
|
| 81 | + | assert STORE.pool.free[2]; |
|
| 82 | + | while not try! backing::endDomainStep(&mut STORE, receiver, 64) {} |
|
| 83 | + | assert STORE.pool.retiredCount == 1 and not STORE.pool.free[1]; |
|
| 84 | + | assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[1]; |
|
| 85 | + | } |
|
| 86 | + | ||
| 29 | 87 | /// Dropping all page handles preserves capacity while an exposed domain lives. |
|
| 30 | 88 | @test unsafe fn exposures() throws (testing::TestError) { |
|
| 31 | 89 | initialize(); |
|
| 32 | 90 | let object = allocate(); |
|
| 33 | 91 | let receiver = abi::Ref { index: 1, generation: 1 }; |
kernel/kernel/tests/budgets.rad
+15 -2
| 33 | 33 | let source = abi::Ref { index: 7, generation: 1 }; |
|
| 34 | 34 | set STORE.windows[entry.object.index].context = source; |
|
| 35 | 35 | let current = abi::CurrentContextInfo { context: abi::id(source), hart: 0, budget: *budget }; |
|
| 36 | 36 | let before = STORE.windows[entry.object.index]; |
|
| 37 | 37 | let mut rejected: u32 = 0; |
|
| 38 | + | set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Dead; |
|
| 39 | + | for phase in 0..3 { |
|
| 40 | + | if phase == 1 { set DOMAINS.reclaimed[permission.object.index] = true; } |
|
| 41 | + | if phase == 2 { set DOMAINS.slots[permission.object.index].state = slots::State::Free; } |
|
| 42 | + | try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error { |
|
| 43 | + | assert error == abi::Error::BadHandle; set rejected += 1; |
|
| 44 | + | }; |
|
| 45 | + | assert STORE.windows[entry.object.index] == before; |
|
| 46 | + | assert try! capability::get(&TABLE, budget) == entry; |
|
| 47 | + | } |
|
| 48 | + | set DOMAINS.slots[permission.object.index].state = slots::State::Live; |
|
| 49 | + | set DOMAINS.reclaimed[permission.object.index] = false; |
|
| 50 | + | set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Pending; |
|
| 38 | 51 | try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error { |
|
| 39 | 52 | assert (error == abi::Error::Busy); set rejected += 1; |
|
| 40 | 53 | }; |
|
| 41 | 54 | set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Active; |
|
| 42 | 55 | set DOMAINS.contexts[initial.index].state = domains::ContextState::Waiting; |
| 66 | 79 | set TABLE.entries[targetSlot.object.index].rights = permission.rights; |
|
| 67 | 80 | for i in 0..MEMORY.table.slots.len { set MEMORY.table.slots[i].state = slots::State::Retired; } |
|
| 68 | 81 | try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error { |
|
| 69 | 82 | assert (error == abi::Error::Busy); set rejected += 1; |
|
| 70 | 83 | }; |
|
| 71 | - | assert (rejected == 7 and STORE.windows[entry.object.index] == before); |
|
| 84 | + | assert (rejected == 10 and STORE.windows[entry.object.index] == before); |
|
| 72 | 85 | assert (try! capability::get(&TABLE, budget) == entry); |
|
| 73 | 86 | capability::initialize(&mut MEMORY.table, permission.object); |
|
| 74 | 87 | try! budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30); |
|
| 75 | 88 | let after = STORE.windows[entry.object.index]; |
|
| 76 | 89 | assert (after.start == 30 and after.end == 100 and after.hart == 0); |
| 185 | 198 | let child = slots::reference(&childSlot); |
|
| 186 | 199 | capability::initialize(&mut MEMORY.table, child); |
|
| 187 | 200 | let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]); |
|
| 188 | 201 | let context = slots::reference(&contextSlot); |
|
| 189 | 202 | set DOMAINS.contexts[context.index] = domains::Context { |
|
| 190 | - | owner: child, state: domains::ContextState::Ready, hart: nil, pending: nil, |
|
| 203 | + | owner: child, state: domains::ContextState::Ready, hart: nil, pending: nil, reservation: false, |
|
| 191 | 204 | kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 }, |
|
| 192 | 205 | userStack: range::Range { start: 0, end: 0 }, |
|
| 193 | 206 | frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 }, |
|
| 194 | 207 | }; |
|
| 195 | 208 | set DOMAINS.records[child.index] = domains::Domain { |
kernel/kernel/tests/calls.rad
+206 -0
| 12 | 12 | use kernel::limits; |
|
| 13 | 13 | use kernel::trap; |
|
| 14 | 14 | use kernel::budgets; |
|
| 15 | 15 | use kernel::remote; |
|
| 16 | 16 | use kernel::interrupts; |
|
| 17 | + | use kernel::events; |
|
| 18 | + | ||
| 19 | + | /// Event storage for administrative destruction targets. |
|
| 20 | + | unsafe static TARGETS: [domains::Memory; 2] = undefined; |
|
| 21 | + | ||
| 22 | + | /// Retained dead-domain records cannot be queried or delegated through handles. |
|
| 23 | + | @test unsafe fn deadHandles() throws (testing::TestError) { |
|
| 24 | + | let owner = initialize(); |
|
| 25 | + | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 26 | + | let object = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 27 | + | try! backing::registerDomain(&mut pages::STORE.backings, object); |
|
| 28 | + | set domains::STORE.records[object.index].state = domains::Lifecycle::Dead; |
|
| 29 | + | set domains::STORE.records[object.index].creator = owner; |
|
| 30 | + | set domains::STORE.records[object.index].parent = nil; |
|
| 31 | + | set domains::STORE.records[object.index].initial = object; |
|
| 32 | + | set domains::STORE.contextSlots[object.index] = slots::Slot { generation: object.generation, state: slots::State::Live }; |
|
| 33 | + | set domains::STORE.contexts[object.index].owner = object; |
|
| 34 | + | set domains::STORE.contexts[object.index].state = domains::ContextState::Stopped; |
|
| 35 | + | set domains::STORE.contexts[object.index].hart = nil; |
|
| 36 | + | set domains::STORE.contexts[object.index].userStack = { start: 0, end: 0 }; |
|
| 37 | + | for operation in [47 as u64, 48, 62, 10, 12, 21, 61, 20, 30, 51] { |
|
| 38 | + | let mut kind = abi::Kind::Domain; |
|
| 39 | + | if operation == 48 { set kind = abi::Kind::Events; } |
|
| 40 | + | let handle = try! capability::install(&mut MEMORY.table, capability::Entry { |
|
| 41 | + | kind: kind, object: object, |
|
| 42 | + | rights: abi::Rights(abi::GRANT | abi::EXECUTE | abi::DESTROY | abi::CREATE | abi::ALLOCATE), |
|
| 43 | + | }); |
|
| 44 | + | let mut rejected = false; |
|
| 45 | + | let mut argument: u64 = 0; |
|
| 46 | + | if operation == 61 { set argument = abi::id(object); } |
|
| 47 | + | try calls::invoke(owner, operation, &[*handle, argument, 0, 0], 0) catch error { |
|
| 48 | + | assert error == abi::Error::BadHandle; |
|
| 49 | + | set rejected = true; |
|
| 50 | + | }; |
|
| 51 | + | assert rejected; |
|
| 52 | + | assert (try! capability::get(&MEMORY.table, handle)).object == object; |
|
| 53 | + | } |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Administrative destruction checks authority and follows creation ancestry only on request. |
|
| 57 | + | @test unsafe fn administrativeTermination() throws (testing::TestError) { |
|
| 58 | + | for flags in [0 as u64, abi::CASCADE] { |
|
| 59 | + | let owner = initialize(); |
|
| 60 | + | try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring); |
|
| 61 | + | let mut objects: [abi::Ref; 2] = undefined; |
|
| 62 | + | for i in 0..2 { |
|
| 63 | + | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 64 | + | let object = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 65 | + | set objects[i] = object; |
|
| 66 | + | set domains::STORE.records[object.index].state = domains::Lifecycle::Pending; |
|
| 67 | + | set domains::STORE.records[object.index].creator = owner; |
|
| 68 | + | set domains::STORE.records[object.index].parent = owner; |
|
| 69 | + | set domains::STORE.records[object.index].memory = &mut TARGETS[i]; |
|
| 70 | + | try! events::open(&mut domains::STORE.events, object, &mut TARGETS[i].ring); |
|
| 71 | + | set domains::STORE.contextSlots[object.index] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 72 | + | set domains::STORE.contexts[object.index].owner = object; |
|
| 73 | + | set domains::STORE.contexts[object.index].state = domains::ContextState::Ready; |
|
| 74 | + | set domains::STORE.contexts[object.index].hart = 1 + 2 * i; |
|
| 75 | + | } |
|
| 76 | + | set domains::STORE.records[objects[1].index].creator = objects[0]; |
|
| 77 | + | set domains::STORE.records[objects[1].index].parent = objects[0]; |
|
| 78 | + | set domains::STORE.ancestors[objects[1].index][0] = 1 << objects[0].index; |
|
| 79 | + | let handle = try! capability::install(&mut MEMORY.table, capability::Entry { |
|
| 80 | + | kind: abi::Kind::Domain, object: objects[0], rights: abi::Rights(0), |
|
| 81 | + | }); |
|
| 82 | + | let mut denied = false; |
|
| 83 | + | try calls::termination(owner, 22, &[*handle, flags, 0, 0]) catch error { |
|
| 84 | + | assert error == abi::Error::Denied; set denied = true; |
|
| 85 | + | }; |
|
| 86 | + | assert denied and domains::STORE.dead == 0; |
|
| 87 | + | let decoded = try! abi::decode(handle); |
|
| 88 | + | set MEMORY.table.entries[decoded.object.index].rights = abi::Rights(abi::DESTROY); |
|
| 89 | + | let mut invalid = false; |
|
| 90 | + | try calls::termination(owner, 22, &[*handle, 2, 0, 0]) catch error { |
|
| 91 | + | assert error == abi::Error::InvalidArg; set invalid = true; |
|
| 92 | + | }; |
|
| 93 | + | assert invalid and domains::STORE.dead == 0; |
|
| 94 | + | let stopped = try! calls::termination(owner, 22, &[*handle, flags, 0, 0]); |
|
| 95 | + | assert domains::STORE.events.terminals[objects[0].index].event.code == 0; |
|
| 96 | + | if flags == 0 { |
|
| 97 | + | assert stopped == 2 and domains::STORE.records[objects[1].index].state == domains::Lifecycle::Pending; |
|
| 98 | + | assert domains::STORE.records[objects[1].index].parent == nil; |
|
| 99 | + | } else { |
|
| 100 | + | assert stopped == 10 and domains::STORE.records[objects[1].index].state == domains::Lifecycle::Dead; |
|
| 101 | + | } |
|
| 102 | + | let mut dead = false; |
|
| 103 | + | try calls::termination(owner, 22, &[*handle, 0, 0, 0]) catch error { |
|
| 104 | + | assert error == abi::Error::BadHandle; set dead = true; |
|
| 105 | + | }; |
|
| 106 | + | assert dead; |
|
| 107 | + | } |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Self termination validates arguments and reports the requested terminal kind. |
|
| 111 | + | @test unsafe fn terminationCalls() throws (testing::TestError) { |
|
| 112 | + | for operation in [22 as u64, 43, 49] { |
|
| 113 | + | let owner = initialize(); |
|
| 114 | + | try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring); |
|
| 115 | + | let pending = try! slots::reserve(&mut domains::STORE.contextSlots[..]); |
|
| 116 | + | let context = try! slots::commit(&mut domains::STORE.contextSlots[..], pending); |
|
| 117 | + | set domains::STORE.contexts[context.index].owner = owner; |
|
| 118 | + | set domains::STORE.contexts[context.index].state = domains::ContextState::Ready; |
|
| 119 | + | set domains::STORE.contexts[context.index].hart = 2; |
|
| 120 | + | let mut invalid = false; |
|
| 121 | + | try calls::termination(owner, operation, &[0, 0, 1, 0]) catch error { |
|
| 122 | + | assert error == abi::Error::InvalidArg; set invalid = true; |
|
| 123 | + | }; |
|
| 124 | + | assert invalid and domains::STORE.records[owner.index].state == domains::Lifecycle::Active; |
|
| 125 | + | let mut code: u64 = 0; |
|
| 126 | + | if operation == 49 { set code = 0xffffffff; } |
|
| 127 | + | assert try! calls::termination(owner, operation, &[code, 0, 0, 0]) == 4; |
|
| 128 | + | let terminal = domains::STORE.events.terminals[owner.index]; |
|
| 129 | + | assert terminal.subject == owner; |
|
| 130 | + | if operation == 43 { |
|
| 131 | + | assert terminal.event.kind == events::FAULT and terminal.event.code == abi::FAULT_ABORT; |
|
| 132 | + | } else { |
|
| 133 | + | assert terminal.event.kind == events::CHILD_EXIT and terminal.event.code == code as u32; |
|
| 134 | + | } |
|
| 135 | + | assert domains::STORE.contexts[context.index].state == domains::ContextState::Stopped; |
|
| 136 | + | assert domains::STORE.contexts[context.index].hart == 2; |
|
| 137 | + | } |
|
| 138 | + | let owner = initialize(); |
|
| 139 | + | for arguments in &[[0 as u64, 1, 0, 0], [0, 2, 0, 0]] { |
|
| 140 | + | let mut invalid = false; |
|
| 141 | + | try calls::termination(owner, 22, &arguments[..]) catch error { |
|
| 142 | + | assert error == abi::Error::InvalidArg; set invalid = true; |
|
| 143 | + | }; |
|
| 144 | + | assert invalid; |
|
| 145 | + | } |
|
| 146 | + | let mut overflow = false; |
|
| 147 | + | try calls::termination(owner, 49, &[0x100000000, 0, 0, 0]) catch error { |
|
| 148 | + | assert error == abi::Error::InvalidArg; set overflow = true; |
|
| 149 | + | }; |
|
| 150 | + | assert overflow; |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Parenting requires both capabilities and preserves creation ancestry. |
|
| 154 | + | @test unsafe fn reparent() throws (testing::TestError) { |
|
| 155 | + | let owner = initialize(); |
|
| 156 | + | let childSlot = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 157 | + | let child = try! slots::commit(&mut domains::STORE.slots[..], childSlot); |
|
| 158 | + | let parentSlot = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 159 | + | let parent = try! slots::commit(&mut domains::STORE.slots[..], parentSlot); |
|
| 160 | + | set domains::STORE.records[child.index].state = domains::Lifecycle::Pending; |
|
| 161 | + | set domains::STORE.records[child.index].creator = owner; |
|
| 162 | + | set domains::STORE.records[child.index].parent = owner; |
|
| 163 | + | set domains::STORE.records[parent.index].state = domains::Lifecycle::Active; |
|
| 164 | + | let childHandle = try! capability::install(&mut MEMORY.table, capability::Entry { |
|
| 165 | + | kind: abi::Kind::Domain, object: child, rights: abi::Rights(abi::DESTROY), |
|
| 166 | + | }); |
|
| 167 | + | let parentHandle = try! capability::install(&mut MEMORY.table, capability::Entry { |
|
| 168 | + | kind: abi::Kind::Domain, object: parent, rights: abi::Rights(abi::WAKE), |
|
| 169 | + | }); |
|
| 170 | + | let changed = try! calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0); |
|
| 171 | + | assert changed == 0 and domains::STORE.records[child.index].parent == parent; |
|
| 172 | + | assert domains::STORE.records[child.index].creator == owner; |
|
| 173 | + | let repeated = try! calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0); |
|
| 174 | + | assert repeated == 0; |
|
| 175 | + | let childCap = try! abi::decode(childHandle); |
|
| 176 | + | let parentCap = try! abi::decode(parentHandle); |
|
| 177 | + | let mut denied: u32 = 0; |
|
| 178 | + | for index in &[childCap.object.index, parentCap.object.index] { |
|
| 179 | + | let rights = MEMORY.table.entries[index].rights; |
|
| 180 | + | set MEMORY.table.entries[index].rights = abi::Rights(0); |
|
| 181 | + | try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error { |
|
| 182 | + | assert error == abi::Error::Denied; set denied += 1; |
|
| 183 | + | }; |
|
| 184 | + | set MEMORY.table.entries[index].rights = rights; |
|
| 185 | + | } |
|
| 186 | + | assert denied == 2 and domains::STORE.records[child.index].parent == parent; |
|
| 187 | + | let mut invalid: u32 = 0; |
|
| 188 | + | for arguments in &[[*childHandle, *parentHandle, 1, 0], [*childHandle, *parentHandle, 0, 1]] { |
|
| 189 | + | try calls::invoke(owner, 23, &arguments[..], 0) catch error { |
|
| 190 | + | assert error == abi::Error::InvalidArg; set invalid += 1; |
|
| 191 | + | }; |
|
| 192 | + | } |
|
| 193 | + | assert invalid == 2; |
|
| 194 | + | let mut dead: u32 = 0; |
|
| 195 | + | for object in &[child, parent] { |
|
| 196 | + | set domains::STORE.records[object.index].state = domains::Lifecycle::Dead; |
|
| 197 | + | try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error { |
|
| 198 | + | assert error == abi::Error::BadHandle; set dead += 1; |
|
| 199 | + | }; |
|
| 200 | + | set domains::STORE.records[object.index].state = domains::Lifecycle::Active; |
|
| 201 | + | } |
|
| 202 | + | assert dead == 2 and domains::STORE.records[child.index].parent == parent; |
|
| 203 | + | try! slots::release(&mut domains::STORE.slots[..], parent); |
|
| 204 | + | try calls::invoke(owner, 23, &[*childHandle, *parentHandle, 0, 0], 0) catch error { |
|
| 205 | + | assert error == abi::Error::BadHandle; set dead += 1; |
|
| 206 | + | }; |
|
| 207 | + | assert dead == 3 and domains::STORE.records[child.index].creator == owner; |
|
| 208 | + | } |
|
| 17 | 209 | ||
| 18 | 210 | /// Interrupt queries write exactly four bytes at four-byte-aligned addresses. |
|
| 19 | 211 | @test unsafe fn interruptRecord() throws (testing::TestError) { |
|
| 20 | 212 | let owner = initialize(); |
|
| 21 | 213 | interrupts::initialize(&mut interrupts::STORE, 8); |
| 228 | 420 | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 229 | 421 | }; |
|
| 230 | 422 | try testing::expect(rejected == 6 and domains::STORE.contextSlots[0].state == slots::State::Free); |
|
| 231 | 423 | } |
|
| 232 | 424 | ||
| 425 | + | /// Invalid infallible queries terminate the caller at the syscall boundary. |
|
| 426 | + | @test unsafe fn invalidQueryAborts() throws (testing::TestError) { |
|
| 427 | + | for operation in [44 as u64, 46, 47, 48] { |
|
| 428 | + | let owner = initialize(); |
|
| 429 | + | try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring); |
|
| 430 | + | let mut frame = trap::Frame { registers: [0; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 }; |
|
| 431 | + | set frame.registers[17] = operation; |
|
| 432 | + | calls::handle(owner, &mut frame, 0, calls::invoke); |
|
| 433 | + | assert domains::STORE.records[owner.index].state == domains::Lifecycle::Dead; |
|
| 434 | + | assert domains::STORE.events.terminals[owner.index].event.code == abi::FAULT_ABORT; |
|
| 435 | + | assert domains::STORE.events.terminals[owner.index].event.kind == events::FAULT; |
|
| 436 | + | } |
|
| 437 | + | } |
|
| 438 | + | ||
| 233 | 439 | /// An unknown user operation advances PC and returns only a negative a0 error. |
|
| 234 | 440 | @test unsafe fn trapReply() throws (testing::TestError) { |
|
| 235 | 441 | let owner = initialize(); |
|
| 236 | 442 | let mut frame = trap::Frame { registers: [123; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 }; |
|
| 237 | 443 | set frame.registers[17] = 0xffffffffffffffff; |
kernel/kernel/tests/domains.rad
+357 -4
| 6 | 6 | use kernel::slots; |
|
| 7 | 7 | use kernel::backing; |
|
| 8 | 8 | use kernel::capability; |
|
| 9 | 9 | use kernel::registry; |
|
| 10 | 10 | use kernel::domains; |
|
| 11 | + | use kernel::lifecycle; |
|
| 12 | + | use kernel::loader; |
|
| 13 | + | use kernel::frames; |
|
| 14 | + | use kernel::budgets; |
|
| 15 | + | use kernel::interrupts; |
|
| 11 | 16 | use kernel::transactions; |
|
| 12 | 17 | use kernel::pages; |
|
| 13 | 18 | use kernel::events; |
|
| 14 | 19 | ||
| 20 | + | /// Exclusive resources released by the domain reaper. |
|
| 21 | + | unsafe static WINDOWS: budgets::Store = undefined; |
|
| 22 | + | /// Interrupt ownership released after hardware masking. |
|
| 23 | + | unsafe static IRQS: interrupts::Store = undefined; |
|
| 24 | + | ||
| 25 | + | /// Caller-owned storage used while a context stack is initialized. |
|
| 26 | + | unsafe static CALLS: lifecycle::Calls = undefined; |
|
| 27 | + | ||
| 15 | 28 | /// Mapped physical memory for child metadata and package instances. |
|
| 16 | 29 | static RAM: [u64; 131072] = [0; 131072]; |
|
| 17 | 30 | /// Domain and context metadata under test. |
|
| 18 | 31 | unsafe static DOMAINS: domains::Store = undefined; |
|
| 19 | 32 | /// Physical allocation and exposure state. |
| 40 | 53 | set BACKINGS.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 41 | 54 | set BACKINGS.pool.free[i] = true; |
|
| 42 | 55 | } |
|
| 43 | 56 | let pending = try! slots::reserve(&mut DOMAINS.slots[..]); |
|
| 44 | 57 | let owner = try! slots::commit(&mut DOMAINS.slots[..], pending); |
|
| 58 | + | set DOMAINS.records[owner.index].state = domains::Lifecycle::Active; |
|
| 59 | + | set DOMAINS.records[owner.index].parent = nil; |
|
| 60 | + | set DOMAINS.records[owner.index].creator = owner; |
|
| 45 | 61 | try! backing::registerDomain(&mut BACKINGS, owner); |
|
| 46 | 62 | capability::initialize(&mut TABLE, owner); |
|
| 47 | 63 | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 48 | 64 | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(rights), |
|
| 49 | 65 | }); |
| 55 | 71 | template: &[7], memory: 8, alignment: 8, relocations: &[], |
|
| 56 | 72 | }); |
|
| 57 | 73 | set IMAGE = try! registry::install(&PACKAGES, &mut TABLE, object); |
|
| 58 | 74 | } |
|
| 59 | 75 | ||
| 76 | + | /// Interrupted creation returns all private storage after its caller releases its hart. |
|
| 77 | + | @test unsafe fn cancelledCreation() throws (testing::TestError) { |
|
| 78 | + | initialize(abi::CREATE); |
|
| 79 | + | lifecycle::initialize(&mut CALLS); |
|
| 80 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 81 | + | let permit = try! capability::get(&TABLE, handle); |
|
| 82 | + | let mut caller = try! domains::get(&DOMAINS, permit.object); |
|
| 83 | + | set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active; |
|
| 84 | + | let authority = try! capability::install(&mut caller.memory.table, capability::Entry { |
|
| 85 | + | kind: abi::Kind::Domain, object: permit.object, rights: abi::Rights(abi::CREATE), |
|
| 86 | + | }); |
|
| 87 | + | let root = caller.image; |
|
| 88 | + | let image = try! registry::install(&PACKAGES, &mut caller.memory.table, root); |
|
| 89 | + | set PAGES.backings = BACKINGS; |
|
| 90 | + | let pending = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image); |
|
| 91 | + | let mut creation: domains::Creation = undefined; |
|
| 92 | + | match &pending { case domains::Reservation::Held(value) => { set creation = *value; }, } |
|
| 93 | + | assert DOMAINS.contexts[creation.context.index].owner == creation.domain; |
|
| 94 | + | let lease = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, pending); |
|
| 95 | + | try! lifecycle::prepareDomain(&CALLS, &PACKAGES, &lease); |
|
| 96 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE); |
|
| 97 | + | assert DOMAINS.slots[creation.domain.index].state == slots::State::Reserved; |
|
| 98 | + | set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Dead; |
|
| 99 | + | set DOMAINS.contexts[caller.initial.index].state = domains::ContextState::Stopped; |
|
| 100 | + | set DOMAINS.contexts[caller.initial.index].hart = 1; |
|
| 101 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE); |
|
| 102 | + | assert DOMAINS.contexts[caller.initial.index].reservation; |
|
| 103 | + | assert not PAGES.backings.pool.free[creation.storage.allocation.first]; |
|
| 104 | + | match lease { case lifecycle::Lease::Held(context) => { assert context == caller.initial; }, } |
|
| 105 | + | set DOMAINS.contexts[caller.initial.index].hart = nil; |
|
| 106 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE); |
|
| 107 | + | assert not DOMAINS.contexts[caller.initial.index].reservation; |
|
| 108 | + | assert DOMAINS.slots[creation.domain.index].state == slots::State::Free; |
|
| 109 | + | assert DOMAINS.contextSlots[creation.context.index].state == slots::State::Free; |
|
| 110 | + | assert caller.memory.table.slots[creation.handle.index].state == slots::State::Free; |
|
| 111 | + | assert PAGES.backings.pool.retiredCount == 3; |
|
| 112 | + | while PAGES.backings.pool.retiredCount > 0 { |
|
| 113 | + | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 114 | + | assert reclaimed > 0 and reclaimed <= 256; |
|
| 115 | + | } |
|
| 116 | + | for run in [creation.storage.allocation, creation.storage.kernelFrames, creation.storage.graph.frames] { |
|
| 117 | + | for i in run.first..run.first + run.count { assert PAGES.backings.pool.free[i]; } |
|
| 118 | + | } |
|
| 119 | + | set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active; |
|
| 120 | + | let next = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image); |
|
| 121 | + | let active = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, next); |
|
| 122 | + | try! lifecycle::prepareDomain(&CALLS, &PACKAGES, &active); |
|
| 123 | + | let ready = lifecycle::takeDomain(&mut CALLS, &mut DOMAINS, active); |
|
| 124 | + | let published = try! domains::publish(&mut DOMAINS, &mut PAGES.backings, &mut caller.memory.table, ready); |
|
| 125 | + | let entry = try! capability::get(&caller.memory.table, published); |
|
| 126 | + | assert entry.object.index == creation.domain.index and entry.object.generation <> creation.domain.generation; |
|
| 127 | + | assert (try! domains::get(&DOMAINS, entry.object)).state == domains::Lifecycle::Pending; |
|
| 128 | + | assert not DOMAINS.contexts[caller.initial.index].reservation; |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | /// Private storage and terminal identity have separate reclamation boundaries. |
|
| 132 | + | @test unsafe fn reclaimDomain() throws (testing::TestError) { |
|
| 133 | + | initialize(abi::CREATE); |
|
| 134 | + | let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 135 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 136 | + | let entry = try! capability::get(&TABLE, handle); |
|
| 137 | + | let child = try! domains::get(&DOMAINS, entry.object); |
|
| 138 | + | try! domains::reparent(&mut DOMAINS, &TABLE, handle, parentHandle); |
|
| 139 | + | let mut private = child.memory; |
|
| 140 | + | let authority = try! capability::install(&mut private.table, capability::Entry { |
|
| 141 | + | kind: abi::Kind::Domain, object: entry.object, rights: abi::Rights(abi::CREATE), |
|
| 142 | + | }); |
|
| 143 | + | let image = try! registry::install(&PACKAGES, &mut private.table, child.image); |
|
| 144 | + | let descendantHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut private.table, authority, image); |
|
| 145 | + | let descendant = try! capability::get(&private.table, descendantHandle); |
|
| 146 | + | let word = entry.object.index / 32; |
|
| 147 | + | let bit = 1 << (entry.object.index % 32); |
|
| 148 | + | assert (DOMAINS.ancestors[descendant.object.index][word] & bit) <> 0; |
|
| 149 | + | assert not try! domains::quiescent(&DOMAINS, entry.object); |
|
| 150 | + | set DOMAINS.contexts[child.initial.index].hart = 1; |
|
| 151 | + | assert try! domains::terminate(&mut DOMAINS, entry.object, events::CHILD_EXIT, 17) == 2; |
|
| 152 | + | assert not try! domains::quiescent(&DOMAINS, entry.object); |
|
| 153 | + | set DOMAINS.contexts[child.initial.index].hart = nil; |
|
| 154 | + | set DOMAINS.contexts[child.initial.index].pending = abi::Ref { index: 0, generation: 1 }; |
|
| 155 | + | assert not try! domains::quiescent(&DOMAINS, entry.object); |
|
| 156 | + | set DOMAINS.contexts[child.initial.index].pending = nil; |
|
| 157 | + | set DOMAINS.contexts[child.initial.index].reservation = true; |
|
| 158 | + | assert not try! domains::quiescent(&DOMAINS, entry.object); |
|
| 159 | + | set DOMAINS.contexts[child.initial.index].reservation = false; |
|
| 160 | + | let reserved = try! slots::reserve(&mut DOMAINS.contextSlots[..]); |
|
| 161 | + | let context = slots::reference(&reserved); |
|
| 162 | + | set DOMAINS.contexts[context.index].owner = entry.object; |
|
| 163 | + | assert not try! domains::quiescent(&DOMAINS, entry.object); |
|
| 164 | + | let mut busy = false; |
|
| 165 | + | try domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object) catch error { |
|
| 166 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 167 | + | }; |
|
| 168 | + | assert busy and BACKINGS.pool.retiredCount == 0; |
|
| 169 | + | try! slots::cancel(&mut DOMAINS.contextSlots[..], reserved); |
|
| 170 | + | assert try! domains::quiescent(&DOMAINS, entry.object); |
|
| 171 | + | assert try! domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object); |
|
| 172 | + | assert not try! domains::retireContext(&mut DOMAINS, &mut BACKINGS.pool, entry.object); |
|
| 173 | + | let mut handles = false; |
|
| 174 | + | try domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object) catch error { |
|
| 175 | + | assert error == abi::Error::Busy; set handles = true; |
|
| 176 | + | }; |
|
| 177 | + | assert handles; |
|
| 178 | + | let mut memory = child.memory; |
|
| 179 | + | for cap in [child.events, authority, image, descendantHandle] { |
|
| 180 | + | let removed = try! capability::invalidate(&mut memory.table, cap); |
|
| 181 | + | } |
|
| 182 | + | let mut exposed = false; |
|
| 183 | + | try domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object) catch error { |
|
| 184 | + | assert error == abi::Error::Busy; set exposed = true; |
|
| 185 | + | }; |
|
| 186 | + | assert exposed; |
|
| 187 | + | while not try! backing::endDomainStep(&mut BACKINGS, entry.object, 64) {} |
|
| 188 | + | try! domains::retireStorage(&mut DOMAINS, &mut BACKINGS, entry.object); |
|
| 189 | + | assert BACKINGS.pool.retiredCount == 3; |
|
| 190 | + | let mut stale = false; |
|
| 191 | + | try domains::get(&DOMAINS, entry.object) catch error { |
|
| 192 | + | assert error == abi::Error::BadHandle; set stale = true; |
|
| 193 | + | }; |
|
| 194 | + | assert stale and DOMAINS.slots[entry.object.index].state == slots::State::Live; |
|
| 195 | + | while BACKINGS.pool.retiredCount > 0 { |
|
| 196 | + | let reclaimed = frames::reclaim(&mut BACKINGS.pool, 256); |
|
| 197 | + | assert reclaimed > 0 and reclaimed <= 256; |
|
| 198 | + | } |
|
| 199 | + | assert not try! events::acknowledged(&DOMAINS.events, entry.object); |
|
| 200 | + | let mut retained = false; |
|
| 201 | + | try domains::releaseIdentity(&mut DOMAINS, entry.object) catch error { |
|
| 202 | + | assert error == abi::Error::Busy; set retained = true; |
|
| 203 | + | }; |
|
| 204 | + | assert retained; |
|
| 205 | + | let parentEntry = try! capability::get(&TABLE, parentHandle); |
|
| 206 | + | let mut parent = try! domains::get(&DOMAINS, parentEntry.object); |
|
| 207 | + | assert try! events::flush(&mut DOMAINS.events, parentEntry.object, &mut parent.memory.ring) == 1; |
|
| 208 | + | let event = try! events::pop(&mut parent.memory.ring) else panic "missing terminal event"; |
|
| 209 | + | assert event.value == abi::id(entry.object) and event.code == 17; |
|
| 210 | + | try! events::refresh(&mut DOMAINS.events, parentEntry.object, &parent.memory.ring); |
|
| 211 | + | try! domains::releaseIdentity(&mut DOMAINS, entry.object); |
|
| 212 | + | assert DOMAINS.slots[entry.object.index].state == slots::State::Free; |
|
| 213 | + | assert (DOMAINS.ancestors[descendant.object.index][word] & bit) == 0; |
|
| 214 | + | let next = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 215 | + | let replacement = try! capability::get(&TABLE, next); |
|
| 216 | + | assert replacement.object.index == entry.object.index and replacement.object.generation <> entry.object.generation; |
|
| 217 | + | assert (try! domains::get(&DOMAINS, replacement.object)).state == domains::Lifecycle::Pending; |
|
| 218 | + | assert try! domains::terminateTree(&mut DOMAINS, replacement.object, events::CHILD_EXIT, 0) == 0; |
|
| 219 | + | assert (try! domains::get(&DOMAINS, descendant.object)).state == domains::Lifecycle::Pending; |
|
| 220 | + | } |
|
| 221 | + | ||
| 222 | + | /// Reaping releases private authority while surviving shared pages retain their frames. |
|
| 223 | + | @test unsafe fn reaper() throws (testing::TestError) { |
|
| 224 | + | initialize(abi::CREATE | abi::ALLOCATE); |
|
| 225 | + | budgets::initialize(&mut WINDOWS); |
|
| 226 | + | interrupts::initialize(&mut IRQS, 8); |
|
| 227 | + | let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 228 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 229 | + | let target = (try! capability::get(&TABLE, handle)).object; |
|
| 230 | + | let receiver = (try! capability::get(&TABLE, parentHandle)).object; |
|
| 231 | + | let mut child = try! domains::get(&DOMAINS, target); |
|
| 232 | + | let mut parent = try! domains::get(&DOMAINS, receiver); |
|
| 233 | + | try! domains::reparent(&mut DOMAINS, &TABLE, handle, parentHandle); |
|
| 234 | + | set PAGES.backings = BACKINGS; |
|
| 235 | + | slots::initialize(&mut PAGES.slots[..]); |
|
| 236 | + | let authority = try! capability::install(&mut child.memory.table, capability::Entry { |
|
| 237 | + | kind: abi::Kind::Domain, object: target, rights: abi::Rights(abi::ALLOCATE), |
|
| 238 | + | }); |
|
| 239 | + | let page = try! pages::allocate(&mut PAGES, &mut child.memory.table, authority, 1); |
|
| 240 | + | let shared = try! pages::grant(&mut PAGES, &child.memory.table, &mut parent.memory.table, page, abi::READ as u64); |
|
| 241 | + | let allocation = (try! pages::get(&PAGES, (try! capability::get(&child.memory.table, page)).object)).backing; |
|
| 242 | + | let run = PAGES.backings.records[allocation.index].run; |
|
| 243 | + | let budget = try! budgets::seed(&mut WINDOWS, &mut child.memory.table, 0, 0, 1000); |
|
| 244 | + | let window = (try! capability::get(&child.memory.table, budget)).object; |
|
| 245 | + | let irq = try! interrupts::seed(&mut IRQS, &mut child.memory.table, 7); |
|
| 246 | + | let dangling = try! capability::install(&mut child.memory.table, capability::Entry { |
|
| 247 | + | kind: abi::Kind::Domain, object: abi::Ref { index: 511, generation: 99 }, rights: abi::Rights(0), |
|
| 248 | + | }); |
|
| 249 | + | set DOMAINS.contexts[child.initial.index].hart = 0; |
|
| 250 | + | assert try! domains::terminate(&mut DOMAINS, target, events::CHILD_EXIT, 19) == 1; |
|
| 251 | + | assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 252 | + | assert (try! capability::get(&child.memory.table, page)).kind == abi::Kind::Page; |
|
| 253 | + | set DOMAINS.contexts[child.initial.index].hart = nil; |
|
| 254 | + | assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 255 | + | assert child.memory.table.slots[0].state == slots::State::Free; |
|
| 256 | + | assert (try! capability::get(&child.memory.table, page)).kind == abi::Kind::Page; |
|
| 257 | + | for step in 0..2000 { |
|
| 258 | + | if DOMAINS.reclaimed[target.index] { break; } |
|
| 259 | + | assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 260 | + | } |
|
| 261 | + | assert DOMAINS.reclaimed[target.index]; |
|
| 262 | + | assert WINDOWS.slots[window.index].state == slots::State::Free; |
|
| 263 | + | assert IRQS.slots[6].state == slots::State::Free; |
|
| 264 | + | assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 265 | + | while PAGES.backings.pool.retiredCount > 0 { |
|
| 266 | + | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 267 | + | assert reclaimed > 0 and reclaimed <= 256; |
|
| 268 | + | } |
|
| 269 | + | assert not PAGES.backings.pool.free[run.first]; |
|
| 270 | + | assert PAGES.backings.records[allocation.index].exposed == 1; |
|
| 271 | + | assert (try! pages::get(&PAGES, (try! capability::get(&parent.memory.table, shared)).object)).count == 1; |
|
| 272 | + | assert try! events::flush(&mut DOMAINS.events, receiver, &mut parent.memory.ring) == 1; |
|
| 273 | + | let event = try! events::pop(&mut parent.memory.ring) else panic "missing reaped child event"; |
|
| 274 | + | assert event.value == abi::id(target) and event.code == 19; |
|
| 275 | + | try! events::refresh(&mut DOMAINS.events, receiver, &parent.memory.ring); |
|
| 276 | + | assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 277 | + | assert DOMAINS.slots[target.index].state == slots::State::Free; |
|
| 278 | + | assert try! domains::terminate(&mut DOMAINS, receiver, events::CHILD_EXIT, 0) == 0; |
|
| 279 | + | for step in 0..2000 { |
|
| 280 | + | if DOMAINS.slots[receiver.index].state == slots::State::Free { break; } |
|
| 281 | + | assert lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS); |
|
| 282 | + | } |
|
| 283 | + | assert DOMAINS.slots[receiver.index].state == slots::State::Free; |
|
| 284 | + | while PAGES.backings.pool.retiredCount > 0 { |
|
| 285 | + | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 286 | + | assert reclaimed > 0 and reclaimed <= 256; |
|
| 287 | + | } |
|
| 288 | + | for i in 0..PAGES.backings.pool.count { assert PAGES.backings.pool.free[i]; } |
|
| 289 | + | } |
|
| 290 | + | ||
| 291 | + | /// Initial return reports to the current parent and retains its terminal identity. |
|
| 292 | + | @test unsafe fn terminalParent() throws (testing::TestError) { |
|
| 293 | + | initialize(abi::CREATE); |
|
| 294 | + | let first = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 295 | + | let second = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 296 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 297 | + | let target = try! capability::get(&TABLE, handle); |
|
| 298 | + | let destination = try! capability::get(&TABLE, second); |
|
| 299 | + | try! domains::reparent(&mut DOMAINS, &TABLE, handle, first); |
|
| 300 | + | try! domains::reparent(&mut DOMAINS, &TABLE, handle, second); |
|
| 301 | + | let child = try! domains::get(&DOMAINS, target.object); |
|
| 302 | + | set DOMAINS.contexts[child.initial.index].hart = 3; |
|
| 303 | + | set DOMAINS.contexts[child.initial.index].pending = abi::Ref { index: 7, generation: 1 }; |
|
| 304 | + | let stopped = try! domains::returned(&mut DOMAINS, target.object, child.initial); |
|
| 305 | + | assert stopped == 8 and DOMAINS.contexts[child.initial.index].hart == 3; |
|
| 306 | + | assert DOMAINS.contexts[child.initial.index].pending <> nil; |
|
| 307 | + | let repeated = try! domains::terminate(&mut DOMAINS, target.object, events::FAULT, 99); |
|
| 308 | + | assert repeated == 0; |
|
| 309 | + | assert DOMAINS.records[target.object.index].state == domains::Lifecycle::Dead; |
|
| 310 | + | assert DOMAINS.events.terminals[target.object.index].state == events::State::Pending; |
|
| 311 | + | assert DOMAINS.events.terminals[target.object.index].receiver == destination.object; |
|
| 312 | + | assert DOMAINS.events.queues[target.object.index].generation == 0; |
|
| 313 | + | let mut rejected = false; |
|
| 314 | + | try domains::reparent(&mut DOMAINS, &TABLE, handle, first) catch error { |
|
| 315 | + | assert error == abi::Error::BadHandle; set rejected = true; |
|
| 316 | + | }; |
|
| 317 | + | assert rejected and DOMAINS.records[target.object.index].creator == TABLE.owner; |
|
| 318 | + | let mut parent = try! domains::get(&DOMAINS, destination.object); |
|
| 319 | + | assert try! events::flush(&mut DOMAINS.events, destination.object, &mut parent.memory.ring) == 1; |
|
| 320 | + | assert not try! events::acknowledged(&DOMAINS.events, target.object); |
|
| 321 | + | let event = try! events::pop(&mut parent.memory.ring) else panic "missing child exit"; |
|
| 322 | + | assert event.kind == events::CHILD_EXIT and event.code == 0 and event.value == abi::id(target.object); |
|
| 323 | + | try! events::refresh(&mut DOMAINS.events, destination.object, &parent.memory.ring); |
|
| 324 | + | assert try! events::acknowledged(&DOMAINS.events, target.object); |
|
| 325 | + | } |
|
| 326 | + | ||
| 327 | + | /// Parent exit leaves surviving children parentless without changing their creator. |
|
| 328 | + | @test unsafe fn parentExit() throws (testing::TestError) { |
|
| 329 | + | initialize(abi::CREATE); |
|
| 330 | + | let parentHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 331 | + | let childHandle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 332 | + | let parentCap = try! capability::get(&TABLE, parentHandle); |
|
| 333 | + | let childCap = try! capability::get(&TABLE, childHandle); |
|
| 334 | + | try! domains::reparent(&mut DOMAINS, &TABLE, childHandle, parentHandle); |
|
| 335 | + | let parent = try! domains::get(&DOMAINS, parentCap.object); |
|
| 336 | + | try! domains::returned(&mut DOMAINS, parentCap.object, parent.initial); |
|
| 337 | + | let child = try! domains::get(&DOMAINS, childCap.object); |
|
| 338 | + | assert child.state == domains::Lifecycle::Pending and child.parent == nil and child.creator == TABLE.owner; |
|
| 339 | + | try! domains::returned(&mut DOMAINS, childCap.object, child.initial); |
|
| 340 | + | assert try! events::acknowledged(&DOMAINS.events, childCap.object); |
|
| 341 | + | } |
|
| 342 | + | ||
| 343 | + | /// Create a child through its creator's own authority and resident image. |
|
| 344 | + | unsafe fn descendant(table: &mut capability::Table, image: abi::Ref) -> abi::Ref { |
|
| 345 | + | let owner = table.owner; |
|
| 346 | + | let authority = try! capability::install(table, capability::Entry { |
|
| 347 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE), |
|
| 348 | + | }); |
|
| 349 | + | let executable = try! registry::install(&PACKAGES, table, image); |
|
| 350 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, table, authority, executable); |
|
| 351 | + | return (try! capability::get(table, handle)).object; |
|
| 352 | + | } |
|
| 353 | + | ||
| 354 | + | /// Cascade retains creation ancestry after reparenting and an intermediate exit. |
|
| 355 | + | @test unsafe fn cascadeAncestry() throws (testing::TestError) { |
|
| 356 | + | initialize(abi::CREATE); |
|
| 357 | + | let package = try! capability::get(&TABLE, IMAGE); |
|
| 358 | + | let first = descendant(&mut TABLE, package.object); |
|
| 359 | + | let mut ancestor = try! domains::get(&DOMAINS, first); |
|
| 360 | + | let second = descendant(&mut ancestor.memory.table, package.object); |
|
| 361 | + | let mut middle = try! domains::get(&DOMAINS, second); |
|
| 362 | + | let third = descendant(&mut middle.memory.table, package.object); |
|
| 363 | + | let child = try! capability::install(&mut TABLE, capability::Entry { |
|
| 364 | + | kind: abi::Kind::Domain, object: third, rights: abi::Rights(abi::DESTROY), |
|
| 365 | + | }); |
|
| 366 | + | let root = TABLE.owner; |
|
| 367 | + | let receiver = try! capability::install(&mut TABLE, capability::Entry { |
|
| 368 | + | kind: abi::Kind::Domain, object: root, rights: abi::Rights(abi::WAKE), |
|
| 369 | + | }); |
|
| 370 | + | try! domains::reparent(&mut DOMAINS, &TABLE, child, receiver); |
|
| 371 | + | let reserved = try! events::reserve(&mut DOMAINS.events, third); |
|
| 372 | + | let mut busy = false; |
|
| 373 | + | try domains::terminateTree(&mut DOMAINS, first, events::CHILD_EXIT, 7) catch error { |
|
| 374 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 375 | + | }; |
|
| 376 | + | assert busy and DOMAINS.records[first.index].state == domains::Lifecycle::Pending; |
|
| 377 | + | assert DOMAINS.records[second.index].state == domains::Lifecycle::Pending; |
|
| 378 | + | assert DOMAINS.events.terminals[first.index].state == events::State::Free; |
|
| 379 | + | events::cancel(&mut DOMAINS.events, reserved); |
|
| 380 | + | let stopped = try! domains::terminate(&mut DOMAINS, second, events::CHILD_EXIT, 12); |
|
| 381 | + | assert stopped == 0 and DOMAINS.records[third.index].state == domains::Lifecycle::Pending; |
|
| 382 | + | assert DOMAINS.records[third.index].parent == TABLE.owner; |
|
| 383 | + | set DOMAINS.contexts[ancestor.initial.index].hart = 1; |
|
| 384 | + | let last = try! domains::get(&DOMAINS, third); |
|
| 385 | + | set DOMAINS.contexts[last.initial.index].hart = 3; |
|
| 386 | + | let harts = try! domains::terminateTree(&mut DOMAINS, first, events::CHILD_EXIT, 7); |
|
| 387 | + | assert harts == 10; |
|
| 388 | + | assert DOMAINS.records[first.index].state == domains::Lifecycle::Dead; |
|
| 389 | + | assert DOMAINS.records[second.index].state == domains::Lifecycle::Dead; |
|
| 390 | + | assert DOMAINS.records[third.index].state == domains::Lifecycle::Dead; |
|
| 391 | + | assert DOMAINS.records[TABLE.owner.index].state == domains::Lifecycle::Active; |
|
| 392 | + | assert DOMAINS.events.terminals[second.index].event.code == 12; |
|
| 393 | + | assert DOMAINS.events.terminals[third.index].event.code == 7; |
|
| 394 | + | assert DOMAINS.records[third.index].creator == second; |
|
| 395 | + | assert DOMAINS.contexts[last.initial.index].hart == 3; |
|
| 396 | + | } |
|
| 397 | + | ||
| 60 | 398 | /// Activation validates child authority before publishing its initial state. |
|
| 61 | 399 | @test unsafe fn activation() throws (testing::TestError) { |
|
| 62 | 400 | initialize(abi::CREATE); |
|
| 63 | 401 | slots::initialize(&mut PAGES.slots[..]); |
|
| 64 | 402 | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
| 147 | 485 | let stackBase = DOMAINS.contexts[index].kernelStack.start; |
|
| 148 | 486 | let base = (&RAM[0]) as u64; |
|
| 149 | 487 | let firstWord = ((stackBase - base) / 8) as u32; |
|
| 150 | 488 | let lastWord = firstWord + domains::KERNEL_STACK_PAGES * 512 - 1; |
|
| 151 | 489 | set RAM[firstWord] = 123; set RAM[lastWord] = 456; |
|
| 152 | - | domains::contextClear(&pendingContext); |
|
| 490 | + | lifecycle::initialize(&mut CALLS); |
|
| 491 | + | let lease = lifecycle::holdContext(&mut CALLS, &mut DOMAINS, child.initial, pendingContext); |
|
| 492 | + | assert DOMAINS.contexts[child.initial.index].reservation; |
|
| 493 | + | lifecycle::clearContext(&CALLS, &lease); |
|
| 153 | 494 | assert RAM[firstWord] == 0 and RAM[lastWord] == 0; |
|
| 154 | - | let context = domains::contextPublish(&mut DOMAINS, pendingContext); |
|
| 495 | + | let ready = lifecycle::takeContext(&mut CALLS, &mut DOMAINS, lease); |
|
| 496 | + | assert not DOMAINS.contexts[child.initial.index].reservation; |
|
| 497 | + | let context = domains::contextPublish(&mut DOMAINS, ready); |
|
| 155 | 498 | let created = try! domains::context(&DOMAINS, authority.object, context); |
|
| 156 | 499 | let initial = DOMAINS.contexts[child.initial.index]; |
|
| 157 | 500 | try testing::expect(context <> child.initial and created.owner == authority.object); |
|
| 158 | 501 | try testing::expect(created.frame.registers[3] == initial.frame.registers[3]); |
|
| 159 | 502 | try testing::expect(created.kernelStack.start >= initial.kernelStack.end or created.kernelStack.end <= initial.kernelStack.start); |
| 221 | 564 | assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live; |
|
| 222 | 565 | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 223 | 566 | assert not PAGES.backings.pool.free[i]; |
|
| 224 | 567 | } |
|
| 225 | 568 | set DOMAINS.contexts[context.index].pending = nil; |
|
| 569 | + | set DOMAINS.contexts[context.index].reservation = true; |
|
| 570 | + | set retained = false; |
|
| 571 | + | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error { |
|
| 572 | + | assert error == abi::Error::Busy; set retained = true; |
|
| 573 | + | }; |
|
| 574 | + | assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live; |
|
| 575 | + | set DOMAINS.contexts[context.index].reservation = false; |
|
| 226 | 576 | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context); |
|
| 227 | 577 | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 228 | 578 | try testing::expect(PAGES.backings.pool.free[i]); |
|
| 229 | 579 | } |
|
| 230 | 580 | try domains::context(&DOMAINS, authority.object, context) catch error { |
| 233 | 583 | let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 234 | 584 | try testing::expect(next.index == context.index and next.generation <> context.generation); |
|
| 235 | 585 | try! domains::returned(&mut DOMAINS, authority.object, child.initial); |
|
| 236 | 586 | try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Dead); |
|
| 237 | 587 | try testing::expect(DOMAINS.contexts[next.index].state == domains::ContextState::Stopped); |
|
| 238 | - | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next); |
|
| 239 | - | try testing::expect(rejected == 11); |
|
| 588 | + | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next) catch error { |
|
| 589 | + | assert error == abi::Error::BadHandle; set rejected += 1; |
|
| 590 | + | }; |
|
| 591 | + | assert DOMAINS.contextSlots[next.index].state == slots::State::Live; |
|
| 592 | + | assert rejected == 12; |
|
| 240 | 593 | } |
|
| 241 | 594 | ||
| 242 | 595 | /// Both serialized wait/wakeup orders preserve notification and runnable state. |
|
| 243 | 596 | @test unsafe fn waitWakeup() throws (testing::TestError) { |
|
| 244 | 597 | initialize(abi::CREATE); |
kernel/kernel/tests/frames.rad
+26 -0
| 1 | 1 | //! Contiguous frame reservation and reserved-memory exclusion. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use kernel::abi; |
|
| 5 | 5 | use kernel::frames; |
|
| 6 | + | use kernel::limits; |
|
| 6 | 7 | use kernel::platform; |
|
| 7 | 8 | use kernel::range; |
|
| 8 | 9 | ||
| 9 | 10 | /// Frame-map workspace is static because its size exceeds a small kernel stack. |
|
| 10 | 11 | unsafe static POOL: frames::Pool = undefined; |
| 17 | 18 | set machine.reservedCount = 1; |
|
| 18 | 19 | set machine.reserved[0] = range::Range { start: 0x80003000, end: 0x80005000 }; |
|
| 19 | 20 | try! frames::initialize(&mut POOL, &machine); |
|
| 20 | 21 | } |
|
| 21 | 22 | ||
| 23 | + | /// Retired frames stay unavailable until bounded reclamation returns them. |
|
| 24 | + | @test unsafe fn retirement() throws (testing::TestError) { |
|
| 25 | + | initialize(); |
|
| 26 | + | set POOL.retiredHead = limits::FRAMES - 1; |
|
| 27 | + | let left = frames::commit(try! frames::reserve(&mut POOL, 3)); |
|
| 28 | + | let right = frames::commit(try! frames::reserve(&mut POOL, 5)); |
|
| 29 | + | try! frames::retire(&mut POOL, left); |
|
| 30 | + | try! frames::retire(&mut POOL, right); |
|
| 31 | + | assert POOL.retiredCount == 2; |
|
| 32 | + | for i in 0..8 { assert not POOL.free[i]; } |
|
| 33 | + | assert frames::reclaim(&mut POOL, 0) == 0; |
|
| 34 | + | assert frames::reclaim(&mut POOL, 2) == 2; |
|
| 35 | + | assert POOL.free[0] and POOL.free[1] and not POOL.free[2]; |
|
| 36 | + | let reused = frames::commit(try! frames::reserve(&mut POOL, 2)); |
|
| 37 | + | assert reused.first == 0; |
|
| 38 | + | assert frames::reclaim(&mut POOL, 3) == 3; |
|
| 39 | + | assert POOL.retiredCount == 1 and not POOL.free[5]; |
|
| 40 | + | assert not POOL.free[0] and not POOL.free[1]; |
|
| 41 | + | try! frames::retire(&mut POOL, reused); |
|
| 42 | + | assert frames::reclaim(&mut POOL, 20) == 5; |
|
| 43 | + | assert POOL.retiredCount == 0; |
|
| 44 | + | for i in 0..8 { assert POOL.free[i]; } |
|
| 45 | + | assert frames::reclaim(&mut POOL, 20) == 0; |
|
| 46 | + | } |
|
| 47 | + | ||
| 22 | 48 | /// Adjacent physical frames form runs; a reserved hole cannot be crossed. |
|
| 23 | 49 | @test unsafe fn fragmentation() throws (testing::TestError) { |
|
| 24 | 50 | initialize(); |
|
| 25 | 51 | try testing::expect(POOL.count == 8); |
|
| 26 | 52 | let left = try! frames::reserve(&mut POOL, 3); |
kernel/kernel/tests/lifecycle.rad
added
+307 -0
| 1 | + | //! Terminal delivery, receiver eligibility, and retained full-queue events. |
|
| 2 | + | use std::testing; |
|
| 3 | + | use kernel::abi; |
|
| 4 | + | use kernel::slots; |
|
| 5 | + | use kernel::domains; |
|
| 6 | + | use kernel::budgets; |
|
| 7 | + | use kernel::events; |
|
| 8 | + | use kernel::lifecycle; |
|
| 9 | + | use kernel::registry; |
|
| 10 | + | use kernel::loader; |
|
| 11 | + | use kernel::limits; |
|
| 12 | + | use kernel::remote; |
|
| 13 | + | use kernel::pages; |
|
| 14 | + | use kernel::backing; |
|
| 15 | + | use kernel::capability; |
|
| 16 | + | use kernel::frames; |
|
| 17 | + | ||
| 18 | + | /// Receiver generations, contexts, and terminal events. |
|
| 19 | + | unsafe static STORE: domains::Store = undefined; |
|
| 20 | + | /// CPU authority retained by receivers. |
|
| 21 | + | unsafe static WINDOWS: budgets::Store = undefined; |
|
| 22 | + | /// Queued calls and acknowledgements retained by stopped contexts. |
|
| 23 | + | unsafe static REQUESTS: remote::Store = undefined; |
|
| 24 | + | /// Physical allocation metadata used for interrupted page calls. |
|
| 25 | + | unsafe static MEMORY: pages::Store = undefined; |
|
| 26 | + | /// Mapped frames available to interrupted page calls. |
|
| 27 | + | static RAM: [u64; 4096] = [0; 4096]; |
|
| 28 | + | /// Reservations retained across private initialization. |
|
| 29 | + | unsafe static CALLS: lifecycle::Calls = undefined; |
|
| 30 | + | /// Shared queue storage for the first receiver. |
|
| 31 | + | unsafe static FIRST: domains::Memory = undefined; |
|
| 32 | + | /// Shared queue storage for the second receiver. |
|
| 33 | + | unsafe static SECOND: domains::Memory = undefined; |
|
| 34 | + | ||
| 35 | + | /// Open one receiver with a waiting initial context. |
|
| 36 | + | unsafe fn open(index: u32, memory: &mut domains::Memory) { |
|
| 37 | + | let owner = abi::Ref { index, generation: 1 }; |
|
| 38 | + | set STORE.slots[index] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 39 | + | set STORE.records[index].state = domains::Lifecycle::Active; |
|
| 40 | + | set STORE.records[index].memory = (&mut *memory) as *unsafe mut domains::Memory; |
|
| 41 | + | set STORE.contextSlots[index] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 42 | + | set STORE.contexts[index].owner = owner; |
|
| 43 | + | set STORE.contexts[index].state = domains::ContextState::Waiting; |
|
| 44 | + | set STORE.contexts[index].hart = nil; |
|
| 45 | + | set STORE.contexts[index].pending = nil; |
|
| 46 | + | try! events::open(&mut STORE.events, owner, &mut memory.ring); |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// Initialize two independent receivers and one future window on hart one. |
|
| 50 | + | unsafe fn initialize() { |
|
| 51 | + | domains::initialize(&mut STORE); |
|
| 52 | + | budgets::initialize(&mut WINDOWS); |
|
| 53 | + | remote::initialize(&mut REQUESTS, 3); |
|
| 54 | + | open(0, &mut FIRST); |
|
| 55 | + | open(1, &mut SECOND); |
|
| 56 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 57 | + | set WINDOWS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 58 | + | set WINDOWS.windows[0] = budgets::Window { |
|
| 59 | + | hart: 1, start: 30, end: 100, owner, handle: abi::Handle(0), context: owner, |
|
| 60 | + | }; |
|
| 61 | + | } |
|
| 62 | + | ||
| 63 | + | /// Retain a normal exit for a chosen receiver. |
|
| 64 | + | unsafe fn retain(index: u32, receiver: u32) { |
|
| 65 | + | let pending = try! events::reserve(&mut STORE.events, abi::Ref { index, generation: 1 }); |
|
| 66 | + | events::finish(&mut STORE.events, pending, abi::Ref { index: receiver, generation: 1 }, events::CHILD_EXIT, index); |
|
| 67 | + | } |
|
| 68 | + | ||
| 69 | + | /// Dead page callers release unpublished capacity only after hart ownership ends. |
|
| 70 | + | @test unsafe fn cancelPage() throws (testing::TestError) { |
|
| 71 | + | initialize(); |
|
| 72 | + | lifecycle::initialize(&mut CALLS); |
|
| 73 | + | backing::initialize(&mut MEMORY.backings); |
|
| 74 | + | slots::initialize(&mut MEMORY.slots[..]); |
|
| 75 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 76 | + | try! backing::registerDomain(&mut MEMORY.backings, owner); |
|
| 77 | + | capability::initialize(&mut FIRST.table, owner); |
|
| 78 | + | let authority = try! capability::install(&mut FIRST.table, capability::Entry { |
|
| 79 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 80 | + | }); |
|
| 81 | + | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 82 | + | set MEMORY.backings.pool.count = 2; |
|
| 83 | + | for i in 0..2 { |
|
| 84 | + | set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096; |
|
| 85 | + | set MEMORY.backings.pool.free[i] = true; |
|
| 86 | + | } |
|
| 87 | + | let reservation = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 2); |
|
| 88 | + | let held = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, reservation); |
|
| 89 | + | assert STORE.contexts[0].reservation; |
|
| 90 | + | lifecycle::clearPage(&CALLS, &held); |
|
| 91 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 92 | + | assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1]; |
|
| 93 | + | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 94 | + | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 95 | + | set STORE.contexts[0].hart = 0; |
|
| 96 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 97 | + | assert not MEMORY.backings.pool.free[0]; |
|
| 98 | + | // Model the end of the stopped continuation before hart release. |
|
| 99 | + | match held { case lifecycle::Lease::Held(context) => { assert context == owner; }, } |
|
| 100 | + | set STORE.contexts[0].hart = nil; |
|
| 101 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 102 | + | assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0]; |
|
| 103 | + | assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 2; |
|
| 104 | + | assert MEMORY.backings.pool.free[0] and MEMORY.backings.pool.free[1]; |
|
| 105 | + | assert MEMORY.slots[0].state == slots::State::Free; |
|
| 106 | + | assert MEMORY.backings.slots[0].state == slots::State::Free; |
|
| 107 | + | assert FIRST.table.slots[1].state == slots::State::Free and CALLS.active[0] == 0; |
|
| 108 | + | assert not STORE.contexts[0].reservation; |
|
| 109 | + | set STORE.records[0].state = domains::Lifecycle::Active; |
|
| 110 | + | let next = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1); |
|
| 111 | + | let lease = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, next); |
|
| 112 | + | lifecycle::clearPage(&CALLS, &lease); |
|
| 113 | + | let ready = lifecycle::takePage(&mut CALLS, &mut STORE, lease); |
|
| 114 | + | let handle = pages::publish(&mut MEMORY, &mut FIRST.table, ready); |
|
| 115 | + | assert CALLS.active[0] == 0; |
|
| 116 | + | let entry = try! capability::get(&FIRST.table, handle); |
|
| 117 | + | assert (try! pages::get(&MEMORY, entry.object)).count == 1; |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | /// One cancellation pass retires at most one caller's private allocation. |
|
| 121 | + | @test unsafe fn cancellationBatch() throws (testing::TestError) { |
|
| 122 | + | initialize(); |
|
| 123 | + | lifecycle::initialize(&mut CALLS); |
|
| 124 | + | backing::initialize(&mut MEMORY.backings); |
|
| 125 | + | slots::initialize(&mut MEMORY.slots[..]); |
|
| 126 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 127 | + | let second = abi::Ref { index: 1, generation: 1 }; |
|
| 128 | + | set STORE.contexts[1].owner = owner; |
|
| 129 | + | try! backing::registerDomain(&mut MEMORY.backings, owner); |
|
| 130 | + | capability::initialize(&mut FIRST.table, owner); |
|
| 131 | + | let authority = try! capability::install(&mut FIRST.table, capability::Entry { |
|
| 132 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 133 | + | }); |
|
| 134 | + | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 135 | + | set MEMORY.backings.pool.count = 2; |
|
| 136 | + | for i in 0..2 { |
|
| 137 | + | set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096; |
|
| 138 | + | set MEMORY.backings.pool.free[i] = true; |
|
| 139 | + | } |
|
| 140 | + | let firstPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1); |
|
| 141 | + | let first = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, firstPage); |
|
| 142 | + | let secondPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1); |
|
| 143 | + | let other = lifecycle::holdPage(&mut CALLS, &mut STORE, second, secondPage); |
|
| 144 | + | match first { case lifecycle::Lease::Held(context) => { assert context == owner; }, } |
|
| 145 | + | match other { case lifecycle::Lease::Held(context) => { assert context == second; }, } |
|
| 146 | + | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 147 | + | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 148 | + | set STORE.contexts[1].state = domains::ContextState::Stopped; |
|
| 149 | + | assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 150 | + | assert CALLS.active[0] == 2 and MEMORY.backings.pool.retiredCount == 1; |
|
| 151 | + | assert not STORE.contexts[0].reservation and STORE.contexts[1].reservation; |
|
| 152 | + | assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 153 | + | assert CALLS.active[0] == 0 and MEMORY.backings.pool.retiredCount == 2; |
|
| 154 | + | assert not lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 155 | + | assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1]; |
|
| 156 | + | assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1; |
|
| 157 | + | assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1; |
|
| 158 | + | assert MEMORY.backings.pool.retiredCount == 0; |
|
| 159 | + | } |
|
| 160 | + | ||
| 161 | + | /// A context call belongs to its caller even when its target domain dies. |
|
| 162 | + | @test unsafe fn cancelContext() throws (testing::TestError) { |
|
| 163 | + | initialize(); |
|
| 164 | + | lifecycle::initialize(&mut CALLS); |
|
| 165 | + | backing::initialize(&mut MEMORY.backings); |
|
| 166 | + | let caller = abi::Ref { index: 0, generation: 1 }; |
|
| 167 | + | let target = abi::Ref { index: 1, generation: 1 }; |
|
| 168 | + | let base = ((&RAM[0]) as u64 + 4095) & ~4095; |
|
| 169 | + | set MEMORY.backings.pool.count = 1; |
|
| 170 | + | set MEMORY.backings.pool.addresses[0] = base; |
|
| 171 | + | set MEMORY.backings.pool.free[0] = true; |
|
| 172 | + | let slot = try! slots::reserve(&mut STORE.contextSlots[..]); |
|
| 173 | + | let object = slots::reference(&slot); |
|
| 174 | + | let run = frames::commit(try! frames::reserve(&mut MEMORY.backings.pool, 1)); |
|
| 175 | + | set STORE.contexts[object.index].owner = target; |
|
| 176 | + | set STORE.contexts[object.index].kernelFrames = run; |
|
| 177 | + | set STORE.contexts[object.index].hart = nil; |
|
| 178 | + | match slot { case slots::Reservation::Held(id) => { assert id == object; }, } |
|
| 179 | + | let pending = domains::ContextReservation::Held(domains::ContextAllocation { |
|
| 180 | + | object, owner: target, frames: run, base, |
|
| 181 | + | }); |
|
| 182 | + | let lease = lifecycle::holdContext(&mut CALLS, &mut STORE, caller, pending); |
|
| 183 | + | lifecycle::clearContext(&CALLS, &lease); |
|
| 184 | + | set STORE.records[1].state = domains::Lifecycle::Dead; |
|
| 185 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 186 | + | assert STORE.contexts[0].reservation and not MEMORY.backings.pool.free[0]; |
|
| 187 | + | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 188 | + | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 189 | + | set STORE.contexts[0].hart = 1; |
|
| 190 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 191 | + | assert STORE.contextSlots[object.index].state == slots::State::Reserved; |
|
| 192 | + | match lease { case lifecycle::Lease::Held(id) => { assert id == caller; }, } |
|
| 193 | + | set STORE.contexts[0].hart = nil; |
|
| 194 | + | lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE); |
|
| 195 | + | assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0]; |
|
| 196 | + | assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 1; |
|
| 197 | + | assert MEMORY.backings.pool.free[0]; |
|
| 198 | + | assert STORE.contextSlots[object.index].state == slots::State::Free; |
|
| 199 | + | assert not STORE.contexts[0].reservation and CALLS.active[0] == 0; |
|
| 200 | + | } |
|
| 201 | + | ||
| 202 | + | /// Dead contexts retain requests until hart release and completion of taken work. |
|
| 203 | + | @test unsafe fn cancelRequests() throws (testing::TestError) { |
|
| 204 | + | initialize(); |
|
| 205 | + | let context = abi::Ref { index: 0, generation: 1 }; |
|
| 206 | + | let pending = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Ready(context)); |
|
| 207 | + | let other = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Ready(context)); |
|
| 208 | + | let delivered = try! remote::take(&mut REQUESTS, 1) else panic "missing readiness request"; |
|
| 209 | + | assert delivered.id == pending; |
|
| 210 | + | set STORE.contexts[0].pending = other; |
|
| 211 | + | lifecycle::cancelRequests(&mut STORE, &mut REQUESTS); |
|
| 212 | + | assert REQUESTS.slots[other.index].state == slots::State::Live; |
|
| 213 | + | set STORE.records[0].state = domains::Lifecycle::Dead; |
|
| 214 | + | set STORE.contexts[0].state = domains::ContextState::Stopped; |
|
| 215 | + | set STORE.contexts[0].hart = 0; |
|
| 216 | + | lifecycle::cancelRequests(&mut STORE, &mut REQUESTS); |
|
| 217 | + | assert REQUESTS.slots[other.index].state == slots::State::Live; |
|
| 218 | + | set STORE.contexts[0].hart = nil; |
|
| 219 | + | lifecycle::cancelRequests(&mut STORE, &mut REQUESTS); |
|
| 220 | + | assert REQUESTS.slots[other.index].state == slots::State::Free; |
|
| 221 | + | assert REQUESTS.slots[pending.index].state == slots::State::Live; |
|
| 222 | + | assert STORE.contexts[0].pending == pending; |
|
| 223 | + | assert remote::acknowledge(&mut REQUESTS, 1, pending); |
|
| 224 | + | lifecycle::cancelRequests(&mut STORE, &mut REQUESTS); |
|
| 225 | + | assert STORE.contexts[0].pending == nil and REQUESTS.slots[pending.index].state == slots::State::Free; |
|
| 226 | + | assert try! remote::take(&mut REQUESTS, 1) == nil; |
|
| 227 | + | assert REQUESTS.queues[1].count == 0; |
|
| 228 | + | let budget = try! remote::send(&mut REQUESTS, 0, 1, remote::Action::Budget(remote::Call { |
|
| 229 | + | owner: context, context, operation: 70, arguments: [0; 4], |
|
| 230 | + | })); |
|
| 231 | + | set STORE.contexts[0].pending = budget; |
|
| 232 | + | let work = try! remote::take(&mut REQUESTS, 1) else panic "missing budget request"; |
|
| 233 | + | remote::respond(&mut REQUESTS, 1, work.id, remote::Reply::Error(abi::Error::BadHandle)); |
|
| 234 | + | assert remote::acknowledge(&mut REQUESTS, 1, work.id); |
|
| 235 | + | lifecycle::cancelRequests(&mut STORE, &mut REQUESTS); |
|
| 236 | + | assert STORE.contexts[0].pending == nil and REQUESTS.slots[budget.index].state == slots::State::Free; |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Closing multiple receivers validates all generations before changing any queue. |
|
| 240 | + | @test unsafe fn closeReceivers() throws (testing::TestError) { |
|
| 241 | + | initialize(); |
|
| 242 | + | retain(2, 0); |
|
| 243 | + | retain(3, 1); |
|
| 244 | + | let mut generations: [u32; limits::DOMAINS] = [0; limits::DOMAINS]; |
|
| 245 | + | set generations[0] = 1; |
|
| 246 | + | set generations[1] = 2; |
|
| 247 | + | let mut rejected = false; |
|
| 248 | + | try events::closeSet(&mut STORE.events, &generations[..]) catch error { |
|
| 249 | + | assert error == abi::Error::BadHandle; set rejected = true; |
|
| 250 | + | }; |
|
| 251 | + | assert rejected and STORE.events.queues[0].generation == 1 and STORE.events.queues[1].generation == 1; |
|
| 252 | + | assert STORE.events.pending == 2; |
|
| 253 | + | set generations[1] = 1; |
|
| 254 | + | try! events::closeSet(&mut STORE.events, &generations[..]); |
|
| 255 | + | assert STORE.events.pending == 0; |
|
| 256 | + | assert STORE.events.queues[0].generation == 0 and STORE.events.queues[1].generation == 0; |
|
| 257 | + | assert try! events::acknowledged(&STORE.events, abi::Ref { index: 2, generation: 1 }); |
|
| 258 | + | assert try! events::acknowledged(&STORE.events, abi::Ref { index: 3, generation: 1 }); |
|
| 259 | + | } |
|
| 260 | + | ||
| 261 | + | /// Events ready both receivers but signal only existing CPU authority. |
|
| 262 | + | @test unsafe fn receivers() throws (testing::TestError) { |
|
| 263 | + | initialize(); |
|
| 264 | + | retain(2, 0); |
|
| 265 | + | retain(3, 1); |
|
| 266 | + | assert STORE.events.pending == 2; |
|
| 267 | + | assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 2; |
|
| 268 | + | assert STORE.events.pending == 0; |
|
| 269 | + | assert STORE.contexts[0].state == domains::ContextState::Ready; |
|
| 270 | + | assert STORE.contexts[1].state == domains::ContextState::Ready; |
|
| 271 | + | assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 0; |
|
| 272 | + | assert STORE.events.queues[0].tail == 1 and STORE.events.queues[1].tail == 1; |
|
| 273 | + | let first = try! events::pop(&mut FIRST.ring) else panic "first exit"; |
|
| 274 | + | let second = try! events::pop(&mut SECOND.ring) else panic "second exit"; |
|
| 275 | + | assert first.kind == events::CHILD_EXIT and first.code == 2; |
|
| 276 | + | assert second.kind == events::CHILD_EXIT and second.code == 3; |
|
| 277 | + | retain(4, 0); |
|
| 278 | + | assert lifecycle::service(&mut STORE, &WINDOWS, 100) == 0; |
|
| 279 | + | assert STORE.events.terminals[4].state == events::State::Queued; |
|
| 280 | + | } |
|
| 281 | + | ||
| 282 | + | /// Full receivers retain terminal events while other receivers make progress. |
|
| 283 | + | @test unsafe fn saturation() throws (testing::TestError) { |
|
| 284 | + | initialize(); |
|
| 285 | + | let receiver = abi::Ref { index: 0, generation: 1 }; |
|
| 286 | + | for i in 0..events::ORDINARY { |
|
| 287 | + | assert try! events::send(&mut STORE.events, receiver, &mut FIRST.ring, |
|
| 288 | + | events::Event { kind: events::WAKEUP, reserved: 0, code: i, value: 0 }); |
|
| 289 | + | } |
|
| 290 | + | for i in 64..128 { retain(i, 0); } |
|
| 291 | + | assert try! events::flush(&mut STORE.events, receiver, &mut FIRST.ring) == 64; |
|
| 292 | + | retain(2, 0); |
|
| 293 | + | retain(3, 1); |
|
| 294 | + | assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 0; |
|
| 295 | + | assert STORE.events.terminals[2].state == events::State::Pending; |
|
| 296 | + | assert STORE.events.pending == 1; |
|
| 297 | + | assert STORE.events.terminals[3].state == events::State::Queued; |
|
| 298 | + | let consumed = try! events::pop(&mut FIRST.ring) else panic "full queue"; |
|
| 299 | + | assert consumed.kind == events::WAKEUP; |
|
| 300 | + | assert lifecycle::service(&mut STORE, &WINDOWS, 20) == 2; |
|
| 301 | + | assert STORE.events.terminals[2].state == events::State::Queued; |
|
| 302 | + | assert STORE.events.queues[0].tail == events::CAPACITY + 1; |
|
| 303 | + | retain(4, 0); |
|
| 304 | + | assert STORE.events.pending == 1; |
|
| 305 | + | try! events::close(&mut STORE.events, receiver); |
|
| 306 | + | assert STORE.events.pending == 0; |
|
| 307 | + | } |
kernel/kernel/tests/loader.rad
+81 -0
| 12 | 12 | use kernel::frames; |
|
| 13 | 13 | use kernel::backing; |
|
| 14 | 14 | use kernel::capability; |
|
| 15 | 15 | use kernel::registry; |
|
| 16 | 16 | use kernel::loader; |
|
| 17 | + | use kernel::domains; |
|
| 18 | + | use kernel::lifecycle; |
|
| 19 | + | ||
| 20 | + | /// Caller metadata used for interrupted loader calls. |
|
| 21 | + | unsafe static DOMAINS: domains::Store = undefined; |
|
| 22 | + | /// Caller-owned private reservations. |
|
| 23 | + | unsafe static CALLS: lifecycle::Calls = undefined; |
|
| 24 | + | /// Capability storage retained until the stopped caller is reclaimed. |
|
| 25 | + | unsafe static MEMORY: domains::Memory = undefined; |
|
| 17 | 26 | ||
| 18 | 27 | /// Mapped frames for temporary compiler storage and persistent native output. |
|
| 19 | 28 | static RAM: [u64; 18874368] = [0; 18874368]; |
|
| 20 | 29 | /// Physical frame and page metadata. |
|
| 21 | 30 | unsafe static PAGES: pages::Store = undefined; |
| 83 | 92 | let mut count: u32 = 0; |
|
| 84 | 93 | for i in 0..PAGES.backings.pool.count { if PAGES.backings.pool.free[i] { set count += 1; } } |
|
| 85 | 94 | return count; |
|
| 86 | 95 | } |
|
| 87 | 96 | ||
| 97 | + | /// Dead loaders retain private resources until hart release, then permit a retry. |
|
| 98 | + | @test unsafe fn cancelledCall() throws (testing::TestError) { |
|
| 99 | + | for stage in 0..3 { |
|
| 100 | + | initialize(); |
|
| 101 | + | domains::initialize(&mut DOMAINS); |
|
| 102 | + | lifecycle::initialize(&mut CALLS); |
|
| 103 | + | let request = input(43, false); |
|
| 104 | + | let before = free(); |
|
| 105 | + | let owner = TABLE.owner; |
|
| 106 | + | set MEMORY.table = TABLE; |
|
| 107 | + | set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 108 | + | set DOMAINS.records[0].state = domains::Lifecycle::Active; |
|
| 109 | + | set DOMAINS.records[0].memory = &mut MEMORY; |
|
| 110 | + | set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 111 | + | set DOMAINS.contexts[0].owner = owner; |
|
| 112 | + | set DOMAINS.contexts[0].state = domains::ContextState::Ready; |
|
| 113 | + | set DOMAINS.contexts[0].hart = nil; |
|
| 114 | + | let pending = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request); |
|
| 115 | + | let lease = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, pending); |
|
| 116 | + | if stage > 0 { |
|
| 117 | + | try! lifecycle::reserveOutput(&mut CALLS, &lease, &mut PAGES, &mut PACKAGES); |
|
| 118 | + | } |
|
| 119 | + | if stage == 2 { |
|
| 120 | + | let retained = lifecycle::load(&CALLS, &lease); |
|
| 121 | + | let decoded = try! loader::decodeInput(&retained.input); |
|
| 122 | + | let output = retained.output else panic "missing output"; |
|
| 123 | + | let compiled = try! loader::compileInput(&PACKAGES, &retained.input, &decoded, &output); |
|
| 124 | + | assert compiled.code > 0; |
|
| 125 | + | } |
|
| 126 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES); |
|
| 127 | + | assert LOADER.busy and DOMAINS.contexts[0].reservation; |
|
| 128 | + | set DOMAINS.records[0].state = domains::Lifecycle::Dead; |
|
| 129 | + | set DOMAINS.contexts[0].state = domains::ContextState::Stopped; |
|
| 130 | + | set DOMAINS.contexts[0].hart = 1; |
|
| 131 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES); |
|
| 132 | + | assert LOADER.busy and free() < before; |
|
| 133 | + | match lease { case lifecycle::Lease::Held(context) => { assert context == owner; }, } |
|
| 134 | + | set DOMAINS.contexts[0].hart = nil; |
|
| 135 | + | lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, &mut PACKAGES); |
|
| 136 | + | assert not LOADER.busy and not DOMAINS.contexts[0].reservation; |
|
| 137 | + | assert free() < before and PAGES.backings.pool.retiredCount > 0; |
|
| 138 | + | while PAGES.backings.pool.retiredCount > 0 { |
|
| 139 | + | let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256); |
|
| 140 | + | assert reclaimed > 0 and reclaimed <= 256; |
|
| 141 | + | } |
|
| 142 | + | assert free() == before and MEMORY.table.slots[2].state == slots::State::Free; |
|
| 143 | + | assert PACKAGES.slots[0].state == slots::State::Free and not LOADER.live[0]; |
|
| 144 | + | set DOMAINS.records[0].state = domains::Lifecycle::Active; |
|
| 145 | + | let retry = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request); |
|
| 146 | + | let active = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, retry); |
|
| 147 | + | try! lifecycle::reserveOutput(&mut CALLS, &active, &mut PAGES, &mut PACKAGES); |
|
| 148 | + | let retained = lifecycle::load(&CALLS, &active); |
|
| 149 | + | let decoded = try! loader::decodeInput(&retained.input); |
|
| 150 | + | let output = retained.output else panic "missing retry output"; |
|
| 151 | + | let compiled = try! loader::compileInput(&PACKAGES, &retained.input, &decoded, &output); |
|
| 152 | + | let handle = try! lifecycle::finishLoad(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, |
|
| 153 | + | &mut PACKAGES, active, lifecycle::LoadResult::Compiled(compiled)); |
|
| 154 | + | let object = try! registry::image(&PACKAGES, &MEMORY.table, handle, abi::Rights(0)); |
|
| 155 | + | assert object.index == 0 and not DOMAINS.contexts[0].reservation; |
|
| 156 | + | let available = free(); |
|
| 157 | + | let duplicate = try! loader::reserve(&mut LOADER, &mut PAGES, &mut MEMORY.table, request); |
|
| 158 | + | let reuse = lifecycle::holdLoad(&mut CALLS, &mut DOMAINS, owner, duplicate); |
|
| 159 | + | let source = lifecycle::load(&CALLS, &reuse); |
|
| 160 | + | let program = try! loader::decodeInput(&source.input); |
|
| 161 | + | assert try! loader::identifyInput(&PACKAGES, &source.input, &program) == object; |
|
| 162 | + | let same = try! lifecycle::finishLoad(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut LOADER, |
|
| 163 | + | &mut PACKAGES, reuse, lifecycle::LoadResult::Existing(object)); |
|
| 164 | + | assert try! registry::image(&PACKAGES, &MEMORY.table, same, abi::Rights(0)) == object; |
|
| 165 | + | assert free() == available and not LOADER.busy; |
|
| 166 | + | } |
|
| 167 | + | } |
|
| 168 | + | ||
| 88 | 169 | /// Reserved input stays private across allocation, source mutation, and contention. |
|
| 89 | 170 | @test unsafe fn stagedInput() throws (testing::TestError) { |
|
| 90 | 171 | initialize(); |
|
| 91 | 172 | let request = input(43, false); |
|
| 92 | 173 | let before = free(); |
lib/std/lang/module.rad
+1 -1
| 12 | 12 | use std::lang::alloc; |
|
| 13 | 13 | use std::lang::ast; |
|
| 14 | 14 | use std::lang::strings; |
|
| 15 | 15 | ||
| 16 | 16 | /// Maximum number of modules tracked in a single compilation graph. |
|
| 17 | - | export constant MAX_MODULES: u32 = 128; |
|
| 17 | + | export constant MAX_MODULES: u32 = 192; |
|
| 18 | 18 | /// Maximum number of characters for a module path. |
|
| 19 | 19 | constant MAX_PATH_LEN: u32 = 256; |
|
| 20 | 20 | /// Maximum number of components that make up a logical module path. |
|
| 21 | 21 | constant MAX_MODULE_PATH_DEPTH: u32 = 16; |
|
| 22 | 22 | /// Filesystem separator used when constructing child paths. |
test/dispatch/kernel/dispatchcheck.rad
+23 -3
| 11 | 11 | use kernel::domains; |
|
| 12 | 12 | use kernel::budgets; |
|
| 13 | 13 | use kernel::dispatch; |
|
| 14 | 14 | use kernel::boot; |
|
| 15 | 15 | use kernel::events; |
|
| 16 | + | use kernel::sync; |
|
| 16 | 17 | use kernel::dispatchinput; |
|
| 17 | 18 | ||
| 18 | 19 | /// Bootstrap authority used to create fixture domains and CPU windows. |
|
| 19 | 20 | unsafe static TABLE: capability::Table = undefined; |
|
| 20 | 21 | /// Domain handles used for budget binding. |
| 71 | 72 | let executable = try! registry::install(®istry::STORE, &mut user.memory.table, root); |
|
| 72 | 73 | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 73 | 74 | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 74 | 75 | let stackCap = try! capability::get(&TABLE, stack); |
|
| 75 | 76 | let stackPage = try! pages::get(&pages::STORE, stackCap.object); |
|
| 76 | - | let args = @sliceOf(memory(stackPage.base), 32); |
|
| 77 | + | let parent = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 78 | + | kind: abi::Kind::Domain, object: domains::STORE.contexts[CONTEXTS[2].index].owner, rights: abi::Rights(abi::WAKE), |
|
| 79 | + | }); |
|
| 80 | + | let args = @sliceOf(memory(stackPage.base), 40); |
|
| 77 | 81 | let words = [*granted, *executable, *selfAuthority]; |
|
| 78 | 82 | for word in 0..3 { |
|
| 79 | 83 | for byte in 0..8 { set args[word * 8 + byte] = (words[word] >> (byte as u64 * 8)) as u8; } |
|
| 80 | 84 | } |
|
| 81 | - | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 32); |
|
| 85 | + | for byte in 0..8 { set args[32 + byte] = (*parent >> (byte as u64 * 8)) as u8; } |
|
| 86 | + | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 40); |
|
| 82 | 87 | let imageRef = try! registry::image(®istry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE)); |
|
| 83 | 88 | let target = try! registry::exported(®istry::STORE, imageRef, &"spin::count"[..]); |
|
| 84 | 89 | let case shared::Target::Data(data) = target else panic "counter"; |
|
| 85 | 90 | let timer = try! dispatch::timer(&boot::PLATFORM, 0); |
|
| 86 | 91 | let clock = dispatch::now(timer); |
|
| 87 | 92 | let start = clock + 1000000; |
|
| 88 | - | let span: u64 = 4000000; |
|
| 93 | + | let span: u64 = 8000000; |
|
| 89 | 94 | set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker(); |
|
| 90 | 95 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64; |
|
| 91 | 96 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock; |
|
| 92 | 97 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span; |
|
| 93 | 98 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span; |
| 124 | 129 | for byte in 0..8 { set progress |= bytes[byte] as u64 << (byte as u64 * 8); } |
|
| 125 | 130 | if progress == 0 { failed(6); } |
|
| 126 | 131 | let checked = try! registry::exported(®istry::STORE, domain.image, &"spin::destroyed"[..]); |
|
| 127 | 132 | let case shared::Target::Data(flag) = checked else panic "destruction flag"; |
|
| 128 | 133 | if *memory(domain.graph.table[flag.slot] + flag.offset as u64) <> 1 { failed(7); } |
|
| 134 | + | let child = try! registry::exported(®istry::STORE, domain.image, &"spin::childId"[..]); |
|
| 135 | + | let case shared::Target::Data(identity) = child else panic "child identity"; |
|
| 136 | + | let idBytes = @sliceOf(memory(domain.graph.table[identity.slot] + identity.offset as u64), 8); |
|
| 137 | + | let mut childId: u64 = 0; |
|
| 138 | + | for byte in 0..8 { set childId |= idBytes[byte] as u64 << (byte as u64 * 8); } |
|
| 139 | + | let owner = domains::STORE.contexts[CONTEXTS[2].index].owner; |
|
| 140 | + | let guard = sync::enter(); |
|
| 141 | + | let mut checker = try! domains::get(&domains::STORE, owner); |
|
| 142 | + | assert try! events::flush(&mut domains::STORE.events, owner, &mut checker.memory.ring) == 0; |
|
| 143 | + | assert not try! events::acknowledged(&domains::STORE.events, abi::reference(childId)); |
|
| 144 | + | let exited = try! events::pop(&mut checker.memory.ring) else panic "child exit"; |
|
| 145 | + | assert exited.kind == events::CHILD_EXIT and exited.code == 0 and exited.value == childId; |
|
| 146 | + | try! events::refresh(&mut domains::STORE.events, owner, &checker.memory.ring); |
|
| 147 | + | assert try! events::acknowledged(&domains::STORE.events, abi::reference(childId)); |
|
| 148 | + | sync::leave(guard); |
|
| 129 | 149 | let wake = try! events::pop(&mut domain.memory.ring) else panic "wake token"; |
|
| 130 | 150 | assert wake.kind == events::WAKEUP and wake.code == 123; |
|
| 131 | 151 | let timeout = try! events::pop(&mut domain.memory.ring) else panic "timeout token"; |
|
| 132 | 152 | assert timeout.kind == events::TIMEOUT and timeout.code == 77 and timeout.value == 0; |
|
| 133 | 153 | assert try! events::pop(&mut domain.memory.ring) == nil; |
test/dispatch/spin.rad
+10 -5
| 11 | 11 | image: u64, |
|
| 12 | 12 | /// Local self-Domain capability with allocation authority. |
|
| 13 | 13 | domain: u64, |
|
| 14 | 14 | /// Future window that the user can partition between its contexts. |
|
| 15 | 15 | future: u64, |
|
| 16 | + | /// Completion checker with authority to receive child lifecycle events. |
|
| 17 | + | parent: u64, |
|
| 16 | 18 | } |
|
| 17 | 19 | ||
| 18 | 20 | /// Startup environment supplied by the kernel. |
|
| 19 | 21 | record Env: Copy { |
|
| 20 | 22 | /// Readable startup capabilities. |
| 32 | 34 | export static count: u64 = 0; |
|
| 33 | 35 | /// Additional-context progress in the same private package state. |
|
| 34 | 36 | export static extraCount: u64 = 0; |
|
| 35 | 37 | /// Successful user-side return and destruction checks. |
|
| 36 | 38 | export static destroyed: u64 = 0; |
|
| 39 | + | /// Generation-bearing identity of the child that returns normally. |
|
| 40 | + | export static childId: u64 = 0; |
|
| 37 | 41 | /// Local self-Domain authority shared by both user contexts. |
|
| 38 | 42 | static SELF: abi::Handle = abi::Handle(0); |
|
| 39 | 43 | ||
| 40 | 44 | /// Update shared private state and return from an additional context. |
|
| 41 | 45 | export fn worker(args: *opaque, size: u64) { |
|
| 42 | 46 | assert size == 0; |
|
| 43 | 47 | set extraCount += 1; |
|
| 44 | - | try! sys::timeout(1000000, 77); |
|
| 48 | + | try! sys::timeout(2000000, 77); |
|
| 45 | 49 | sys::wait(); |
|
| 46 | 50 | set extraCount += 1; |
|
| 47 | 51 | set count += 1; |
|
| 48 | 52 | } |
|
| 49 | 53 | /// Check direct calls and handoff, then exercise independently bound contexts. |
| 51 | 55 | if env.argsSize == 0 { |
|
| 52 | 56 | try! sys::timeout(10000, 88); |
|
| 53 | 57 | sys::wait(); |
|
| 54 | 58 | return; |
|
| 55 | 59 | } |
|
| 56 | - | assert env.argsSize == 32; |
|
| 60 | + | assert env.argsSize == 40; |
|
| 57 | 61 | set SELF = abi::Handle(env.argsPointer.domain); |
|
| 58 | 62 | let handle = env.argsPointer.stack as i64; |
|
| 59 | 63 | let info = sys::queryPage(abi::Handle(handle as u64)); |
|
| 60 | 64 | assert info.base <> 0 and info.count == 1; |
|
| 61 | 65 | let mut rejected = false; |
| 73 | 77 | let domain = sys::queryDomain(abi::Handle(env.argsPointer.domain)); |
|
| 74 | 78 | assert domain.id <> 0 and domain.state == 1; |
|
| 75 | 79 | let image = try! sys::queryImage(abi::Handle(env.argsPointer.image)); |
|
| 76 | 80 | assert image.entry <> 0 and image.codeBase <> 0 and image.codeBytes > 0 and image.dataBytes > 0; |
|
| 77 | 81 | let child = try! sys::domainCreate(abi::Handle(0), abi::Handle(env.argsPointer.image)); |
|
| 82 | + | set childId = sys::queryDomain(child).id; |
|
| 83 | + | try! sys::domainReparent(child, abi::Handle(env.argsPointer.parent)); |
|
| 78 | 84 | let stack = try! sys::pageAllocate(abi::Handle(0), 1); |
|
| 79 | 85 | let granted = try! sys::capabilityGrant(stack, child, (abi::READ | abi::WRITE) as u64); |
|
| 80 | 86 | let childStack = sys::queryPage(stack); |
|
| 81 | 87 | try! sys::domainActivate(child, childStack.base + 4096, (&*env) as *unsafe opaque, 0); |
|
| 82 | 88 | assert sys::queryDomain(child).state == 1; |
| 130 | 136 | assert rejected and sys::currentContext().budget == current.budget; |
|
| 131 | 137 | set count = 1; |
|
| 132 | 138 | try! sys::yield(child); |
|
| 133 | 139 | let continued = sys::currentContext(); |
|
| 134 | 140 | assert continued.context == current.context and continued.budget == *future; |
|
| 135 | - | assert sys::queryDomain(child).state == 2; |
|
| 136 | 141 | while extraCount == 0 { set count += 1; } |
|
| 137 | 142 | let waiting = try! sys::queryContext(SELF, extra); |
|
| 138 | 143 | assert waiting.state == 2 and waiting.hart == 0xffffffffffffffff; |
|
| 139 | 144 | try! sys::wakeup(SELF, 123); |
|
| 140 | - | assert extraCount == 1; |
|
| 145 | + | assert extraCount == 1 or extraCount == 2; |
|
| 141 | 146 | while extraCount == 1 { set count += 1; } |
|
| 142 | 147 | let stopped = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra); |
|
| 143 | 148 | assert stopped.state == 1 and stopped.hart == 0xffffffffffffffff; |
|
| 144 | 149 | try! sys::contextDestroy(abi::Handle(env.argsPointer.domain), extra); |
|
| 145 | 150 | set rejected = false; |
|
| 146 | 151 | try sys::queryContext(abi::Handle(env.argsPointer.domain), extra) catch error { |
|
| 147 | 152 | assert error == abi::Error::BadHandle; |
|
| 148 | 153 | set rejected = true; |
|
| 149 | 154 | }; |
|
| 150 | - | assert rejected and sys::queryDomain(child).state == 2; |
|
| 155 | + | assert rejected; |
|
| 151 | 156 | set destroyed = 1; |
|
| 152 | 157 | while true { set count += 1; } |
|
| 153 | 158 | } |
test/modules/run
added
+21 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Resolve and execute references beyond module index 127. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | work=$(mktemp -d) |
|
| 6 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | + | mkdir "$work/capacity" |
|
| 8 | + | set -- -pkg capacity -mod "$work/capacity.rad" |
|
| 9 | + | index=0 |
|
| 10 | + | while [ "$index" -lt 127 ]; do |
|
| 11 | + | printf 'export mod m%s;\n' "$index" >> "$work/capacity.rad" |
|
| 12 | + | printf '/// Return this module index.\nexport fn value() -> u64 { return %s; }\n' "$index" > "$work/capacity/m$index.rad" |
|
| 13 | + | set -- "$@" -mod "$work/capacity/m$index.rad" |
|
| 14 | + | index=$((index + 1)) |
|
| 15 | + | done |
|
| 16 | + | printf 'use capacity::m126;\n/// Check a package root beyond module index 127.\n@default fn main() -> u64 { assert m126::value() == 126; return 0; }\n' > "$work/entry.rad" |
|
| 17 | + | set -- "$@" -pkg entry -mod "$work/entry.rad" |
|
| 18 | + | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \ |
|
| 19 | + | -run bin/radiance.rv64.dev "$@" -entry entry -o "$work/capacity.rv64" |
|
| 20 | + | "$emulator" -run "$work/capacity.rv64" |
|
| 21 | + | printf 'module scopes: references beyond index 127 passed\n' |
test/smp/kernel/dispatchcheck.rad
+207 -5
| 17 | 17 | use kernel::calls; |
|
| 18 | 18 | use kernel::events; |
|
| 19 | 19 | use kernel::timers; |
|
| 20 | 20 | use kernel::interrupts; |
|
| 21 | 21 | use kernel::plic; |
|
| 22 | + | use kernel::lifecycle; |
|
| 22 | 23 | use kernel::dispatchinput; |
|
| 23 | 24 | ||
| 24 | 25 | /// Bootstrap resource authority. |
|
| 25 | 26 | unsafe static TABLE: capability::Table = undefined; |
|
| 26 | 27 | /// Release/acquire barrier after fixture publication. |
|
| 27 | 28 | static READY: u64 = 0; |
|
| 28 | 29 | /// Shared user domain generation. |
|
| 29 | 30 | unsafe static USER: abi::Ref = undefined; |
|
| 31 | + | /// Pending domain in the user's creation subtree. |
|
| 32 | + | unsafe static DESCENDANT: abi::Ref = undefined; |
|
| 30 | 33 | /// Per-hart user execution contexts. |
|
| 31 | 34 | unsafe static CONTEXTS: [abi::Ref; 8] = undefined; |
|
| 32 | 35 | /// Mapped progress counters in the user's private state. |
|
| 33 | 36 | static COUNTERS: u64 = 0; |
|
| 34 | 37 | /// Per-context migration observations in the user's private state. |
| 43 | 46 | static IDLE_STACK: u64 = 0; |
|
| 44 | 47 | /// Generation of the idle-wakeup domain. |
|
| 45 | 48 | unsafe static IDLE_DOMAIN: abi::Ref = undefined; |
|
| 46 | 49 | /// Completion checker's live domain generation. |
|
| 47 | 50 | unsafe static CHECKER: abi::Ref = undefined; |
|
| 51 | + | /// Unbound window that expires before the checker starts. |
|
| 52 | + | unsafe static EXPIRED: abi::Handle = undefined; |
|
| 48 | 53 | /// Checker's authority to activate and notify the idle-wakeup domain. |
|
| 49 | 54 | unsafe static RECEIVER: abi::Handle = undefined; |
|
| 50 | 55 | /// Unbound authority reserved for the idle-wakeup check on hart one. |
|
| 51 | 56 | unsafe static IDLE_WINDOW: abi::Handle = undefined; |
|
| 52 | 57 | /// Validated physical memory mapping. |
| 85 | 90 | let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE, |
|
| 86 | 91 | loader::Request { authority, source, offset: 0, length: length as u64 }); |
|
| 87 | 92 | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 88 | 93 | let cap = try! capability::get(&TABLE, handle); |
|
| 89 | 94 | set USER = cap.object; |
|
| 95 | + | createDescendant(); |
|
| 90 | 96 | let mut user = try! domains::get(&domains::STORE, USER); |
|
| 91 | 97 | let self = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 92 | 98 | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE | abi::WAKE), |
|
| 93 | 99 | }); |
|
| 94 | 100 | assert boot::PLATFORM.irqSources == 31; |
| 116 | 122 | &TABLE, handle, abi::ContextStart { entry, stack: page.base + 4096, args: page.base, size: 8 }); |
|
| 117 | 123 | } |
|
| 118 | 124 | } |
|
| 119 | 125 | let checkerHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 120 | 126 | let checkerCap = try! capability::get(&TABLE, checkerHandle); |
|
| 121 | - | let checker = try! domains::get(&domains::STORE, checkerCap.object); |
|
| 127 | + | let mut checker = try! domains::get(&domains::STORE, checkerCap.object); |
|
| 122 | 128 | set CHECKER = checkerCap.object; |
|
| 123 | 129 | set domains::STORE.records[checkerCap.object.index].state = domains::Lifecycle::Active; |
|
| 124 | 130 | set domains::STORE.contexts[checker.initial.index].frame.pc = completion(); |
|
| 125 | 131 | set domains::STORE.contexts[checker.initial.index].frame.status = 0x1880; |
|
| 126 | 132 | set domains::STORE.contexts[checker.initial.index].frame.registers[2] = domains::STORE.contexts[checker.initial.index].kernelStack.end; |
| 133 | 139 | let end = start + 10000000 * harts * harts; |
|
| 134 | 140 | let migrationEnd = end + 10000000 * harts * harts; |
|
| 135 | 141 | let windowBase = dataAddress(&user, &"spin::windows"[..]); |
|
| 136 | 142 | for hart in 0..8 { |
|
| 137 | 143 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 138 | - | let budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, start, 0xffffffffffffffff); |
|
| 144 | + | let begin = clock if hart == 0 else start; |
|
| 145 | + | let mut budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, begin, 0xffffffffffffffff); |
|
| 146 | + | if hart == 0 { |
|
| 147 | + | let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, start, clock); |
|
| 148 | + | set EXPIRED = try! budgets::transfer(&mut budgets::STORE, &mut TABLE, &mut checker.memory.table, |
|
| 149 | + | budget, budgets::DEFAULT_RIGHTS as u64, clock); |
|
| 150 | + | set budget = next; |
|
| 151 | + | } |
|
| 139 | 152 | let final = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, end, clock); |
|
| 140 | 153 | let checking = try! budgets::split(&mut budgets::STORE, &mut TABLE, final, migrationEnd, clock); |
|
| 141 | 154 | let future = try! budgets::split(&mut budgets::STORE, &mut TABLE, checking, migrationEnd + 10000000000, clock); |
|
| 142 | 155 | let transferred = try! budgets::transfer(&mut budgets::STORE, &mut TABLE, &mut user.memory.table, |
|
| 143 | 156 | future, budgets::DEFAULT_RIGHTS as u64, clock); |
| 155 | 168 | } |
|
| 156 | 169 | if hart == 1 { set IDLE_WINDOW = checking; } |
|
| 157 | 170 | } |
|
| 158 | 171 | } |
|
| 159 | 172 | ||
| 173 | + | /// Create an actual pending descendant before secondary harts enter dispatch. |
|
| 174 | + | unsafe fn createDescendant() { |
|
| 175 | + | let mut user = try! domains::get(&domains::STORE, USER); |
|
| 176 | + | let root = user.image; |
|
| 177 | + | let authority = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 178 | + | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::CREATE), |
|
| 179 | + | }); |
|
| 180 | + | let image = try! registry::install(®istry::STORE, &mut user.memory.table, root); |
|
| 181 | + | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, |
|
| 182 | + | ®istry::STORE, &mut user.memory.table, authority, image); |
|
| 183 | + | set DESCENDANT = (try! capability::get(&user.memory.table, handle)).object; |
|
| 184 | + | } |
|
| 185 | + | ||
| 160 | 186 | /// Resolve one exported private-data address in the fixture's user instance. |
|
| 161 | 187 | unsafe fn dataAddress(user: &domains::Domain, name: &[u8]) -> u64 { |
|
| 162 | 188 | let target = try! registry::exported(®istry::STORE, user.image, name); |
|
| 163 | 189 | let case shared::Target::Data(data) = target else panic "fixture data export"; |
|
| 164 | 190 | return user.graph.table[data.slot] + data.offset as u64; |
| 169 | 195 | set IDLE_HANDLE = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, |
|
| 170 | 196 | ®istry::STORE, &mut TABLE, authority, image); |
|
| 171 | 197 | let permit = try! capability::get(&TABLE, IDLE_HANDLE); |
|
| 172 | 198 | set IDLE_DOMAIN = permit.object; |
|
| 173 | 199 | let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN); |
|
| 200 | + | let allocation = try! capability::install(&mut domain.memory.table, capability::Entry { |
|
| 201 | + | kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::ALLOCATE), |
|
| 202 | + | }); |
|
| 174 | 203 | set IDLE_CONTEXT = domain.initial; |
|
| 175 | 204 | set WAKE_STATE = dataAddress(&domain, &"spin::wakeState"[..]); |
|
| 176 | 205 | sync::storeRelease(memory(dataAddress(&domain, &"spin::idle"[..])) as *mut u64, 1); |
|
| 177 | 206 | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 178 | 207 | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut domain.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 179 | 208 | let permission = try! capability::get(&TABLE, stack); |
|
| 180 | 209 | let page = try! pages::get(&pages::STORE, permission.object); |
|
| 181 | 210 | set IDLE_STACK = page.base + 4096; |
|
| 182 | 211 | let mut checker = try! domains::get(&domains::STORE, CHECKER); |
|
| 183 | 212 | set RECEIVER = try! capability::install(&mut checker.memory.table, capability::Entry { |
|
| 184 | - | kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::WAKE | abi::EXECUTE), |
|
| 213 | + | kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::WAKE | abi::EXECUTE | abi::DESTROY), |
|
| 185 | 214 | }); |
|
| 186 | 215 | } |
|
| 187 | 216 | ||
| 188 | 217 | /// Check shared progress and context isolation after the user windows finish. |
|
| 189 | 218 | export unsafe fn verify() { |
|
| 190 | 219 | let guard = sync::enter(); |
|
| 220 | + | let user = try! domains::get(&domains::STORE, USER); |
|
| 191 | 221 | let mut counts: [u64; 8] = [0; 8]; |
|
| 192 | 222 | let mut notifications: u32 = 0; |
|
| 193 | 223 | for hart in 0..8 { if (boot::PLATFORM.harts & (1 << hart)) <> 0 { set notifications += 1; } } |
|
| 194 | 224 | for hart in 0..8 { |
|
| 195 | 225 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 196 | 226 | let value = sync::loadAcquire(memory(COUNTERS + hart as u64 * 8) as *u64); |
|
| 197 | 227 | set counts[hart] = value; |
|
| 198 | 228 | let context = try! domains::context(&domains::STORE, USER, CONTEXTS[hart]); |
|
| 199 | - | assert context.owner == USER and (context.frame.status & 0x1800) == 0; |
|
| 229 | + | assert context.owner == USER; |
|
| 230 | + | let mode = context.frame.status & 0x1800; |
|
| 231 | + | assert mode == 0 or mode == 0x1800; |
|
| 232 | + | if mode == 0x1800 { |
|
| 233 | + | assert context.frame.registers[3] == kernelGp(); |
|
| 234 | + | assert context.frame.registers[2] >= context.kernelStack.start and context.frame.registers[2] <= context.kernelStack.end; |
|
| 235 | + | } else { |
|
| 236 | + | assert context.frame.registers[3] == user.graph.table.ptr as u64; |
|
| 237 | + | } |
|
| 200 | 238 | let destination = (hart + notifications - 1) % notifications; |
|
| 201 | 239 | assert context.hart == nil or context.hart == destination; |
|
| 202 | 240 | assert sync::loadAcquire(memory(MIGRATIONS + hart as u64 * 8) as *u64) == destination as u64 + 1; |
|
| 203 | 241 | assert context.pending == nil; |
|
| 204 | 242 | } |
|
| 205 | 243 | assert domains::STORE.events.queues[USER.index].tail == notifications + 1; |
|
| 206 | - | let user = try! domains::get(&domains::STORE, USER); |
|
| 207 | 244 | assert user.memory.ring.data[0].kind == events::INTERRUPT and user.memory.ring.data[0].code == 7; |
|
| 208 | 245 | sync::leave(guard); |
|
| 209 | 246 | for byte in "smp completed transactions:" { put(byte); } |
|
| 210 | 247 | for hart in 0..8 { |
|
| 211 | 248 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
| 233 | 270 | } |
|
| 234 | 271 | assert acknowledged; |
|
| 235 | 272 | } |
|
| 236 | 273 | } |
|
| 237 | 274 | for byte in "smp request acknowledgements passed\n" { put(byte); } |
|
| 275 | + | expiredCall(); |
|
| 238 | 276 | if (boot::PLATFORM.harts & 2) <> 0 { idleWakeup(); } |
|
| 239 | 277 | interruptOwnership(); |
|
| 240 | 278 | interruptStorm(); |
|
| 279 | + | if (boot::PLATFORM.harts & 2) <> 0 { terminalWakeup(); cancelAllocation(); reclaimedDomains(); } |
|
| 241 | 280 | let maximum = sync::maximum(); |
|
| 242 | 281 | assert maximum > 0 and maximum < 1000000; |
|
| 243 | 282 | for byte in "smp metadata instructions: 0x" { put(byte); } |
|
| 244 | 283 | number(maximum); |
|
| 245 | 284 | put(10); |
|
| 246 | 285 | finish(); |
|
| 247 | 286 | } |
|
| 248 | 287 | ||
| 288 | + | /// Validate elapsed authority against the current clock at the serialized boundary. |
|
| 289 | + | unsafe fn expiredCall() { |
|
| 290 | + | let guard = sync::enter(); |
|
| 291 | + | let checker = try! domains::get(&domains::STORE, CHECKER); |
|
| 292 | + | let before = try! budgets::query(&budgets::STORE, &checker.memory.table, EXPIRED); |
|
| 293 | + | assert before.end < dispatch::clock(); |
|
| 294 | + | sync::leave(guard); |
|
| 295 | + | let mut rejected = false; |
|
| 296 | + | try calls::synchronized(CHECKER, 70, &[*EXPIRED, before.start + (before.end - before.start) / 2, 0, 0], 0) catch error { |
|
| 297 | + | assert error == abi::Error::Busy; set rejected = true; |
|
| 298 | + | }; |
|
| 299 | + | assert rejected; |
|
| 300 | + | let checking = sync::enter(); |
|
| 301 | + | let after = try! budgets::query(&budgets::STORE, &checker.memory.table, EXPIRED); |
|
| 302 | + | assert after.start == before.start and after.end == before.end; |
|
| 303 | + | sync::leave(checking); |
|
| 304 | + | for byte in "smp resumed call rejected expired authority\n" { put(byte); } |
|
| 305 | + | } |
|
| 306 | + | ||
| 249 | 307 | /// Transfer a queued IRQ, rearm after consumption, and mask it before capability drop. |
|
| 250 | 308 | unsafe fn interruptOwnership() { |
|
| 251 | 309 | let guard = sync::enter(); |
|
| 252 | 310 | let mut user = try! domains::get(&domains::STORE, USER); |
|
| 253 | 311 | let mut checker = try! domains::get(&domains::STORE, CHECKER); |
| 405 | 463 | } |
|
| 406 | 464 | assert resumed; |
|
| 407 | 465 | for byte in "smp remote timeout passed\n" { put(byte); } |
|
| 408 | 466 | } |
|
| 409 | 467 | ||
| 468 | + | /// Wake a parent waiting on hart one through terminal delivery on hart zero. |
|
| 469 | + | unsafe fn terminalWakeup() { |
|
| 470 | + | let guard = sync::enter(); |
|
| 471 | + | let mut receiver = try! domains::get(&domains::STORE, IDLE_DOMAIN); |
|
| 472 | + | let timeout = try! events::pop(&mut receiver.memory.ring) else panic "missing idle timeout"; |
|
| 473 | + | assert timeout.kind == events::TIMEOUT and timeout.code == 0x66; |
|
| 474 | + | sync::storeRelease(memory(WAKE_STATE) as *mut u64, 7); |
|
| 475 | + | sync::leave(guard); |
|
| 476 | + | assert waiting(); |
|
| 477 | + | let stopping = sync::enter(); |
|
| 478 | + | let mut checker = try! domains::get(&domains::STORE, CHECKER); |
|
| 479 | + | let child = try! capability::install(&mut checker.memory.table, capability::Entry { |
|
| 480 | + | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::DESTROY), |
|
| 481 | + | }); |
|
| 482 | + | let parent = try! calls::invoke(CHECKER, 23, &[*child, *RECEIVER, 0, 0], 0); |
|
| 483 | + | assert parent == 0; |
|
| 484 | + | let cancelled = try! dispatch::request(1, remote::Action::Budget(remote::Call { |
|
| 485 | + | owner: USER, context: CONTEXTS[0], operation: 70, arguments: [0; 4], |
|
| 486 | + | })); |
|
| 487 | + | set domains::STORE.contexts[CONTEXTS[0].index].pending = cancelled; |
|
| 488 | + | let readiness = try! remote::reserve(&mut remote::STORE, 0, 2); |
|
| 489 | + | let batch = remote::publish(&mut remote::STORE, readiness, remote::Action::Ready(CONTEXTS[1])); |
|
| 490 | + | dispatch::startReady(CONTEXTS[1], &batch); |
|
| 491 | + | let stopped = try! domains::terminateTree(&mut domains::STORE, USER, events::CHILD_EXIT, 23); |
|
| 492 | + | assert stopped == 0; |
|
| 493 | + | assert domains::STORE.records[DESCENDANT.index].state == domains::Lifecycle::Dead; |
|
| 494 | + | assert try! events::acknowledged(&domains::STORE.events, DESCENDANT); |
|
| 495 | + | let request = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 496 | + | sync::leave(stopping); |
|
| 497 | + | let acknowledged = try! dispatch::awaitRequest(0, request); |
|
| 498 | + | assert acknowledged == 0; |
|
| 499 | + | let mut resumed = false; |
|
| 500 | + | for attempt in 0..100000 { |
|
| 501 | + | if sync::loadAcquire(memory(WAKE_STATE) as *u64) == 9 { set resumed = true; break; } |
|
| 502 | + | } |
|
| 503 | + | assert resumed; |
|
| 504 | + | let checking = sync::enter(); |
|
| 505 | + | assert remote::STORE.slots[cancelled.index].state == slots::State::Free; |
|
| 506 | + | assert remote::STORE.slots[batch.ids[1].index].state == slots::State::Free; |
|
| 507 | + | assert domains::STORE.contexts[CONTEXTS[0].index].pending == nil; |
|
| 508 | + | assert domains::STORE.contexts[CONTEXTS[1].index].pending == nil; |
|
| 509 | + | let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN); |
|
| 510 | + | let event = try! events::pop(&mut domain.memory.ring) else panic "missing remote exit"; |
|
| 511 | + | assert event.kind == events::CHILD_EXIT and event.code == 23 and event.value == abi::id(USER); |
|
| 512 | + | sync::leave(checking); |
|
| 513 | + | for byte in "smp cascade cancelled calls and woke remote parent\n" { put(byte); } |
|
| 514 | + | } |
|
| 515 | + | ||
| 516 | + | /// Stop a real page-clear continuation and recover its unpublished frames. |
|
| 517 | + | unsafe fn cancelAllocation() { |
|
| 518 | + | sync::storeRelease(memory(WAKE_STATE) as *mut u64, 10); |
|
| 519 | + | let mut allocation: pages::Allocation = undefined; |
|
| 520 | + | let mut request: ?abi::Ref = nil; |
|
| 521 | + | for attempt in 0..100000 { |
|
| 522 | + | let guard = sync::enter(); |
|
| 523 | + | let index = IDLE_CONTEXT.index; |
|
| 524 | + | if (lifecycle::CALLS.active[index / 32] & (1 << (index % 32))) <> 0 { |
|
| 525 | + | match lifecycle::CALLS.records[index].allocation { |
|
| 526 | + | case lifecycle::Allocation::Page(value) => { set allocation = value; }, |
|
| 527 | + | else => panic "page allocation required", |
|
| 528 | + | } |
|
| 529 | + | assert allocation.owner == IDLE_DOMAIN and allocation.run.count == 4096; |
|
| 530 | + | sync::leave(guard); |
|
| 531 | + | let result = try! calls::synchronized(CHECKER, 22, &[*RECEIVER, 0, 0, 0], dispatch::clock()); |
|
| 532 | + | assert result == 0; |
|
| 533 | + | let checking = sync::enter(); |
|
| 534 | + | assert domains::STORE.records[IDLE_DOMAIN.index].state == domains::Lifecycle::Dead; |
|
| 535 | + | set request = try! dispatch::request(1, remote::Action::Reschedule); |
|
| 536 | + | sync::leave(checking); |
|
| 537 | + | break; |
|
| 538 | + | } |
|
| 539 | + | sync::leave(guard); |
|
| 540 | + | } |
|
| 541 | + | let stopped = request else panic "page call did not reserve frames"; |
|
| 542 | + | let acknowledged = try! dispatch::awaitRequest(0, stopped); |
|
| 543 | + | assert acknowledged == 0; |
|
| 544 | + | let snapshot = sync::enter(); |
|
| 545 | + | let context = try! domains::context(&domains::STORE, IDLE_DOMAIN, IDLE_CONTEXT); |
|
| 546 | + | for byte in "smp cancelled page status and progress: " { put(byte); } |
|
| 547 | + | number(context.frame.status); put(32); |
|
| 548 | + | number(sync::loadAcquire(memory(WAKE_STATE) as *u64)); put(10); |
|
| 549 | + | assert context.hart == nil and context.state == domains::ContextState::Stopped; |
|
| 550 | + | assert (context.frame.status & 0x1800) == 0x1800; |
|
| 551 | + | assert sync::loadAcquire(memory(WAKE_STATE) as *u64) == 11; |
|
| 552 | + | sync::leave(snapshot); |
|
| 553 | + | let mut reclaimed = false; |
|
| 554 | + | for attempt in 0..1000 { |
|
| 555 | + | let checking = sync::enter(); |
|
| 556 | + | set reclaimed = pages::STORE.backings.pool.retiredCount == 0; |
|
| 557 | + | if reclaimed { sync::leave(checking); break; } |
|
| 558 | + | let progress = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 559 | + | sync::leave(checking); |
|
| 560 | + | let result = try! dispatch::awaitRequest(0, progress); |
|
| 561 | + | assert result == 0; |
|
| 562 | + | } |
|
| 563 | + | assert reclaimed; |
|
| 564 | + | let guard = sync::enter(); |
|
| 565 | + | assert (lifecycle::CALLS.active[IDLE_CONTEXT.index / 32] & (1 << (IDLE_CONTEXT.index % 32))) == 0; |
|
| 566 | + | assert pages::STORE.slots[allocation.page.index].state == slots::State::Free; |
|
| 567 | + | assert pages::STORE.backings.slots[allocation.backing.index].state == slots::State::Free; |
|
| 568 | + | for i in allocation.run.first..allocation.run.first + allocation.run.count { |
|
| 569 | + | assert pages::STORE.backings.pool.free[i]; |
|
| 570 | + | } |
|
| 571 | + | sync::leave(guard); |
|
| 572 | + | for byte in "smp cancelled preempted page allocation\n" { put(byte); } |
|
| 573 | + | } |
|
| 574 | + | ||
| 575 | + | /// Wait for dispatch-driven teardown and verify all dead execution ownership is gone. |
|
| 576 | + | unsafe fn reclaimedDomains() { |
|
| 577 | + | for attempt in 0..100000 { |
|
| 578 | + | let guard = sync::enter(); |
|
| 579 | + | let mut done = pages::STORE.backings.pool.retiredCount == 0; |
|
| 580 | + | for owner in [USER, IDLE_DOMAIN, DESCENDANT] { |
|
| 581 | + | if slots::matches(&domains::STORE.slots[..], owner, slots::State::Live) { set done = false; } |
|
| 582 | + | } |
|
| 583 | + | if done { |
|
| 584 | + | assert domains::STORE.dead == 0; |
|
| 585 | + | for owner in [USER, IDLE_DOMAIN, DESCENDANT] { |
|
| 586 | + | assert domains::STORE.slots[owner.index].state == slots::State::Free; |
|
| 587 | + | assert pages::STORE.backings.domains[owner.index] == 0; |
|
| 588 | + | for i in 0..domains::STORE.contextSlots.len { |
|
| 589 | + | if domains::STORE.contextSlots[i].state == slots::State::Live { |
|
| 590 | + | assert domains::STORE.contexts[i].owner <> owner; |
|
| 591 | + | } |
|
| 592 | + | } |
|
| 593 | + | for i in 0..budgets::STORE.slots.len { |
|
| 594 | + | if budgets::STORE.slots[i].state == slots::State::Live { assert budgets::STORE.windows[i].owner <> owner; } |
|
| 595 | + | } |
|
| 596 | + | for i in 0..interrupts::STORE.slots.len { |
|
| 597 | + | if interrupts::STORE.slots[i].state == slots::State::Live { assert interrupts::STORE.records[i].owner <> owner; } |
|
| 598 | + | } |
|
| 599 | + | } |
|
| 600 | + | sync::leave(guard); |
|
| 601 | + | for byte in "smp reaped dead domains and recovered ownership\n" { put(byte); } |
|
| 602 | + | return; |
|
| 603 | + | } |
|
| 604 | + | let progress = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 605 | + | sync::leave(guard); |
|
| 606 | + | let result = try! dispatch::awaitRequest(0, progress); |
|
| 607 | + | assert result == 0; |
|
| 608 | + | } |
|
| 609 | + | panic "domain reaper did not finish"; |
|
| 610 | + | } |
|
| 611 | + | ||
| 410 | 612 | /// Confirm the waiting context has released hart ownership before its wakeup. |
|
| 411 | 613 | unsafe fn waiting() -> bool { |
|
| 412 | 614 | for attempt in 0..100000 { |
|
| 413 | 615 | let guard = sync::enter(); |
|
| 414 | 616 | let context = try! domains::context(&domains::STORE, IDLE_DOMAIN, IDLE_CONTEXT); |
test/smp/spin.rad
+9 -1
| 15 | 15 | export static interrupt: u64 = 0; |
|
| 16 | 16 | ||
| 17 | 17 | /// Observed destination hart plus one after each context changes budget. |
|
| 18 | 18 | export static migrations: [u64; 8] = [0; 8]; |
|
| 19 | 19 | ||
| 20 | - | /// Wait handshake: 2/3 bracket wakeup; 4 requests a second wait, bracketed by 5/6. |
|
| 20 | + | /// Progress shared with the machine fixture for waits and interrupted allocation. |
|
| 21 | 21 | export static wakeState: u64 = 0; |
|
| 22 | 22 | ||
| 23 | 23 | /// Nonzero selects the independently activated idle-wakeup context. |
|
| 24 | 24 | export static idle: u64 = 0; |
|
| 25 | 25 |
| 72 | 72 | set wakeState = 3; |
|
| 73 | 73 | while wakeState <> 4 {} |
|
| 74 | 74 | set wakeState = 5; |
|
| 75 | 75 | sys::wait(); |
|
| 76 | 76 | set wakeState = 6; |
|
| 77 | + | while wakeState <> 7 {} |
|
| 78 | + | set wakeState = 8; |
|
| 79 | + | sys::wait(); |
|
| 80 | + | set wakeState = 9; |
|
| 81 | + | while wakeState <> 10 {} |
|
| 82 | + | set wakeState = 11; |
|
| 83 | + | let allocation = try! sys::pageAllocate(abi::Handle(0), 4096); |
|
| 84 | + | set wakeState = 12; |
|
| 77 | 85 | while true {} |
|
| 78 | 86 | } |
|
| 79 | 87 | run(0); |
|
| 80 | 88 | } |
test/termination/kernel/dispatchcheck.rad
added
+101 -0
| 1 | + | //! Native exit and fault reporting to a surviving parent. |
|
| 2 | + | use std::mem; |
|
| 3 | + | use kernel::abi; |
|
| 4 | + | use kernel::slots; |
|
| 5 | + | use kernel::backing; |
|
| 6 | + | use kernel::pages; |
|
| 7 | + | use kernel::capability; |
|
| 8 | + | use kernel::registry; |
|
| 9 | + | use kernel::loader; |
|
| 10 | + | use kernel::domains; |
|
| 11 | + | use kernel::budgets; |
|
| 12 | + | use kernel::dispatch; |
|
| 13 | + | use kernel::boot; |
|
| 14 | + | use kernel::events; |
|
| 15 | + | use kernel::sync; |
|
| 16 | + | use kernel::dispatchinput; |
|
| 17 | + | ||
| 18 | + | /// Bootstrap creation, allocation, and budget authority. |
|
| 19 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 20 | + | /// Child domain identities retained by terminal events. |
|
| 21 | + | static CHILDREN: [abi::Ref; 5] = undefined; |
|
| 22 | + | /// Surviving event receiver and native completion checker. |
|
| 23 | + | unsafe static PARENT: abi::Ref = undefined; |
|
| 24 | + | /// Map a validated physical page. |
|
| 25 | + | fn memory(address: u64) -> *mut u8; |
|
| 26 | + | /// Current kernel package-state base. |
|
| 27 | + | fn kernelGp() -> u64; |
|
| 28 | + | /// Native entry for the completion checker. |
|
| 29 | + | fn completion() -> u64; |
|
| 30 | + | /// Signal successful completion to the machine fixture. |
|
| 31 | + | fn finish(); |
|
| 32 | + | ||
| 33 | + | /// Give four children disjoint windows, followed by the surviving parent. |
|
| 34 | + | export unsafe fn setup() { |
|
| 35 | + | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 36 | + | let owner = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 37 | + | capability::initialize(&mut TABLE, owner); |
|
| 38 | + | try! backing::registerDomain(&mut pages::STORE.backings, owner); |
|
| 39 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 40 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE), |
|
| 41 | + | }); |
|
| 42 | + | let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (dispatchinput::INPUT.len as u64 + 4095) / 4096); |
|
| 43 | + | let page = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, source)).object); |
|
| 44 | + | let bytes = @sliceOf(memory(page.base), page.count * 4096); |
|
| 45 | + | let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]); |
|
| 46 | + | let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE, |
|
| 47 | + | loader::Request { authority, source, offset: 0, length: length as u64 }); |
|
| 48 | + | let parentHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 49 | + | set PARENT = (try! capability::get(&TABLE, parentHandle)).object; |
|
| 50 | + | let parent = try! domains::get(&domains::STORE, PARENT); |
|
| 51 | + | set domains::STORE.records[PARENT.index].state = domains::Lifecycle::Active; |
|
| 52 | + | set domains::STORE.contexts[parent.initial.index].frame.pc = completion(); |
|
| 53 | + | set domains::STORE.contexts[parent.initial.index].frame.status = 0x1880; |
|
| 54 | + | set domains::STORE.contexts[parent.initial.index].frame.registers[2] = domains::STORE.contexts[parent.initial.index].kernelStack.end; |
|
| 55 | + | set domains::STORE.contexts[parent.initial.index].frame.registers[3] = kernelGp(); |
|
| 56 | + | let mut handles: [abi::Handle; 5] = undefined; |
|
| 57 | + | let mut contexts: [abi::Ref; 5] = undefined; |
|
| 58 | + | for i in 0..5 { |
|
| 59 | + | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 60 | + | let object = (try! capability::get(&TABLE, handle)).object; |
|
| 61 | + | let mut child = try! domains::get(&domains::STORE, object); |
|
| 62 | + | set CHILDREN[i] = object; |
|
| 63 | + | set handles[i] = handle; |
|
| 64 | + | set contexts[i] = child.initial; |
|
| 65 | + | try! domains::reparent(&mut domains::STORE, &TABLE, handle, parentHandle); |
|
| 66 | + | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 67 | + | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 68 | + | let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object); |
|
| 69 | + | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, i as u64); |
|
| 70 | + | } |
|
| 71 | + | let timer = try! dispatch::timer(&boot::PLATFORM, 0); |
|
| 72 | + | let clock = dispatch::now(timer); |
|
| 73 | + | let start = clock + 1000000; |
|
| 74 | + | let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 12000000); |
|
| 75 | + | for i in 0..5 { |
|
| 76 | + | let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * 2000000, clock); |
|
| 77 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 78 | + | budgets::Binding { budget: remaining, domain: handles[i], context: contexts[i] }, clock); |
|
| 79 | + | set remaining = next; |
|
| 80 | + | } |
|
| 81 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 82 | + | budgets::Binding { budget: remaining, domain: parentHandle, context: parent.initial }, clock); |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Check each terminal kind, status, and generation while the parent continues to run. |
|
| 86 | + | export unsafe fn verify() { |
|
| 87 | + | let guard = sync::enter(); |
|
| 88 | + | let mut parent = try! domains::get(&domains::STORE, PARENT); |
|
| 89 | + | let codes = [37 as u32, abi::FAULT_ABORT, 3, 0, abi::FAULT_ABORT]; |
|
| 90 | + | for i in 0..5 { |
|
| 91 | + | let event = try! events::pop(&mut parent.memory.ring) else panic "missing termination event"; |
|
| 92 | + | assert event.value == abi::id(CHILDREN[i]) and event.code == codes[i]; |
|
| 93 | + | if i == 0 or i == 3 { assert event.kind == events::CHILD_EXIT; } |
|
| 94 | + | else { assert event.kind == events::FAULT; } |
|
| 95 | + | assert domains::STORE.records[CHILDREN[i].index].state == domains::Lifecycle::Dead; |
|
| 96 | + | } |
|
| 97 | + | try! events::refresh(&mut domains::STORE.events, PARENT, &parent.memory.ring); |
|
| 98 | + | assert try! events::pop(&mut parent.memory.ring) == nil; |
|
| 99 | + | sync::leave(guard); |
|
| 100 | + | finish(); |
|
| 101 | + | } |
test/termination/machine.ras
added
+25 -0
| 1 | + | //! Boot one hart and check user termination through production dispatch. |
|
| 2 | + | .text; |
|
| 3 | + | call @kernel::boot::initialize; |
|
| 4 | + | call @kernel::dispatchcheck::setup; |
|
| 5 | + | call @kernel::boot::run; |
|
| 6 | + | .export @kernel::dispatchcheck::memory; |
|
| 7 | + | .export @kernel::dispatchcheck::kernelGp; |
|
| 8 | + | .export @kernel::dispatchcheck::completion; |
|
| 9 | + | .export @kernel::dispatchcheck::finish; |
|
| 10 | + | @kernel::dispatchcheck::memory |
|
| 11 | + | ret; |
|
| 12 | + | @kernel::dispatchcheck::kernelGp |
|
| 13 | + | mv %a0 %gp; |
|
| 14 | + | ret; |
|
| 15 | + | @kernel::dispatchcheck::completion |
|
| 16 | + | la %a0 @complete; |
|
| 17 | + | ret; |
|
| 18 | + | @complete |
|
| 19 | + | call @kernel::dispatchcheck::verify; |
|
| 20 | + | ebreak; |
|
| 21 | + | @kernel::dispatchcheck::finish |
|
| 22 | + | li %t0 0x10001000; |
|
| 23 | + | li %t1 0x5555; |
|
| 24 | + | sw %t1 0(%t0); |
|
| 25 | + | ebreak; |
test/termination/spin.rad
added
+30 -0
| 1 | + | //! User termination and CPU-fault entry paths. |
|
| 2 | + | export mod abi; |
|
| 3 | + | export mod sys; |
|
| 4 | + | ||
| 5 | + | /// Issue a raw call to test the kernel error boundary. |
|
| 6 | + | @intrinsic fn ecall(operation: u32, a0: i64, a1: i64, a2: i64, a3: i64) -> i64; |
|
| 7 | + | ||
| 8 | + | /// Startup values used to select the termination operation. |
|
| 9 | + | record Env: Copy { |
|
| 10 | + | /// Optional argument bytes supplied by the parent. |
|
| 11 | + | argsPointer: *u8, |
|
| 12 | + | /// Termination case selected by the parent. |
|
| 13 | + | argsSize: u64, |
|
| 14 | + | /// Installed event handle. |
|
| 15 | + | eventsHandle: u64, |
|
| 16 | + | /// Shared event-ring address. |
|
| 17 | + | eventsPointer: u64, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Exercise an explicit exit, explicit abort, or a user breakpoint fault. |
|
| 21 | + | @default fn main(env: *Env) { |
|
| 22 | + | if env.argsSize == 0 { sys::exit(37); } |
|
| 23 | + | if env.argsSize == 1 { sys::abort(); } |
|
| 24 | + | if env.argsSize == 3 { try! sys::domainDestroy(abi::Handle(0), 0); } |
|
| 25 | + | if env.argsSize == 4 { |
|
| 26 | + | let result = ecall(47, 0, 0, 0, 0); |
|
| 27 | + | sys::exit(99); |
|
| 28 | + | } |
|
| 29 | + | assert false; |
|
| 30 | + | } |