kernel: Start secondary harts
8aaf2eee4dfb86e25f5b9d3769907ea395fe545f6a8244e19a698655b926e704
Serialize shared metadata with interrupt-masked linear guards. Keep initialization and compilation outside the lock, exchange contexts on hart-owned stacks, and gate dispatch on complete hart initialization. Verify concurrent contexts of one domain on 1, 2, and 8 harts, repeated capability transactions, register isolation, and measured metadata work. Full library, kernel, compiler, and machine suites pass. Assisted-by: Codex:gpt-6
1 parent
f2d48725
Makefile
+6 -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 |
|
| 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 |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 206 | 206 | ||
| 207 | 207 | # Native preemption across user code, idle, and retained machine work. |
|
| 208 | 208 | .PHONY: dispatch-test |
|
| 209 | 209 | dispatch-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64 |
|
| 210 | 210 | @RAD_EMULATOR="$(EMU)" sh test/dispatch/run |
|
| 211 | + | ||
| 212 | + | # Concurrent user contexts and metadata transactions on every online hart. |
|
| 213 | + | .PHONY: smp-test |
|
| 214 | + | smp-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64 |
|
| 215 | + | @RAD_EMULATOR="$(EMU)" sh test/dispatch/run smp "1 2 8" |
kernel/kernel/boot.rad
+9 -2
| 98 | 98 | assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0; |
|
| 99 | 99 | let stack = PLATFORM.stacks[hart as u32]; |
|
| 100 | 100 | set HARTS[hart as u32] = trap::Hart { |
|
| 101 | 101 | stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected, |
|
| 102 | 102 | savedT0: 0, savedT1: 0, savedSp: 0, |
|
| 103 | + | dispatchTop: stack.end, dispatchFrame: undefined, |
|
| 103 | 104 | }; |
|
| 104 | 105 | trap::install(&mut HARTS[hart as u32]); |
|
| 105 | 106 | try! dispatch::initialize(&PLATFORM, hart as u32, stateTable as u64); |
|
| 106 | 107 | let mut count: u64 = 0; |
|
| 107 | 108 | for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } } |
|
| 108 | 109 | return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count; |
|
| 109 | 110 | } |
|
| 110 | 111 | ||
| 111 | - | /// Enter dispatch after this hart completes platform initialization. |
|
| 112 | - | export unsafe fn run() { dispatch::start(&mut HARTS[dispatch::hart()]); } |
|
| 112 | + | /// Enter dispatch after every online hart publishes its initialized state. |
|
| 113 | + | export unsafe fn run() -> ! { |
|
| 114 | + | let mut count: u64 = 0; |
|
| 115 | + | for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } } |
|
| 116 | + | while sync::loadAcquire(&ARRIVED) < count {} |
|
| 117 | + | sync::syncInstructions(); |
|
| 118 | + | dispatch::start(&mut HARTS[dispatch::hart()]); |
|
| 119 | + | } |
kernel/kernel/calls.rad
+133 -3
| 12 | 12 | use super::events; |
|
| 13 | 13 | use super::transactions; |
|
| 14 | 14 | use super::loader; |
|
| 15 | 15 | use super::dispatch; |
|
| 16 | 16 | use super::timers; |
|
| 17 | + | use super::sync; |
|
| 17 | 18 | ||
| 18 | 19 | /// Validate resident object generations for capabilities without page ownership. |
|
| 19 | 20 | unsafe fn resident(entry: capability::Entry) throws (abi::Error) { |
|
| 20 | 21 | match entry.kind { |
|
| 21 | 22 | case abi::Kind::Domain, abi::Kind::Events => { let object = try domains::get(&domains::STORE, entry.object); }, |
| 216 | 217 | budgets::Binding { budget: handle, domain: abi::Handle(arguments[1]), context: abi::reference(arguments[2]) }, now), |
|
| 217 | 218 | else => throw abi::Error::InvalidArg, |
|
| 218 | 219 | } |
|
| 219 | 220 | } |
|
| 220 | 221 | ||
| 221 | - | /// Complete a user ecall and preserve all registers except its scalar result. |
|
| 222 | - | export unsafe fn handle(owner: abi::Ref, frame: &mut trap::Frame, now: u64) { |
|
| 222 | + | /// Check a retained domain's lifetime while metadata is serialized. |
|
| 223 | + | unsafe fn live(owner: abi::Ref) -> bool { |
|
| 224 | + | return slots::matches(&domains::STORE.slots[..], owner, slots::State::Live) |
|
| 225 | + | and domains::STORE.records[owner.index].state <> domains::Lifecycle::Dead; |
|
| 226 | + | } |
|
| 227 | + | ||
| 228 | + | /// Load a package with metadata locked only around resource transactions. |
|
| 229 | + | unsafe fn runtimeLoad(domain: &mut domains::Domain, request: loader::Request, guard: sync::Guard) -> u64 throws (abi::Error) { |
|
| 230 | + | let owner = domain.memory.table.owner; |
|
| 231 | + | let pending = try loader::reserve(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, request) |
|
| 232 | + | catch error { sync::leave(guard); throw error; }; |
|
| 233 | + | sync::leave(guard); |
|
| 234 | + | let input = try loader::decode(&pending) catch error { |
|
| 235 | + | let guard = sync::enter(); |
|
| 236 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 237 | + | sync::leave(guard); throw error; |
|
| 238 | + | }; |
|
| 239 | + | let existing = try loader::identify(®istry::STORE, &pending, &input) catch error { |
|
| 240 | + | let guard = sync::enter(); |
|
| 241 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 242 | + | sync::leave(guard); throw error; |
|
| 243 | + | }; |
|
| 244 | + | let allocationGuard = sync::enter(); |
|
| 245 | + | if not live(owner) { |
|
| 246 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 247 | + | sync::leave(allocationGuard); throw abi::Error::BadHandle; |
|
| 248 | + | } |
|
| 249 | + | if let object = existing { |
|
| 250 | + | let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object) |
|
| 251 | + | catch error { sync::leave(allocationGuard); throw error; }; |
|
| 252 | + | sync::leave(allocationGuard); return *result; |
|
| 253 | + | } |
|
| 254 | + | let output = try loader::reserveOutput(&mut pages::STORE.backings.pool, &mut registry::STORE) catch error { |
|
| 255 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 256 | + | sync::leave(allocationGuard); throw error; |
|
| 257 | + | }; |
|
| 258 | + | sync::leave(allocationGuard); |
|
| 259 | + | let compiled = try loader::compile(®istry::STORE, &pending, &input, &output) catch error { |
|
| 260 | + | let guard = sync::enter(); |
|
| 261 | + | loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output); |
|
| 262 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 263 | + | sync::leave(guard); throw error; |
|
| 264 | + | }; |
|
| 265 | + | let publicationGuard = sync::enter(); |
|
| 266 | + | if not live(owner) { |
|
| 267 | + | loader::cancelOutput(&mut pages::STORE.backings.pool, &mut registry::STORE, output); |
|
| 268 | + | loader::cancel(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending); |
|
| 269 | + | sync::leave(publicationGuard); throw abi::Error::BadHandle; |
|
| 270 | + | } |
|
| 271 | + | let object = loader::publishOutput(&mut loader::STATE, &mut pages::STORE.backings.pool, &mut registry::STORE, output, compiled); |
|
| 272 | + | let result = try loader::finish(&mut loader::STATE, &mut pages::STORE, &mut domain.memory.table, pending, object) |
|
| 273 | + | catch error { sync::leave(publicationGuard); throw error; }; |
|
| 274 | + | sync::leave(publicationGuard); |
|
| 275 | + | return *result; |
|
| 276 | + | } |
|
| 277 | + | ||
| 278 | + | /// Execute a call with short metadata transactions and private bulk initialization. |
|
| 279 | + | /// The running context keeps its domain storage live until dispatch releases it. |
|
| 280 | + | export unsafe fn synchronized(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 281 | + | assert arguments.len == 4; |
|
| 282 | + | let guard = sync::enter(); |
|
| 283 | + | let mut domain = try domains::get(&domains::STORE, owner) catch error { sync::leave(guard); throw error; }; |
|
| 284 | + | if domain.state <> domains::Lifecycle::Active { sync::leave(guard); throw abi::Error::BadHandle; } |
|
| 285 | + | let handle = abi::Handle(arguments[0]); |
|
| 286 | + | match operation { |
|
| 287 | + | case 20 => { |
|
| 288 | + | let pending = try domains::reserve(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, |
|
| 289 | + | &mut domain.memory.table, handle, abi::Handle(arguments[1])) catch error { sync::leave(guard); throw error; }; |
|
| 290 | + | sync::leave(guard); |
|
| 291 | + | try domains::prepare(®istry::STORE, &pending) catch error { |
|
| 292 | + | let guard = sync::enter(); |
|
| 293 | + | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending); |
|
| 294 | + | sync::leave(guard); throw error; |
|
| 295 | + | }; |
|
| 296 | + | let guard = sync::enter(); |
|
| 297 | + | if not live(owner) { |
|
| 298 | + | domains::cancel(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending); |
|
| 299 | + | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 300 | + | } |
|
| 301 | + | let result = try domains::publish(&mut domains::STORE, &mut pages::STORE.backings, &mut domain.memory.table, pending) |
|
| 302 | + | catch error { sync::leave(guard); throw error; }; |
|
| 303 | + | sync::leave(guard); return *result; |
|
| 304 | + | }, |
|
| 305 | + | case 30 => { |
|
| 306 | + | let pending = try pages::reserve(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]) |
|
| 307 | + | catch error { sync::leave(guard); throw error; }; |
|
| 308 | + | sync::leave(guard); |
|
| 309 | + | pages::clear(&pending); |
|
| 310 | + | let guard = sync::enter(); |
|
| 311 | + | if not live(owner) { |
|
| 312 | + | pages::cancel(&mut pages::STORE, &mut domain.memory.table, pending); |
|
| 313 | + | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 314 | + | } |
|
| 315 | + | let result = pages::publish(&mut pages::STORE, &mut domain.memory.table, pending); |
|
| 316 | + | sync::leave(guard); return *result; |
|
| 317 | + | }, |
|
| 318 | + | case 51 => return try runtimeLoad(&mut domain, loader::Request { |
|
| 319 | + | authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3], |
|
| 320 | + | }, guard), |
|
| 321 | + | case 60 => { |
|
| 322 | + | if arguments[2] <> @sizeOf(abi::ContextStart) as u64 or arguments[3] <> 0 |
|
| 323 | + | or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ)) { |
|
| 324 | + | sync::leave(guard); throw abi::Error::InvalidArg; |
|
| 325 | + | } |
|
| 326 | + | let words = @sliceOf(memory(arguments[1]), 4); |
|
| 327 | + | let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] }; |
|
| 328 | + | let pending = try domains::contextReserve(&mut domains::STORE, &mut pages::STORE, ®istry::STORE, |
|
| 329 | + | &domain.memory.table, handle, start) catch error { sync::leave(guard); throw error; }; |
|
| 330 | + | let mut target: abi::Ref = undefined; |
|
| 331 | + | match &pending { case domains::ContextReservation::Held(allocation) => { set target = allocation.owner; }, } |
|
| 332 | + | sync::leave(guard); |
|
| 333 | + | domains::contextClear(&pending); |
|
| 334 | + | let guard = sync::enter(); |
|
| 335 | + | if not live(owner) or not live(target) { |
|
| 336 | + | domains::contextCancel(&mut domains::STORE, &mut pages::STORE, pending); |
|
| 337 | + | sync::leave(guard); throw abi::Error::BadHandle; |
|
| 338 | + | } |
|
| 339 | + | let result = domains::contextPublish(&mut domains::STORE, pending); |
|
| 340 | + | sync::leave(guard); return abi::id(result); |
|
| 341 | + | }, |
|
| 342 | + | else => { |
|
| 343 | + | let result = try invoke(owner, operation, arguments, now) catch error { sync::leave(guard); throw error; }; |
|
| 344 | + | sync::leave(guard); return result; |
|
| 345 | + | }, |
|
| 346 | + | } |
|
| 347 | + | } |
|
| 348 | + | ||
| 349 | + | /// Complete a user ecall through the supplied execution boundary. |
|
| 350 | + | export unsafe fn handle(owner: abi::Ref, frame: &mut trap::Frame, now: u64, |
|
| 351 | + | execute: unsafe fn(abi::Ref, u64, &[u64], u64) -> u64 throws (abi::Error)) |
|
| 352 | + | { |
|
| 223 | 353 | assert trap::fromUser(frame) and frame.cause == 8; |
|
| 224 | 354 | assert trap::advanceCall(frame); |
|
| 225 | 355 | let args = [frame.registers[10], frame.registers[11], frame.registers[12], frame.registers[13]]; |
|
| 226 | - | let result = try invoke(owner, frame.registers[17], &args[..], now) catch error { |
|
| 356 | + | let result = try execute(owner, frame.registers[17], &args[..], now) catch error { |
|
| 227 | 357 | set frame.registers[10] = (0 as u64) - error as u64; |
|
| 228 | 358 | return; |
|
| 229 | 359 | }; |
|
| 230 | 360 | set frame.registers[10] = result; |
|
| 231 | 361 | } |
kernel/kernel/dispatch.rad
+26 -7
| 8 | 8 | use super::platform; |
|
| 9 | 9 | use super::trap; |
|
| 10 | 10 | use super::range; |
|
| 11 | 11 | use super::calls; |
|
| 12 | 12 | use super::timers; |
|
| 13 | + | use super::sync; |
|
| 13 | 14 | ||
| 14 | 15 | /// Dispatch ownership and idle state retained by one physical hart. |
|
| 15 | 16 | export record State: Copy { |
|
| 16 | 17 | /// Physical hart whose windows this state selects. |
|
| 17 | 18 | hart: u32, |
| 44 | 45 | /// Runtime slots initialized before each hart enables timer interrupts. |
|
| 45 | 46 | unsafe static HARTS: [Runtime; limits::HARTS] = undefined; |
|
| 46 | 47 | ||
| 47 | 48 | /// Read the executing physical hart identifier. |
|
| 48 | 49 | export fn hart() -> u32; |
|
| 50 | + | /// Read the current machine stack pointer. |
|
| 51 | + | fn stackPointer() -> u64; |
|
| 49 | 52 | /// Address of the M-mode idle loop. |
|
| 50 | 53 | fn idleAddress() -> u64; |
|
| 51 | 54 | /// Enable machine timer delivery while global interrupts remain disabled. |
|
| 52 | 55 | fn enable(); |
|
| 53 | 56 |
| 62 | 65 | state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device, |
|
| 63 | 66 | }; |
|
| 64 | 67 | arm(device, 0xffffffffffffffff); |
|
| 65 | 68 | } |
|
| 66 | 69 | ||
| 67 | - | /// Reevaluate authority after a timer interrupt with shared metadata serialized. |
|
| 68 | - | export unsafe fn interrupt(frame: &mut trap::Frame, anchor: &mut trap::Hart) { |
|
| 70 | + | /// Tail-enter dispatch on the firmware stack and resume the selected frame. |
|
| 71 | + | export fn interrupt(frame: &mut trap::Frame, anchor: &mut trap::Hart) -> !; |
|
| 72 | + | ||
| 73 | + | /// Select authority from hart-owned storage with shared metadata serialized. |
|
| 74 | + | export unsafe fn choose(frame: &mut trap::Frame, anchor: &mut trap::Hart) { |
|
| 69 | 75 | let id = hart(); |
|
| 70 | 76 | assert id < limits::HARTS; |
|
| 77 | + | let stack = HARTS[id].state.stack; |
|
| 78 | + | let pointer = stackPointer(); |
|
| 79 | + | assert pointer >= stack.start and pointer < stack.end; |
|
| 80 | + | let guard = sync::enter(); |
|
| 71 | 81 | let clock = now(HARTS[id].timer); |
|
| 72 | 82 | let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, id, clock); |
|
| 73 | 83 | let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock); |
|
| 74 | 84 | let mut deadline = choice.deadline; |
|
| 75 | 85 | if timeout < deadline { set deadline = timeout; } |
|
| 76 | 86 | arm(HARTS[id].timer, deadline); |
|
| 87 | + | sync::leave(guard); |
|
| 77 | 88 | } |
|
| 78 | 89 | ||
| 79 | 90 | /// Read the running context's identity and domain-relative CPU authority. |
|
| 80 | 91 | export unsafe fn current() -> abi::CurrentContextInfo { |
|
| 81 | 92 | let id = hart(); |
| 85 | 96 | let window = try! budgets::get(&budgets::STORE, budget); |
|
| 86 | 97 | return abi::CurrentContextInfo { context: abi::id(context), hart: id as u64, budget: *window.handle }; |
|
| 87 | 98 | } |
|
| 88 | 99 | ||
| 89 | 100 | /// Complete the current user's call and select authority at the return boundary. |
|
| 90 | - | export unsafe fn call(frame: &mut trap::Frame, anchor: &mut trap::Hart) { |
|
| 101 | + | export unsafe fn call(frame: &mut trap::Frame, anchor: &mut trap::Hart) -> ! { |
|
| 91 | 102 | let id = hart(); |
|
| 92 | 103 | assert id < limits::HARTS; |
|
| 93 | - | let context = HARTS[id].state.current else { trap::halt(); return; }; |
|
| 104 | + | let context = HARTS[id].state.current else trap::halt(); |
|
| 105 | + | let guard = sync::enter(); |
|
| 94 | 106 | assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live); |
|
| 95 | 107 | let owner = domains::STORE.contexts[context.index].owner; |
|
| 96 | 108 | if frame.registers[17] == abi::Operation::ContextReturn as u64 { |
|
| 97 | 109 | assert trap::advanceCall(frame); |
|
| 98 | 110 | try! domains::returned(&mut domains::STORE, owner, context); |
|
| 111 | + | sync::leave(guard); |
|
| 99 | 112 | } else { |
|
| 100 | - | calls::handle(owner, frame, now(HARTS[id].timer)); |
|
| 113 | + | sync::leave(guard); |
|
| 114 | + | calls::handle(owner, frame, now(HARTS[id].timer), calls::synchronized); |
|
| 101 | 115 | } |
|
| 102 | 116 | interrupt(frame, anchor); |
|
| 103 | 117 | } |
|
| 104 | 118 | ||
| 105 | 119 | /// Enter the first authorized context or the hart's retained idle frame. |
|
| 106 | 120 | export unsafe fn start(anchor: &mut trap::Hart) -> ! { |
|
| 107 | 121 | let id = hart(); |
|
| 108 | 122 | assert id < limits::HARTS; |
|
| 109 | 123 | let mut frame = HARTS[id].state.idle; |
|
| 110 | - | interrupt(&mut frame, anchor); |
|
| 111 | 124 | enable(); |
|
| 112 | - | trap::resume(&frame); |
|
| 125 | + | interrupt(&mut frame, anchor); |
|
| 113 | 126 | } |
|
| 114 | 127 | ||
| 115 | 128 | /// Read one validated, naturally aligned 64-bit device register. |
|
| 116 | 129 | fn read(address: u64) -> u64; |
|
| 117 | 130 | /// Write one validated, naturally aligned 64-bit device register. |
| 173 | 186 | if not runnable(contexts, window.owner, context) { continue; } |
|
| 174 | 187 | if window.start > now { |
|
| 175 | 188 | if window.start < result.deadline { set result.deadline = window.start; } |
|
| 176 | 189 | continue; |
|
| 177 | 190 | } |
|
| 191 | + | if let owner = contexts.contexts[context.index].hart { |
|
| 192 | + | if owner <> hart { |
|
| 193 | + | if window.end < result.deadline { set result.deadline = window.end; } |
|
| 194 | + | continue; |
|
| 195 | + | } |
|
| 196 | + | } |
|
| 178 | 197 | assert result.budget == nil; |
|
| 179 | 198 | set result.budget = abi::Ref { index: i, generation: windows.slots[i].generation }; |
|
| 180 | 199 | set result.context = context; |
|
| 181 | 200 | if window.end < result.deadline { set result.deadline = window.end; } |
|
| 182 | 201 | } |
kernel/kernel/dispatch.ras
+26 -0
| 1 | 1 | //! Naturally aligned RV64 CLINT access with device ordering. |
|
| 2 | 2 | .text; |
|
| 3 | 3 | .export @kernel::dispatch::read; |
|
| 4 | 4 | .export @kernel::dispatch::write; |
|
| 5 | 5 | .export @kernel::dispatch::hart; |
|
| 6 | + | .export @kernel::dispatch::stackPointer; |
|
| 6 | 7 | .export @kernel::dispatch::idleAddress; |
|
| 7 | 8 | .export @kernel::dispatch::enable; |
|
| 9 | + | .export @kernel::dispatch::interrupt; |
|
| 10 | + | ||
| 11 | + | // Retain the frame outside the context stack before publishing its release. |
|
| 12 | + | @kernel::dispatch::interrupt |
|
| 13 | + | mv %t0 %a0; |
|
| 14 | + | addi %t1 %a1 64; |
|
| 15 | + | li %t2 36; |
|
| 16 | + | @retainDispatchFrame |
|
| 17 | + | ld %t3 0(%t0); |
|
| 18 | + | sd %t3 0(%t1); |
|
| 19 | + | addi %t0 %t0 8; |
|
| 20 | + | addi %t1 %t1 8; |
|
| 21 | + | addi %t2 %t2 -1; |
|
| 22 | + | bnez %t2 @retainDispatchFrame; |
|
| 23 | + | ld %sp 56(%a1); |
|
| 24 | + | addi %a0 %a1 64; |
|
| 25 | + | call @kernel::dispatch::choose; |
|
| 26 | + | csrr %a0 mscratch; |
|
| 27 | + | addi %a0 %a0 64; |
|
| 28 | + | j @kernel::trap::resume; |
|
| 8 | 29 | ||
| 9 | 30 | // The executing hart selects its private dispatch runtime slot. |
|
| 10 | 31 | @kernel::dispatch::hart |
|
| 11 | 32 | csrr %a0 mhartid; |
|
| 12 | 33 | ret; |
|
| 13 | 34 | ||
| 35 | + | // Dispatch releases context stacks only from the hart's firmware stack. |
|
| 36 | + | @kernel::dispatch::stackPointer |
|
| 37 | + | mv %a0 %sp; |
|
| 38 | + | ret; |
|
| 39 | + | ||
| 14 | 40 | // Saved idle frames enter this loop with machine interrupts enabled by mret. |
|
| 15 | 41 | @kernel::dispatch::idleAddress |
|
| 16 | 42 | la %a0 @dispatchIdle; |
|
| 17 | 43 | ret; |
|
| 18 | 44 | @dispatchIdle |
kernel/kernel/domains.rad
+216 -58
| 210 | 210 | } |
|
| 211 | 211 | } |
|
| 212 | 212 | throw abi::Error::InvalidArg; |
|
| 213 | 213 | } |
|
| 214 | 214 | ||
| 215 | - | /// Reject physical overlap with any live context's reserved user stack. |
|
| 215 | + | /// Reject physical overlap with published or reserved user stacks. |
|
| 216 | 216 | fn stackAvailable(store: &Store, candidate: range::Range) throws (abi::Error) { |
|
| 217 | 217 | for i in 0..limits::CONTEXTS { |
|
| 218 | - | if store.contextSlots[i].state <> slots::State::Live { continue; } |
|
| 218 | + | if store.contextSlots[i].state <> slots::State::Live and store.contextSlots[i].state <> slots::State::Reserved { continue; } |
|
| 219 | 219 | let used = store.contexts[i].userStack; |
|
| 220 | 220 | if used.start < used.end and candidate.start < used.end and used.start < candidate.end { |
|
| 221 | 221 | throw abi::Error::Busy; |
|
| 222 | 222 | } |
|
| 223 | 223 | } |
| 261 | 261 | } |
|
| 262 | 262 | } |
|
| 263 | 263 | return false; |
|
| 264 | 264 | } |
|
| 265 | 265 | ||
| 266 | - | /// Create an additional context with private kernel storage and shared domain state. |
|
| 267 | - | /// The caller serializes domain, capability, and physical allocation metadata. |
|
| 268 | - | export unsafe fn contextCreate(store: &mut Store, memory: &mut pages::Store, packages: ®istry::Store, |
|
| 269 | - | table: &capability::Table, authority: abi::Handle, start: abi::ContextStart) -> abi::Ref throws (abi::Error) |
|
| 266 | + | /// Private kernel storage retained by an unpublished context. |
|
| 267 | + | export record ContextAllocation: Copy { |
|
| 268 | + | /// Reserved context slot with an initialized user-stack claim. |
|
| 269 | + | object: abi::Ref, |
|
| 270 | + | /// Domain kept live until publication or cancellation. |
|
| 271 | + | owner: abi::Ref, |
|
| 272 | + | /// Reserved kernel-stack frames. |
|
| 273 | + | frames: frames::Run, |
|
| 274 | + | /// Validated physical base for private stack clearing. |
|
| 275 | + | base: u64, |
|
| 276 | + | } |
|
| 277 | + | ||
| 278 | + | /// Context storage that must be published or cancelled under serialization. |
|
| 279 | + | export union ContextReservation: Once { |
|
| 280 | + | /// One reserved context and its private kernel storage. |
|
| 281 | + | Held(ContextAllocation), |
|
| 282 | + | } |
|
| 283 | + | ||
| 284 | + | /// Reserve an additional context and both stack claims under metadata serialization. |
|
| 285 | + | export unsafe fn contextReserve(store: &mut Store, memory: &mut pages::Store, packages: ®istry::Store, |
|
| 286 | + | table: &capability::Table, authority: abi::Handle, start: abi::ContextStart) -> ContextReservation throws (abi::Error) |
|
| 270 | 287 | { |
|
| 271 | 288 | let permission = try capability::authority(table, authority, abi::Rights(abi::EXECUTE)); |
|
| 272 | 289 | let domain = try get(store, permission.object); |
|
| 273 | 290 | if domain.state == Lifecycle::Dead { throw abi::Error::BadHandle; } |
|
| 274 | 291 | if start.entry == 0 or not exportedEntry(&domain, packages, start.entry) |
| 281 | 298 | let pending = try frames::reserve(&mut memory.backings.pool, KERNEL_STACK_PAGES) catch error { |
|
| 282 | 299 | try! slots::cancel(&mut store.contextSlots[..], slot); throw error; |
|
| 283 | 300 | }; |
|
| 284 | 301 | let kernelFrames = frames::commit(pending); |
|
| 285 | 302 | let kernelStack = try! frames::extent(&memory.backings.pool, kernelFrames); |
|
| 286 | - | zero(kernelStack.start, kernelFrames.count); |
|
| 287 | 303 | let mut frame = trap::Frame { registers: [0; 32], pc: start.entry, status: 0x80, cause: 0, value: 0 }; |
|
| 288 | 304 | set frame.registers[1] = returnAddress(); |
|
| 289 | 305 | set frame.registers[2] = start.stack; |
|
| 290 | 306 | set frame.registers[3] = domain.graph.table.ptr as u64; |
|
| 291 | 307 | set frame.registers[10] = start.args; |
|
| 292 | 308 | set frame.registers[11] = start.size; |
|
| 293 | 309 | let object = slots::reference(&slot); |
|
| 294 | 310 | set store.contexts[object.index] = Context { |
|
| 295 | 311 | owner: permission.object, state: ContextState::Ready, hart: nil, kernelFrames, kernelStack, userStack, frame, |
|
| 296 | 312 | }; |
|
| 297 | - | return try! slots::commit(&mut store.contextSlots[..], slot); |
|
| 313 | + | match slot { |
|
| 314 | + | case slots::Reservation::Held(object) => return ContextReservation::Held(ContextAllocation { |
|
| 315 | + | object, owner: permission.object, frames: kernelFrames, base: kernelStack.start, |
|
| 316 | + | }), |
|
| 317 | + | } |
|
| 318 | + | } |
|
| 319 | + | ||
| 320 | + | /// Clear an unpublished context's private kernel stack. |
|
| 321 | + | export fn contextClear(reservation: &ContextReservation) { |
|
| 322 | + | match reservation { case ContextReservation::Held(allocation) => zero(allocation.base, allocation.frames.count), } |
|
| 323 | + | } |
|
| 324 | + | ||
| 325 | + | /// Check the reserved context and its retained physical allocation. |
|
| 326 | + | fn contextRequired(store: &Store, allocation: &ContextAllocation) { |
|
| 327 | + | assert slots::matches(&store.contextSlots[..], allocation.object, slots::State::Reserved); |
|
| 328 | + | let context = store.contexts[allocation.object.index]; |
|
| 329 | + | assert context.owner == allocation.owner and context.kernelFrames == allocation.frames; |
|
| 330 | + | assert context.hart == nil; |
|
| 331 | + | } |
|
| 332 | + | ||
| 333 | + | /// Publish an initialized context while its domain stays live under serialization. |
|
| 334 | + | export fn contextPublish(store: &mut Store, reservation: ContextReservation) -> abi::Ref { |
|
| 335 | + | match reservation { |
|
| 336 | + | case ContextReservation::Held(allocation) => { |
|
| 337 | + | contextRequired(store, &allocation); |
|
| 338 | + | assert slots::matches(&store.slots[..], allocation.owner, slots::State::Live); |
|
| 339 | + | return try! slots::commit(&mut store.contextSlots[..], slots::Reservation::Held(allocation.object)); |
|
| 340 | + | }, |
|
| 341 | + | } |
|
| 342 | + | } |
|
| 343 | + | ||
| 344 | + | /// Return an unpublished context's stack and slot under metadata serialization. |
|
| 345 | + | export fn contextCancel(store: &mut Store, memory: &mut pages::Store, reservation: ContextReservation) { |
|
| 346 | + | match reservation { |
|
| 347 | + | case ContextReservation::Held(allocation) => { |
|
| 348 | + | contextRequired(store, &allocation); |
|
| 349 | + | try! frames::release(&mut memory.backings.pool, allocation.frames); |
|
| 350 | + | try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(allocation.object)); |
|
| 351 | + | }, |
|
| 352 | + | } |
|
| 353 | + | } |
|
| 354 | + | ||
| 355 | + | /// Create a context while the caller has exclusive access to shared metadata. |
|
| 356 | + | export unsafe fn contextCreate(store: &mut Store, memory: &mut pages::Store, packages: ®istry::Store, |
|
| 357 | + | table: &capability::Table, authority: abi::Handle, start: abi::ContextStart) -> abi::Ref throws (abi::Error) |
|
| 358 | + | { |
|
| 359 | + | let pending = try contextReserve(store, memory, packages, table, authority, start); |
|
| 360 | + | contextClear(&pending); |
|
| 361 | + | return contextPublish(store, pending); |
|
| 298 | 362 | } |
|
| 299 | 363 | ||
| 300 | 364 | /// Memory allocated before the domain and its context become observable. |
|
| 301 | - | record Prepared: Copy { |
|
| 365 | + | export record Prepared: Copy { |
|
| 302 | 366 | /// Frames for capabilities and the event ring. |
|
| 303 | 367 | allocation: frames::Run, |
|
| 304 | 368 | /// Mapped metadata at the allocation's start. |
|
| 305 | 369 | memory: *unsafe mut Memory, |
|
| 306 | - | /// Independent package-state graph. |
|
| 307 | - | graph: instances::Instance, |
|
| 370 | + | /// Reserved private package-state graph. |
|
| 371 | + | graph: instances::Allocation, |
|
| 308 | 372 | /// Retained stack allocation for the initial context. |
|
| 309 | 373 | kernelFrames: frames::Run, |
|
| 310 | 374 | /// Initial context's mapped stack bounds. |
|
| 311 | 375 | kernelStack: range::Range, |
|
| 312 | 376 | } |
|
| 313 | 377 | ||
| 314 | 378 | /// Allocate private storage and return all earlier allocations on failure. |
|
| 315 | - | unsafe fn prepare(pool: &mut frames::Pool, packages: ®istry::Store, image: abi::Ref) -> Prepared throws (abi::Error) { |
|
| 379 | + | unsafe fn allocate(pool: &mut frames::Pool, packages: ®istry::Store, image: abi::Ref) -> Prepared throws (abi::Error) { |
|
| 316 | 380 | let pending = try frames::reserve(pool, (@sizeOf(Memory) + 4095) / 4096); |
|
| 317 | 381 | let allocation = frames::commit(pending); |
|
| 318 | - | let graph = try instances::create(packages, pool, image) catch err { |
|
| 382 | + | let graph = try instances::reserve(packages, pool, image) catch err { |
|
| 319 | 383 | try! frames::release(pool, allocation); throw err; |
|
| 320 | 384 | }; |
|
| 321 | 385 | let pendingStack = try frames::reserve(pool, KERNEL_STACK_PAGES) catch err { |
|
| 322 | - | try! frames::release(pool, graph.frames); |
|
| 386 | + | instances::cancel(pool, graph); |
|
| 323 | 387 | try! frames::release(pool, allocation); throw err; |
|
| 324 | 388 | }; |
|
| 325 | 389 | let kernelFrames = frames::commit(pendingStack); |
|
| 326 | 390 | let kernelStack = try! frames::extent(pool, kernelFrames); |
|
| 327 | 391 | let extent = try! frames::extent(pool, allocation); |
|
| 328 | - | zero(extent.start, allocation.count); |
|
| 329 | - | zero(kernelStack.start, kernelFrames.count); |
|
| 330 | - | return Prepared { allocation, memory: memory(extent.start), graph, kernelFrames, kernelStack }; |
|
| 392 | + | match graph { |
|
| 393 | + | case instances::Reservation::Held(graph) => return Prepared { |
|
| 394 | + | allocation, memory: memory(extent.start), graph, kernelFrames, kernelStack, |
|
| 395 | + | }, |
|
| 396 | + | } |
|
| 331 | 397 | } |
|
| 332 | 398 | ||
| 333 | 399 | /// Return storage that has never been exposed to a published domain. |
|
| 334 | 400 | fn discard(pool: &mut frames::Pool, prepared: Prepared) { |
|
| 335 | 401 | try! frames::release(pool, prepared.kernelFrames); |
|
| 336 | 402 | try! frames::release(pool, prepared.graph.frames); |
|
| 337 | 403 | try! frames::release(pool, prepared.allocation); |
|
| 338 | 404 | } |
|
| 339 | 405 | ||
| 340 | - | /// Create an unscheduled domain with Events and one initial integer context. |
|
| 341 | - | /// The caller serializes publication and keeps the authorizing domain alive. |
|
| 342 | - | export unsafe fn create(store: &mut Store, backings: &mut backing::Store, packages: ®istry::Store, |
|
| 343 | - | table: &mut capability::Table, authority: abi::Handle, image: abi::Handle) -> abi::Handle throws (abi::Error) |
|
| 406 | + | /// Unpublished domain metadata retained during private initialization. |
|
| 407 | + | export record Creation: Copy { |
|
| 408 | + | /// Calling domain kept live until publication or cancellation. |
|
| 409 | + | owner: abi::Ref, |
|
| 410 | + | /// Reserved capability slot in the calling domain. |
|
| 411 | + | handle: abi::Ref, |
|
| 412 | + | /// Reserved domain slot. |
|
| 413 | + | domain: abi::Ref, |
|
| 414 | + | /// Reserved initial-context slot. |
|
| 415 | + | context: abi::Ref, |
|
| 416 | + | /// Management and inherited authority selected at admission. |
|
| 417 | + | rights: abi::Rights, |
|
| 418 | + | /// Resident root package. |
|
| 419 | + | image: abi::Ref, |
|
| 420 | + | /// Validated initial entry address. |
|
| 421 | + | entry: u64, |
|
| 422 | + | /// Private allocations retained until the domain becomes observable. |
|
| 423 | + | storage: Prepared, |
|
| 424 | + | } |
|
| 425 | + | ||
| 426 | + | /// Domain creation resources that must be published or cancelled. |
|
| 427 | + | export union Reservation: Once { |
|
| 428 | + | /// One private domain and its reserved metadata capacity. |
|
| 429 | + | Held(Creation), |
|
| 430 | + | } |
|
| 431 | + | ||
| 432 | + | /// Reserve domain capacity under metadata serialization while the caller stays live. |
|
| 433 | + | export unsafe fn reserve(store: &mut Store, backings: &mut backing::Store, packages: ®istry::Store, |
|
| 434 | + | table: &mut capability::Table, authority: abi::Handle, image: abi::Handle) -> Reservation throws (abi::Error) |
|
| 344 | 435 | { |
|
| 345 | 436 | let permit = try capability::authority(table, authority, abi::Rights(abi::CREATE)); |
|
| 346 | 437 | if not backing::domainLive(backings, table.owner) or not backing::domainLive(backings, permit.object) { |
|
| 347 | 438 | throw abi::Error::BadHandle; |
|
| 348 | 439 | } |
| 355 | 446 | }; |
|
| 356 | 447 | let contextSlot = try slots::reserve(&mut store.contextSlots[..]) catch err { |
|
| 357 | 448 | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 358 | 449 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 359 | 450 | }; |
|
| 360 | - | let mut prepared = try prepare(&mut backings.pool, packages, root) catch err { |
|
| 361 | - | try! slots::cancel(&mut store.contextSlots[..], contextSlot); |
|
| 362 | - | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 363 | - | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 364 | - | }; |
|
| 365 | - | let object = slots::reference(&domainSlot); |
|
| 366 | - | try backing::registerDomain(backings, object) catch err { |
|
| 367 | - | discard(&mut backings.pool, prepared); |
|
| 368 | - | try! slots::cancel(&mut store.contextSlots[..], contextSlot); |
|
| 369 | - | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 370 | - | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 371 | - | }; |
|
| 372 | - | capability::initialize(&mut prepared.memory.table, object); |
|
| 373 | - | try events::open(&mut store.events, object, &mut prepared.memory.ring) catch err { |
|
| 374 | - | try! backing::endDomain(backings, object); |
|
| 375 | - | discard(&mut backings.pool, prepared); |
|
| 451 | + | let initialSlot = slots::reference(&contextSlot); |
|
| 452 | + | set store.contexts[initialSlot.index].userStack = range::Range { start: 0, end: 0 }; |
|
| 453 | + | let prepared = try allocate(&mut backings.pool, packages, root) catch err { |
|
| 376 | 454 | try! slots::cancel(&mut store.contextSlots[..], contextSlot); |
|
| 377 | 455 | try! slots::cancel(&mut store.slots[..], domainSlot); |
|
| 378 | 456 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 379 | 457 | }; |
|
| 380 | - | let queue = try! capability::install(&mut prepared.memory.table, capability::Entry { |
|
| 381 | - | kind: abi::Kind::Events, object, rights: abi::Rights(abi::READ | abi::WRITE), |
|
| 382 | - | }); |
|
| 383 | - | let initial = slots::reference(&contextSlot); |
|
| 384 | - | // MPIE enables interrupts after the first return into U-mode. |
|
| 385 | - | let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 }; |
|
| 386 | - | set frame.registers[1] = returnAddress(); |
|
| 387 | - | set frame.registers[3] = prepared.graph.table.ptr as u64; |
|
| 388 | - | set store.contexts[initial.index] = Context { |
|
| 389 | - | owner: object, state: ContextState::Ready, hart: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 390 | - | userStack: range::Range { start: 0, end: 0 }, frame, |
|
| 391 | - | }; |
|
| 392 | - | set store.records[object.index] = Domain { |
|
| 393 | - | state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial, |
|
| 394 | - | allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue, |
|
| 458 | + | match handleSlot { |
|
| 459 | + | case slots::Reservation::Held(handle) => match domainSlot { |
|
| 460 | + | case slots::Reservation::Held(domain) => match contextSlot { |
|
| 461 | + | case slots::Reservation::Held(context) => return Reservation::Held(Creation { |
|
| 462 | + | owner: table.owner, handle, domain, context, |
|
| 463 | + | rights: abi::Rights(MANAGEMENT_RIGHTS | (*permit.rights & (abi::CREATE | abi::ALLOCATE))), |
|
| 464 | + | image: root, entry, storage: prepared, |
|
| 465 | + | }), |
|
| 466 | + | }, |
|
| 467 | + | }, |
|
| 468 | + | } |
|
| 469 | + | } |
|
| 470 | + | ||
| 471 | + | /// Initialize private storage from resident packages while metadata can change. |
|
| 472 | + | export unsafe fn prepare(packages: ®istry::Store, reservation: &Reservation) throws (abi::Error) { |
|
| 473 | + | match reservation { |
|
| 474 | + | case Reservation::Held(creation) => { |
|
| 475 | + | let mut storage = creation.storage; |
|
| 476 | + | zero(storage.memory as u64, storage.allocation.count); |
|
| 477 | + | zero(storage.kernelStack.start, storage.kernelFrames.count); |
|
| 478 | + | try instances::fill(packages, &storage.graph.graph, storage.graph.base); |
|
| 479 | + | capability::initialize(&mut storage.memory.table, creation.domain); |
|
| 480 | + | }, |
|
| 481 | + | } |
|
| 482 | + | } |
|
| 483 | + | ||
| 484 | + | /// Check the unpublished capacity retained by one creation transaction. |
|
| 485 | + | fn require(store: &Store, table: &capability::Table, creation: &Creation) { |
|
| 486 | + | assert table.owner == creation.owner; |
|
| 487 | + | assert slots::matches(&table.slots[..], creation.handle, slots::State::Reserved); |
|
| 488 | + | assert slots::matches(&store.slots[..], creation.domain, slots::State::Reserved); |
|
| 489 | + | assert slots::matches(&store.contextSlots[..], creation.context, slots::State::Reserved); |
|
| 490 | + | } |
|
| 491 | + | ||
| 492 | + | /// Return private domain storage and reserved slots under metadata serialization. |
|
| 493 | + | export fn cancel(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 494 | + | match reservation { |
|
| 495 | + | case Reservation::Held(creation) => { |
|
| 496 | + | require(store, table, &creation); |
|
| 497 | + | discard(&mut backings.pool, creation.storage); |
|
| 498 | + | try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(creation.context)); |
|
| 499 | + | try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(creation.domain)); |
|
| 500 | + | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(creation.handle)); |
|
| 501 | + | }, |
|
| 502 | + | } |
|
| 503 | + | } |
|
| 504 | + | ||
| 505 | + | /// Publish an initialized domain and its Events under metadata serialization. |
|
| 506 | + | export unsafe fn publish(store: &mut Store, backings: &mut backing::Store, table: &mut capability::Table, reservation: Reservation) |
|
| 507 | + | -> abi::Handle throws (abi::Error) |
|
| 508 | + | { |
|
| 509 | + | match reservation { |
|
| 510 | + | case Reservation::Held(creation) => { |
|
| 511 | + | require(store, table, &creation); |
|
| 512 | + | if not backing::domainLive(backings, creation.owner) { |
|
| 513 | + | cancel(store, backings, table, Reservation::Held(creation)); |
|
| 514 | + | throw abi::Error::BadHandle; |
|
| 515 | + | } |
|
| 516 | + | let object = creation.domain; |
|
| 517 | + | let initial = creation.context; |
|
| 518 | + | let mut prepared = creation.storage; |
|
| 519 | + | try backing::registerDomain(backings, object) catch err { |
|
| 520 | + | cancel(store, backings, table, Reservation::Held(creation)); throw err; |
|
| 521 | + | }; |
|
| 522 | + | try events::open(&mut store.events, object, &mut prepared.memory.ring) catch err { |
|
| 523 | + | try! backing::endDomain(backings, object); |
|
| 524 | + | cancel(store, backings, table, Reservation::Held(creation)); throw err; |
|
| 525 | + | }; |
|
| 526 | + | let queue = try! capability::install(&mut prepared.memory.table, capability::Entry { |
|
| 527 | + | kind: abi::Kind::Events, object, rights: abi::Rights(abi::READ | abi::WRITE), |
|
| 528 | + | }); |
|
| 529 | + | let graph = instances::commit(instances::Reservation::Held(prepared.graph)); |
|
| 530 | + | // MPIE enables interrupts after the first return into U-mode. |
|
| 531 | + | let mut frame = trap::Frame { registers: [0; 32], pc: creation.entry, status: 0x80, cause: 0, value: 0 }; |
|
| 532 | + | set frame.registers[1] = returnAddress(); |
|
| 533 | + | set frame.registers[3] = graph.table.ptr as u64; |
|
| 534 | + | set store.contexts[initial.index] = Context { |
|
| 535 | + | owner: object, state: ContextState::Ready, hart: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 536 | + | userStack: range::Range { start: 0, end: 0 }, frame, |
|
| 537 | + | }; |
|
| 538 | + | set store.records[object.index] = Domain { |
|
| 539 | + | state: Lifecycle::Pending, creator: creation.owner, parent: creation.owner, image: creation.image, initial, |
|
| 540 | + | allocation: prepared.allocation, memory: prepared.memory, graph, events: queue, |
|
| 541 | + | }; |
|
| 542 | + | let context = try! slots::commit(&mut store.contextSlots[..], slots::Reservation::Held(initial)); |
|
| 543 | + | let domain = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(object)); |
|
| 544 | + | return capability::publish(table, slots::Reservation::Held(creation.handle), capability::Entry { |
|
| 545 | + | kind: abi::Kind::Domain, object: domain, rights: creation.rights, |
|
| 546 | + | }); |
|
| 547 | + | }, |
|
| 548 | + | } |
|
| 549 | + | } |
|
| 550 | + | ||
| 551 | + | /// Create a private domain while the caller has exclusive access to metadata. |
|
| 552 | + | export unsafe fn create(store: &mut Store, backings: &mut backing::Store, packages: ®istry::Store, |
|
| 553 | + | table: &mut capability::Table, authority: abi::Handle, image: abi::Handle) -> abi::Handle throws (abi::Error) |
|
| 554 | + | { |
|
| 555 | + | let pending = try reserve(store, backings, packages, table, authority, image); |
|
| 556 | + | try prepare(packages, &pending) catch err { |
|
| 557 | + | cancel(store, backings, table, pending); throw err; |
|
| 395 | 558 | }; |
|
| 396 | - | let context = try! slots::commit(&mut store.contextSlots[..], contextSlot); |
|
| 397 | - | let domain = try! slots::commit(&mut store.slots[..], domainSlot); |
|
| 398 | - | return capability::publish(table, handleSlot, capability::Entry { |
|
| 399 | - | kind: abi::Kind::Domain, object: domain, |
|
| 400 | - | rights: abi::Rights(MANAGEMENT_RIGHTS | (*permit.rights & (abi::CREATE | abi::ALLOCATE))), |
|
| 401 | - | }); |
|
| 559 | + | return try publish(store, backings, table, pending); |
|
| 402 | 560 | } |
kernel/kernel/instances.rad
+52 -9
| 13 | 13 | /// Package bases indexed by resident slot; absent packages have zero bases. |
|
| 14 | 14 | table: *unsafe [u64], |
|
| 15 | 15 | } |
|
| 16 | 16 | ||
| 17 | 17 | /// Bounded traversal and conservative storage requirement for a package graph. |
|
| 18 | - | record Graph: Copy { |
|
| 18 | + | export record Graph: Copy { |
|
| 19 | 19 | /// Reachable package references, each present once. |
|
| 20 | 20 | packages: [abi::Ref; limits::PACKAGES], |
|
| 21 | 21 | /// Number of initialized package references. |
|
| 22 | 22 | count: u32, |
|
| 23 | 23 | /// Bytes for the base table, private state, and worst-case alignment padding. |
|
| 24 | 24 | bytes: u64, |
|
| 25 | 25 | } |
|
| 26 | 26 | ||
| 27 | + | /// Private storage and the resident graph selected at reservation time. |
|
| 28 | + | export record Allocation: Copy { |
|
| 29 | + | /// Reachable immutable packages and their storage bounds. |
|
| 30 | + | graph: Graph, |
|
| 31 | + | /// Physical frames reserved for this instance. |
|
| 32 | + | frames: frames::Run, |
|
| 33 | + | /// Validated physical base for private initialization. |
|
| 34 | + | base: u64, |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Graph storage that must be committed or returned to its frame pool. |
|
| 38 | + | export union Reservation: Once { |
|
| 39 | + | /// An unpublished private instance. |
|
| 40 | + | Held(Allocation), |
|
| 41 | + | } |
|
| 42 | + | ||
| 27 | 43 | /// Map an allocated physical extent to typed storage at the call site. |
|
| 28 | 44 | fn memory(address: u64) -> *unsafe mut opaque; |
|
| 29 | 45 | ||
| 30 | 46 | /// Collect the reachable immutable packages with a bounded queue. |
|
| 31 | 47 | unsafe fn graph(store: ®istry::Store, root: abi::Ref) -> Graph throws (abi::Error) { |
| 55 | 71 | } |
|
| 56 | 72 | return result; |
|
| 57 | 73 | } |
|
| 58 | 74 | ||
| 59 | 75 | /// Assign every base before initializing templates and their private pointers. |
|
| 60 | - | unsafe fn initialize(store: ®istry::Store, graph: &Graph, base: u64) -> *unsafe [u64] throws (abi::Error) { |
|
| 76 | + | /// The caller owns a writable extent of at least graph.bytes bytes at base. |
|
| 77 | + | export unsafe fn fill(store: ®istry::Store, graph: &Graph, base: u64) throws (abi::Error) { |
|
| 61 | 78 | let table = @sliceOf(memory(base) as *unsafe mut u64, limits::PACKAGES); |
|
| 62 | 79 | for i in 0..table.len { set table[i] = 0; } |
|
| 63 | 80 | let mut next = base + limits::PACKAGES as u64 * 8; |
|
| 64 | 81 | for object in &graph.packages[..graph.count] { |
|
| 65 | 82 | let package = try! registry::get(store, object); |
| 71 | 88 | for object in &graph.packages[..graph.count] { |
|
| 72 | 89 | let package = try! registry::get(store, object); |
|
| 73 | 90 | let bytes = @sliceOf(memory(table[object.index]) as *unsafe mut u8, package.memory); |
|
| 74 | 91 | try shared::instantiate(&package, &table[..], bytes) catch { throw abi::Error::VerifyFailed; }; |
|
| 75 | 92 | } |
|
| 76 | - | return &table[..]; |
|
| 77 | 93 | } |
|
| 78 | 94 | ||
| 79 | - | /// Allocate and initialize one private graph without copying resident code. |
|
| 80 | - | /// The caller serializes frame reservations and keeps the registry immutable. |
|
| 81 | - | export unsafe fn create(store: ®istry::Store, pool: &mut frames::Pool, root: abi::Ref) -> Instance throws (abi::Error) { |
|
| 95 | + | /// Reserve one graph under metadata serialization while its packages stay resident. |
|
| 96 | + | export unsafe fn reserve(store: ®istry::Store, pool: &mut frames::Pool, root: abi::Ref) -> Reservation throws (abi::Error) { |
|
| 82 | 97 | let layout = try graph(store, root); |
|
| 83 | 98 | let count = ((layout.bytes + limits::FRAME_SIZE - 1) / limits::FRAME_SIZE) as u32; |
|
| 84 | 99 | let pending = try frames::reserve(pool, count); |
|
| 85 | 100 | let run = frames::commit(pending); |
|
| 86 | 101 | let extent = try! frames::extent(pool, run); |
|
| 87 | - | let table = try initialize(store, &layout, extent.start) catch err { |
|
| 88 | - | try! frames::release(pool, run); throw err; |
|
| 102 | + | return Reservation::Held(Allocation { graph: layout, frames: run, base: extent.start }); |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Initialize private state from the reservation's immutable resident packages. |
|
| 106 | + | export unsafe fn initialize(store: ®istry::Store, reservation: &Reservation) throws (abi::Error) { |
|
| 107 | + | match reservation { |
|
| 108 | + | case Reservation::Held(allocation) => try fill(store, &allocation.graph, allocation.base), |
|
| 109 | + | } |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | /// Consume initialized private storage for publication in a domain. |
|
| 113 | + | export unsafe fn commit(reservation: Reservation) -> Instance { |
|
| 114 | + | match reservation { |
|
| 115 | + | case Reservation::Held(allocation) => return Instance { |
|
| 116 | + | frames: allocation.frames, |
|
| 117 | + | table: @sliceOf(memory(allocation.base) as *unsafe u64, limits::PACKAGES), |
|
| 118 | + | }, |
|
| 119 | + | } |
|
| 120 | + | } |
|
| 121 | + | ||
| 122 | + | /// Return unpublished graph storage under frame-pool serialization. |
|
| 123 | + | export fn cancel(pool: &mut frames::Pool, reservation: Reservation) { |
|
| 124 | + | match reservation { case Reservation::Held(allocation) => { try! frames::release(pool, allocation.frames); }, } |
|
| 125 | + | } |
|
| 126 | + | ||
| 127 | + | /// Allocate and initialize a private graph while the caller has exclusive metadata access. |
|
| 128 | + | export unsafe fn create(store: ®istry::Store, pool: &mut frames::Pool, root: abi::Ref) -> Instance throws (abi::Error) { |
|
| 129 | + | let pending = try reserve(store, pool, root); |
|
| 130 | + | try initialize(store, &pending) catch err { |
|
| 131 | + | cancel(pool, pending); throw err; |
|
| 89 | 132 | }; |
|
| 90 | - | return Instance { frames: run, table }; |
|
| 133 | + | return commit(pending); |
|
| 91 | 134 | } |
kernel/kernel/loader.rad
+189 -45
| 85 | 85 | export union Lease: Once { |
|
| 86 | 86 | /// Private temporary frames owned by the active load. |
|
| 87 | 87 | Held(frames::Run), |
|
| 88 | 88 | } |
|
| 89 | 89 | ||
| 90 | + | /// Private input capacity retained throughout one load. |
|
| 91 | + | export record Input: Copy { |
|
| 92 | + | /// Domain kept live while the source and reserved handle are in use. |
|
| 93 | + | owner: abi::Ref, |
|
| 94 | + | /// Reserved Image handle slot in the caller's table. |
|
| 95 | + | handle: abi::Ref, |
|
| 96 | + | /// Private workspace frames. |
|
| 97 | + | frames: frames::Run, |
|
| 98 | + | /// Physical workspace base retained by the lease. |
|
| 99 | + | work: u64, |
|
| 100 | + | /// Validated physical source address. |
|
| 101 | + | address: u64, |
|
| 102 | + | /// Number of source bytes to snapshot. |
|
| 103 | + | length: u32, |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Input capacity that must be cancelled or completed under metadata serialization. |
|
| 107 | + | export union Reservation: Once { |
|
| 108 | + | /// One admitted load and its private compiler storage. |
|
| 109 | + | Held(Input), |
|
| 110 | + | } |
|
| 111 | + | ||
| 90 | 112 | /// Global runtime-loader ownership. |
|
| 91 | 113 | export unsafe static STATE: State = State { busy: false, owner: abi::Ref { index: 0, generation: 0 }, |
|
| 92 | 114 | live: [false; limits::PACKAGES], resident: undefined }; |
|
| 93 | 115 | ||
| 94 | 116 | /// Map a validated physical workspace extent to its compiler storage. |
| 161 | 183 | }, |
|
| 162 | 184 | else => return abi::Error::VerifyFailed, |
|
| 163 | 185 | } |
|
| 164 | 186 | } |
|
| 165 | 187 | ||
| 166 | - | /// Used page counts for unpublished native output. |
|
| 167 | - | record OutputSize: Copy { |
|
| 188 | + | /// Used native extents and validated metadata ready for publication. |
|
| 189 | + | export record Compiled: Copy { |
|
| 168 | 190 | /// Pages occupied by generated code. |
|
| 169 | 191 | code: u32, |
|
| 170 | 192 | /// Pages occupied by the packed native catalog. |
|
| 171 | 193 | metadata: u32, |
|
| 194 | + | /// Immutable descriptor and resolved dependencies for the reserved package. |
|
| 195 | + | admission: registry::Admission, |
|
| 172 | 196 | } |
|
| 173 | 197 | ||
| 174 | 198 | /// Compile and pack one candidate into caller-owned physical output extents. |
|
| 175 | 199 | unsafe fn generate(work: *mut Workspace, input: &binary::Package, packages: ®istry::Store, |
|
| 176 | - | slot: u32, code: u64, metadata: u64, length: u32) -> OutputSize throws (abi::Error) |
|
| 200 | + | object: abi::Ref, code: u64, metadata: u64, length: u32) -> Compiled throws (abi::Error) |
|
| 177 | 201 | { |
|
| 178 | 202 | let count = try registry::imports(packages, &input.dependencies[..], &mut work.imports[..]); |
|
| 179 | 203 | let mut arena = alloc::new(&mut work.code[..]); |
|
| 180 | 204 | let mut scratch = alloc::new(&mut work.scratch[..]); |
|
| 181 | - | let package = try shared::compile(&*input, slot, code, &work.imports[..count], |
|
| 205 | + | let package = try shared::compile(&*input, object.index, code, &work.imports[..count], |
|
| 182 | 206 | shared::Storage { |
|
| 183 | 207 | data: &mut work.data[..], symbols: &mut work.symbols[..], exports: &mut work.exports[..], |
|
| 184 | 208 | template: &mut work.template[..], relocations: &mut work.relocations[..], |
|
| 185 | 209 | }, &mut arena, &mut scratch) catch error { throw backend(error); }; |
|
| 186 | 210 | if package.code.len > CODE_BYTES / 4 { throw abi::Error::Exhausted; } |
| 190 | 214 | let metadataMemory = @sliceOf(memory(metadata), METADATA_BYTES); |
|
| 191 | 215 | let used = try catalog::pack(&[catalog::Entry { source: &work.input[..length], package }], metadata, metadataMemory) |
|
| 192 | 216 | catch error { throw backend(error); }; |
|
| 193 | 217 | let mut codePages = (codeBytes + 4095) / 4096; |
|
| 194 | 218 | if codePages == 0 { set codePages = 1; } |
|
| 195 | - | return OutputSize { code: codePages, metadata: (used + 4095) / 4096 }; |
|
| 219 | + | let native = descriptor(metadata); |
|
| 220 | + | let admission = try registry::admit(packages, object, native.source, native.package); |
|
| 221 | + | return Compiled { code: codePages, metadata: (used + 4095) / 4096, admission }; |
|
| 196 | 222 | } |
|
| 197 | 223 | ||
| 198 | 224 | /// Return unused output frames while preserving at least one frame for each extent. |
|
| 199 | 225 | fn trim(pool: &mut frames::Pool, run: frames::Run, count: u32) -> frames::Run { |
|
| 200 | 226 | assert count > 0 and count <= run.count; |
|
| 201 | 227 | if count < run.count { try! frames::release(pool, frames::Run { first: run.first + count, count: run.count - count }); } |
|
| 202 | 228 | return frames::Run { first: run.first, count }; |
|
| 203 | 229 | } |
|
| 204 | 230 | ||
| 205 | - | /// Snapshot, decode, compile, and publish a package while its caller remains live. |
|
| 206 | - | unsafe fn produce(state: &mut State, pool: &mut frames::Pool, packages: &mut registry::Store, |
|
| 207 | - | work: *mut Workspace, address: u64, length: u32) -> abi::Ref throws (abi::Error) |
|
| 208 | - | { |
|
| 209 | - | try! mem::copy(&mut work.input[..length], @sliceOf(memory(address), length)); |
|
| 210 | - | let mut decoder = alloc::new(&mut work.decoded[..]); |
|
| 211 | - | let input = try program::decode(&work.input[..length], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }) catch err { |
|
| 212 | - | if err == binary::Error::Storage { throw abi::Error::Exhausted; } |
|
| 213 | - | throw abi::Error::VerifyFailed; |
|
| 214 | - | }; |
|
| 215 | - | let existing = try registry::identify(packages, &input.name[..], &work.input[..length]); |
|
| 216 | - | if let object = existing { return object; } |
|
| 231 | + | /// Snapshot and decode trusted input into the held private workspace. |
|
| 232 | + | /// The decoded package stays valid until this reservation is completed or cancelled. |
|
| 233 | + | 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 | + | } |
|
| 245 | + | } |
|
| 246 | + | ||
| 247 | + | /// Reserved native output and its immutable physical extents. |
|
| 248 | + | export record Output: Copy { |
|
| 249 | + | /// Reserved package slot selected for generated state references. |
|
| 250 | + | object: abi::Ref, |
|
| 251 | + | /// Private executable frames. |
|
| 252 | + | code: frames::Run, |
|
| 253 | + | /// Private source and descriptor frames. |
|
| 254 | + | metadata: frames::Run, |
|
| 255 | + | /// Validated physical executable base. |
|
| 256 | + | codeBase: u64, |
|
| 257 | + | /// Validated physical descriptor base. |
|
| 258 | + | metadataBase: u64, |
|
| 259 | + | } |
|
| 260 | + | ||
| 261 | + | /// Native output that must be published or cancelled under serialization. |
|
| 262 | + | export union OutputReservation: Once { |
|
| 263 | + | /// One unpublished package and its private output frames. |
|
| 264 | + | Held(Output), |
|
| 265 | + | } |
|
| 266 | + | ||
| 267 | + | /// Reserve a package slot and native output under metadata serialization. |
|
| 268 | + | export fn reserveOutput(pool: &mut frames::Pool, packages: &mut registry::Store) -> OutputReservation throws (abi::Error) { |
|
| 217 | 269 | let slot = try registry::reserve(packages); |
|
| 218 | 270 | let object = slots::reference(&slot); |
|
| 219 | 271 | let codeReservation = try frames::reserve(pool, CODE_BYTES / 4096) catch err { registry::cancel(packages, slot); throw err; }; |
|
| 220 | 272 | let code = frames::commit(codeReservation); |
|
| 221 | 273 | let metadataReservation = try frames::reserve(pool, METADATA_BYTES / 4096) catch err { |
|
| 222 | 274 | try! frames::release(pool, code); registry::cancel(packages, slot); throw err; |
|
| 223 | 275 | }; |
|
| 224 | 276 | let metadata = frames::commit(metadataReservation); |
|
| 225 | 277 | let codeRange = try! frames::extent(pool, code); |
|
| 226 | 278 | let metadataRange = try! frames::extent(pool, metadata); |
|
| 227 | - | let sizes = try generate(work, &input, packages, object.index, codeRange.start, metadataRange.start, length) catch err { |
|
| 228 | - | try! frames::release(pool, metadata); try! frames::release(pool, code); |
|
| 229 | - | registry::cancel(packages, slot); throw err; |
|
| 230 | - | }; |
|
| 231 | - | let retainedCode = trim(pool, code, sizes.code); |
|
| 232 | - | let retainedMetadata = trim(pool, metadata, sizes.metadata); |
|
| 233 | - | let native = descriptor(metadataRange.start); |
|
| 234 | - | sync::syncInstructions(); |
|
| 235 | - | let result = try registry::publish(packages, slot, native.source, native.package) catch err { |
|
| 236 | - | try! frames::release(pool, retainedMetadata); try! frames::release(pool, retainedCode); throw err; |
|
| 237 | - | }; |
|
| 238 | - | assert result == object; |
|
| 239 | - | set state.resident[result.index] = Resident { code: retainedCode, metadata: retainedMetadata }; |
|
| 240 | - | set state.live[result.index] = true; |
|
| 241 | - | return result; |
|
| 279 | + | match slot { |
|
| 280 | + | case slots::Reservation::Held(object) => return OutputReservation::Held(Output { |
|
| 281 | + | object, code, metadata, codeBase: codeRange.start, metadataBase: metadataRange.start, |
|
| 282 | + | }), |
|
| 283 | + | } |
|
| 242 | 284 | } |
|
| 243 | 285 | ||
| 244 | - | /// Load trusted RIL and install an Image capability after complete native publication. |
|
| 245 | - | /// The caller keeps its domain alive and serializes shared metadata transactions. |
|
| 246 | - | export unsafe fn load(state: &mut State, store: &mut pages::Store, packages: &mut registry::Store, |
|
| 247 | - | table: &mut capability::Table, request: Request) -> abi::Handle throws (abi::Error) |
|
| 286 | + | /// Compile a decoded package into reserved private output using resident dependencies. |
|
| 287 | + | export unsafe fn compile(packages: ®istry::Store, reservation: &Reservation, input: &binary::Package, output: &OutputReservation) |
|
| 288 | + | -> Compiled throws (abi::Error) |
|
| 289 | + | { |
|
| 290 | + | match reservation { |
|
| 291 | + | case Reservation::Held(source) => match output { |
|
| 292 | + | 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); |
|
| 295 | + | }, |
|
| 296 | + | }, |
|
| 297 | + | } |
|
| 298 | + | } |
|
| 299 | + | ||
| 300 | + | /// Return unpublished output frames and its package slot under serialization. |
|
| 301 | + | export fn cancelOutput(pool: &mut frames::Pool, packages: &mut registry::Store, reservation: OutputReservation) { |
|
| 302 | + | match reservation { |
|
| 303 | + | case OutputReservation::Held(output) => { |
|
| 304 | + | assert slots::matches(&packages.slots[..], output.object, slots::State::Reserved); |
|
| 305 | + | try! frames::release(pool, output.metadata); |
|
| 306 | + | try! frames::release(pool, output.code); |
|
| 307 | + | registry::cancel(packages, slots::Reservation::Held(output.object)); |
|
| 308 | + | }, |
|
| 309 | + | } |
|
| 310 | + | } |
|
| 311 | + | ||
| 312 | + | /// Publish completed native output and retain its used frames under serialization. |
|
| 313 | + | export fn publishOutput(state: &mut State, pool: &mut frames::Pool, packages: &mut registry::Store, |
|
| 314 | + | reservation: OutputReservation, sizes: Compiled) -> abi::Ref |
|
| 315 | + | { |
|
| 316 | + | match reservation { |
|
| 317 | + | case OutputReservation::Held(output) => { |
|
| 318 | + | assert slots::matches(&packages.slots[..], output.object, slots::State::Reserved); |
|
| 319 | + | let retainedCode = trim(pool, output.code, sizes.code); |
|
| 320 | + | let retainedMetadata = trim(pool, output.metadata, sizes.metadata); |
|
| 321 | + | sync::syncInstructions(); |
|
| 322 | + | let result = registry::commit(packages, slots::Reservation::Held(output.object), sizes.admission); |
|
| 323 | + | assert result == output.object; |
|
| 324 | + | set state.resident[result.index] = Resident { code: retainedCode, metadata: retainedMetadata }; |
|
| 325 | + | set state.live[result.index] = true; |
|
| 326 | + | return result; |
|
| 327 | + | }, |
|
| 328 | + | } |
|
| 329 | + | } |
|
| 330 | + | ||
| 331 | + | /// Compare the snapshot with immutable resident content while the workspace is held. |
|
| 332 | + | export unsafe fn identify(packages: ®istry::Store, reservation: &Reservation, input: &binary::Package) -> ?abi::Ref throws (abi::Error) { |
|
| 333 | + | match reservation { |
|
| 334 | + | case Reservation::Held(source) => return try registry::identify(packages, &input.name[..], &workspace(source.work).input[..source.length]), |
|
| 335 | + | } |
|
| 336 | + | } |
|
| 337 | + | ||
| 338 | + | /// Reserve an Image handle and exclusive workspace under metadata serialization. |
|
| 339 | + | export unsafe fn reserve(state: &mut State, store: &mut pages::Store, |
|
| 340 | + | table: &mut capability::Table, request: Request) -> Reservation throws (abi::Error) |
|
| 248 | 341 | { |
|
| 249 | 342 | let address = try source(store, table, request); |
|
| 250 | 343 | let handle = try slots::reserve(&mut table.slots[..]); |
|
| 251 | 344 | let lease = try acquire(state, &mut store.backings.pool, table.owner) catch err { |
|
| 252 | 345 | try! slots::cancel(&mut table.slots[..], handle); throw err; |
|
| 253 | 346 | }; |
|
| 254 | - | let work = storage(&store.backings.pool, &lease); |
|
| 255 | - | let object = try produce(state, &mut store.backings.pool, packages, work, address, request.length as u32) catch err { |
|
| 256 | - | release(state, &mut store.backings.pool, lease); |
|
| 257 | - | try! slots::cancel(&mut table.slots[..], handle); throw err; |
|
| 347 | + | let work = storage(&store.backings.pool, &lease) as u64; |
|
| 348 | + | match handle { |
|
| 349 | + | case slots::Reservation::Held(handle) => match lease { |
|
| 350 | + | case Lease::Held(frames) => return Reservation::Held(Input { |
|
| 351 | + | owner: table.owner, handle, frames, work, address, length: request.length as u32, |
|
| 352 | + | }), |
|
| 353 | + | }, |
|
| 354 | + | } |
|
| 355 | + | } |
|
| 356 | + | ||
| 357 | + | /// Check workspace ownership and the caller's unpublished handle slot. |
|
| 358 | + | fn require(state: &State, table: &capability::Table, input: &Input) { |
|
| 359 | + | assert state.busy and state.owner == input.owner and table.owner == input.owner; |
|
| 360 | + | assert slots::matches(&table.slots[..], input.handle, slots::State::Reserved); |
|
| 361 | + | } |
|
| 362 | + | ||
| 363 | + | /// Return the workspace and reserved handle under metadata serialization. |
|
| 364 | + | export fn cancel(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 365 | + | match reservation { |
|
| 366 | + | case Reservation::Held(input) => { |
|
| 367 | + | require(state, table, &input); |
|
| 368 | + | release(state, &mut store.backings.pool, Lease::Held(input.frames)); |
|
| 369 | + | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(input.handle)); |
|
| 370 | + | }, |
|
| 371 | + | } |
|
| 372 | + | } |
|
| 373 | + | ||
| 374 | + | /// Install a resident package handle and return the workspace under serialization. |
|
| 375 | + | export fn finish(state: &mut State, store: &mut pages::Store, table: &mut capability::Table, |
|
| 376 | + | reservation: Reservation, object: abi::Ref) -> abi::Handle throws (abi::Error) |
|
| 377 | + | { |
|
| 378 | + | match reservation { |
|
| 379 | + | case Reservation::Held(input) => { |
|
| 380 | + | require(state, table, &input); |
|
| 381 | + | if not backing::domainLive(&store.backings, input.owner) { |
|
| 382 | + | cancel(state, store, table, Reservation::Held(input)); throw abi::Error::BadHandle; |
|
| 383 | + | } |
|
| 384 | + | let result = capability::publish(table, slots::Reservation::Held(input.handle), capability::Entry { |
|
| 385 | + | kind: abi::Kind::Image, object, rights: abi::Rights(registry::IMAGE_RIGHTS), |
|
| 386 | + | }); |
|
| 387 | + | release(state, &mut store.backings.pool, Lease::Held(input.frames)); |
|
| 388 | + | return result; |
|
| 389 | + | }, |
|
| 390 | + | } |
|
| 391 | + | } |
|
| 392 | + | ||
| 393 | + | /// Load trusted RIL while the caller has exclusive access to shared metadata. |
|
| 394 | + | export unsafe fn load(state: &mut State, store: &mut pages::Store, packages: &mut registry::Store, |
|
| 395 | + | table: &mut capability::Table, request: Request) -> abi::Handle throws (abi::Error) |
|
| 396 | + | { |
|
| 397 | + | let pending = try reserve(state, store, table, request); |
|
| 398 | + | let input = try decode(&pending) catch err { cancel(state, store, table, pending); throw err; }; |
|
| 399 | + | let existing = try identify(packages, &pending, &input) catch err { cancel(state, store, table, pending); throw err; }; |
|
| 400 | + | if let object = existing { return try finish(state, store, table, pending, object); } |
|
| 401 | + | let output = try reserveOutput(&mut store.backings.pool, packages) catch err { cancel(state, store, table, pending); throw err; }; |
|
| 402 | + | let sizes = try compile(packages, &pending, &input, &output) catch err { |
|
| 403 | + | cancelOutput(&mut store.backings.pool, packages, output); |
|
| 404 | + | cancel(state, store, table, pending); throw err; |
|
| 258 | 405 | }; |
|
| 259 | - | let result = capability::publish(table, handle, capability::Entry { |
|
| 260 | - | kind: abi::Kind::Image, object, rights: abi::Rights(registry::IMAGE_RIGHTS), |
|
| 261 | - | }); |
|
| 262 | - | release(state, &mut store.backings.pool, lease); |
|
| 263 | - | return result; |
|
| 406 | + | let object = publishOutput(state, &mut store.backings.pool, packages, output, sizes); |
|
| 407 | + | return try finish(state, store, table, pending, object); |
|
| 264 | 408 | } |
kernel/kernel/pages.rad
+93 -16
| 78 | 78 | if cursor == before { return false; } |
|
| 79 | 79 | } |
|
| 80 | 80 | return true; |
|
| 81 | 81 | } |
|
| 82 | 82 | ||
| 83 | - | /// Allocate and zero frames before publishing a page and its capability. |
|
| 83 | + | /// Private frames and unpublished slots held while memory is cleared. |
|
| 84 | + | export record Allocation: Copy { |
|
| 85 | + | /// Calling domain whose table contains the reserved handle. |
|
| 86 | + | owner: abi::Ref, |
|
| 87 | + | /// Unpublished capability slot. |
|
| 88 | + | handle: abi::Ref, |
|
| 89 | + | /// Unpublished page-object slot. |
|
| 90 | + | page: abi::Ref, |
|
| 91 | + | /// Unpublished backing-allocation slot. |
|
| 92 | + | backing: abi::Ref, |
|
| 93 | + | /// Frames excluded from allocation until publication or cancellation. |
|
| 94 | + | run: frames::Run, |
|
| 95 | + | /// Validated physical base for private initialization. |
|
| 96 | + | base: u64, |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Allocation capacity that must be published or cancelled under serialization. |
|
| 100 | + | export union Reservation: Once { |
|
| 101 | + | /// Private memory and its three reserved metadata slots. |
|
| 102 | + | Held(Allocation), |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Reserve page capacity under metadata serialization for private initialization. |
|
| 84 | 106 | /// Allocation authority must name the calling domain. Failure cancels reservations. |
|
| 85 | - | export unsafe fn allocate(store: &mut Store, table: &mut capability::Table, authority: abi::Handle, count: u64) |
|
| 86 | - | -> abi::Handle throws (abi::Error) |
|
| 107 | + | export fn reserve(store: &mut Store, table: &mut capability::Table, authority: abi::Handle, count: u64) |
|
| 108 | + | -> Reservation throws (abi::Error) |
|
| 87 | 109 | { |
|
| 88 | 110 | if count == 0 { throw abi::Error::InvalidArg; } |
|
| 89 | 111 | if count > limits::FRAMES as u64 { throw abi::Error::OutOfMemory; } |
|
| 90 | 112 | let owner = table.owner; |
|
| 91 | 113 | let permission = try capability::authority(table, authority, abi::Rights(abi::ALLOCATE)); |
|
| 92 | 114 | if permission.object <> owner { throw abi::Error::Denied; } |
|
| 93 | - | if not backing::domainLive(&store.backings, owner) { throw abi::Error::BadHandle; } |
|
| 115 | + | let backings = &mut store.backings; |
|
| 116 | + | if not backing::domainLive(backings, owner) { throw abi::Error::BadHandle; } |
|
| 94 | 117 | let handleSlot = try slots::reserve(&mut table.slots[..]); |
|
| 95 | 118 | let pageSlot = try slots::reserve(&mut store.slots[..]) catch err { |
|
| 96 | 119 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 97 | 120 | }; |
|
| 98 | - | let backingSlot = try slots::reserve(&mut store.backings.slots[..]) catch err { |
|
| 121 | + | let backingSlot = try slots::reserve(&mut backings.slots[..]) catch err { |
|
| 99 | 122 | try! slots::cancel(&mut store.slots[..], pageSlot); |
|
| 100 | 123 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 101 | 124 | }; |
|
| 102 | - | let memory = try frames::reserve(&mut store.backings.pool, count as u32) catch err { |
|
| 103 | - | try! slots::cancel(&mut store.backings.slots[..], backingSlot); |
|
| 125 | + | let memory = try frames::reserve(&mut backings.pool, count as u32) catch err { |
|
| 126 | + | try! slots::cancel(&mut backings.slots[..], backingSlot); |
|
| 104 | 127 | try! slots::cancel(&mut store.slots[..], pageSlot); |
|
| 105 | 128 | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 106 | 129 | }; |
|
| 107 | 130 | let run = frames::commit(memory); |
|
| 108 | - | let extent = try! frames::extent(&store.backings.pool, run); |
|
| 109 | - | zero(extent.start, run.count); |
|
| 110 | - | let allocation = backing::publish(&mut store.backings, backingSlot, run, owner); |
|
| 111 | - | let object = slots::reference(&pageSlot); |
|
| 112 | - | set store.records[object.index] = Page { backing: allocation, base: extent.start, count: run.count, origin: owner, handles: 1 }; |
|
| 113 | - | let page = try! slots::commit(&mut store.slots[..], pageSlot); |
|
| 114 | - | return capability::publish(table, handleSlot, capability::Entry { |
|
| 115 | - | kind: abi::Kind::Page, object: page, rights: abi::Rights(DEFAULT_RIGHTS), |
|
| 116 | - | }); |
|
| 131 | + | let extent = try! frames::extent(&backings.pool, run); |
|
| 132 | + | match handleSlot { |
|
| 133 | + | case slots::Reservation::Held(handle) => match pageSlot { |
|
| 134 | + | case slots::Reservation::Held(page) => match backingSlot { |
|
| 135 | + | case slots::Reservation::Held(backing) => return Reservation::Held(Allocation { |
|
| 136 | + | owner, handle, page, backing, run, base: extent.start, |
|
| 137 | + | }), |
|
| 138 | + | }, |
|
| 139 | + | }, |
|
| 140 | + | } |
|
| 141 | + | } |
|
| 142 | + | ||
| 143 | + | /// Clear private frames while shared metadata can be used by other harts. |
|
| 144 | + | export fn clear(reservation: &Reservation) { |
|
| 145 | + | match reservation { case Reservation::Held(allocation) => zero(allocation.base, allocation.run.count), } |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Check reserved generations before publishing or cancelling an allocation. |
|
| 149 | + | fn require(store: &Store, table: &capability::Table, allocation: &Allocation) { |
|
| 150 | + | assert table.owner == allocation.owner; |
|
| 151 | + | assert slots::matches(&table.slots[..], allocation.handle, slots::State::Reserved); |
|
| 152 | + | assert slots::matches(&store.slots[..], allocation.page, slots::State::Reserved); |
|
| 153 | + | assert slots::matches(&store.backings.slots[..], allocation.backing, slots::State::Reserved); |
|
| 154 | + | } |
|
| 155 | + | ||
| 156 | + | /// Publish cleared memory under metadata serialization while its owner stays live. |
|
| 157 | + | export unsafe fn publish(store: &mut Store, table: &mut capability::Table, reservation: Reservation) -> abi::Handle { |
|
| 158 | + | match reservation { |
|
| 159 | + | case Reservation::Held(allocation) => { |
|
| 160 | + | require(store, table, &allocation); |
|
| 161 | + | assert backing::domainLive(&store.backings, allocation.owner); |
|
| 162 | + | let backing = backing::publish(&mut store.backings, slots::Reservation::Held(allocation.backing), allocation.run, allocation.owner); |
|
| 163 | + | set store.records[allocation.page.index] = Page { |
|
| 164 | + | backing, base: allocation.base, count: allocation.run.count, origin: allocation.owner, handles: 1, |
|
| 165 | + | }; |
|
| 166 | + | let page = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(allocation.page)); |
|
| 167 | + | return capability::publish(table, slots::Reservation::Held(allocation.handle), capability::Entry { |
|
| 168 | + | kind: abi::Kind::Page, object: page, rights: abi::Rights(DEFAULT_RIGHTS), |
|
| 169 | + | }); |
|
| 170 | + | }, |
|
| 171 | + | } |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | /// Return private allocation capacity under metadata serialization. |
|
| 175 | + | export fn cancel(store: &mut Store, table: &mut capability::Table, reservation: Reservation) { |
|
| 176 | + | match reservation { |
|
| 177 | + | case Reservation::Held(allocation) => { |
|
| 178 | + | require(store, table, &allocation); |
|
| 179 | + | try! frames::release(&mut store.backings.pool, allocation.run); |
|
| 180 | + | try! slots::cancel(&mut store.backings.slots[..], slots::Reservation::Held(allocation.backing)); |
|
| 181 | + | try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(allocation.page)); |
|
| 182 | + | try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(allocation.handle)); |
|
| 183 | + | }, |
|
| 184 | + | } |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// Allocate zeroed pages while the caller has exclusive access to metadata. |
|
| 188 | + | export unsafe fn allocate(store: &mut Store, table: &mut capability::Table, authority: abi::Handle, count: u64) |
|
| 189 | + | -> abi::Handle throws (abi::Error) |
|
| 190 | + | { |
|
| 191 | + | let pending = try reserve(store, table, authority, count); |
|
| 192 | + | clear(&pending); |
|
| 193 | + | return publish(store, table, pending); |
|
| 117 | 194 | } |
|
| 118 | 195 | ||
| 119 | 196 | /// Split an exclusive page into two segments with the same backing and rights. |
|
| 120 | 197 | export fn split(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, leftCount: u64) |
|
| 121 | 198 | -> abi::Handle throws (abi::Error) |
kernel/kernel/registry.rad
+39 -12
| 55 | 55 | /// Return unpublished capacity after a failed load. |
|
| 56 | 56 | export fn cancel(store: &mut Store, reservation: slots::Reservation) { |
|
| 57 | 57 | try! slots::cancel(&mut store.slots[..], reservation); |
|
| 58 | 58 | } |
|
| 59 | 59 | ||
| 60 | - | /// Validate dependencies without changing registry payloads. |
|
| 61 | - | unsafe fn validate(store: &Store, object: abi::Ref, package: &shared::Package) throws (abi::Error) { |
|
| 60 | + | /// Validated immutable package metadata ready for bounded publication. |
|
| 61 | + | export record Admission: Copy { |
|
| 62 | + | /// Reserved package generation to receive the descriptor. |
|
| 63 | + | object: abi::Ref, |
|
| 64 | + | /// Persistent source and native descriptor. |
|
| 65 | + | entry: catalog::Entry, |
|
| 66 | + | /// Resolved resident dependencies in their declared order. |
|
| 67 | + | dependencies: [abi::Ref; limits::PACKAGES], |
|
| 68 | + | /// Number of initialized dependency references. |
|
| 69 | + | count: u32, |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Resolve admission against immutable packages while holding sole publisher ownership. |
|
| 73 | + | /// The caller keeps that ownership and the descriptor storage through commit or cancellation. |
|
| 74 | + | export unsafe fn admit(store: &Store, object: abi::Ref, source: *[u8], package: shared::Package) -> Admission throws (abi::Error) { |
|
| 75 | + | if not slots::matches(&store.slots[..], object, slots::State::Reserved) { throw abi::Error::BadHandle; } |
|
| 76 | + | if try identify(store, &package.name[..], &source[..]) <> nil { throw abi::Error::Busy; } |
|
| 62 | 77 | if package.name.len == 0 or package.slot <> object.index or package.dependencies.len > limits::PACKAGES { |
|
| 63 | 78 | throw abi::Error::VerifyFailed; |
|
| 64 | 79 | } |
|
| 80 | + | let mut result = Admission { object, entry: catalog::Entry { source, package }, dependencies: undefined, count: package.dependencies.len }; |
|
| 65 | 81 | for dependency, i in package.dependencies { |
|
| 66 | 82 | let target = find(store, &dependency[..]) else { throw abi::Error::VerifyFailed; }; |
|
| 67 | - | for prior in &package.dependencies[..i] { |
|
| 68 | - | if mem::eq(prior, dependency) { throw abi::Error::VerifyFailed; } |
|
| 83 | + | for prior in &result.dependencies[..i] { |
|
| 84 | + | if prior == target { throw abi::Error::VerifyFailed; } |
|
| 69 | 85 | } |
|
| 86 | + | set result.dependencies[i] = target; |
|
| 87 | + | } |
|
| 88 | + | return result; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// Publish admitted metadata under serialization using only bounded reference copies. |
|
| 92 | + | export fn commit(store: &mut Store, reservation: slots::Reservation, admission: Admission) -> abi::Ref { |
|
| 93 | + | let object = slots::reference(&reservation); |
|
| 94 | + | assert object == admission.object and slots::matches(&store.slots[..], object, slots::State::Reserved); |
|
| 95 | + | assert admission.count <= limits::PACKAGES; |
|
| 96 | + | for i in 0..admission.count { |
|
| 97 | + | let target = admission.dependencies[i]; |
|
| 98 | + | assert slots::matches(&store.slots[..], target, slots::State::Live); |
|
| 99 | + | set store.dependencies[object.index][i] = target; |
|
| 70 | 100 | } |
|
| 101 | + | set store.counts[object.index] = admission.count; |
|
| 102 | + | set store.entries[object.index] = admission.entry; |
|
| 103 | + | return try! slots::commit(&mut store.slots[..], reservation); |
|
| 71 | 104 | } |
|
| 72 | 105 | ||
| 73 | 106 | /// Publish persistent compiler output after successful admission checks. |
|
| 74 | 107 | /// All descriptor pointers and source bytes must remain immutable and resident. |
|
| 75 | 108 | /// Failure cancels the reservation; duplicate content returns its existing slot. |
| 79 | 112 | let existing = try identify(store, &package.name[..], &source[..]) catch err { |
|
| 80 | 113 | cancel(store, reservation); throw err; |
|
| 81 | 114 | }; |
|
| 82 | 115 | if let object = existing { cancel(store, reservation); return object; } |
|
| 83 | 116 | let object = slots::reference(&reservation); |
|
| 84 | - | try validate(store, object, &package) catch err { cancel(store, reservation); throw err; }; |
|
| 85 | - | for dependency, i in package.dependencies { |
|
| 86 | - | let target = find(store, &dependency[..]) else panic "resident dependency"; |
|
| 87 | - | set store.dependencies[object.index][i] = target; |
|
| 88 | - | } |
|
| 89 | - | set store.counts[object.index] = package.dependencies.len; |
|
| 90 | - | set store.entries[object.index] = catalog::Entry { source, package }; |
|
| 91 | - | return try! slots::commit(&mut store.slots[..], reservation); |
|
| 117 | + | let admission = try admit(store, object, source, package) catch err { cancel(store, reservation); throw err; }; |
|
| 118 | + | return commit(store, reservation, admission); |
|
| 92 | 119 | } |
|
| 93 | 120 | ||
| 94 | 121 | /// Read resident metadata through a generation-bearing package reference. |
|
| 95 | 122 | export fn get(store: &Store, object: abi::Ref) -> shared::Package throws (abi::Error) { |
|
| 96 | 123 | if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; } |
kernel/kernel/sync.rad
+60 -0
| 1 | 1 | //! RV64 synchronization and ordered device access. |
|
| 2 | 2 | ||
| 3 | + | /// Next metadata ticket, modulo 2^32. Each hart can hold at most one ticket. |
|
| 4 | + | static NEXT: u32 = 0; |
|
| 5 | + | /// Metadata ticket permitted to enter the critical section. |
|
| 6 | + | static SERVING: u32 = 0; |
|
| 7 | + | /// Largest observed instruction count inside the metadata lock. |
|
| 8 | + | static MAXIMUM: u64 = 0; |
|
| 9 | + | ||
| 10 | + | /// Interrupt state and start counter retained by one metadata transaction. |
|
| 11 | + | export record Section: Copy { |
|
| 12 | + | /// Original machine interrupt-enable bit. |
|
| 13 | + | interrupts: u64, |
|
| 14 | + | /// Retired instruction count after lock acquisition. |
|
| 15 | + | start: u64, |
|
| 16 | + | /// Ticket held by the acquiring hart until release. |
|
| 17 | + | ticket: u32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Lock ownership that must be released on its acquiring hart. |
|
| 21 | + | export union Guard: Once { |
|
| 22 | + | /// One serialized metadata transaction with machine interrupts masked. |
|
| 23 | + | Held(Section), |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Mask machine interrupts and return the original enable bit. |
|
| 27 | + | export fn maskInterrupts() -> u64; |
|
| 28 | + | /// Restore the supplied machine interrupt-enable bit. |
|
| 29 | + | export fn restoreInterrupts(value: u64); |
|
| 30 | + | /// Read this hart's retired instruction counter. |
|
| 31 | + | fn retired() -> u64; |
|
| 32 | + | ||
| 33 | + | /// Acquire shared metadata in ticket order with local interrupts masked. |
|
| 34 | + | /// Calls must not nest. At most one ticket per online hart can be outstanding. |
|
| 35 | + | export fn enter() -> Guard { |
|
| 36 | + | let interrupts = maskInterrupts(); |
|
| 37 | + | let ticket = nextTicket(&mut NEXT); |
|
| 38 | + | while loadAcquire32(&SERVING) <> ticket {} |
|
| 39 | + | let start = retired(); |
|
| 40 | + | return Guard::Held(Section { interrupts, start, ticket }); |
|
| 41 | + | } |
|
| 42 | + | ||
| 43 | + | /// Publish metadata and restore the acquiring hart's interrupt state. |
|
| 44 | + | export fn leave(guard: Guard) { |
|
| 45 | + | match guard { |
|
| 46 | + | case Guard::Held(section) => { |
|
| 47 | + | let work = retired() - section.start; |
|
| 48 | + | if work > MAXIMUM { set MAXIMUM = work; } |
|
| 49 | + | storeRelease32(&mut SERVING, ((section.ticket as u64 + 1) & 0xffffffff) as u32); |
|
| 50 | + | restoreInterrupts(section.interrupts); |
|
| 51 | + | }, |
|
| 52 | + | } |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// Read the maximum measured metadata work under serialization. |
|
| 56 | + | export fn maximum() -> u64 { |
|
| 57 | + | let guard = enter(); |
|
| 58 | + | let value = MAXIMUM; |
|
| 59 | + | leave(guard); |
|
| 60 | + | return value; |
|
| 61 | + | } |
|
| 62 | + | ||
| 3 | 63 | /// Allocate one wrapping ticket from a naturally aligned u32 counter. |
|
| 4 | 64 | export fn nextTicket(counter: &mut u32) -> u32; |
|
| 5 | 65 | /// Read a naturally aligned shared word with acquire ordering. |
|
| 6 | 66 | export fn loadAcquire(value: &u64) -> u64; |
|
| 7 | 67 | /// Publish a naturally aligned shared word with release ordering. |
kernel/kernel/sync.ras
+24 -0
| 8 | 8 | .export @kernel::sync::fetchAdd; |
|
| 9 | 9 | .export @kernel::sync::syncInstructions; |
|
| 10 | 10 | .export @kernel::sync::deviceFence; |
|
| 11 | 11 | .export @kernel::sync::read32; |
|
| 12 | 12 | .export @kernel::sync::write32; |
|
| 13 | + | .export @kernel::sync::maskInterrupts; |
|
| 14 | + | .export @kernel::sync::restoreInterrupts; |
|
| 15 | + | .export @kernel::sync::retired; |
|
| 16 | + | ||
| 17 | + | // Preserve the previous interrupt-enable bit while masking machine interrupts. |
|
| 18 | + | @kernel::sync::maskInterrupts |
|
| 19 | + | csrr %t0 mstatus; |
|
| 20 | + | andi %a0 %t0 8; |
|
| 21 | + | andi %t0 %t0 -9; |
|
| 22 | + | csrw mstatus %t0; |
|
| 23 | + | ret; |
|
| 24 | + | ||
| 25 | + | // Restore interrupt delivery while preserving other machine status fields. |
|
| 26 | + | @kernel::sync::restoreInterrupts |
|
| 27 | + | csrr %t0 mstatus; |
|
| 28 | + | andi %t0 %t0 -9; |
|
| 29 | + | or %t0 %t0 %a0; |
|
| 30 | + | csrw mstatus %t0; |
|
| 31 | + | ret; |
|
| 32 | + | ||
| 33 | + | // Count local retired instructions independently of other harts' progress. |
|
| 34 | + | @kernel::sync::retired |
|
| 35 | + | csrr %a0 instret; |
|
| 36 | + | ret; |
|
| 13 | 37 | ||
| 14 | 38 | // Allocate a wrapping ticket and return its zero-extended u32 value. |
|
| 15 | 39 | @kernel::sync::nextTicket |
|
| 16 | 40 | li %t0 1; |
|
| 17 | 41 | amoadd.w.aqrl %a0 %t0 (%a0); |
kernel/kernel/tests/calls.rad
+1 -1
| 164 | 164 | /// An unknown user operation advances PC and returns only a negative a0 error. |
|
| 165 | 165 | @test unsafe fn trapReply() throws (testing::TestError) { |
|
| 166 | 166 | let owner = initialize(); |
|
| 167 | 167 | let mut frame = trap::Frame { registers: [123; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 }; |
|
| 168 | 168 | set frame.registers[17] = 0xffffffffffffffff; |
|
| 169 | - | calls::handle(owner, &mut frame, 0); |
|
| 169 | + | calls::handle(owner, &mut frame, 0, calls::invoke); |
|
| 170 | 170 | try testing::expect(frame.pc == 0x80000004 and frame.registers[10] == 0xfffffffffffffffc); |
|
| 171 | 171 | for i in 0..32 { |
|
| 172 | 172 | if i == 10 or i == 17 { continue; } |
|
| 173 | 173 | try testing::expect(frame.registers[i] == 123); |
|
| 174 | 174 | } |
kernel/kernel/tests/dispatch.rad
+17 -0
| 57 | 57 | try testing::expect(ended.context == nil and ended.deadline == 0xffffffffffffffff); |
|
| 58 | 58 | let other = try! dispatch::select(&BUDGETS, &DOMAINS, 1, 10); |
|
| 59 | 59 | try testing::expect(other.context == nil and other.deadline == 0xffffffffffffffff); |
|
| 60 | 60 | } |
|
| 61 | 61 | ||
| 62 | + | /// A context becomes eligible only after its previous hart releases ownership. |
|
| 63 | + | @test unsafe fn ownership() throws (testing::TestError) { |
|
| 64 | + | initialize(); |
|
| 65 | + | set DOMAINS.contexts[0].hart = 1; |
|
| 66 | + | let future = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 9); |
|
| 67 | + | assert future.context == nil and future.deadline == 10; |
|
| 68 | + | let occupied = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
|
| 69 | + | assert occupied.context == nil and occupied.deadline == 20; |
|
| 70 | + | set DOMAINS.contexts[0].hart = nil; |
|
| 71 | + | let free = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
|
| 72 | + | assert free.context == TABLE.owner and free.deadline == 20; |
|
| 73 | + | set DOMAINS.contexts[0].hart = 0; |
|
| 74 | + | let local = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
|
| 75 | + | assert local == free; |
|
| 76 | + | } |
|
| 77 | + | ||
| 62 | 78 | /// Pending domains and stale context generations cannot become runnable through a budget. |
|
| 63 | 79 | @test unsafe fn lifetimes() throws (testing::TestError) { |
|
| 64 | 80 | initialize(); |
|
| 65 | 81 | set DOMAINS.records[0].state = domains::Lifecycle::Pending; |
|
| 66 | 82 | let pending = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10); |
| 122 | 138 | stack: range::Range { start: 0x2000, end: 0x4000 }, |
|
| 123 | 139 | }; |
|
| 124 | 140 | let mut anchor = trap::Hart { |
|
| 125 | 141 | stackTop: 0x4000, stackBottom: 0x2000, kernelGp: 0x20000, handler, |
|
| 126 | 142 | savedT0: 0, savedT1: 0, savedSp: 0, |
|
| 143 | + | dispatchTop: 0x4000, dispatchFrame: undefined, |
|
| 127 | 144 | }; |
|
| 128 | 145 | let first = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 10); |
|
| 129 | 146 | try testing::expect(frame.pc == 0x40 and anchor.stackTop == 0xc000); |
|
| 130 | 147 | try testing::expect(DOMAINS.contexts[0].hart == 0); |
|
| 131 | 148 | set frame.pc = 0x88; |
kernel/kernel/tests/domains.rad
+68 -1
| 129 | 129 | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error { |
|
| 130 | 130 | try testing::expect(error == abi::Error::OutOfMemory); set exhausted = true; |
|
| 131 | 131 | }; |
|
| 132 | 132 | try testing::expect(exhausted and DOMAINS.contextSlots[child.initial.index + 1].state == slots::State::Free); |
|
| 133 | 133 | set PAGES.backings.pool.count = count; |
|
| 134 | - | let context = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 134 | + | let reserved = try! domains::contextReserve(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 135 | + | let index = child.initial.index + 1; |
|
| 136 | + | assert DOMAINS.contextSlots[index].state == slots::State::Reserved; |
|
| 137 | + | let mut overlap = false; |
|
| 138 | + | try domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start) catch error { |
|
| 139 | + | assert error == abi::Error::Busy; set overlap = true; |
|
| 140 | + | }; |
|
| 141 | + | assert overlap; |
|
| 142 | + | let reservedFrames = DOMAINS.contexts[index].kernelFrames; |
|
| 143 | + | domains::contextCancel(&mut DOMAINS, &mut PAGES, reserved); |
|
| 144 | + | assert DOMAINS.contextSlots[index].state == slots::State::Free; |
|
| 145 | + | for i in reservedFrames.first..reservedFrames.first + reservedFrames.count { assert PAGES.backings.pool.free[i]; } |
|
| 146 | + | let pendingContext = try! domains::contextReserve(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start); |
|
| 147 | + | let stackBase = DOMAINS.contexts[index].kernelStack.start; |
|
| 148 | + | let base = (&RAM[0]) as u64; |
|
| 149 | + | let firstWord = ((stackBase - base) / 8) as u32; |
|
| 150 | + | let lastWord = firstWord + domains::KERNEL_STACK_PAGES * 512 - 1; |
|
| 151 | + | set RAM[firstWord] = 123; set RAM[lastWord] = 456; |
|
| 152 | + | domains::contextClear(&pendingContext); |
|
| 153 | + | assert RAM[firstWord] == 0 and RAM[lastWord] == 0; |
|
| 154 | + | let context = domains::contextPublish(&mut DOMAINS, pendingContext); |
|
| 135 | 155 | let created = try! domains::context(&DOMAINS, authority.object, context); |
|
| 136 | 156 | let initial = DOMAINS.contexts[child.initial.index]; |
|
| 137 | 157 | try testing::expect(context <> child.initial and created.owner == authority.object); |
|
| 138 | 158 | try testing::expect(created.frame.registers[3] == initial.frame.registers[3]); |
|
| 139 | 159 | try testing::expect(created.kernelStack.start >= initial.kernelStack.end or created.kernelStack.end <= initial.kernelStack.start); |
| 244 | 264 | assert (error == abi::Error::BadHandle); set rejected += 1; |
|
| 245 | 265 | }; |
|
| 246 | 266 | assert (rejected == 3); |
|
| 247 | 267 | } |
|
| 248 | 268 | ||
| 269 | + | /// Domain reservations retain private capacity across another complete creation. |
|
| 270 | + | @test unsafe fn stagedCreation() throws (testing::TestError) { |
|
| 271 | + | initialize(abi::CREATE); |
|
| 272 | + | let pending = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 273 | + | assert DOMAINS.slots[1].state == slots::State::Reserved; |
|
| 274 | + | assert DOMAINS.contextSlots[0].state == slots::State::Reserved; |
|
| 275 | + | assert TABLE.slots[2].state == slots::State::Reserved; |
|
| 276 | + | assert BACKINGS.domains[1] == 0 and DOMAINS.events.queues[1].generation == 0; |
|
| 277 | + | let other = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 278 | + | try! domains::prepare(&PACKAGES, &pending); |
|
| 279 | + | let first = try! domains::publish(&mut DOMAINS, &mut BACKINGS, &mut TABLE, pending); |
|
| 280 | + | let a = try! capability::get(&TABLE, first); |
|
| 281 | + | let b = try! capability::get(&TABLE, other); |
|
| 282 | + | let left = try! domains::get(&DOMAINS, a.object); |
|
| 283 | + | let right = try! domains::get(&DOMAINS, b.object); |
|
| 284 | + | assert a.object.index == 1 and b.object.index == 2; |
|
| 285 | + | assert left.graph.table[0] <> right.graph.table[0]; |
|
| 286 | + | assert left.state == domains::Lifecycle::Pending and right.state == domains::Lifecycle::Pending; |
|
| 287 | + | initialize(abi::CREATE); |
|
| 288 | + | let cancelled = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 289 | + | domains::cancel(&mut DOMAINS, &mut BACKINGS, &mut TABLE, cancelled); |
|
| 290 | + | assert DOMAINS.slots[1].state == slots::State::Free; |
|
| 291 | + | assert DOMAINS.contextSlots[0].state == slots::State::Free; |
|
| 292 | + | assert TABLE.slots[2].state == slots::State::Free; |
|
| 293 | + | for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; } |
|
| 294 | + | set PACKAGES.entries[0].package.relocations = &[shared::Relocation { |
|
| 295 | + | offset: 0, count: 1, target: shared::DataRef { slot: 5, offset: 0 }, |
|
| 296 | + | }]; |
|
| 297 | + | let mut invalid = false; |
|
| 298 | + | try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch error { |
|
| 299 | + | assert error == abi::Error::VerifyFailed; set invalid = true; |
|
| 300 | + | }; |
|
| 301 | + | assert invalid and DOMAINS.slots[1].state == slots::State::Free; |
|
| 302 | + | for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; } |
|
| 303 | + | initialize(abi::CREATE); |
|
| 304 | + | let abandoned = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 305 | + | try! domains::prepare(&PACKAGES, &abandoned); |
|
| 306 | + | try! backing::endDomain(&mut BACKINGS, TABLE.owner); |
|
| 307 | + | let mut dead = false; |
|
| 308 | + | try domains::publish(&mut DOMAINS, &mut BACKINGS, &mut TABLE, abandoned) catch error { |
|
| 309 | + | assert error == abi::Error::BadHandle; set dead = true; |
|
| 310 | + | }; |
|
| 311 | + | assert dead and DOMAINS.slots[1].state == slots::State::Free; |
|
| 312 | + | assert TABLE.slots[2].state == slots::State::Free; |
|
| 313 | + | for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; } |
|
| 314 | + | } |
|
| 315 | + | ||
| 249 | 316 | /// Creation installs only Events in the child and preserves selected authorizing rights. |
|
| 250 | 317 | @test unsafe fn pending() throws (testing::TestError) { |
|
| 251 | 318 | initialize(abi::CREATE | abi::ALLOCATE); |
|
| 252 | 319 | let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE); |
|
| 253 | 320 | let authority = try! capability::get(&TABLE, handle); |
kernel/kernel/tests/instances.rad
+21 -0
| 48 | 48 | fn word(address: u64) -> *mut u64 { |
|
| 49 | 49 | let pointer = &RAM[0]; |
|
| 50 | 50 | return &mut RAM[((address - pointer as u64) / 8) as u32]; |
|
| 51 | 51 | } |
|
| 52 | 52 | ||
| 53 | + | /// Private initialization retains its graph across other registry and frame transactions. |
|
| 54 | + | @test unsafe fn stagedGraph() throws (testing::TestError) { |
|
| 55 | + | initialize(); |
|
| 56 | + | let root = register("root", &[], &[]); |
|
| 57 | + | let pending = try! instances::reserve(&PACKAGES, &mut POOL, root); |
|
| 58 | + | let later = register("later", &[], &[]); |
|
| 59 | + | let other = try! instances::create(&PACKAGES, &mut POOL, later); |
|
| 60 | + | set *word(other.table[later.index]) = 99; |
|
| 61 | + | try! instances::initialize(&PACKAGES, &pending); |
|
| 62 | + | let first = instances::commit(pending); |
|
| 63 | + | assert first.table[root.index] <> 0 and first.table[later.index] == 0; |
|
| 64 | + | assert *word(first.table[root.index]) == 7; |
|
| 65 | + | assert *word(other.table[later.index]) == 99; |
|
| 66 | + | assert first.frames.first + first.frames.count <= other.frames.first; |
|
| 67 | + | try! frames::release(&mut POOL, first.frames); |
|
| 68 | + | try! frames::release(&mut POOL, other.frames); |
|
| 69 | + | let cancelled = try! instances::reserve(&PACKAGES, &mut POOL, root); |
|
| 70 | + | instances::cancel(&mut POOL, cancelled); |
|
| 71 | + | for i in 0..POOL.count { assert POOL.free[i]; } |
|
| 72 | + | } |
|
| 73 | + | ||
| 53 | 74 | /// A diamond has one dependency instance per domain and independent relocated pointers. |
|
| 54 | 75 | @test unsafe fn diamond() throws (testing::TestError) { |
|
| 55 | 76 | initialize(); |
|
| 56 | 77 | let base = register("base", &[], &[]); |
|
| 57 | 78 | unsafe static relocation: [shared::Relocation; 1] = undefined; |
kernel/kernel/tests/loader.rad
+62 -0
| 83 | 83 | let mut count: u32 = 0; |
|
| 84 | 84 | for i in 0..PAGES.backings.pool.count { if PAGES.backings.pool.free[i] { set count += 1; } } |
|
| 85 | 85 | return count; |
|
| 86 | 86 | } |
|
| 87 | 87 | ||
| 88 | + | /// Reserved input stays private across allocation, source mutation, and contention. |
|
| 89 | + | @test unsafe fn stagedInput() throws (testing::TestError) { |
|
| 90 | + | initialize(); |
|
| 91 | + | let request = input(43, false); |
|
| 92 | + | let before = free(); |
|
| 93 | + | let pending = try! loader::reserve(&mut LOADER, &mut PAGES, &mut TABLE, request); |
|
| 94 | + | assert LOADER.busy and TABLE.slots[2].state == slots::State::Reserved; |
|
| 95 | + | let other = try! pages::allocate(&mut PAGES, &mut TABLE, abi::Handle(0), 1); |
|
| 96 | + | let decoded = try! loader::decode(&pending); |
|
| 97 | + | assert decoded.program.fns.len == 1 and decoded.name.len == 6; |
|
| 98 | + | let replacement = input(99, false); |
|
| 99 | + | assert decoded.program.fns[0].blocks[0].instrs[0] == il::Instr::Ret { val: il::Val::Imm(43) }; |
|
| 100 | + | let mut busy = false; |
|
| 101 | + | try loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request) catch error { |
|
| 102 | + | assert error == abi::Error::Busy; set busy = true; |
|
| 103 | + | }; |
|
| 104 | + | assert busy; |
|
| 105 | + | loader::cancel(&mut LOADER, &mut PAGES, &mut TABLE, pending); |
|
| 106 | + | assert not LOADER.busy and TABLE.slots[2].state == slots::State::Free; |
|
| 107 | + | assert free() + 1 == before; |
|
| 108 | + | assert (try! pages::get(&PAGES, (try! capability::get(&TABLE, other)).object)).count == 1; |
|
| 109 | + | } |
|
| 110 | + | ||
| 111 | + | /// Compiled output stays private until publication and cancellation restores capacity. |
|
| 112 | + | @test unsafe fn stagedOutput() throws (testing::TestError) { |
|
| 113 | + | initialize(); |
|
| 114 | + | let request = input(43, false); |
|
| 115 | + | let before = free(); |
|
| 116 | + | let pending = try! loader::reserve(&mut LOADER, &mut PAGES, &mut TABLE, request); |
|
| 117 | + | let decoded = try! loader::decode(&pending); |
|
| 118 | + | let output = try! loader::reserveOutput(&mut PAGES.backings.pool, &mut PACKAGES); |
|
| 119 | + | assert PACKAGES.slots[0].state == slots::State::Reserved and not LOADER.live[0]; |
|
| 120 | + | let other = try! pages::allocate(&mut PAGES, &mut TABLE, abi::Handle(0), 1); |
|
| 121 | + | let used = try! loader::compile(&PACKAGES, &pending, &decoded, &output); |
|
| 122 | + | assert used.code > 0 and used.metadata > 0; |
|
| 123 | + | assert PACKAGES.slots[0].state == slots::State::Reserved and not LOADER.live[0]; |
|
| 124 | + | loader::cancelOutput(&mut PAGES.backings.pool, &mut PACKAGES, output); |
|
| 125 | + | loader::cancel(&mut LOADER, &mut PAGES, &mut TABLE, pending); |
|
| 126 | + | assert free() + 1 == before and not LOADER.busy; |
|
| 127 | + | assert PACKAGES.slots[0].state == slots::State::Free; |
|
| 128 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request); |
|
| 129 | + | assert (try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(0))).index == 0; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | /// A caller whose backing lifetime ended cannot receive a completed Image handle. |
|
| 133 | + | @test unsafe fn completionLifetime() throws (testing::TestError) { |
|
| 134 | + | initialize(); |
|
| 135 | + | let request = input(43, false); |
|
| 136 | + | let handle = try! loader::load(&mut LOADER, &mut PAGES, &mut PACKAGES, &mut TABLE, request); |
|
| 137 | + | let object = try! registry::image(&PACKAGES, &TABLE, handle, abi::Rights(0)); |
|
| 138 | + | let before = free(); |
|
| 139 | + | let pending = try! loader::reserve(&mut LOADER, &mut PAGES, &mut TABLE, request); |
|
| 140 | + | try! backing::endDomain(&mut PAGES.backings, TABLE.owner); |
|
| 141 | + | let mut dead = false; |
|
| 142 | + | try loader::finish(&mut LOADER, &mut PAGES, &mut TABLE, pending, object) catch error { |
|
| 143 | + | assert error == abi::Error::BadHandle; set dead = true; |
|
| 144 | + | }; |
|
| 145 | + | assert dead and not LOADER.busy and free() == before; |
|
| 146 | + | assert TABLE.slots[3].state == slots::State::Free; |
|
| 147 | + | assert slots::matches(&PACKAGES.slots[..], object, slots::State::Live); |
|
| 148 | + | } |
|
| 149 | + | ||
| 88 | 150 | /// Resident source bytes survive mutation of the source page and workspace release. |
|
| 89 | 151 | @test unsafe fn residentSnapshot() throws (testing::TestError) { |
|
| 90 | 152 | initialize(); |
|
| 91 | 153 | let request = input(43, false); |
|
| 92 | 154 | let before = free(); |
kernel/kernel/tests/pages.rad
+37 -0
| 41 | 41 | /// Drop the test's handle and its object reference in the same serialized operation. |
|
| 42 | 42 | unsafe fn drop(handle: abi::Handle) { |
|
| 43 | 43 | try! pages::drop(&mut STORE, &mut TABLE, handle); |
|
| 44 | 44 | } |
|
| 45 | 45 | ||
| 46 | + | /// Reserved frames stay private while another allocation changes shared metadata. |
|
| 47 | + | @test unsafe fn stagedAllocation() throws (testing::TestError) { |
|
| 48 | + | initialize(); |
|
| 49 | + | let pending = try! pages::reserve(&mut STORE, &mut TABLE, abi::Handle(0), 2); |
|
| 50 | + | assert STORE.slots[0].state == slots::State::Reserved; |
|
| 51 | + | assert TABLE.slots[1].state == slots::State::Reserved; |
|
| 52 | + | assert not STORE.backings.pool.free[0] and not STORE.backings.pool.free[1]; |
|
| 53 | + | assert RAM[OFFSET] == 0xa5; |
|
| 54 | + | let other = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 6); |
|
| 55 | + | set RAM[OFFSET + 8192] = 0x7b; |
|
| 56 | + | pages::clear(&pending); |
|
| 57 | + | for i in OFFSET..OFFSET + 8192 { assert RAM[i] == 0; } |
|
| 58 | + | assert RAM[OFFSET + 8192] == 0x7b; |
|
| 59 | + | let handle = pages::publish(&mut STORE, &mut TABLE, pending); |
|
| 60 | + | let entry = try! capability::get(&TABLE, handle); |
|
| 61 | + | let page = try! pages::get(&STORE, entry.object); |
|
| 62 | + | assert page.count == 2 and page.origin == TABLE.owner; |
|
| 63 | + | drop(handle); drop(other); |
|
| 64 | + | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 65 | + | for i in 0..8 { assert STORE.backings.pool.free[i]; } |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Cancelling unpublished storage returns all capacity without exposing its bytes. |
|
| 69 | + | @test unsafe fn cancelAllocation() throws (testing::TestError) { |
|
| 70 | + | initialize(); |
|
| 71 | + | let pending = try! pages::reserve(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 72 | + | pages::cancel(&mut STORE, &mut TABLE, pending); |
|
| 73 | + | assert STORE.slots[0].state == slots::State::Free; |
|
| 74 | + | assert STORE.backings.slots[0].state == slots::State::Free; |
|
| 75 | + | assert TABLE.slots[1].state == slots::State::Free; |
|
| 76 | + | for i in 0..8 { assert STORE.backings.pool.free[i]; } |
|
| 77 | + | assert RAM[OFFSET] == 0xa5; |
|
| 78 | + | let full = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 79 | + | let entry = try! capability::get(&TABLE, full); |
|
| 80 | + | assert (try! pages::get(&STORE, entry.object)).count == 8; |
|
| 81 | + | } |
|
| 82 | + | ||
| 46 | 83 | /// Transfers keep both domains exposed without duplicating page handles. |
|
| 47 | 84 | @test unsafe fn transferExposure() throws (testing::TestError) { |
|
| 48 | 85 | initialize(); |
|
| 49 | 86 | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 50 | 87 | capability::initialize(&mut TARGET, receiver); |
kernel/kernel/tests/registry.rad
+24 -0
| 27 | 27 | let pending = try registry::reserve(&mut STORE); |
|
| 28 | 28 | let slot = slots::reference(&pending); |
|
| 29 | 29 | return try registry::publish(&mut STORE, pending, bytes, package(name, dependencies, slot.index)); |
|
| 30 | 30 | } |
|
| 31 | 31 | ||
| 32 | + | /// Admission resolves dependencies before its reserved package becomes visible. |
|
| 33 | + | @test unsafe fn stagedAdmission() throws (testing::TestError) { |
|
| 34 | + | registry::initialize(&mut STORE); |
|
| 35 | + | let base = try! register("base", &[], "base bytes"); |
|
| 36 | + | let pending = try! registry::reserve(&mut STORE); |
|
| 37 | + | let object = slots::reference(&pending); |
|
| 38 | + | let admitted = try! registry::admit(&STORE, object, "child bytes", package("child", &["base"], object.index)); |
|
| 39 | + | assert STORE.slots[object.index].state == slots::State::Reserved; |
|
| 40 | + | assert registry::find(&STORE, &"child"[..]) == nil; |
|
| 41 | + | assert admitted.dependencies[0] == base; |
|
| 42 | + | let published = registry::commit(&mut STORE, pending, admitted); |
|
| 43 | + | assert published == object and STORE.counts[object.index] == 1; |
|
| 44 | + | assert STORE.dependencies[object.index][0] == base; |
|
| 45 | + | let rejected = try! registry::reserve(&mut STORE); |
|
| 46 | + | let slot = slots::reference(&rejected); |
|
| 47 | + | let mut duplicate = false; |
|
| 48 | + | try registry::admit(&STORE, slot, "bad bytes", package("bad", &["base", "base"], slot.index)) catch error { |
|
| 49 | + | assert error == abi::Error::VerifyFailed; set duplicate = true; |
|
| 50 | + | }; |
|
| 51 | + | assert duplicate and STORE.slots[slot.index].state == slots::State::Reserved; |
|
| 52 | + | registry::cancel(&mut STORE, rejected); |
|
| 53 | + | assert STORE.slots[slot.index].state == slots::State::Free; |
|
| 54 | + | } |
|
| 55 | + | ||
| 32 | 56 | /// Identical bytes reuse one package; a name cannot identify different content. |
|
| 33 | 57 | @test unsafe fn identity() throws (testing::TestError) { |
|
| 34 | 58 | registry::initialize(&mut STORE); |
|
| 35 | 59 | let first = try! register("a", &[], "binary a"); |
|
| 36 | 60 | let second = try! register("a", &[], "binary a"); |
kernel/kernel/tests/trap.rad
+1 -1
| 4 | 4 | use kernel::trap; |
|
| 5 | 5 | ||
| 6 | 6 | /// The assembly frame includes x0-x31 and all four trap CSRs. |
|
| 7 | 7 | @test fn layout() throws (testing::TestError) { |
|
| 8 | 8 | try testing::expect(@sizeOf(trap::Frame) == 288); |
|
| 9 | - | try testing::expect(@sizeOf(trap::Hart) == 56); |
|
| 9 | + | assert @sizeOf(trap::Hart) == 352; |
|
| 10 | 10 | try testing::expect(trap::FRAME_SIZE % 16 == 0); |
|
| 11 | 11 | } |
|
| 12 | 12 | ||
| 13 | 13 | /// Interrupts retain their source identity; exceptions retain their fault class. |
|
| 14 | 14 | @test fn classification() throws (testing::TestError) { |
kernel/kernel/trap.rad
+5 -1
| 32 | 32 | savedT0: u64, |
|
| 33 | 33 | /// Temporary preservation of x6 during entry. |
|
| 34 | 34 | savedT1: u64, |
|
| 35 | 35 | /// Interrupted stack pointer, before stack selection. |
|
| 36 | 36 | savedSp: u64, |
|
| 37 | + | /// Firmware stack top used while releasing and selecting context ownership. |
|
| 38 | + | dispatchTop: u64, |
|
| 39 | + | /// Hart-owned frame used after the interrupted context's stack is released. |
|
| 40 | + | dispatchFrame: Frame, |
|
| 37 | 41 | } |
|
| 38 | 42 | ||
| 39 | 43 | /// Trap classes consumed by kernel dispatch. |
|
| 40 | 44 | export union Cause: Copy { |
|
| 41 | 45 | /// Machine software interrupt. |
| 86 | 90 | ||
| 87 | 91 | /// Install trap entry for the current hart with machine interrupts disabled. |
|
| 88 | 92 | export fn install(hart: *mut Hart); |
|
| 89 | 93 | /// Restore a complete integer context through mret. The current mscratch must |
|
| 90 | 94 | /// name an installed Hart, and the frame must have machine interrupts disabled. |
|
| 91 | - | export fn resume(frame: &Frame) -> !; |
|
| 95 | + | export fn resume(frame: *Frame) -> !; |
|
| 92 | 96 | ||
| 93 | 97 | /// Disable machine interrupts and stop the current hart. |
|
| 94 | 98 | export fn halt() -> !; |
lib/std/arch/rv64/asm.rad
+2 -1
| 406 | 406 | { name: "tp", reg: rv64::TP }, |
|
| 407 | 407 | { name: "zero", reg: rv64::ZERO }, |
|
| 408 | 408 | ]; |
|
| 409 | 409 | ||
| 410 | 410 | /// Sorted CSR-name lookup table used by the assembler parser. |
|
| 411 | - | export constant CSRS: [CsrEntry; 9] = [ |
|
| 411 | + | export constant CSRS: [CsrEntry; 10] = [ |
|
| 412 | + | { name: "instret", csr: 0xC02 }, |
|
| 412 | 413 | { name: "mcause", csr: 0x342 }, |
|
| 413 | 414 | { name: "mepc", csr: 0x341 }, |
|
| 414 | 415 | { name: "mhartid", csr: 0xF14 }, |
|
| 415 | 416 | { name: "mie", csr: 0x304 }, |
|
| 416 | 417 | { name: "mip", csr: 0x344 }, |
lib/std/arch/rv64/asm/tests.rad
+6 -0
| 16 | 16 | static ASM_DATA_STORAGE: [u8; 1024] = [0; 1024]; |
|
| 17 | 17 | unsafe static ASM_STRING_POOL: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 18 | 18 | static PRINT_ARENA_STORAGE: [u8; 1024] = [0; 1024]; |
|
| 19 | 19 | static PRINT_BUFFER: [u8; 128] = [0; 128]; |
|
| 20 | 20 | ||
| 21 | + | /// The retired-instruction CSR assembles to its exact architectural encoding. |
|
| 22 | + | @test unsafe fn retiredCounter() throws (testing::TestError) { |
|
| 23 | + | let program = try assembleSource(".text;\ncsrr %a0 instret;\n"); |
|
| 24 | + | assert program.text.len == 1 and program.text[0] == 0xc0202573; |
|
| 25 | + | } |
|
| 26 | + | ||
| 21 | 27 | /// Source buffer for comment capacity tests. |
|
| 22 | 28 | static SOURCE: [u8; 4096] = [0; 4096]; |
|
| 23 | 29 | ||
| 24 | 30 | /// Long comments consume source space while leaving symbol and fixup demand small. |
|
| 25 | 31 | @test unsafe fn commentStorage() throws (testing::TestError) { |
test/dispatch/run
+9 -5
| 1 | 1 | #!/bin/sh |
|
| 2 | 2 | # Preempt and resume U-mode and M-mode work through production dispatch. |
|
| 3 | 3 | set -eu |
|
| 4 | 4 | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | fixture=${1:-dispatch} |
|
| 6 | + | harts=${2:-1} |
|
| 5 | 7 | work=$(mktemp -d) |
|
| 6 | 8 | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | 9 | mkdir "$work/spin" |
|
| 8 | - | cp test/dispatch/spin.rad "$work/spin.rad" |
|
| 10 | + | cp "test/$fixture/spin.rad" "$work/spin.rad" |
|
| 9 | 11 | cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/" |
|
| 10 | 12 | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \ |
|
| 11 | 13 | -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad" -entry spin -ril "$work" |
|
| 12 | 14 | cp kernel/kernel.rad "$work/kernel.rad" |
|
| 13 | 15 | printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad" |
|
| 14 | 16 | mkdir "$work/kernel" |
|
| 15 | 17 | cp kernel/kernel/*.rad "$work/kernel/" |
|
| 16 | - | cp test/dispatch/kernel/dispatchcheck.rad "$work/kernel/" |
|
| 18 | + | cp "test/$fixture/kernel/dispatchcheck.rad" "$work/kernel/" |
|
| 17 | 19 | length=$(wc -c < "$work/spin.ril") |
|
| 18 | 20 | printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad" |
|
| 19 | 21 | od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad" |
|
| 20 | 22 | printf '];\n' >> "$work/kernel/dispatchinput.rad" |
|
| 21 | 23 | sh test/acceptance/compile "$emulator" "$work" |
|
| 22 | - | cat test/dispatch/machine.ras kernel/kernel/*.ras > "$work/dispatch.ras" |
|
| 24 | + | cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras" |
|
| 23 | 25 | "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \ |
|
| 24 | 26 | -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64" |
|
| 25 | - | "$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/dispatch.rv64" |
|
| 26 | - | printf 'dispatch: user calls, preemption, idle gap, and retained machine continuation passed\n' |
|
| 27 | + | for count in $harts; do |
|
| 28 | + | "$emulator" -machine -harts="$count" -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64" |
|
| 29 | + | printf '%s: %s-hart execution passed\n' "$fixture" "$count" |
|
| 30 | + | done |
test/smp/kernel/dispatchcheck.rad
added
+149 -0
| 1 | + | //! One domain executing separate user contexts on every online hart. |
|
| 2 | + | use std::mem; |
|
| 3 | + | use std::arch::rv64::shared; |
|
| 4 | + | use kernel::abi; |
|
| 5 | + | use kernel::slots; |
|
| 6 | + | use kernel::backing; |
|
| 7 | + | use kernel::pages; |
|
| 8 | + | use kernel::capability; |
|
| 9 | + | use kernel::registry; |
|
| 10 | + | use kernel::loader; |
|
| 11 | + | use kernel::domains; |
|
| 12 | + | use kernel::budgets; |
|
| 13 | + | use kernel::dispatch; |
|
| 14 | + | use kernel::boot; |
|
| 15 | + | use kernel::sync; |
|
| 16 | + | use kernel::dispatchinput; |
|
| 17 | + | ||
| 18 | + | /// Bootstrap resource authority. |
|
| 19 | + | unsafe static TABLE: capability::Table = undefined; |
|
| 20 | + | /// Release/acquire barrier after fixture publication. |
|
| 21 | + | static READY: u64 = 0; |
|
| 22 | + | /// Shared user domain generation. |
|
| 23 | + | unsafe static USER: abi::Ref = undefined; |
|
| 24 | + | /// Per-hart user execution contexts. |
|
| 25 | + | unsafe static CONTEXTS: [abi::Ref; 8] = undefined; |
|
| 26 | + | /// Mapped progress counters in the user's private state. |
|
| 27 | + | static COUNTERS: u64 = 0; |
|
| 28 | + | /// Validated physical memory mapping. |
|
| 29 | + | fn memory(address: u64) -> *mut u8; |
|
| 30 | + | /// Current kernel package-state table. |
|
| 31 | + | fn kernelGp() -> u64; |
|
| 32 | + | /// M-mode entry of the completion checker. |
|
| 33 | + | fn completion() -> u64; |
|
| 34 | + | /// Write one diagnostic byte to the fixture UART. |
|
| 35 | + | fn put(byte: u8); |
|
| 36 | + | /// Finish the machine test successfully. |
|
| 37 | + | fn finish(); |
|
| 38 | + | ||
| 39 | + | /// Publish fixture state on the last initialized hart and wait on every hart. |
|
| 40 | + | export unsafe fn start(last: bool) { |
|
| 41 | + | if last { setup(); sync::storeRelease(&mut READY, 1); } |
|
| 42 | + | while sync::loadAcquire(&READY) == 0 {} |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Create one context per online hart before any dispatch can access the fixture. |
|
| 46 | + | unsafe fn setup() { |
|
| 47 | + | let pending = try! slots::reserve(&mut domains::STORE.slots[..]); |
|
| 48 | + | let owner = try! slots::commit(&mut domains::STORE.slots[..], pending); |
|
| 49 | + | capability::initialize(&mut TABLE, owner); |
|
| 50 | + | try! backing::registerDomain(&mut pages::STORE.backings, owner); |
|
| 51 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 52 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE), |
|
| 53 | + | }); |
|
| 54 | + | let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (dispatchinput::INPUT.len as u64 + 4095) / 4096); |
|
| 55 | + | let sourceEntry = try! capability::get(&TABLE, source); |
|
| 56 | + | let page = try! pages::get(&pages::STORE, sourceEntry.object); |
|
| 57 | + | let bytes = @sliceOf(memory(page.base), page.count * 4096); |
|
| 58 | + | let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]); |
|
| 59 | + | let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE, |
|
| 60 | + | loader::Request { authority, source, offset: 0, length: length as u64 }); |
|
| 61 | + | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 62 | + | let cap = try! capability::get(&TABLE, handle); |
|
| 63 | + | set USER = cap.object; |
|
| 64 | + | let mut user = try! domains::get(&domains::STORE, USER); |
|
| 65 | + | let self = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 66 | + | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE), |
|
| 67 | + | }); |
|
| 68 | + | let target = try! registry::exported(®istry::STORE, user.image, &"spin::worker"[..]); |
|
| 69 | + | let case shared::Target::Function(entry) = target else panic "worker entry"; |
|
| 70 | + | let counterTarget = try! registry::exported(®istry::STORE, user.image, &"spin::count"[..]); |
|
| 71 | + | let case shared::Target::Data(data) = counterTarget else panic "counter state"; |
|
| 72 | + | set COUNTERS = user.graph.table[data.slot] + data.offset as u64; |
|
| 73 | + | for hart in 0..8 { |
|
| 74 | + | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 75 | + | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 76 | + | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 77 | + | let permission = try! capability::get(&TABLE, stack); |
|
| 78 | + | let page = try! pages::get(&pages::STORE, permission.object); |
|
| 79 | + | let argument = @sliceOf(memory(page.base), 8); |
|
| 80 | + | for i in 0..8 { set argument[i] = ((hart as u64) >> (i as u64 * 8)) as u8; } |
|
| 81 | + | if hart == 0 { |
|
| 82 | + | try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, page.base + 4096, page.base, 8); |
|
| 83 | + | set CONTEXTS[hart] = user.initial; |
|
| 84 | + | } else { |
|
| 85 | + | set CONTEXTS[hart] = try! domains::contextCreate(&mut domains::STORE, &mut pages::STORE, ®istry::STORE, |
|
| 86 | + | &TABLE, handle, abi::ContextStart { entry, stack: page.base + 4096, args: page.base, size: 8 }); |
|
| 87 | + | } |
|
| 88 | + | } |
|
| 89 | + | let checkerHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 90 | + | let checkerCap = try! capability::get(&TABLE, checkerHandle); |
|
| 91 | + | let checker = try! domains::get(&domains::STORE, checkerCap.object); |
|
| 92 | + | set domains::STORE.records[checkerCap.object.index].state = domains::Lifecycle::Active; |
|
| 93 | + | set domains::STORE.contexts[checker.initial.index].frame.pc = completion(); |
|
| 94 | + | set domains::STORE.contexts[checker.initial.index].frame.status = 0x1880; |
|
| 95 | + | set domains::STORE.contexts[checker.initial.index].frame.registers[2] = domains::STORE.contexts[checker.initial.index].kernelStack.end; |
|
| 96 | + | set domains::STORE.contexts[checker.initial.index].frame.registers[3] = kernelGp(); |
|
| 97 | + | let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)); |
|
| 98 | + | let start = clock + 1000000; |
|
| 99 | + | let mut harts: u64 = 0; |
|
| 100 | + | for hart in 0..8 { if (boot::PLATFORM.harts & (1 << hart)) <> 0 { set harts += 1; } } |
|
| 101 | + | let end = start + 10000000 * harts * harts; |
|
| 102 | + | for hart in 0..8 { |
|
| 103 | + | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 104 | + | let budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, start, 0xffffffffffffffff); |
|
| 105 | + | let final = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, end, clock); |
|
| 106 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 107 | + | budgets::Binding { budget, domain: handle, context: CONTEXTS[hart] }, clock); |
|
| 108 | + | if hart == 0 { |
|
| 109 | + | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 110 | + | budgets::Binding { budget: final, domain: checkerHandle, context: checker.initial }, clock); |
|
| 111 | + | } |
|
| 112 | + | } |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | /// Check shared progress and context isolation after the user windows finish. |
|
| 116 | + | export unsafe fn verify() { |
|
| 117 | + | let guard = sync::enter(); |
|
| 118 | + | let mut counts: [u64; 8] = [0; 8]; |
|
| 119 | + | for hart in 0..8 { |
|
| 120 | + | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 121 | + | let value = sync::loadAcquire(memory(COUNTERS + hart as u64 * 8) as *u64); |
|
| 122 | + | set counts[hart] = value; |
|
| 123 | + | let context = try! domains::context(&domains::STORE, USER, CONTEXTS[hart]); |
|
| 124 | + | assert context.owner == USER and (context.frame.status & 0x1800) == 0; |
|
| 125 | + | assert context.hart == nil or context.hart == hart; |
|
| 126 | + | } |
|
| 127 | + | sync::leave(guard); |
|
| 128 | + | for byte in "smp completed transactions:" { put(byte); } |
|
| 129 | + | for hart in 0..8 { |
|
| 130 | + | if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; } |
|
| 131 | + | put(32); number(counts[hart]); |
|
| 132 | + | } |
|
| 133 | + | put(10); |
|
| 134 | + | for hart in 0..8 { |
|
| 135 | + | if (boot::PLATFORM.harts & (1 << hart)) <> 0 { assert counts[hart] == 16; } |
|
| 136 | + | } |
|
| 137 | + | let maximum = sync::maximum(); |
|
| 138 | + | assert maximum > 0 and maximum < 1000000; |
|
| 139 | + | for byte in "smp metadata instructions: 0x" { put(byte); } |
|
| 140 | + | number(maximum); |
|
| 141 | + | put(10); |
|
| 142 | + | finish(); |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Print one full-width hexadecimal diagnostic value. |
|
| 146 | + | fn number(value: u64) { |
|
| 147 | + | let digits = "0123456789abcdef"; |
|
| 148 | + | for i in 0..16 { put(digits[((value >> ((15 - i) as u64 * 4)) & 15) as u32]); } |
|
| 149 | + | } |
test/smp/machine.ras
added
+31 -0
| 1 | + | //! Start all initialized harts after the fixture publishes its shared domains. |
|
| 2 | + | .text; |
|
| 3 | + | call @kernel::boot::initialize; |
|
| 4 | + | call @kernel::dispatchcheck::start; |
|
| 5 | + | call @kernel::boot::run; |
|
| 6 | + | ebreak; |
|
| 7 | + | .export @kernel::dispatchcheck::memory; |
|
| 8 | + | .export @kernel::dispatchcheck::kernelGp; |
|
| 9 | + | .export @kernel::dispatchcheck::completion; |
|
| 10 | + | .export @kernel::dispatchcheck::finish; |
|
| 11 | + | .export @kernel::dispatchcheck::put; |
|
| 12 | + | @kernel::dispatchcheck::memory |
|
| 13 | + | ret; |
|
| 14 | + | @kernel::dispatchcheck::kernelGp |
|
| 15 | + | mv %a0 %gp; |
|
| 16 | + | ret; |
|
| 17 | + | @kernel::dispatchcheck::completion |
|
| 18 | + | la %a0 @complete; |
|
| 19 | + | ret; |
|
| 20 | + | @complete |
|
| 21 | + | call @kernel::dispatchcheck::verify; |
|
| 22 | + | ebreak; |
|
| 23 | + | @kernel::dispatchcheck::put |
|
| 24 | + | li %t0 0x10000000; |
|
| 25 | + | sb %a0 0(%t0); |
|
| 26 | + | ret; |
|
| 27 | + | @kernel::dispatchcheck::finish |
|
| 28 | + | li %t0 0x10001000; |
|
| 29 | + | li %t1 0x5555; |
|
| 30 | + | sw %t1 0(%t0); |
|
| 31 | + | ebreak; |
test/smp/spin.rad
added
+31 -0
| 1 | + | //! Concurrent user contexts with shared private state and separate call buffers. |
|
| 2 | + | export mod abi; |
|
| 3 | + | export mod sys; |
|
| 4 | + | ||
| 5 | + | /// Completed allocation transactions, indexed by the executing hart. |
|
| 6 | + | export static count: [u64; 8] = [0; 8]; |
|
| 7 | + | ||
| 8 | + | /// Check hart identity and shared-table allocation through repeated direct calls. |
|
| 9 | + | fn run(hart: u64) { |
|
| 10 | + | let original = sys::currentContext(); |
|
| 11 | + | assert original.hart == hart; |
|
| 12 | + | for i in 0..16 { |
|
| 13 | + | let page = try! sys::pageAllocate(abi::Handle(0), 1); |
|
| 14 | + | let info = sys::queryPage(page); |
|
| 15 | + | assert info.count == 1 and info.base <> 0; |
|
| 16 | + | try! sys::capabilityDrop(page); |
|
| 17 | + | let current = sys::currentContext(); |
|
| 18 | + | assert current.context == original.context and current.hart == hart; |
|
| 19 | + | set count[hart as u32] += 1; |
|
| 20 | + | } |
|
| 21 | + | while true {} |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Start an additional context with its physical hart index in validated memory. |
|
| 25 | + | export fn worker(args: *u64, size: u64) { |
|
| 26 | + | assert size == 8; |
|
| 27 | + | run(*args); |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// The initial context owns hart zero's window. |
|
| 31 | + | @default fn main(env: *opaque) { run(0); } |
test/sync/machine.ras
+21 -0
| 1 | 1 | //! Exercise code publication and contended metadata updates on each hart. |
|
| 2 | 2 | .constant EXPECTED HARTS * 100; |
|
| 3 | 3 | .text; |
|
| 4 | 4 | @entry |
|
| 5 | + | li %t0 0x1888; |
|
| 6 | + | csrw mstatus %t0; |
|
| 7 | + | csrr %s7 mstatus; |
|
| 8 | + | call @kernel::sync::maskInterrupts; |
|
| 9 | + | li %t0 8; |
|
| 10 | + | bne %a0 %t0 @fail; |
|
| 11 | + | csrr %t0 mstatus; |
|
| 12 | + | andi %t1 %s7 -9; |
|
| 13 | + | bne %t0 %t1 @fail; |
|
| 14 | + | call @kernel::sync::restoreInterrupts; |
|
| 15 | + | csrr %t0 mstatus; |
|
| 16 | + | bne %t0 %s7 @fail; |
|
| 17 | + | li %a0 0; |
|
| 18 | + | call @kernel::sync::restoreInterrupts; |
|
| 19 | + | csrr %t0 mstatus; |
|
| 20 | + | andi %t1 %s7 -9; |
|
| 21 | + | bne %t0 %t1 @fail; |
|
| 22 | + | call @kernel::sync::retired; |
|
| 23 | + | mv %s5 %a0; |
|
| 24 | + | call @kernel::sync::retired; |
|
| 25 | + | bgeu %s5 %a0 @fail; |
|
| 5 | 26 | csrr %s6 mhartid; |
|
| 6 | 27 | li %s0 0x40010000; |
|
| 7 | 28 | slli %s0 %s0 1; |
|
| 8 | 29 | bnez %s6 @waitCode; |
|
| 9 | 30 | li %t0 1; |