kernel: Manage additional execution contexts
52449bef848b31f41da152b9bf5fb2f82a63f378164573e3321f41ecd4badd43
Assisted-by: Codex:gpt-6
1 parent
a6324524
kernel/kernel/abi.rad
+30 -0
| 55 | 55 | codeBytes: u64, |
|
| 56 | 56 | /// Number of private state bytes per instance. |
|
| 57 | 57 | dataBytes: u64, |
|
| 58 | 58 | } |
|
| 59 | 59 | ||
| 60 | + | /// Validated entry, stack, and arguments for an additional context. |
|
| 61 | + | export record ContextStart: Copy { |
|
| 62 | + | /// Exported native entry in the target domain's package graph. |
|
| 63 | + | entry: u64, |
|
| 64 | + | /// Aligned top of writable user stack storage. |
|
| 65 | + | stack: u64, |
|
| 66 | + | /// Readable startup argument address passed in a0. |
|
| 67 | + | args: u64, |
|
| 68 | + | /// Startup argument byte count passed in a1. |
|
| 69 | + | size: u64, |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Identity, owner, and user stack bounds of a live context. |
|
| 73 | + | export record ContextInfo: Copy { |
|
| 74 | + | /// Generation-bearing context identity. |
|
| 75 | + | context: u64, |
|
| 76 | + | /// Owning domain identity. |
|
| 77 | + | domain: u64, |
|
| 78 | + | /// First byte of the reserved user stack. |
|
| 79 | + | stackBase: u64, |
|
| 80 | + | /// Exclusive upper bound of the reserved user stack. |
|
| 81 | + | stackTop: u64, |
|
| 82 | + | /// Ready or Stopped encoded as zero or one. |
|
| 83 | + | state: u64, |
|
| 84 | + | /// Executing hart, or all ones while no hart owns execution. |
|
| 85 | + | hart: u64, |
|
| 86 | + | } |
|
| 87 | + | ||
| 60 | 88 | /// Identity and CPU authority of the calling execution context. |
|
| 61 | 89 | export record CurrentContextInfo: Copy { |
|
| 62 | 90 | /// Generation-bearing context identity. |
|
| 63 | 91 | context: u64, |
|
| 64 | 92 | /// Physical hart executing this call. |
| 196 | 224 | ContextDestroy = 61, |
|
| 197 | 225 | /// Query an execution context. |
|
| 198 | 226 | QueryContext = 62, |
|
| 199 | 227 | /// Query the current context and execution window. |
|
| 200 | 228 | CurrentContext = 63, |
|
| 229 | + | /// Report normal return from the calling execution context. |
|
| 230 | + | ContextReturn = 64, |
|
| 201 | 231 | /// Partition a CPU time window. |
|
| 202 | 232 | BudgetSplit = 70, |
|
| 203 | 233 | /// Combine adjacent CPU time windows. |
|
| 204 | 234 | BudgetMerge = 71, |
|
| 205 | 235 | /// Query a CPU time window. |
kernel/kernel/budgets.rad
+1 -0
| 211 | 211 | let permission = try capability::lookup(table, request.domain, abi::Kind::Domain, abi::Rights(abi::WAKE)); |
|
| 212 | 212 | let mut target = try domains::get(domainStore, permission.object); |
|
| 213 | 213 | if target.state == domains::Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 214 | 214 | if not slots::matches(&domainStore.contextSlots[..], request.context, slots::State::Live) |
|
| 215 | 215 | or domainStore.contexts[request.context.index].owner <> permission.object { throw abi::Error::BadHandle; } |
|
| 216 | + | if domainStore.contexts[request.context.index].state == domains::ContextState::Stopped { throw abi::Error::InvalidArg; } |
|
| 216 | 217 | if not available(store, object, candidate, request.context) { throw abi::Error::Busy; } |
|
| 217 | 218 | let mut result = request.budget; |
|
| 218 | 219 | if permission.object <> table.owner { |
|
| 219 | 220 | let entry = try! capability::get(table, request.budget); |
|
| 220 | 221 | set result = try transfer(store, table, &mut target.memory.table, request.budget, *entry.rights as u64, now); |
kernel/kernel/calls.rad
+34 -6
| 64 | 64 | let removed = try! capability::invalidate(table, handle); |
|
| 65 | 65 | }, |
|
| 66 | 66 | } |
|
| 67 | 67 | } |
|
| 68 | 68 | ||
| 69 | - | /// Map a checked, aligned result address to writable words. |
|
| 69 | + | /// Map a checked, aligned caller record address to words. |
|
| 70 | 70 | fn memory(address: u64) -> *mut u64; |
|
| 71 | 71 | ||
| 72 | 72 | /// Test whether one nonwrapping extent contains the complete result range. |
|
| 73 | 73 | fn contains(base: u64, size: u64, address: u64, length: u64) -> bool { |
|
| 74 | 74 | return address >= base and address - base <= size and length <= size - (address - base); |
|
| 75 | 75 | } |
|
| 76 | 76 | ||
| 77 | - | /// Validate writable Page or private package-data storage for a result record. |
|
| 78 | - | /// Package-state tables and kernel metadata are not writable result storage. |
|
| 79 | - | export unsafe fn resultBuffer(domain: &domains::Domain, address: u64, size: u64) -> bool { |
|
| 77 | + | /// Validate aligned Page or private package-data storage with the given rights. |
|
| 78 | + | /// Package-state tables and kernel metadata cannot hold caller-supplied records. |
|
| 79 | + | unsafe fn buffer(domain: &domains::Domain, address: u64, size: u64, rights: abi::Rights) -> bool { |
|
| 80 | 80 | if (address & 7) <> 0 or size > 0xffffffffffffffff - address { return false; } |
|
| 81 | - | if pages::accessible(&pages::STORE, &domain.memory.table, address, size, abi::Rights(abi::WRITE)) { return true; } |
|
| 81 | + | if pages::accessible(&pages::STORE, &domain.memory.table, address, size, rights) { return true; } |
|
| 82 | 82 | for i in 0..limits::PACKAGES { |
|
| 83 | 83 | if registry::STORE.slots[i].state <> slots::State::Live { continue; } |
|
| 84 | 84 | let base = domain.graph.table[i]; |
|
| 85 | 85 | if base == 0 { continue; } |
|
| 86 | 86 | let package = try! registry::get(®istry::STORE, abi::Ref { index: i, generation: registry::STORE.slots[i].generation }); |
| 90 | 90 | } |
|
| 91 | 91 | ||
| 92 | 92 | /// Copy a fixed query record only after validating its exact buffer size. |
|
| 93 | 93 | unsafe fn reply(domain: &domains::Domain, arguments: &[u64], words: &[u64]) throws (abi::Error) { |
|
| 94 | 94 | let size = words.len as u64 * 8; |
|
| 95 | - | if arguments[2] <> size or arguments[3] <> 0 or not resultBuffer(domain, arguments[1], size) { |
|
| 95 | + | if arguments[2] <> size or arguments[3] <> 0 or not buffer(domain, arguments[1], size, abi::Rights(abi::WRITE)) { |
|
| 96 | 96 | throw abi::Error::InvalidArg; |
|
| 97 | 97 | } |
|
| 98 | 98 | let output = @sliceOf(memory(arguments[1]), words.len); |
|
| 99 | 99 | for i in 0..words.len { set output[i] = words[i]; } |
|
| 100 | 100 | } |
| 144 | 144 | let mut entry: u64 = 0; |
|
| 145 | 145 | if let address = package.entry { set entry = address; } |
|
| 146 | 146 | try reply(&domain, arguments, &[abi::id(object), entry, package.codeAddress, package.code.len as u64 * 4, package.memory as u64]); |
|
| 147 | 147 | return 0; |
|
| 148 | 148 | }, |
|
| 149 | + | case 60 => { |
|
| 150 | + | if arguments[2] <> @sizeOf(abi::ContextStart) as u64 or arguments[3] <> 0 |
|
| 151 | + | or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ)) { |
|
| 152 | + | throw abi::Error::InvalidArg; |
|
| 153 | + | } |
|
| 154 | + | let words = @sliceOf(memory(arguments[1]), 4); |
|
| 155 | + | let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] }; |
|
| 156 | + | return abi::id(try domains::contextCreate(&mut domains::STORE, &mut pages::STORE, ®istry::STORE, |
|
| 157 | + | &domain.memory.table, handle, start)); |
|
| 158 | + | }, |
|
| 159 | + | case 61 => { |
|
| 160 | + | if arguments[2] <> 0 or arguments[3] <> 0 { throw abi::Error::InvalidArg; } |
|
| 161 | + | try domains::contextDestroy(&mut domains::STORE, &mut pages::STORE, &domain.memory.table, |
|
| 162 | + | handle, abi::reference(arguments[1])); |
|
| 163 | + | return 0; |
|
| 164 | + | }, |
|
| 165 | + | case 62 => { |
|
| 166 | + | let permission = try capability::authority(&domain.memory.table, handle, abi::Rights(0)); |
|
| 167 | + | let target = try domains::get(&domains::STORE, permission.object); |
|
| 168 | + | let mut object = target.initial; |
|
| 169 | + | if arguments[1] <> 0 { set object = abi::reference(arguments[1]); } |
|
| 170 | + | let context = try domains::context(&domains::STORE, permission.object, object); |
|
| 171 | + | let mut hart: u64 = 0xffffffffffffffff; |
|
| 172 | + | if let id = context.hart { set hart = id as u64; } |
|
| 173 | + | try reply(&domain, &[0, arguments[2], arguments[3], 0], |
|
| 174 | + | &[abi::id(object), abi::id(permission.object), context.userStack.start, context.userStack.end, context.state as u64, hart]); |
|
| 175 | + | return 0; |
|
| 176 | + | }, |
|
| 149 | 177 | case 63 => { |
|
| 150 | 178 | if arguments[0] <> 0 { throw abi::Error::InvalidArg; } |
|
| 151 | 179 | let current = dispatch::current(); |
|
| 152 | 180 | try reply(&domain, arguments, &[current.context, current.hart, current.budget]); |
|
| 153 | 181 | return 0; |
kernel/kernel/calls.ras
+2 -2
| 1 | - | //! Typed access to a checked user result buffer. |
|
| 1 | + | //! Typed access to a checked user record buffer. |
|
| 2 | 2 | .text; |
|
| 3 | 3 | .export @kernel::calls::memory; |
|
| 4 | 4 | ||
| 5 | - | // The caller checked alignment, extent, and writable domain ownership. |
|
| 5 | + | // The caller checked alignment, extent, and the required domain access rights. |
|
| 6 | 6 | @kernel::calls::memory |
|
| 7 | 7 | ret; |
kernel/kernel/dispatch.rad
+10 -2
| 86 | 86 | export unsafe fn call(frame: &mut trap::Frame, anchor: &mut trap::Hart) { |
|
| 87 | 87 | let id = hart(); |
|
| 88 | 88 | assert id < limits::HARTS; |
|
| 89 | 89 | let context = HARTS[id].state.current else { trap::halt(); return; }; |
|
| 90 | 90 | assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live); |
|
| 91 | - | calls::handle(domains::STORE.contexts[context.index].owner, frame, now(HARTS[id].timer)); |
|
| 91 | + | let owner = domains::STORE.contexts[context.index].owner; |
|
| 92 | + | if frame.registers[17] == abi::Operation::ContextReturn as u64 { |
|
| 93 | + | assert trap::advanceCall(frame); |
|
| 94 | + | try! domains::returned(&mut domains::STORE, owner, context); |
|
| 95 | + | } else { |
|
| 96 | + | calls::handle(owner, frame, now(HARTS[id].timer)); |
|
| 97 | + | } |
|
| 92 | 98 | interrupt(frame, anchor); |
|
| 93 | 99 | } |
|
| 94 | 100 | ||
| 95 | 101 | /// Enter the first authorized context or the hart's retained idle frame. |
|
| 96 | 102 | export unsafe fn start(anchor: &mut trap::Hart) -> ! { |
| 143 | 149 | /// Test whether a live context belongs to the active domain owning its budget. |
|
| 144 | 150 | fn runnable(store: &domains::Store, owner: abi::Ref, context: abi::Ref) -> bool { |
|
| 145 | 151 | if not slots::matches(&store.slots[..], owner, slots::State::Live) { return false; } |
|
| 146 | 152 | if store.records[owner.index].state <> domains::Lifecycle::Active { return false; } |
|
| 147 | 153 | if not slots::matches(&store.contextSlots[..], context, slots::State::Live) { return false; } |
|
| 148 | - | return store.contexts[context.index].owner == owner; |
|
| 154 | + | return store.contexts[context.index].owner == owner and store.contexts[context.index].state == domains::ContextState::Ready; |
|
| 149 | 155 | } |
|
| 150 | 156 | ||
| 151 | 157 | /// Select the unique runnable window and the earliest relevant timer boundary. |
|
| 152 | 158 | /// The caller serializes budget and domain metadata while taking this snapshot. |
|
| 153 | 159 | export fn select(windows: &budgets::Store, contexts: &domains::Store, hart: u32, now: u64) |
| 180 | 186 | { |
|
| 181 | 187 | let choice = try select(windows, contexts, state.hart, now); |
|
| 182 | 188 | if let current = state.current { |
|
| 183 | 189 | assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live); |
|
| 184 | 190 | set contexts.contexts[current.index].frame = *frame; |
|
| 191 | + | set contexts.contexts[current.index].hart = nil; |
|
| 185 | 192 | } |
|
| 186 | 193 | if let next = choice.context { |
|
| 187 | 194 | let context = contexts.contexts[next.index]; |
|
| 188 | 195 | assert context.kernelStack.start < context.kernelStack.end; |
|
| 189 | 196 | set *frame = context.frame; |
|
| 190 | 197 | set anchor.stackBottom = context.kernelStack.start; |
|
| 191 | 198 | set anchor.stackTop = context.kernelStack.end; |
|
| 199 | + | set contexts.contexts[next.index].hart = state.hart; |
|
| 192 | 200 | } else { |
|
| 193 | 201 | set *frame = state.idle; |
|
| 194 | 202 | set anchor.stackBottom = state.stack.start; |
|
| 195 | 203 | set anchor.stackTop = state.stack.end; |
|
| 196 | 204 | } |
kernel/kernel/domains.rad
+134 -5
| 10 | 10 | use super::instances; |
|
| 11 | 11 | use super::events; |
|
| 12 | 12 | use super::trap; |
|
| 13 | 13 | use super::range; |
|
| 14 | 14 | use super::pages; |
|
| 15 | + | use std::arch::rv64::shared; |
|
| 15 | 16 | ||
| 16 | 17 | /// Retained kernel stack capacity for one suspended execution context. |
|
| 17 | 18 | export constant KERNEL_STACK_PAGES: u32 = 64; |
|
| 18 | 19 | ||
| 19 | 20 | /// Management rights returned to a domain's creator. |
| 37 | 38 | ring: events::Ring, |
|
| 38 | 39 | /// Startup values exposed read-only to this domain. |
|
| 39 | 40 | env: abi::Env, |
|
| 40 | 41 | } |
|
| 41 | 42 | ||
| 43 | + | /// Eligibility to resume a live execution context. |
|
| 44 | + | export union ContextState: Copy { |
|
| 45 | + | /// The context can execute with CPU authority in an active domain. |
|
| 46 | + | Ready, |
|
| 47 | + | /// The context has returned and cannot execute again. |
|
| 48 | + | Stopped, |
|
| 49 | + | } |
|
| 50 | + | ||
| 42 | 51 | /// Saved integer state owned by one execution context. |
|
| 43 | 52 | export record Context: Copy { |
|
| 44 | 53 | /// Domain that owns this context. |
|
| 45 | 54 | owner: abi::Ref, |
|
| 55 | + | /// Whether this context can resume. |
|
| 56 | + | state: ContextState, |
|
| 57 | + | /// Hart retaining this context as its current execution owner. |
|
| 58 | + | hart: ?u32, |
|
| 46 | 59 | /// Physical frames retained until this context stops. |
|
| 47 | 60 | kernelFrames: frames::Run, |
|
| 48 | 61 | /// Exclusive stack bounds used by trap entry and suspended M-mode calls. |
|
| 49 | 62 | kernelStack: range::Range, |
|
| 63 | + | /// Exclusive user stack extent; empty until initial activation. |
|
| 64 | + | userStack: range::Range, |
|
| 50 | 65 | /// Architectural state used when this context resumes. |
|
| 51 | 66 | frame: trap::Frame, |
|
| 52 | 67 | } |
|
| 53 | 68 | ||
| 54 | 69 | /// Published domain resources and lifecycle metadata. |
| 92 | 107 | ||
| 93 | 108 | /// View an allocated physical extent that contains a complete Memory record. |
|
| 94 | 109 | fn memory(address: u64) -> *unsafe mut Memory; |
|
| 95 | 110 | /// Clear whole mapped physical frames before installing domain metadata. |
|
| 96 | 111 | fn zero(address: u64, count: u32); |
|
| 112 | + | /// Address of the U-mode normal-return call sequence. |
|
| 113 | + | fn returnAddress() -> u64; |
|
| 97 | 114 | ||
| 98 | 115 | /// Initialize fresh metadata before boot-domain publication. |
|
| 99 | 116 | export fn initialize(store: &mut Store) { |
|
| 100 | 117 | slots::initialize(&mut store.slots[..]); |
|
| 101 | 118 | slots::initialize(&mut store.contextSlots[..]); |
| 106 | 123 | export fn get(store: &Store, object: abi::Ref) -> Domain throws (abi::Error) { |
|
| 107 | 124 | if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; } |
|
| 108 | 125 | return store.records[object.index]; |
|
| 109 | 126 | } |
|
| 110 | 127 | ||
| 128 | + | /// Read a live context after checking its generation and owning domain. |
|
| 129 | + | export fn context(store: &Store, owner: abi::Ref, object: abi::Ref) -> Context throws (abi::Error) { |
|
| 130 | + | let domain = try get(store, owner); |
|
| 131 | + | if not slots::matches(&store.contextSlots[..], object, slots::State::Live) |
|
| 132 | + | or store.contexts[object.index].owner <> owner { throw abi::Error::BadHandle; } |
|
| 133 | + | return store.contexts[object.index]; |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Stop a returned context; initial return ends execution of its whole domain. |
|
| 137 | + | export fn returned(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) { |
|
| 138 | + | let current = try context(store, owner, object); |
|
| 139 | + | if store.records[owner.index].initial == object { |
|
| 140 | + | set store.records[owner.index].state = Lifecycle::Dead; |
|
| 141 | + | for i in 0..limits::CONTEXTS { |
|
| 142 | + | if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == owner { |
|
| 143 | + | set store.contexts[i].state = ContextState::Stopped; |
|
| 144 | + | } |
|
| 145 | + | } |
|
| 146 | + | } else { |
|
| 147 | + | set store.contexts[object.index].state = ContextState::Stopped; |
|
| 148 | + | } |
|
| 149 | + | } |
|
| 150 | + | ||
| 151 | + | /// Reclaim an additional context after all harts have released execution ownership. |
|
| 152 | + | export fn contextDestroy(store: &mut Store, memory: &mut pages::Store, table: &capability::Table, |
|
| 153 | + | authority: abi::Handle, object: abi::Ref) throws (abi::Error) |
|
| 154 | + | { |
|
| 155 | + | let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY)); |
|
| 156 | + | let target = try context(store, permit.object, object); |
|
| 157 | + | if store.records[permit.object.index].initial == object { throw abi::Error::Denied; } |
|
| 158 | + | if target.hart <> nil { throw abi::Error::Busy; } |
|
| 159 | + | try! frames::release(&mut memory.backings.pool, target.kernelFrames); |
|
| 160 | + | try! slots::release(&mut store.contextSlots[..], object); |
|
| 161 | + | } |
|
| 162 | + | ||
| 163 | + | /// Derive a descending stack extent from one writable Page capability. |
|
| 164 | + | fn stackRange(memory: &pages::Store, table: &capability::Table, top: u64) -> range::Range throws (abi::Error) { |
|
| 165 | + | if top < 16 or (top & 15) <> 0 { throw abi::Error::InvalidArg; } |
|
| 166 | + | for i in 0..limits::HANDLES { |
|
| 167 | + | if table.slots[i].state <> slots::State::Live { continue; } |
|
| 168 | + | let entry = table.entries[i]; |
|
| 169 | + | if entry.kind <> abi::Kind::Page or not abi::permits(entry.rights, abi::Rights(abi::WRITE)) { continue; } |
|
| 170 | + | let page = try pages::get(memory, entry.object) catch { continue; }; |
|
| 171 | + | if page.base <= top - 16 and top - page.base <= page.count as u64 * limits::FRAME_SIZE { |
|
| 172 | + | return range::Range { start: page.base, end: top }; |
|
| 173 | + | } |
|
| 174 | + | } |
|
| 175 | + | throw abi::Error::InvalidArg; |
|
| 176 | + | } |
|
| 177 | + | ||
| 178 | + | /// Reject physical overlap with any live context's reserved user stack. |
|
| 179 | + | fn stackAvailable(store: &Store, candidate: range::Range) throws (abi::Error) { |
|
| 180 | + | for i in 0..limits::CONTEXTS { |
|
| 181 | + | if store.contextSlots[i].state <> slots::State::Live { continue; } |
|
| 182 | + | let used = store.contexts[i].userStack; |
|
| 183 | + | if used.start < used.end and candidate.start < used.end and used.start < candidate.end { |
|
| 184 | + | throw abi::Error::Busy; |
|
| 185 | + | } |
|
| 186 | + | } |
|
| 187 | + | } |
|
| 188 | + | ||
| 111 | 189 | /// Activate a pending domain after checking its stack and argument authority. |
|
| 112 | 190 | /// The caller serializes metadata until the initial frame and state are published. |
|
| 113 | 191 | export unsafe fn activate(store: &mut Store, memory: &pages::Store, table: &capability::Table, |
|
| 114 | 192 | handle: abi::Handle, stack: u64, args: u64, size: u64) throws (abi::Error) |
|
| 115 | 193 | { |
|
| 116 | 194 | let entry = try capability::lookup(table, handle, abi::Kind::Domain, abi::Rights(abi::EXECUTE)); |
|
| 117 | 195 | let mut domain = try get(store, entry.object); |
|
| 118 | 196 | if domain.state <> Lifecycle::Pending { throw abi::Error::NotPending; } |
|
| 119 | - | if (stack & 15) <> 0 or stack < 16 |
|
| 120 | - | or not pages::accessible(memory, &domain.memory.table, stack - 16, 16, abi::Rights(abi::WRITE)) { |
|
| 121 | - | throw abi::Error::InvalidArg; |
|
| 122 | - | } |
|
| 197 | + | let userStack = try stackRange(memory, &domain.memory.table, stack); |
|
| 123 | 198 | if size <> 0 and not pages::accessible(memory, &domain.memory.table, args, size, abi::Rights(abi::READ)) { |
|
| 124 | 199 | throw abi::Error::InvalidArg; |
|
| 125 | 200 | } |
|
| 126 | 201 | assert slots::matches(&store.contextSlots[..], domain.initial, slots::State::Live); |
|
| 202 | + | try stackAvailable(store, userStack); |
|
| 127 | 203 | set domain.memory.env = abi::Env { |
|
| 128 | 204 | argsPointer: args, argsSize: size, eventsHandle: *domain.events, |
|
| 129 | 205 | eventsPointer: (&domain.memory.ring) as u64, |
|
| 130 | 206 | }; |
|
| 131 | 207 | set store.contexts[domain.initial.index].frame.registers[2] = stack; |
|
| 132 | 208 | set store.contexts[domain.initial.index].frame.registers[10] = (&domain.memory.env) as u64; |
|
| 209 | + | set store.contexts[domain.initial.index].userStack = userStack; |
|
| 133 | 210 | set store.records[entry.object.index].state = Lifecycle::Active; |
|
| 134 | 211 | } |
|
| 135 | 212 | ||
| 213 | + | /// Check an entry against the target's resident package graph exports. |
|
| 214 | + | unsafe fn exportedEntry(domain: &Domain, packages: ®istry::Store, address: u64) -> bool { |
|
| 215 | + | for i in 0..limits::PACKAGES { |
|
| 216 | + | if domain.graph.table[i] == 0 or packages.slots[i].state <> slots::State::Live { continue; } |
|
| 217 | + | let package = try! registry::get(packages, abi::Ref { index: i, generation: packages.slots[i].generation }); |
|
| 218 | + | if package.entry == address { return true; } |
|
| 219 | + | for symbol in package.exports { |
|
| 220 | + | match symbol.target { |
|
| 221 | + | case shared::Target::Function(entry) => { if entry == address { return true; } }, |
|
| 222 | + | else => {}, |
|
| 223 | + | } |
|
| 224 | + | } |
|
| 225 | + | } |
|
| 226 | + | return false; |
|
| 227 | + | } |
|
| 228 | + | ||
| 229 | + | /// Create an additional context with private kernel storage and shared domain state. |
|
| 230 | + | /// The caller serializes domain, capability, and physical allocation metadata. |
|
| 231 | + | export unsafe fn contextCreate(store: &mut Store, memory: &mut pages::Store, packages: ®istry::Store, |
|
| 232 | + | table: &capability::Table, authority: abi::Handle, start: abi::ContextStart) -> abi::Ref throws (abi::Error) |
|
| 233 | + | { |
|
| 234 | + | let permission = try capability::authority(table, authority, abi::Rights(abi::EXECUTE)); |
|
| 235 | + | let domain = try get(store, permission.object); |
|
| 236 | + | if domain.state == Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 237 | + | if start.entry == 0 or not exportedEntry(&domain, packages, start.entry) |
|
| 238 | + | or (start.size <> 0 and not pages::accessible(memory, &domain.memory.table, start.args, start.size, abi::Rights(abi::READ))) { |
|
| 239 | + | throw abi::Error::InvalidArg; |
|
| 240 | + | } |
|
| 241 | + | let userStack = try stackRange(memory, &domain.memory.table, start.stack); |
|
| 242 | + | try stackAvailable(store, userStack); |
|
| 243 | + | let slot = try slots::reserve(&mut store.contextSlots[..]); |
|
| 244 | + | let pending = try frames::reserve(&mut memory.backings.pool, KERNEL_STACK_PAGES) catch error { |
|
| 245 | + | try! slots::cancel(&mut store.contextSlots[..], slot); throw error; |
|
| 246 | + | }; |
|
| 247 | + | let kernelFrames = frames::commit(pending); |
|
| 248 | + | let kernelStack = try! frames::extent(&memory.backings.pool, kernelFrames); |
|
| 249 | + | zero(kernelStack.start, kernelFrames.count); |
|
| 250 | + | let mut frame = trap::Frame { registers: [0; 32], pc: start.entry, status: 0x80, cause: 0, value: 0 }; |
|
| 251 | + | set frame.registers[1] = returnAddress(); |
|
| 252 | + | set frame.registers[2] = start.stack; |
|
| 253 | + | set frame.registers[3] = domain.graph.table.ptr as u64; |
|
| 254 | + | set frame.registers[10] = start.args; |
|
| 255 | + | set frame.registers[11] = start.size; |
|
| 256 | + | let object = slots::reference(&slot); |
|
| 257 | + | set store.contexts[object.index] = Context { |
|
| 258 | + | owner: permission.object, state: ContextState::Ready, hart: nil, kernelFrames, kernelStack, userStack, frame, |
|
| 259 | + | }; |
|
| 260 | + | return try! slots::commit(&mut store.contextSlots[..], slot); |
|
| 261 | + | } |
|
| 262 | + | ||
| 136 | 263 | /// Memory allocated before the domain and its context become observable. |
|
| 137 | 264 | record Prepared: Copy { |
|
| 138 | 265 | /// Frames for capabilities and the event ring. |
|
| 139 | 266 | allocation: frames::Run, |
|
| 140 | 267 | /// Mapped metadata at the allocation's start. |
| 217 | 344 | kind: abi::Kind::Events, object, rights: abi::Rights(abi::READ | abi::WRITE), |
|
| 218 | 345 | }); |
|
| 219 | 346 | let initial = slots::reference(&contextSlot); |
|
| 220 | 347 | // MPIE enables interrupts after the first return into U-mode. |
|
| 221 | 348 | let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 }; |
|
| 349 | + | set frame.registers[1] = returnAddress(); |
|
| 222 | 350 | set frame.registers[3] = prepared.graph.table.ptr as u64; |
|
| 223 | 351 | set store.contexts[initial.index] = Context { |
|
| 224 | - | owner: object, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, frame, |
|
| 352 | + | owner: object, state: ContextState::Ready, hart: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 353 | + | userStack: range::Range { start: 0, end: 0 }, frame, |
|
| 225 | 354 | }; |
|
| 226 | 355 | set store.records[object.index] = Domain { |
|
| 227 | 356 | state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial, |
|
| 228 | 357 | allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue, |
|
| 229 | 358 | }; |
kernel/kernel/domains.ras
+10 -0
| 1 | 1 | //! Physical domain metadata views and complete frame clearing. |
|
| 2 | 2 | .text; |
|
| 3 | 3 | .export @kernel::domains::memory; |
|
| 4 | 4 | .export @kernel::domains::zero; |
|
| 5 | + | .export @kernel::domains::returnAddress; |
|
| 6 | + | ||
| 7 | + | // Native entries return to a gp-independent user call sequence. |
|
| 8 | + | @kernel::domains::returnAddress |
|
| 9 | + | la %a0 @contextReturn; |
|
| 10 | + | ret; |
|
| 11 | + | @contextReturn |
|
| 12 | + | li %a7 64; |
|
| 13 | + | ecall; |
|
| 14 | + | ebreak; |
|
| 5 | 15 | ||
| 6 | 16 | // The allocated frame extent contains one complete aligned Memory record. |
|
| 7 | 17 | @kernel::domains::memory |
|
| 8 | 18 | ret; |
|
| 9 | 19 |
kernel/kernel/sys.rad
+21 -0
| 120 | 120 | try! result(ecall(abi::Operation::QueryEvents as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::EventsInfo) as i64, 0)); |
|
| 121 | 121 | return info; |
|
| 122 | 122 | } |
|
| 123 | 123 | } |
|
| 124 | 124 | ||
| 125 | + | /// Create an additional context with Execute authority over its domain. |
|
| 126 | + | export fn contextCreate(domain: abi::Handle, start: abi::ContextStart) -> u64 throws (abi::Error) { |
|
| 127 | + | return try result(ecall(abi::Operation::ContextCreate as u32, *domain as i64, |
|
| 128 | + | (&start) as i64, @sizeOf(abi::ContextStart) as i64, 0)); |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | /// Reclaim an additional context that no hart is executing. |
|
| 132 | + | export fn contextDestroy(domain: abi::Handle, context: u64) throws (abi::Error) { |
|
| 133 | + | try result(ecall(abi::Operation::ContextDestroy as u32, *domain as i64, context as i64, 0, 0)); |
|
| 134 | + | } |
|
| 135 | + | ||
| 136 | + | /// Read a context's owner, stack, and state. Zero selects the initial context. |
|
| 137 | + | export fn queryContext(domain: abi::Handle, context: u64) -> abi::ContextInfo throws (abi::Error) { |
|
| 138 | + | unsafe { |
|
| 139 | + | let mut info: abi::ContextInfo = undefined; |
|
| 140 | + | try result(ecall(abi::Operation::QueryContext as u32, *domain as i64, context as i64, |
|
| 141 | + | (&mut info) as i64, @sizeOf(abi::ContextInfo) as i64)); |
|
| 142 | + | return info; |
|
| 143 | + | } |
|
| 144 | + | } |
|
| 145 | + | ||
| 125 | 146 | /// Read the current extent and binding of a held CPU window. |
|
| 126 | 147 | export fn queryBudget(handle: abi::Handle) -> abi::BudgetInfo throws (abi::Error) { |
|
| 127 | 148 | unsafe { |
|
| 128 | 149 | let mut info: abi::BudgetInfo = undefined; |
|
| 129 | 150 | try result(ecall(abi::Operation::QueryBudget as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::BudgetInfo) as i64, 0)); |
kernel/kernel/tests/budgets.rad
+3 -1
| 126 | 126 | let child = slots::reference(&childSlot); |
|
| 127 | 127 | capability::initialize(&mut MEMORY.table, child); |
|
| 128 | 128 | let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]); |
|
| 129 | 129 | let context = slots::reference(&contextSlot); |
|
| 130 | 130 | set DOMAINS.contexts[context.index] = domains::Context { |
|
| 131 | - | owner: child, kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 }, |
|
| 131 | + | owner: child, state: domains::ContextState::Ready, hart: nil, |
|
| 132 | + | kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 }, |
|
| 133 | + | userStack: range::Range { start: 0, end: 0 }, |
|
| 132 | 134 | frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 }, |
|
| 133 | 135 | }; |
|
| 134 | 136 | set DOMAINS.records[child.index] = domains::Domain { |
|
| 135 | 137 | state: domains::Lifecycle::Pending, creator: root, parent: root, |
|
| 136 | 138 | image: abi::Ref { index: 0, generation: 1 }, initial: context, |
kernel/kernel/tests/calls.rad
+20 -0
| 139 | 139 | try testing::expect(error == abi::Error::InvalidArg); set rejected += 1; |
|
| 140 | 140 | }; |
|
| 141 | 141 | try testing::expect(rejected == 6 and RAM[OFFSET + 2] == 0x12345678); |
|
| 142 | 142 | } |
|
| 143 | 143 | ||
| 144 | + | /// Context calls validate descriptor storage and preserve capacity on failure. |
|
| 145 | + | @test unsafe fn contextBuffers() throws (testing::TestError) { |
|
| 146 | + | let owner = initialize(); |
|
| 147 | + | let page = try! calls::invoke(owner, 30, &[0, 1, 0, 0], 0); |
|
| 148 | + | let base = (&RAM[OFFSET]) as u64; |
|
| 149 | + | let mut rejected: u32 = 0; |
|
| 150 | + | for args in &[[0 as u64, 0, 32, 0], [0, base, 24, 0], [0, base, 32, 1], [0, base + 1, 32, 0]] { |
|
| 151 | + | try calls::invoke(owner, 60, &args[..], 0) catch error { |
|
| 152 | + | try testing::expect(error == abi::Error::InvalidArg); set rejected += 1; |
|
| 153 | + | }; |
|
| 154 | + | } |
|
| 155 | + | try calls::invoke(owner, 60, &[0, base, 32, 0], 0) catch error { |
|
| 156 | + | try testing::expect(error == abi::Error::Denied); set rejected += 1; |
|
| 157 | + | }; |
|
| 158 | + | try calls::invoke(owner, 62, &[0, 0, base, 32], 0) catch error { |
|
| 159 | + | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 160 | + | }; |
|
| 161 | + | try testing::expect(rejected == 6 and domains::STORE.contextSlots[0].state == slots::State::Free); |
|
| 162 | + | } |
|
| 163 | + | ||
| 144 | 164 | /// An unknown user operation advances PC and returns only a negative a0 error. |
|
| 145 | 165 | @test unsafe fn trapReply() throws (testing::TestError) { |
|
| 146 | 166 | let owner = initialize(); |
|
| 147 | 167 | let mut frame = trap::Frame { registers: [123; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 }; |
|
| 148 | 168 | set frame.registers[17] = 0xffffffffffffffff; |
kernel/kernel/tests/dispatch.rad
+10 -0
| 29 | 29 | capability::initialize(&mut TABLE, owner); |
|
| 30 | 30 | set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 31 | 31 | set DOMAINS.records[0].state = domains::Lifecycle::Active; |
|
| 32 | 32 | set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 33 | 33 | set DOMAINS.contexts[0].owner = owner; |
|
| 34 | + | set DOMAINS.contexts[0].state = domains::ContextState::Ready; |
|
| 35 | + | set DOMAINS.contexts[0].hart = nil; |
|
| 34 | 36 | let first = try! budgets::seed(&mut BUDGETS, &mut TABLE, 0, 10, 40); |
|
| 35 | 37 | let gap = try! budgets::split(&mut BUDGETS, &mut TABLE, first, 20, 0); |
|
| 36 | 38 | let last = try! budgets::split(&mut BUDGETS, &mut TABLE, gap, 30, 0); |
|
| 37 | 39 | let a = try! capability::get(&TABLE, first); |
|
| 38 | 40 | let b = try! capability::get(&TABLE, last); |
| 65 | 67 | try testing::expect(pending.context == nil); |
|
| 66 | 68 | set DOMAINS.records[0].state = domains::Lifecycle::Active; |
|
| 67 | 69 | set DOMAINS.contextSlots[0].generation = 2; |
|
| 68 | 70 | let stale = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
|
| 69 | 71 | try testing::expect(stale.context == nil); |
|
| 72 | + | set DOMAINS.contextSlots[0].generation = 1; |
|
| 73 | + | set DOMAINS.contexts[0].state = domains::ContextState::Stopped; |
|
| 74 | + | let stopped = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
|
| 75 | + | try testing::expect(stopped.context == nil); |
|
| 70 | 76 | } |
|
| 71 | 77 | ||
| 72 | 78 | /// Each online hart has a separate comparison word and shares one clock register. |
|
| 73 | 79 | @test unsafe fn timerRegisters() throws (testing::TestError) { |
|
| 74 | 80 | set PLATFORM.harts = 0x81; |
| 100 | 106 | let mut frame = trap::Frame { registers: [0; 32], pc: 0x40, status: 0x80, cause: 0, value: 0 }; |
|
| 101 | 107 | set DOMAINS.contexts[0].frame = frame; |
|
| 102 | 108 | set DOMAINS.contexts[0].kernelStack = range::Range { start: 0x8000, end: 0xc000 }; |
|
| 103 | 109 | set DOMAINS.contextSlots[1] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 104 | 110 | set DOMAINS.contexts[1].owner = TABLE.owner; |
|
| 111 | + | set DOMAINS.contexts[1].state = domains::ContextState::Ready; |
|
| 112 | + | set DOMAINS.contexts[1].hart = nil; |
|
| 105 | 113 | set DOMAINS.contexts[1].frame = frame; |
|
| 106 | 114 | set DOMAINS.contexts[1].kernelStack = range::Range { start: 0xc000, end: 0x10000 }; |
|
| 107 | 115 | set BUDGETS.windows[1].context = abi::Ref { index: 1, generation: 1 }; |
|
| 108 | 116 | let mut idle = frame; |
|
| 109 | 117 | set idle.pc = 0x100; |
| 117 | 125 | stackTop: 0x4000, stackBottom: 0x2000, kernelGp: 0x20000, handler, |
|
| 118 | 126 | savedT0: 0, savedT1: 0, savedSp: 0, |
|
| 119 | 127 | }; |
|
| 120 | 128 | let first = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 10); |
|
| 121 | 129 | try testing::expect(frame.pc == 0x40 and anchor.stackTop == 0xc000); |
|
| 130 | + | try testing::expect(DOMAINS.contexts[0].hart == 0); |
|
| 122 | 131 | set frame.pc = 0x88; |
|
| 123 | 132 | set frame.status = 0x1880; |
|
| 124 | 133 | set frame.registers[2] = 0xb000; |
|
| 125 | 134 | set frame.registers[5] = 43; |
|
| 126 | 135 | let second = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 20); |
|
| 127 | 136 | try testing::expect(anchor.stackBottom == 0xc000 and anchor.stackTop == 0x10000); |
|
| 137 | + | try testing::expect(DOMAINS.contexts[0].hart == nil and DOMAINS.contexts[1].hart == 0); |
|
| 128 | 138 | set frame.pc = 0x77; |
|
| 129 | 139 | let resumed = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 30); |
|
| 130 | 140 | try testing::expect(frame.pc == 0x88 and frame.status == 0x1880); |
|
| 131 | 141 | try testing::expect(frame.registers[2] == 0xb000 and frame.registers[5] == 43); |
|
| 132 | 142 | try testing::expect(DOMAINS.contexts[1].frame.pc == 0x77 and anchor.stackTop == 0xc000); |
kernel/kernel/tests/domains.rad
+105 -0
| 101 | 101 | try testing::expect(err == abi::Error::NotPending); set failures += 1; |
|
| 102 | 102 | }; |
|
| 103 | 103 | try testing::expect(failures == 5); |
|
| 104 | 104 | } |
|
| 105 | 105 | ||
| 106 | + | /// Additional contexts share private package state and reserve distinct kernel stacks. |
|
| 107 | + | @test unsafe fn additionalContext() throws (testing::TestError) { |
|
| 108 | + | initialize(abi::CREATE); |
|
| 109 | + | slots::initialize(&mut PAGES.slots[..]); |
|
| 110 | + | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 111 | + | let authority = try! capability::get(&TABLE, handle); |
|
| 112 | + | let mut child = try! domains::get(&DOMAINS, authority.object); |
|
| 113 | + | // Move the fixture's physical allocation state into its Page store. |
|
| 114 | + | set PAGES.backings = BACKINGS; |
|
| 115 | + | let pending = try! slots::reserve(&mut PAGES.slots[..]); |
|
| 116 | + | let object = slots::reference(&pending); |
|
| 117 | + | set PAGES.records[object.index] = pages::Page { |
|
| 118 | + | backing: object, base: 0x90000000, count: 2, origin: authority.object, handles: 1, |
|
| 119 | + | }; |
|
| 120 | + | let published = try! slots::commit(&mut PAGES.slots[..], pending); |
|
| 121 | + | let memory = try! capability::install(&mut child.memory.table, capability::Entry { |
|
| 122 | + | kind: abi::Kind::Page, object: published, rights: abi::Rights(abi::READ | abi::WRITE), |
|
| 123 | + | }); |
|
| 124 | + | let start = abi::ContextStart { entry: 0x80000000, stack: 0x90002000, args: 0x90000000, size: 16 }; |
|
| 125 | + | let count = PAGES.backings.pool.count; |
|
| 126 | + | set PAGES.backings.pool.count = 0; |
|
| 127 | + | let mut exhausted = false; |
|
| 128 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error { |
|
| 129 | + | try testing::expect(error == abi::Error::OutOfMemory); set exhausted = true; |
|
| 130 | + | }; |
|
| 131 | + | try testing::expect(exhausted and DOMAINS.contextSlots[child.initial.index + 1].state == slots::State::Free); |
|
| 132 | + | set PAGES.backings.pool.count = count; |
|
| 133 | + | let context = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 134 | + | let created = try! domains::context(&DOMAINS, authority.object, context); |
|
| 135 | + | let initial = DOMAINS.contexts[child.initial.index]; |
|
| 136 | + | try testing::expect(context <> child.initial and created.owner == authority.object); |
|
| 137 | + | try testing::expect(created.frame.registers[3] == initial.frame.registers[3]); |
|
| 138 | + | try testing::expect(created.kernelStack.start >= initial.kernelStack.end or created.kernelStack.end <= initial.kernelStack.start); |
|
| 139 | + | try testing::expect(created.frame.pc == start.entry and created.frame.registers[2] == start.stack); |
|
| 140 | + | try testing::expect(created.frame.registers[10] == start.args and created.frame.registers[11] == start.size); |
|
| 141 | + | try testing::expect(created.userStack.start == 0x90000000 and created.userStack.end == start.stack); |
|
| 142 | + | let mut invalid = start; |
|
| 143 | + | set invalid.entry += 4; |
|
| 144 | + | let mut rejected: u32 = 0; |
|
| 145 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error { |
|
| 146 | + | try testing::expect(error == abi::Error::InvalidArg); set rejected += 1; |
|
| 147 | + | }; |
|
| 148 | + | set invalid = start; |
|
| 149 | + | set invalid.stack -= 1; |
|
| 150 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error { |
|
| 151 | + | try testing::expect(error == abi::Error::InvalidArg); set rejected += 1; |
|
| 152 | + | }; |
|
| 153 | + | set invalid = start; |
|
| 154 | + | set invalid.size = 0xffffffffffffffff; |
|
| 155 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, invalid) catch error { |
|
| 156 | + | try testing::expect(error == abi::Error::InvalidArg); set rejected += 1; |
|
| 157 | + | }; |
|
| 158 | + | try testing::expect(rejected == 3 and DOMAINS.contextSlots[context.index + 1].state == slots::State::Free); |
|
| 159 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error { |
|
| 160 | + | try testing::expect(error == abi::Error::Busy); set rejected += 1; |
|
| 161 | + | }; |
|
| 162 | + | try testing::expect(rejected == 4 and DOMAINS.contextSlots[context.index + 1].state == slots::State::Free); |
|
| 163 | + | try domains::activate(&mut DOMAINS, &PAGES, &TABLE, handle, start.stack, 0, 0) catch error { |
|
| 164 | + | try testing::expect(error == abi::Error::Busy); set rejected += 1; |
|
| 165 | + | }; |
|
| 166 | + | try testing::expect(rejected == 5 and DOMAINS.records[authority.object.index].state == domains::Lifecycle::Pending); |
|
| 167 | + | let other = try! domains::create(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 168 | + | let otherEntry = try! capability::get(&TABLE, other); |
|
| 169 | + | let mut receiver = try! domains::get(&DOMAINS, otherEntry.object); |
|
| 170 | + | let shared = try! capability::install(&mut receiver.memory.table, capability::Entry { |
|
| 171 | + | kind: abi::Kind::Page, object: published, rights: abi::Rights(abi::WRITE), |
|
| 172 | + | }); |
|
| 173 | + | try domains::activate(&mut DOMAINS, &PAGES, &TABLE, other, start.stack, 0, 0) catch error { |
|
| 174 | + | try testing::expect(error == abi::Error::Busy); set rejected += 1; |
|
| 175 | + | }; |
|
| 176 | + | try testing::expect(rejected == 6 and DOMAINS.records[otherEntry.object.index].state == domains::Lifecycle::Pending); |
|
| 177 | + | try domains::context(&DOMAINS, otherEntry.object, context) catch error { |
|
| 178 | + | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 179 | + | }; |
|
| 180 | + | try domains::context(&DOMAINS, authority.object, abi::Ref { index: context.index, generation: context.generation + 1 }) catch error { |
|
| 181 | + | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 182 | + | }; |
|
| 183 | + | try testing::expect(rejected == 8); |
|
| 184 | + | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, child.initial) catch error { |
|
| 185 | + | try testing::expect(error == abi::Error::Denied); set rejected += 1; |
|
| 186 | + | }; |
|
| 187 | + | set DOMAINS.contexts[context.index].hart = 0; |
|
| 188 | + | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error { |
|
| 189 | + | try testing::expect(error == abi::Error::Busy); set rejected += 1; |
|
| 190 | + | }; |
|
| 191 | + | try! domains::returned(&mut DOMAINS, authority.object, context); |
|
| 192 | + | try testing::expect(DOMAINS.contexts[context.index].state == domains::ContextState::Stopped); |
|
| 193 | + | try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Pending); |
|
| 194 | + | set DOMAINS.contexts[context.index].hart = nil; |
|
| 195 | + | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context); |
|
| 196 | + | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 197 | + | try testing::expect(PAGES.backings.pool.free[i]); |
|
| 198 | + | } |
|
| 199 | + | try domains::context(&DOMAINS, authority.object, context) catch error { |
|
| 200 | + | try testing::expect(error == abi::Error::BadHandle); set rejected += 1; |
|
| 201 | + | }; |
|
| 202 | + | let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 203 | + | try testing::expect(next.index == context.index and next.generation <> context.generation); |
|
| 204 | + | try! domains::returned(&mut DOMAINS, authority.object, child.initial); |
|
| 205 | + | try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Dead); |
|
| 206 | + | try testing::expect(DOMAINS.contexts[next.index].state == domains::ContextState::Stopped); |
|
| 207 | + | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next); |
|
| 208 | + | try testing::expect(rejected == 11); |
|
| 209 | + | } |
|
| 210 | + | ||
| 106 | 211 | /// Creation installs only Events in the child and preserves selected authorizing rights. |
|
| 107 | 212 | @test unsafe fn pending() throws (testing::TestError) { |
|
| 108 | 213 | initialize(abi::CREATE | abi::ALLOCATE); |
|
| 109 | 214 | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 110 | 215 | let authority = try! capability::get(&TABLE, handle); |
test/dispatch/kernel/dispatchcheck.rad
+17 -4
| 60 | 60 | } |
|
| 61 | 61 | } |
|
| 62 | 62 | let cap = try! capability::get(&TABLE, HANDLES[0]); |
|
| 63 | 63 | let mut user = try! domains::get(&domains::STORE, cap.object); |
|
| 64 | 64 | let selfAuthority = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 65 | - | kind: abi::Kind::Domain, object: cap.object, rights: abi::Rights(abi::CREATE | abi::ALLOCATE), |
|
| 65 | + | kind: abi::Kind::Domain, object: cap.object, rights: abi::Rights(abi::CREATE | abi::ALLOCATE | abi::EXECUTE | abi::WAKE | abi::DESTROY), |
|
| 66 | 66 | }); |
|
| 67 | 67 | let root = user.image; |
|
| 68 | 68 | let executable = try! registry::install(®istry::STORE, &mut user.memory.table, root); |
|
| 69 | 69 | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 70 | 70 | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 71 | 71 | let stackCap = try! capability::get(&TABLE, stack); |
|
| 72 | 72 | let stackPage = try! pages::get(&pages::STORE, stackCap.object); |
|
| 73 | - | let args = @sliceOf(memory(stackPage.base), 24); |
|
| 73 | + | let args = @sliceOf(memory(stackPage.base), 32); |
|
| 74 | 74 | let words = [*granted, *executable, *selfAuthority]; |
|
| 75 | 75 | for word in 0..3 { |
|
| 76 | 76 | for byte in 0..8 { set args[word * 8 + byte] = (words[word] >> (byte as u64 * 8)) as u8; } |
|
| 77 | 77 | } |
|
| 78 | - | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 24); |
|
| 78 | + | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, HANDLES[0], stackPage.base + 4096, stackPage.base, 32); |
|
| 79 | 79 | let imageRef = try! registry::image(®istry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE)); |
|
| 80 | 80 | let target = try! registry::exported(®istry::STORE, imageRef, &"spin::count"[..]); |
|
| 81 | 81 | let case shared::Target::Data(data) = target else panic "counter"; |
|
| 82 | 82 | let timer = try! dispatch::timer(&boot::PLATFORM, 0); |
|
| 83 | 83 | let clock = dispatch::now(timer); |
|
| 84 | 84 | let start = clock + 1000000; |
|
| 85 | - | let span: u64 = 1000000; |
|
| 85 | + | let span: u64 = 4000000; |
|
| 86 | 86 | set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker(); |
|
| 87 | 87 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64; |
|
| 88 | 88 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock; |
|
| 89 | 89 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span; |
|
| 90 | 90 | set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span; |
| 99 | 99 | for i in 0..6 { |
|
| 100 | 100 | if i == 1 { continue; } |
|
| 101 | 101 | let owner = owners[i]; |
|
| 102 | 102 | let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 103 | 103 | budgets::Binding { budget: windows[i], domain: HANDLES[owner], context: CONTEXTS[owner] }, clock); |
|
| 104 | + | if i == 3 { |
|
| 105 | + | for byte in 0..8 { set args[24 + byte] = (*handle >> (byte as u64 * 8)) as u8; } |
|
| 106 | + | } |
|
| 104 | 107 | } |
|
| 105 | 108 | } |
|
| 106 | 109 | ||
| 107 | 110 | /// Check state captured by production timer handling after both resumptions. |
|
| 108 | 111 | export unsafe fn verify() { |
|
| 109 | 112 | let user = domains::STORE.contexts[CONTEXTS[0].index].frame; |
|
| 110 | 113 | let machine = domains::STORE.contexts[CONTEXTS[1].index].frame; |
|
| 111 | 114 | assert (user.status & 0x1800) == 0 and (machine.status & 0x1800) == 0x1800; |
|
| 112 | 115 | assert machine.registers[8] > 0 and machine.registers[9] == 1 and machine.registers[18] > 0; |
|
| 116 | + | let domain = try! domains::get(&domains::STORE, domains::STORE.contexts[CONTEXTS[0].index].owner); |
|
| 117 | + | let target = try! registry::exported(®istry::STORE, domain.image, &"spin::extraCount"[..]); |
|
| 118 | + | let case shared::Target::Data(data) = target else panic "extra counter"; |
|
| 119 | + | let bytes = @sliceOf(memory(domain.graph.table[data.slot] + data.offset as u64), 8); |
|
| 120 | + | let mut progress: u64 = 0; |
|
| 121 | + | for byte in 0..8 { set progress |= bytes[byte] as u64 << (byte as u64 * 8); } |
|
| 122 | + | assert progress > 0; |
|
| 123 | + | let checked = try! registry::exported(®istry::STORE, domain.image, &"spin::destroyed"[..]); |
|
| 124 | + | let case shared::Target::Data(flag) = checked else panic "destruction flag"; |
|
| 125 | + | assert *memory(domain.graph.table[flag.slot] + flag.offset as u64) == 1; |
|
| 113 | 126 | finish(); |
|
| 114 | 127 | } |
test/dispatch/machine.ras
+21 -5
| 2 | 2 | .text; |
|
| 3 | 3 | call @kernel::boot::initialize; |
|
| 4 | 4 | call @kernel::dispatchcheck::setup; |
|
| 5 | 5 | call @kernel::boot::run; |
|
| 6 | 6 | @fail |
|
| 7 | + | li %a0 1; |
|
| 8 | + | j @failure; |
|
| 9 | + | @late |
|
| 10 | + | li %a0 2; |
|
| 11 | + | j @failure; |
|
| 12 | + | @noProgress |
|
| 13 | + | li %a0 3; |
|
| 14 | + | j @failure; |
|
| 15 | + | @noResumption |
|
| 16 | + | li %a0 4; |
|
| 17 | + | j @failure; |
|
| 18 | + | @stackCorruption |
|
| 19 | + | li %a0 5; |
|
| 20 | + | @failure |
|
| 7 | 21 | li %t0 0x10001000; |
|
| 8 | - | li %t1 0x13333; |
|
| 22 | + | slli %t1 %a0 16; |
|
| 23 | + | li %t2 0x3333; |
|
| 24 | + | or %t1 %t1 %t2; |
|
| 9 | 25 | sw %t1 0(%t0); |
|
| 10 | 26 | .export @kernel::dispatchcheck::memory; |
|
| 11 | 27 | .export @kernel::dispatchcheck::kernelGp; |
|
| 12 | 28 | .export @kernel::dispatchcheck::worker; |
|
| 13 | 29 | .export @kernel::dispatchcheck::completion; |
| 23 | 39 | @kernel::dispatchcheck::completion |
|
| 24 | 40 | la %a0 @complete; |
|
| 25 | 41 | ret; |
|
| 26 | 42 | @machineWork |
|
| 27 | 43 | ld %t0 0(%a1); |
|
| 28 | - | bgeu %t0 %a2 @fail; |
|
| 44 | + | bgeu %t0 %a2 @late; |
|
| 29 | 45 | ld %s2 0(%a0); |
|
| 30 | - | beqz %s2 @fail; |
|
| 46 | + | beqz %s2 @noProgress; |
|
| 31 | 47 | addi %sp %sp -16; |
|
| 32 | 48 | li %s3 1234567; |
|
| 33 | 49 | sd %s3 0(%sp); |
|
| 34 | 50 | li %s0 0; |
|
| 35 | 51 | li %s1 0; |
|
| 36 | 52 | @workLoop |
|
| 37 | 53 | addi %s0 %s0 1; |
|
| 38 | 54 | ld %t0 0(%sp); |
|
| 39 | - | bne %t0 %s3 @fail; |
|
| 55 | + | bne %t0 %s3 @stackCorruption; |
|
| 40 | 56 | ld %t0 0(%a1); |
|
| 41 | 57 | bltu %t0 %a3 @workLoop; |
|
| 42 | 58 | ld %t0 0(%a0); |
|
| 43 | - | bgeu %s2 %t0 @fail; |
|
| 59 | + | bgeu %s2 %t0 @noResumption; |
|
| 44 | 60 | li %s1 1; |
|
| 45 | 61 | j @workLoop; |
|
| 46 | 62 | @complete |
|
| 47 | 63 | call @kernel::dispatchcheck::verify; |
|
| 48 | 64 | j @fail; |
test/dispatch/run
+1 -1
| 20 | 20 | printf '];\n' >> "$work/kernel/dispatchinput.rad" |
|
| 21 | 21 | sh test/acceptance/compile "$emulator" "$work" |
|
| 22 | 22 | cat test/dispatch/machine.ras kernel/kernel/*.ras > "$work/dispatch.ras" |
|
| 23 | 23 | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \ |
|
| 24 | 24 | -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64" |
|
| 25 | - | "$emulator" -machine -memory-size=262144 -max-steps=80000000 -run "$work/dispatch.rv64" |
|
| 25 | + | "$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/dispatch.rv64" |
|
| 26 | 26 | printf 'dispatch: user calls, preemption, idle gap, and retained machine continuation passed\n' |
test/dispatch/spin.rad
+42 -1
| 9 | 9 | stack: u64, |
|
| 10 | 10 | /// Local capability for the resident user package. |
|
| 11 | 11 | image: u64, |
|
| 12 | 12 | /// Local self-Domain capability with allocation authority. |
|
| 13 | 13 | domain: u64, |
|
| 14 | + | /// Future window that the user can partition between its contexts. |
|
| 15 | + | future: u64, |
|
| 14 | 16 | } |
|
| 15 | 17 | ||
| 16 | 18 | /// Startup environment supplied by the kernel. |
|
| 17 | 19 | record Env: Copy { |
|
| 18 | 20 | /// Readable startup capabilities. |
| 26 | 28 | } |
|
| 27 | 29 | /// Private query output used to check writable package-state authority. |
|
| 28 | 30 | static INFO: [u64; 2] = [0; 2]; |
|
| 29 | 31 | /// Private progress counter observed by the machine fixture. |
|
| 30 | 32 | export static count: u64 = 0; |
|
| 33 | + | /// Additional-context progress in the same private package state. |
|
| 34 | + | export static extraCount: u64 = 0; |
|
| 35 | + | /// Successful user-side return and destruction checks. |
|
| 36 | + | export static destroyed: u64 = 0; |
|
| 37 | + | ||
| 38 | + | /// Update shared private state and return from an additional context. |
|
| 39 | + | export fn worker(args: *opaque, size: u64) { |
|
| 40 | + | assert size == 0; |
|
| 41 | + | set extraCount += 1; |
|
| 42 | + | set count += 1; |
|
| 43 | + | } |
|
| 31 | 44 | /// Check direct calls, then consume each window without yielding. |
|
| 32 | 45 | @default unsafe fn main(env: *Env) { |
|
| 33 | - | assert env.argsSize == 24; |
|
| 46 | + | if env.argsSize == 0 { return; } |
|
| 47 | + | assert env.argsSize == 32; |
|
| 34 | 48 | let handle = env.argsPointer.stack as i64; |
|
| 35 | 49 | let info = sys::queryPage(abi::Handle(handle as u64)); |
|
| 36 | 50 | assert info.base <> 0 and info.count == 1; |
|
| 37 | 51 | let mut rejected = false; |
|
| 38 | 52 | try sys::pageAllocate(abi::Handle(0), 0) catch error { |
| 70 | 84 | assert rejected; |
|
| 71 | 85 | let current = sys::currentContext(); |
|
| 72 | 86 | assert current.context <> 0 and current.hart == 0 and current.budget <> 0; |
|
| 73 | 87 | let window = try! sys::queryBudget(abi::Handle(current.budget)); |
|
| 74 | 88 | assert window.context == current.context and window.hart == current.hart and window.start < window.end; |
|
| 89 | + | let extraStack = try! sys::pageAllocate(abi::Handle(0), 1); |
|
| 90 | + | let extraMemory = sys::queryPage(extraStack); |
|
| 91 | + | let extra = try! sys::contextCreate(abi::Handle(env.argsPointer.domain), abi::ContextStart { |
|
| 92 | + | entry: worker as u64, stack: extraMemory.base + 4096, args: 0, size: 0, |
|
| 93 | + | }); |
|
| 94 | + | let context = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra); |
|
| 95 | + | assert context.context == extra and context.domain == domain.id; |
|
| 96 | + | assert context.stackBase == extraMemory.base and context.stackTop == extraMemory.base + 4096; |
|
| 97 | + | let future = abi::Handle(env.argsPointer.future); |
|
| 98 | + | let later = try! sys::queryBudget(future); |
|
| 99 | + | let tail = try! sys::budgetSplit(future, later.start + (later.end - later.start) / 2); |
|
| 100 | + | let childWindow = try! sys::budgetSplit(tail, later.start + (later.end - later.start) * 3 / 4); |
|
| 101 | + | let resumption = try! sys::budgetSplit(childWindow, later.start + (later.end - later.start) * 7 / 8); |
|
| 102 | + | let bound = try! sys::budgetBind(tail, abi::Handle(env.argsPointer.domain), extra); |
|
| 103 | + | let initial = try! sys::queryContext(child, 0); |
|
| 104 | + | let childBound = try! sys::budgetBind(childWindow, child, initial.context); |
|
| 75 | 105 | let mut local: [u64; 2] = [0; 2]; |
|
| 76 | 106 | assert ecall(44, handle, (&mut local[0]) as i64, 16, 0) == 0; |
|
| 77 | 107 | assert local[0] <> 0 and local[1] == 1; |
|
| 78 | 108 | assert ecall(44, handle, (&mut INFO[0]) as i64, 16, 0) == 0; |
|
| 79 | 109 | assert INFO[0] == local[0] and INFO[1] == local[1]; |
|
| 80 | 110 | assert ecall(44, handle, 0, 16, 0) == -4; |
|
| 81 | 111 | assert ecall(0xffffffff, 0, 0, 0, 0) == -4; |
|
| 112 | + | while extraCount == 0 { set count += 1; } |
|
| 113 | + | let stopped = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra); |
|
| 114 | + | assert stopped.state == 1 and stopped.hart == 0xffffffffffffffff; |
|
| 115 | + | try! sys::contextDestroy(abi::Handle(env.argsPointer.domain), extra); |
|
| 116 | + | set rejected = false; |
|
| 117 | + | try sys::queryContext(abi::Handle(env.argsPointer.domain), extra) catch error { |
|
| 118 | + | assert error == abi::Error::BadHandle; |
|
| 119 | + | set rejected = true; |
|
| 120 | + | }; |
|
| 121 | + | assert rejected and sys::queryDomain(child).state == 2; |
|
| 122 | + | set destroyed = 1; |
|
| 82 | 123 | while true { set count += 1; } |
|
| 83 | 124 | } |