kernel: Complete cross-hart transactions
04c663fcabe11a081cb2038ad5147863668277122093799071366cec0d8b328d
Execute remote budget changes at the owning hart after saving its context. Reserve bounded readiness batches before activation or wakeup, retain preemptible callers through acknowledgement, and notify migrated timeout receivers. Validate queue capacity, stale replies, queued expiration, context retention, and atomic rejection. Native 1/2/8-hart checks cover concurrent calls, context migration, idle-hart activation and wakeup, and remote timeout delivery. Full make test passes with the sibling kernel-emulator.
1 parent
ba7c3317
kernel/kernel.rad
+1 -0
| 21 | 21 | export mod instances; |
|
| 22 | 22 | export mod domains; |
|
| 23 | 23 | export mod budgets; |
|
| 24 | 24 | export mod dispatch; |
|
| 25 | 25 | export mod timers; |
|
| 26 | + | export mod remote; |
|
| 26 | 27 | export mod calls; |
|
| 27 | 28 | export mod sys; |
|
| 28 | 29 | export mod boot; |
|
| 29 | 30 | @test export mod tests; |
kernel/kernel/boot.rad
+5 -2
| 10 | 10 | use super::registry; |
|
| 11 | 11 | use super::domains; |
|
| 12 | 12 | use super::budgets; |
|
| 13 | 13 | use super::dispatch; |
|
| 14 | 14 | use super::timers; |
|
| 15 | + | use super::remote; |
|
| 15 | 16 | use std::arch::rv64::shared::catalog; |
|
| 16 | 17 | ||
| 17 | 18 | /// Platform data published by hart zero before secondary initialization. |
|
| 18 | 19 | export unsafe static PLATFORM: platform::Platform = undefined; |
|
| 19 | 20 | /// Release/acquire publication flag for PLATFORM. |
| 23 | 24 | /// Trap anchors owned by their physical harts. |
|
| 24 | 25 | unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined; |
|
| 25 | 26 | ||
| 26 | 27 | /// Handle timer boundaries and user calls, and stop on unsupported traps. |
|
| 27 | 28 | unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) { |
|
| 28 | - | if trap::classify(frame.cause) == trap::Cause::Timer { |
|
| 29 | + | let cause = trap::classify(frame.cause); |
|
| 30 | + | if cause == trap::Cause::Timer or cause == trap::Cause::Software { |
|
| 29 | 31 | dispatch::interrupt(frame, hart); |
|
| 30 | 32 | return; |
|
| 31 | 33 | } |
|
| 32 | - | if trap::classify(frame.cause) == trap::Cause::UserCall and trap::fromUser(frame) { |
|
| 34 | + | if cause == trap::Cause::UserCall and trap::fromUser(frame) { |
|
| 33 | 35 | dispatch::call(frame, hart); |
|
| 34 | 36 | return; |
|
| 35 | 37 | } |
|
| 36 | 38 | print("kernel: unexpected trap\n"); |
|
| 37 | 39 | trap::halt(); |
| 83 | 85 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 84 | 86 | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 85 | 87 | domains::initialize(&mut domains::STORE); |
|
| 86 | 88 | budgets::initialize(&mut budgets::STORE); |
|
| 87 | 89 | timers::initialize(&mut timers::STORE); |
|
| 90 | + | remote::initialize(&mut remote::STORE, PLATFORM.harts); |
|
| 88 | 91 | assert entryCount == 2; |
|
| 89 | 92 | registry::initialize(&mut registry::STORE); |
|
| 90 | 93 | let bootCatalog = @sliceOf(entries, entryCount); |
|
| 91 | 94 | try! registry::boot(&mut registry::STORE, &bootCatalog[..]); |
|
| 92 | 95 | let library = registry::find(®istry::STORE, &"std"[..]) else panic "boot std"; |
kernel/kernel/budgets.rad
+26 -0
| 295 | 295 | /// Test half-open execution authority on the selected hart at the current tick. |
|
| 296 | 296 | export fn active(store: &Store, object: abi::Ref, hart: u32, now: u64) -> bool throws (abi::Error) { |
|
| 297 | 297 | let window = try get(store, object); |
|
| 298 | 298 | return window.context <> nil and window.hart == hart and window.start <= now and now < window.end; |
|
| 299 | 299 | } |
|
| 300 | + | ||
| 301 | + | /// Find all harts with retained authority for a receiver's live contexts. |
|
| 302 | + | export fn wakeTargets(windows: &Store, contexts: &domains::Store, receiver: abi::Ref, now: u64) -> u32 { |
|
| 303 | + | let mut targets: u32 = 0; |
|
| 304 | + | for i in 0..limits::BUDGETS { |
|
| 305 | + | if windows.slots[i].state <> slots::State::Live { |
|
| 306 | + | continue; |
|
| 307 | + | } |
|
| 308 | + | let window = windows.windows[i]; |
|
| 309 | + | if window.owner <> receiver or window.end <= now { |
|
| 310 | + | continue; |
|
| 311 | + | } |
|
| 312 | + | let context = window.context else { |
|
| 313 | + | continue; |
|
| 314 | + | }; |
|
| 315 | + | if not slots::matches(&contexts.contextSlots[..], context, slots::State::Live) { |
|
| 316 | + | continue; |
|
| 317 | + | } |
|
| 318 | + | if contexts.contexts[context.index].owner <> receiver |
|
| 319 | + | or contexts.contexts[context.index].state == domains::ContextState::Stopped { |
|
| 320 | + | continue; |
|
| 321 | + | } |
|
| 322 | + | set targets |= 1 << window.hart; |
|
| 323 | + | } |
|
| 324 | + | return targets; |
|
| 325 | + | } |
kernel/kernel/calls.rad
+94 -1
| 13 | 13 | use super::transactions; |
|
| 14 | 14 | use super::loader; |
|
| 15 | 15 | use super::dispatch; |
|
| 16 | 16 | use super::timers; |
|
| 17 | 17 | use super::sync; |
|
| 18 | + | use super::remote; |
|
| 19 | + | ||
| 20 | + | /// Resolve the owning hart for an operation that can change CPU authority. |
|
| 21 | + | unsafe fn budgetHart(table: &capability::Table, operation: u64, arguments: &[u64]) -> ?u32 throws (abi::Error) { |
|
| 22 | + | match operation { |
|
| 23 | + | case 11, 12, 70, 71, 73 => { |
|
| 24 | + | }, |
|
| 25 | + | else => return nil, |
|
| 26 | + | } |
|
| 27 | + | let entry = try capability::get(table, abi::Handle(arguments[0])); |
|
| 28 | + | if entry.kind <> abi::Kind::Budget { |
|
| 29 | + | return nil; |
|
| 30 | + | } |
|
| 31 | + | return (try budgets::get(&budgets::STORE, entry.object)).hart; |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Execute a queued budget change at its owning hart's dispatch boundary. |
|
| 35 | + | export unsafe fn remoteBudget(call: remote::Call, hart: u32, now: u64) -> remote::Reply { |
|
| 36 | + | let domain = try domains::get(&domains::STORE, call.owner) catch error { |
|
| 37 | + | return remote::Reply::Error(error); |
|
| 38 | + | }; |
|
| 39 | + | if domain.state <> domains::Lifecycle::Active { |
|
| 40 | + | return remote::Reply::Error(abi::Error::BadHandle); |
|
| 41 | + | } |
|
| 42 | + | let target = try budgetHart(&domain.memory.table, call.operation, &call.arguments[..]) catch error { |
|
| 43 | + | return remote::Reply::Error(error); |
|
| 44 | + | }; |
|
| 45 | + | if target <> hart { |
|
| 46 | + | return remote::Reply::Error(abi::Error::BadHandle); |
|
| 47 | + | } |
|
| 48 | + | let result = try invoke(call.owner, call.operation, &call.arguments[..], now) catch error { |
|
| 49 | + | return remote::Reply::Error(error); |
|
| 50 | + | }; |
|
| 51 | + | return remote::Reply::Value(result); |
|
| 52 | + | } |
|
| 18 | 53 | ||
| 19 | 54 | /// Validate resident object generations for capabilities without page ownership. |
|
| 20 | 55 | unsafe fn resident(entry: capability::Entry) throws (abi::Error) { |
|
| 21 | 56 | match entry.kind { |
|
| 22 | 57 | case abi::Kind::Domain, abi::Kind::Events => { |
| 336 | 371 | sync::leave(publicationGuard); |
|
| 337 | 372 | return *result; |
|
| 338 | 373 | } |
|
| 339 | 374 | ||
| 340 | 375 | /// Execute a call with short metadata transactions and private bulk initialization. |
|
| 341 | - | /// The running context keeps its domain storage live until dispatch releases it. |
|
| 376 | + | /// Context ownership and pending calls retain storage across dispatch boundaries. |
|
| 342 | 377 | export unsafe fn synchronized(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 343 | 378 | assert arguments.len == 4; |
|
| 379 | + | if operation == 21 or operation == 50 { |
|
| 380 | + | return try runtimeReady(owner, operation, arguments, now); |
|
| 381 | + | } |
|
| 344 | 382 | let guard = sync::enter(); |
|
| 345 | 383 | let mut domain = try domains::get(&domains::STORE, owner) catch error { |
|
| 346 | 384 | sync::leave(guard); |
|
| 347 | 385 | throw error; |
|
| 348 | 386 | }; |
|
| 349 | 387 | if domain.state <> domains::Lifecycle::Active { |
|
| 350 | 388 | sync::leave(guard); |
|
| 351 | 389 | throw abi::Error::BadHandle; |
|
| 352 | 390 | } |
|
| 391 | + | let target = try budgetHart(&domain.memory.table, operation, arguments) catch error { |
|
| 392 | + | sync::leave(guard); |
|
| 393 | + | throw error; |
|
| 394 | + | }; |
|
| 395 | + | if let destination = target { |
|
| 396 | + | let source = dispatch::hart(); |
|
| 397 | + | if destination <> source { |
|
| 398 | + | let call = remote::Call { owner, context: abi::reference(dispatch::current().context), operation, |
|
| 399 | + | arguments: [arguments[0], arguments[1], arguments[2], arguments[3]] }; |
|
| 400 | + | let request = try dispatch::request(destination, remote::Action::Budget(call)) catch error { |
|
| 401 | + | sync::leave(guard); throw error; |
|
| 402 | + | }; |
|
| 403 | + | assert domains::STORE.contexts[call.context.index].pending == nil; |
|
| 404 | + | set domains::STORE.contexts[call.context.index].pending = request; |
|
| 405 | + | sync::leave(guard); |
|
| 406 | + | return try dispatch::awaitRequest(source, request); |
|
| 407 | + | } |
|
| 408 | + | } |
|
| 353 | 409 | let handle = abi::Handle(arguments[0]); |
|
| 354 | 410 | match operation { |
|
| 355 | 411 | case 20 => { |
|
| 356 | 412 | let pending = try domains::reserve(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, |
|
| 357 | 413 | &mut domain.memory.table, handle, abi::Handle(arguments[1])) catch error { |
| 431 | 487 | sync::leave(guard); return result; |
|
| 432 | 488 | }, |
|
| 433 | 489 | } |
|
| 434 | 490 | } |
|
| 435 | 491 | ||
| 492 | + | /// Reserve remote dispatch capacity before activation or event publication. |
|
| 493 | + | unsafe fn runtimeReady(owner: abi::Ref, operation: u64, arguments: &[u64], now: u64) -> u64 throws (abi::Error) { |
|
| 494 | + | if operation == 50 and (arguments[1] > 0xffffffff or arguments[2] <> 0 or arguments[3] <> 0) { |
|
| 495 | + | throw abi::Error::InvalidArg; |
|
| 496 | + | } |
|
| 497 | + | let guard = sync::enter(); |
|
| 498 | + | let domain = try domains::get(&domains::STORE, owner) catch error { |
|
| 499 | + | sync::leave(guard); |
|
| 500 | + | throw error; |
|
| 501 | + | }; |
|
| 502 | + | if domain.state <> domains::Lifecycle::Active { |
|
| 503 | + | sync::leave(guard); |
|
| 504 | + | throw abi::Error::BadHandle; |
|
| 505 | + | } |
|
| 506 | + | let handle = abi::Handle(arguments[0]); |
|
| 507 | + | let rights = abi::EXECUTE if operation == 21 else abi::WAKE; |
|
| 508 | + | let permit = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(rights)) catch error { |
|
| 509 | + | sync::leave(guard); throw error; |
|
| 510 | + | }; |
|
| 511 | + | let source = dispatch::hart(); |
|
| 512 | + | let context = abi::reference(dispatch::current().context); |
|
| 513 | + | let targets = budgets::wakeTargets(&budgets::STORE, &domains::STORE, permit.object, now) & ~(1 << source); |
|
| 514 | + | let pending = try remote::reserve(&mut remote::STORE, source, targets) catch error { |
|
| 515 | + | sync::leave(guard); |
|
| 516 | + | throw error; |
|
| 517 | + | }; |
|
| 518 | + | let result = try invoke(owner, operation, arguments, now) catch error { |
|
| 519 | + | remote::cancelReservation(&mut remote::STORE, pending); |
|
| 520 | + | sync::leave(guard); throw error; |
|
| 521 | + | }; |
|
| 522 | + | let batch = remote::publish(&mut remote::STORE, pending, remote::Action::Ready(context)); |
|
| 523 | + | dispatch::startReady(context, &batch); |
|
| 524 | + | sync::leave(guard); |
|
| 525 | + | dispatch::finishReady(context, &batch); |
|
| 526 | + | return 0; |
|
| 527 | + | } |
|
| 528 | + | ||
| 436 | 529 | /// Complete a user ecall through the supplied execution boundary. |
|
| 437 | 530 | export unsafe fn handle(owner: abi::Ref, frame: &mut trap::Frame, now: u64, |
|
| 438 | 531 | execute: unsafe fn(abi::Ref, u64, &[u64], u64) -> u64 throws (abi::Error)) |
|
| 439 | 532 | { |
|
| 440 | 533 | assert trap::fromUser(frame) and frame.cause == 8; |
kernel/kernel/dispatch.rad
+181 -14
| 9 | 9 | use super::trap; |
|
| 10 | 10 | use super::range; |
|
| 11 | 11 | use super::calls; |
|
| 12 | 12 | use super::timers; |
|
| 13 | 13 | use super::sync; |
|
| 14 | + | use super::remote; |
|
| 14 | 15 | ||
| 15 | 16 | /// Dispatch ownership and idle state retained by one physical hart. |
|
| 16 | 17 | export record State: Copy { |
|
| 17 | 18 | /// Physical hart whose windows this state selects. |
|
| 18 | 19 | hart: u32, |
| 30 | 31 | export record Timer: Copy { |
|
| 31 | 32 | /// Shared monotonic mtime register. |
|
| 32 | 33 | clock: u64, |
|
| 33 | 34 | /// This hart's mtimecmp register. |
|
| 34 | 35 | compare: u64, |
|
| 36 | + | /// This hart's machine software-interrupt word. |
|
| 37 | + | software: u64, |
|
| 35 | 38 | } |
|
| 36 | 39 | ||
| 37 | 40 | /// Timer and dispatch state owned by one physical hart. |
|
| 38 | 41 | record Runtime: Copy { |
|
| 39 | 42 | /// Current context and retained idle frame. |
| 47 | 50 | ||
| 48 | 51 | /// Read the executing physical hart identifier. |
|
| 49 | 52 | export fn hart() -> u32; |
|
| 50 | 53 | /// Read the current machine stack pointer. |
|
| 51 | 54 | fn stackPointer() -> u64; |
|
| 55 | + | /// Trap anchor of the hart currently executing this continuation. |
|
| 56 | + | fn localAnchor() -> *mut trap::Hart; |
|
| 52 | 57 | /// Address of the M-mode idle loop. |
|
| 53 | 58 | fn idleAddress() -> u64; |
|
| 54 | - | /// Enable machine timer delivery while global interrupts remain disabled. |
|
| 59 | + | /// Enable timer and software interrupt delivery with global interrupts disabled. |
|
| 55 | 60 | fn enable(); |
|
| 56 | 61 | ||
| 57 | 62 | /// Initialize this hart's idle frame and validated timer before installing dispatch. |
|
| 58 | 63 | export unsafe fn initialize(machine: &platform::Platform, id: u32, kernelGp: u64) throws (abi::Error) { |
|
| 59 | 64 | let device = try timer(machine, id); |
| 76 | 81 | assert id < limits::HARTS; |
|
| 77 | 82 | let stack = HARTS[id].state.stack; |
|
| 78 | 83 | let pointer = stackPointer(); |
|
| 79 | 84 | assert pointer >= stack.start and pointer < stack.end; |
|
| 80 | 85 | let guard = sync::enter(); |
|
| 86 | + | writeSoftware(HARTS[id].timer.software, 0); |
|
| 87 | + | let previous = HARTS[id].state.current; |
|
| 88 | + | save(&mut HARTS[id].state, &mut domains::STORE, frame); |
|
| 89 | + | let mut deliveries: [remote::Delivery; limits::REMOTE_PER_HART] = undefined; |
|
| 90 | + | let mut count: u32 = 0; |
|
| 91 | + | for i in 0..limits::REMOTE_PER_HART { |
|
| 92 | + | let delivery = try! remote::take(&mut remote::STORE, id) else { |
|
| 93 | + | break; |
|
| 94 | + | }; |
|
| 95 | + | match delivery.request.action { |
|
| 96 | + | case remote::Action::InstructionSync => sync::syncInstructions(), |
|
| 97 | + | case remote::Action::Reschedule, remote::Action::Ready(_) => { |
|
| 98 | + | }, |
|
| 99 | + | case remote::Action::Budget(call) => { |
|
| 100 | + | let reply = calls::remoteBudget(call, id, now(HARTS[id].timer)); |
|
| 101 | + | remote::respond(&mut remote::STORE, id, delivery.id, reply); |
|
| 102 | + | }, |
|
| 103 | + | } |
|
| 104 | + | set deliveries[count] = delivery; |
|
| 105 | + | set count += 1; |
|
| 106 | + | } |
|
| 81 | 107 | let clock = now(HARTS[id].timer); |
|
| 82 | - | let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, id, clock); |
|
| 108 | + | let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, &budgets::STORE, id, clock); |
|
| 83 | 109 | let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock); |
|
| 84 | 110 | let mut deadline = choice.deadline; |
|
| 85 | - | if timeout < deadline { |
|
| 86 | - | set deadline = timeout; |
|
| 111 | + | if timeout.deadline < deadline { |
|
| 112 | + | set deadline = timeout.deadline; |
|
| 113 | + | } |
|
| 114 | + | for target in 0..limits::HARTS { |
|
| 115 | + | if target <> id and (timeout.harts & (1 << target)) <> 0 { |
|
| 116 | + | signal(target); |
|
| 117 | + | } |
|
| 87 | 118 | } |
|
| 88 | 119 | arm(HARTS[id].timer, deadline); |
|
| 120 | + | if previous <> choice.context { |
|
| 121 | + | if let context = previous { |
|
| 122 | + | signalContext(context, clock); |
|
| 123 | + | } |
|
| 124 | + | } |
|
| 125 | + | for delivery in &deliveries[..count] { |
|
| 126 | + | assert remote::acknowledge(&mut remote::STORE, id, delivery.id); |
|
| 127 | + | if delivery.request.source <> id { |
|
| 128 | + | signal(delivery.request.source); |
|
| 129 | + | } |
|
| 130 | + | if let case remote::Action::Budget(call) = delivery.request.action { |
|
| 131 | + | signalContext(call.context, clock); |
|
| 132 | + | } |
|
| 133 | + | if let case remote::Action::Ready(context) = delivery.request.action { |
|
| 134 | + | signalContext(context, clock); |
|
| 135 | + | } |
|
| 136 | + | } |
|
| 89 | 137 | sync::leave(guard); |
|
| 90 | 138 | } |
|
| 91 | 139 | ||
| 140 | + | /// Signal an initialized online hart while request metadata is serialized. |
|
| 141 | + | unsafe fn signal(target: u32) { |
|
| 142 | + | writeSoftware(HARTS[target].timer.software, 1); |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Notify the current owner or the eligible hart after a context is released. |
|
| 146 | + | unsafe fn signalContext(context: abi::Ref, now: u64) { |
|
| 147 | + | if not slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live) { |
|
| 148 | + | return; |
|
| 149 | + | } |
|
| 150 | + | if let owner = domains::STORE.contexts[context.index].hart { |
|
| 151 | + | signal(owner); |
|
| 152 | + | return; |
|
| 153 | + | } |
|
| 154 | + | for i in 0..limits::BUDGETS { |
|
| 155 | + | if budgets::STORE.slots[i].state <> slots::State::Live { |
|
| 156 | + | continue; |
|
| 157 | + | } |
|
| 158 | + | let window = budgets::STORE.windows[i]; |
|
| 159 | + | if window.context == context and window.start <= now and now < window.end { |
|
| 160 | + | signal(window.hart); |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | } |
|
| 164 | + | ||
| 165 | + | /// Write one naturally aligned CLINT software-interrupt register. |
|
| 166 | + | fn writeSoftware(address: u64, value: u32); |
|
| 167 | + | ||
| 168 | + | /// Post a local action and interrupt its target under metadata serialization. |
|
| 169 | + | export unsafe fn request(target: u32, action: remote::Action) -> abi::Ref throws (abi::Error) { |
|
| 170 | + | let id = try remote::send(&mut remote::STORE, hart(), target, action); |
|
| 171 | + | signal(target); |
|
| 172 | + | return id; |
|
| 173 | + | } |
|
| 174 | + | ||
| 175 | + | /// Retain the caller and signal every published readiness request under the lock. |
|
| 176 | + | export unsafe fn startReady(context: abi::Ref, batch: &remote::Batch) { |
|
| 177 | + | assert domains::STORE.contexts[context.index].pending == nil; |
|
| 178 | + | for target in 0..limits::HARTS { |
|
| 179 | + | if (batch.targets & (1 << target)) == 0 { |
|
| 180 | + | continue; |
|
| 181 | + | } |
|
| 182 | + | if domains::STORE.contexts[context.index].pending == nil { |
|
| 183 | + | set domains::STORE.contexts[context.index].pending = batch.ids[target]; |
|
| 184 | + | } |
|
| 185 | + | signal(target); |
|
| 186 | + | } |
|
| 187 | + | } |
|
| 188 | + | ||
| 189 | + | /// Consume every readiness acknowledgement before releasing the caller's storage. |
|
| 190 | + | export unsafe fn finishReady(context: abi::Ref, batch: &remote::Batch) { |
|
| 191 | + | for target in 0..limits::HARTS { |
|
| 192 | + | if (batch.targets & (1 << target)) == 0 { |
|
| 193 | + | continue; |
|
| 194 | + | } |
|
| 195 | + | waitRequest(batch.source, batch.ids[target]); |
|
| 196 | + | } |
|
| 197 | + | let guard = sync::enter(); |
|
| 198 | + | assert slots::matches(&domains::STORE.contextSlots[..], context, slots::State::Live); |
|
| 199 | + | for target in 0..limits::HARTS { |
|
| 200 | + | if (batch.targets & (1 << target)) == 0 { |
|
| 201 | + | continue; |
|
| 202 | + | } |
|
| 203 | + | assert try! remote::result(&remote::STORE, batch.source, batch.ids[target]) == 0; |
|
| 204 | + | try! remote::release(&mut remote::STORE, batch.source, batch.ids[target]); |
|
| 205 | + | } |
|
| 206 | + | set domains::STORE.contexts[context.index].pending = nil; |
|
| 207 | + | sync::leave(guard); |
|
| 208 | + | } |
|
| 209 | + | ||
| 210 | + | /// Wait for an interrupt with global interrupts masked, then enable delivery. |
|
| 211 | + | fn waitInterrupt(); |
|
| 212 | + | ||
| 213 | + | /// Release a completed call's context retention and request generation. |
|
| 214 | + | unsafe fn releaseRequest(source: u32, request: abi::Ref) { |
|
| 215 | + | if let case remote::Action::Budget(call) = remote::STORE.requests[request.index].action { |
|
| 216 | + | assert slots::matches(&domains::STORE.contextSlots[..], call.context, slots::State::Live); |
|
| 217 | + | assert domains::STORE.contexts[call.context.index].pending == request; |
|
| 218 | + | set domains::STORE.contexts[call.context.index].pending = nil; |
|
| 219 | + | } |
|
| 220 | + | try! remote::release(&mut remote::STORE, source, request); |
|
| 221 | + | } |
|
| 222 | + | ||
| 223 | + | /// Await acknowledgement while retaining the record and allowing preemption. |
|
| 224 | + | unsafe fn waitRequest(source: u32, request: abi::Ref) { |
|
| 225 | + | let interrupts = sync::maskInterrupts(); |
|
| 226 | + | loop { |
|
| 227 | + | let guard = sync::enter(); |
|
| 228 | + | let complete = try! remote::completed(&remote::STORE, source, request); |
|
| 229 | + | sync::leave(guard); |
|
| 230 | + | if complete { |
|
| 231 | + | break; |
|
| 232 | + | } |
|
| 233 | + | waitInterrupt(); |
|
| 234 | + | sync::maskInterrupts(); |
|
| 235 | + | } |
|
| 236 | + | sync::restoreInterrupts(interrupts); |
|
| 237 | + | } |
|
| 238 | + | ||
| 239 | + | /// Await a retained reply through a preemptible kernel continuation. |
|
| 240 | + | export unsafe fn awaitRequest(source: u32, request: abi::Ref) -> u64 throws (abi::Error) { |
|
| 241 | + | waitRequest(source, request); |
|
| 242 | + | let guard = sync::enter(); |
|
| 243 | + | let result = try remote::result(&remote::STORE, source, request) catch error { |
|
| 244 | + | releaseRequest(source, request); |
|
| 245 | + | sync::leave(guard); throw error; |
|
| 246 | + | }; |
|
| 247 | + | releaseRequest(source, request); |
|
| 248 | + | sync::leave(guard); return result; |
|
| 249 | + | } |
|
| 250 | + | ||
| 92 | 251 | /// Read the running context's identity and domain-relative CPU authority. |
|
| 93 | 252 | export unsafe fn current() -> abi::CurrentContextInfo { |
|
| 94 | 253 | let id = hart(); |
|
| 95 | 254 | assert id < limits::HARTS; |
|
| 96 | 255 | let context = HARTS[id].state.current else panic "no current context"; |
| 113 | 272 | sync::leave(guard); |
|
| 114 | 273 | } else { |
|
| 115 | 274 | sync::leave(guard); |
|
| 116 | 275 | calls::handle(owner, frame, now(HARTS[id].timer), calls::synchronized); |
|
| 117 | 276 | } |
|
| 118 | - | interrupt(frame, anchor); |
|
| 277 | + | interrupt(frame, &mut *localAnchor()); |
|
| 119 | 278 | } |
|
| 120 | 279 | ||
| 121 | 280 | /// Enter the first authorized context or the hart's retained idle frame. |
|
| 122 | 281 | export unsafe fn start(anchor: &mut trap::Hart) -> ! { |
|
| 123 | 282 | let id = hart(); |
| 145 | 304 | } |
|
| 146 | 305 | if result <> nil or device.memory.end < device.memory.start |
|
| 147 | 306 | or device.memory.end - device.memory.start < 0xc000 or (device.memory.start & 7) <> 0 { |
|
| 148 | 307 | throw abi::Error::InvalidArg; |
|
| 149 | 308 | } |
|
| 150 | - | set result = Timer { clock: device.memory.start + 0xbff8, compare: device.memory.start + 0x4000 + hart as u64 * 8 }; |
|
| 309 | + | set result = Timer { |
|
| 310 | + | clock: device.memory.start + 0xbff8, compare: device.memory.start + 0x4000 + hart as u64 * 8, |
|
| 311 | + | software: device.memory.start + hart as u64 * 4, |
|
| 312 | + | }; |
|
| 151 | 313 | } |
|
| 152 | 314 | let selected = result else { |
|
| 153 | 315 | throw abi::Error::InvalidArg; |
|
| 154 | 316 | }; |
|
| 155 | 317 | return selected; |
|
| 156 | 318 | } |
|
| 157 | 319 | ||
| 158 | 320 | /// Read the platform's monotonic clock in mtime ticks. |
|
| 159 | - | export fn now(timer: Timer) -> u64 { |
|
| 160 | - | return read(timer.clock); |
|
| 161 | - | } |
|
| 321 | + | export fn now(timer: Timer) -> u64 { return read(timer.clock); } |
|
| 162 | 322 | ||
| 163 | 323 | /// Program the next boundary while this hart's timer handling is serialized. |
|
| 164 | 324 | export fn arm(timer: Timer, deadline: u64) { |
|
| 165 | 325 | write(timer.compare, deadline); |
|
| 166 | 326 | } |
| 240 | 400 | /// Interrupts remain disabled and shared metadata stays serialized through exchange. |
|
| 241 | 401 | export fn exchange(state: &mut State, windows: &budgets::Store, contexts: &mut domains::Store, |
|
| 242 | 402 | frame: &mut trap::Frame, anchor: &mut trap::Hart, now: u64) -> Choice throws (abi::Error) |
|
| 243 | 403 | { |
|
| 244 | 404 | let choice = try select(windows, contexts, state.hart, now); |
|
| 245 | - | if let current = state.current { |
|
| 246 | - | assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live); |
|
| 247 | - | set contexts.contexts[current.index].frame = *frame; |
|
| 248 | - | set contexts.contexts[current.index].hart = nil; |
|
| 249 | - | } |
|
| 405 | + | save(state, contexts, frame); |
|
| 250 | 406 | if let next = choice.context { |
|
| 251 | 407 | let context = contexts.contexts[next.index]; |
|
| 252 | 408 | assert context.kernelStack.start < context.kernelStack.end; |
|
| 253 | 409 | set *frame = context.frame; |
|
| 254 | 410 | set anchor.stackBottom = context.kernelStack.start; |
| 261 | 417 | } |
|
| 262 | 418 | set state.current = choice.context; |
|
| 263 | 419 | set state.budget = choice.budget; |
|
| 264 | 420 | return choice; |
|
| 265 | 421 | } |
|
| 422 | + | ||
| 423 | + | /// Retain a context frame before releasing this hart's execution ownership. |
|
| 424 | + | fn save(state: &mut State, contexts: &mut domains::Store, frame: &trap::Frame) { |
|
| 425 | + | if let current = state.current { |
|
| 426 | + | assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live); |
|
| 427 | + | set contexts.contexts[current.index].frame = *frame; |
|
| 428 | + | set contexts.contexts[current.index].hart = nil; |
|
| 429 | + | } |
|
| 430 | + | set state.current = nil; |
|
| 431 | + | set state.budget = nil; |
|
| 432 | + | } |
kernel/kernel/dispatch.ras
+22 -1
| 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 | + | .export @kernel::dispatch::writeSoftware; |
|
| 5 | 6 | .export @kernel::dispatch::hart; |
|
| 6 | 7 | .export @kernel::dispatch::stackPointer; |
|
| 8 | + | .export @kernel::dispatch::localAnchor; |
|
| 7 | 9 | .export @kernel::dispatch::idleAddress; |
|
| 8 | 10 | .export @kernel::dispatch::enable; |
|
| 9 | 11 | .export @kernel::dispatch::interrupt; |
|
| 12 | + | .export @kernel::dispatch::waitInterrupt; |
|
| 13 | + | ||
| 14 | + | // A pending enabled interrupt wakes wfi while global delivery is masked. |
|
| 15 | + | @kernel::dispatch::waitInterrupt |
|
| 16 | + | wfi; |
|
| 17 | + | csrsi mstatus 8; |
|
| 18 | + | ret; |
|
| 10 | 19 | ||
| 11 | 20 | // Retain the frame outside the context stack before publishing its release. |
|
| 12 | 21 | @kernel::dispatch::interrupt |
|
| 13 | 22 | mv %t0 %a0; |
|
| 14 | 23 | addi %t1 %a1 64; |
| 35 | 44 | // Dispatch releases context stacks only from the hart's firmware stack. |
|
| 36 | 45 | @kernel::dispatch::stackPointer |
|
| 37 | 46 | mv %a0 %sp; |
|
| 38 | 47 | ret; |
|
| 39 | 48 | ||
| 49 | + | // A resumed kernel continuation uses its executing hart's trap storage. |
|
| 50 | + | @kernel::dispatch::localAnchor |
|
| 51 | + | csrr %a0 mscratch; |
|
| 52 | + | ret; |
|
| 53 | + | ||
| 40 | 54 | // Saved idle frames enter this loop with machine interrupts enabled by mret. |
|
| 41 | 55 | @kernel::dispatch::idleAddress |
|
| 42 | 56 | la %a0 @dispatchIdle; |
|
| 43 | 57 | ret; |
|
| 44 | 58 | @dispatchIdle |
| 46 | 60 | j @dispatchIdle; |
|
| 47 | 61 | ||
| 48 | 62 | // Global interrupt enable is restored from the selected frame by mret. |
|
| 49 | 63 | @kernel::dispatch::enable |
|
| 50 | 64 | csrr %t0 mie; |
|
| 51 | - | ori %t0 %t0 128; |
|
| 65 | + | ori %t0 %t0 136; |
|
| 52 | 66 | csrw mie %t0; |
|
| 53 | 67 | ret; |
|
| 54 | 68 | ||
| 55 | 69 | // The validated CLINT mapping contains a naturally aligned mtime register. |
|
| 56 | 70 | @kernel::dispatch::read |
| 63 | 77 | @kernel::dispatch::write |
|
| 64 | 78 | fence iorw iorw; |
|
| 65 | 79 | sd %a1 0(%a0); |
|
| 66 | 80 | fence iorw iorw; |
|
| 67 | 81 | ret; |
|
| 82 | + | ||
| 83 | + | // CLINT software-interrupt registers are naturally aligned 32-bit words. |
|
| 84 | + | @kernel::dispatch::writeSoftware |
|
| 85 | + | fence iorw iorw; |
|
| 86 | + | sw %a1 0(%a0); |
|
| 87 | + | fence iorw iorw; |
|
| 88 | + | ret; |
kernel/kernel/domains.rad
+5 -3
| 56 | 56 | owner: abi::Ref, |
|
| 57 | 57 | /// Whether this context can resume. |
|
| 58 | 58 | state: ContextState, |
|
| 59 | 59 | /// Hart retaining this context as its current execution owner. |
|
| 60 | 60 | hart: ?u32, |
|
| 61 | + | /// Remote call whose retained continuation requires this context's storage. |
|
| 62 | + | pending: ?abi::Ref, |
|
| 61 | 63 | /// Physical frames retained until this context stops. |
|
| 62 | 64 | kernelFrames: frames::Run, |
|
| 63 | 65 | /// Exclusive stack bounds used by trap entry and suspended M-mode calls. |
|
| 64 | 66 | kernelStack: range::Range, |
|
| 65 | 67 | /// Exclusive user stack extent; empty until initial activation. |
| 204 | 206 | let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY)); |
|
| 205 | 207 | let target = try context(store, permit.object, object); |
|
| 206 | 208 | if store.records[permit.object.index].initial == object { |
|
| 207 | 209 | throw abi::Error::Denied; |
|
| 208 | 210 | } |
|
| 209 | - | if target.hart <> nil { |
|
| 211 | + | if target.hart <> nil or target.pending <> nil { |
|
| 210 | 212 | throw abi::Error::Busy; |
|
| 211 | 213 | } |
|
| 212 | 214 | try! frames::release(&mut memory.backings.pool, target.kernelFrames); |
|
| 213 | 215 | try! slots::release(&mut store.contextSlots[..], object); |
|
| 214 | 216 | } |
| 345 | 347 | set frame.registers[3] = domain.graph.table.ptr as u64; |
|
| 346 | 348 | set frame.registers[10] = start.args; |
|
| 347 | 349 | set frame.registers[11] = start.size; |
|
| 348 | 350 | let object = slots::reference(&slot); |
|
| 349 | 351 | set store.contexts[object.index] = Context { |
|
| 350 | - | owner: permission.object, state: ContextState::Ready, hart: nil, kernelFrames, kernelStack, userStack, frame, |
|
| 352 | + | owner: permission.object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames, kernelStack, userStack, frame, |
|
| 351 | 353 | }; |
|
| 352 | 354 | match slot { |
|
| 353 | 355 | case slots::Reservation::Held(object) => return ContextReservation::Held(ContextAllocation { |
|
| 354 | 356 | object, owner: permission.object, frames: kernelFrames, base: kernelStack.start, |
|
| 355 | 357 | }), |
| 571 | 573 | // MPIE enables interrupts after the first return into U-mode. |
|
| 572 | 574 | let mut frame = trap::Frame { registers: [0; 32], pc: creation.entry, status: 0x80, cause: 0, value: 0 }; |
|
| 573 | 575 | set frame.registers[1] = returnAddress(); |
|
| 574 | 576 | set frame.registers[3] = graph.table.ptr as u64; |
|
| 575 | 577 | set store.contexts[initial.index] = Context { |
|
| 576 | - | owner: object, state: ContextState::Ready, hart: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 578 | + | owner: object, state: ContextState::Ready, hart: nil, pending: nil, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, |
|
| 577 | 579 | userStack: range::Range { start: 0, end: 0 }, frame, |
|
| 578 | 580 | }; |
|
| 579 | 581 | set store.records[object.index] = Domain { |
|
| 580 | 582 | state: Lifecycle::Pending, creator: creation.owner, parent: creation.owner, image: creation.image, initial, |
|
| 581 | 583 | allocation: prepared.allocation, memory: prepared.memory, graph, events: queue, |
kernel/kernel/limits.rad
+4 -0
| 22 | 22 | export constant CONTEXTS: u32 = 2048; |
|
| 23 | 23 | /// Maximum hardware threads. |
|
| 24 | 24 | export constant HARTS: u32 = 8; |
|
| 25 | 25 | /// One-shot timeouts retained by each hart, including pending delivery. |
|
| 26 | 26 | export constant TIMEOUTS_PER_HART: u32 = 64; |
|
| 27 | + | /// Outstanding cross-hart requests, including acknowledgements awaiting release. |
|
| 28 | + | export constant REMOTE_REQUESTS: u32 = 128; |
|
| 29 | + | /// Queued remote requests accepted by each hart. |
|
| 30 | + | export constant REMOTE_PER_HART: u32 = 16; |
|
| 27 | 31 | /// Maximum resident package records and gp table entries. |
|
| 28 | 32 | export constant PACKAGES: u32 = 256; |
|
| 29 | 33 | /// Generation values never wrap. |
|
| 30 | 34 | export constant LAST_GENERATION: u32 = 0xffffffff; |
kernel/kernel/remote.rad
added
+317 -0
| 1 | + | //! Fixed request queues and generation-checked completion records. |
|
| 2 | + | use super::abi; |
|
| 3 | + | use super::limits; |
|
| 4 | + | use super::slots; |
|
| 5 | + | ||
| 6 | + | /// A scalar operation retained until its owning hart reaches dispatch. |
|
| 7 | + | export record Call: Copy { |
|
| 8 | + | /// Domain whose capability table supplies the arguments. |
|
| 9 | + | owner: abi::Ref, |
|
| 10 | + | /// Context whose kernel continuation consumes the result. |
|
| 11 | + | context: abi::Ref, |
|
| 12 | + | /// Budget operation to execute with serialized metadata. |
|
| 13 | + | operation: u64, |
|
| 14 | + | /// Original scalar syscall arguments. |
|
| 15 | + | arguments: [u64; 4], |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | /// Result retained with a request's acknowledgement. |
|
| 19 | + | export union Reply: Copy { |
|
| 20 | + | /// Successful scalar return value. |
|
| 21 | + | Value(u64), |
|
| 22 | + | /// Operation failure with no partial commit. |
|
| 23 | + | Error(abi::Error), |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Work performed by the receiving hart before acknowledgement. |
|
| 27 | + | export union Action: Copy { |
|
| 28 | + | /// Refresh dispatch eligibility and its timer boundary. |
|
| 29 | + | Reschedule, |
|
| 30 | + | /// Synchronize local instruction fetch before dispatch. |
|
| 31 | + | InstructionSync, |
|
| 32 | + | /// Execute a budget operation after releasing local context ownership. |
|
| 33 | + | Budget(Call), |
|
| 34 | + | /// Refresh domain eligibility and notify the waiting call's context. |
|
| 35 | + | Ready(abi::Ref), |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | /// Request delivery state retained until the source releases it. |
|
| 39 | + | export union State: Copy { |
|
| 40 | + | /// The request is queued for its target. |
|
| 41 | + | Pending, |
|
| 42 | + | /// The target has taken the request. |
|
| 43 | + | Delivered, |
|
| 44 | + | /// The target completed the requested work. |
|
| 45 | + | Acknowledged, |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | /// One request owned by a source hart. |
|
| 49 | + | export record Request: Copy { |
|
| 50 | + | /// Hart that must release the completion record. |
|
| 51 | + | source: u32, |
|
| 52 | + | /// Hart that performs and acknowledges the action. |
|
| 53 | + | target: u32, |
|
| 54 | + | /// Required local action. |
|
| 55 | + | action: Action, |
|
| 56 | + | /// Current delivery state. |
|
| 57 | + | state: State, |
|
| 58 | + | /// Scalar result available after acknowledgement. |
|
| 59 | + | reply: Reply, |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Bounded FIFO whose indices remain within its fixed array. |
|
| 63 | + | export record Queue: Copy { |
|
| 64 | + | /// Generation-bearing request references in delivery order. |
|
| 65 | + | entries: [abi::Ref; limits::REMOTE_PER_HART], |
|
| 66 | + | /// Next entry to consume. |
|
| 67 | + | read: u32, |
|
| 68 | + | /// Next entry to produce. |
|
| 69 | + | write: u32, |
|
| 70 | + | /// Number of occupied entries, including cancelled references. |
|
| 71 | + | count: u32, |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Remote request metadata serialized by the shared kernel lock. |
|
| 75 | + | export record Store: Copy { |
|
| 76 | + | /// Online physical hart mask fixed at boot. |
|
| 77 | + | online: u32, |
|
| 78 | + | /// Request generations retained through acknowledgement. |
|
| 79 | + | slots: [slots::Slot; limits::REMOTE_REQUESTS], |
|
| 80 | + | /// Live request payloads. |
|
| 81 | + | requests: [Request; limits::REMOTE_REQUESTS], |
|
| 82 | + | /// FIFO owned by each receiving hart. |
|
| 83 | + | queues: [Queue; limits::HARTS], |
|
| 84 | + | } |
|
| 85 | + | ||
| 86 | + | /// Global cross-hart request storage. |
|
| 87 | + | export unsafe static STORE: Store = undefined; |
|
| 88 | + | ||
| 89 | + | /// Delivery identity and immutable routing information. |
|
| 90 | + | export record Delivery: Copy { |
|
| 91 | + | /// Live request generation to acknowledge. |
|
| 92 | + | id: abi::Ref, |
|
| 93 | + | /// Routing, action, and delivered state. |
|
| 94 | + | request: Request, |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// One completion reference for each hart selected by a bounded request batch. |
|
| 98 | + | export record Batch: Copy { |
|
| 99 | + | /// Hart that owns and releases all completion records. |
|
| 100 | + | source: u32, |
|
| 101 | + | /// Target hart mask; only these reference array elements are initialized. |
|
| 102 | + | targets: u32, |
|
| 103 | + | /// Request references indexed by target hart. |
|
| 104 | + | ids: [abi::Ref; limits::HARTS], |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | /// Reserved records whose queue capacity stays protected by the metadata lock. |
|
| 108 | + | export union Reservation: Once { |
|
| 109 | + | /// Batch to publish or cancel before releasing metadata serialization. |
|
| 110 | + | Held(Batch), |
|
| 111 | + | } |
|
| 112 | + | ||
| 113 | + | /// Reserve every target before any request or associated state becomes visible. |
|
| 114 | + | export unsafe fn reserve(store: &mut Store, source: u32, targets: u32) -> Reservation throws (abi::Error) { |
|
| 115 | + | try hart(store, source); |
|
| 116 | + | if (targets & ~store.online) <> 0 { |
|
| 117 | + | throw abi::Error::InvalidArg; |
|
| 118 | + | } |
|
| 119 | + | let mut count: u32 = 0; |
|
| 120 | + | for target in 0..limits::HARTS { |
|
| 121 | + | if (targets & (1 << target)) == 0 { |
|
| 122 | + | continue; |
|
| 123 | + | } |
|
| 124 | + | if store.queues[target].count == limits::REMOTE_PER_HART { |
|
| 125 | + | throw abi::Error::Busy; |
|
| 126 | + | } |
|
| 127 | + | set count += 1; |
|
| 128 | + | } |
|
| 129 | + | let mut free: u32 = 0; |
|
| 130 | + | for slot in &store.slots[..] { |
|
| 131 | + | if slot.state == slots::State::Free { |
|
| 132 | + | set free += 1; |
|
| 133 | + | } |
|
| 134 | + | } |
|
| 135 | + | if free < count { |
|
| 136 | + | throw abi::Error::Busy; |
|
| 137 | + | } |
|
| 138 | + | let mut batch = Batch { source, targets, ids: undefined }; |
|
| 139 | + | for target in 0..limits::HARTS { |
|
| 140 | + | if (targets & (1 << target)) == 0 { |
|
| 141 | + | continue; |
|
| 142 | + | } |
|
| 143 | + | let pending = try! slots::reserve(&mut store.slots[..]); |
|
| 144 | + | match pending { |
|
| 145 | + | case slots::Reservation::Held(id) => { |
|
| 146 | + | set batch.ids[target] = id; |
|
| 147 | + | }, |
|
| 148 | + | } |
|
| 149 | + | } |
|
| 150 | + | return Reservation::Held(batch); |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Return all unpublished records while the queue-capacity lock remains held. |
|
| 154 | + | export fn cancelReservation(store: &mut Store, reservation: Reservation) { |
|
| 155 | + | match reservation { |
|
| 156 | + | case Reservation::Held(batch) => { |
|
| 157 | + | for target in 0..limits::HARTS { |
|
| 158 | + | if (batch.targets & (1 << target)) == 0 { |
|
| 159 | + | continue; |
|
| 160 | + | } |
|
| 161 | + | try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(batch.ids[target])); |
|
| 162 | + | } |
|
| 163 | + | }, |
|
| 164 | + | } |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | /// Publish the reserved batch before releasing metadata serialization. |
|
| 168 | + | export fn publish(store: &mut Store, reservation: Reservation, action: Action) -> Batch { |
|
| 169 | + | match reservation { |
|
| 170 | + | case Reservation::Held(batch) => { |
|
| 171 | + | for target in 0..limits::HARTS { |
|
| 172 | + | if (batch.targets & (1 << target)) == 0 { |
|
| 173 | + | continue; |
|
| 174 | + | } |
|
| 175 | + | let id = batch.ids[target]; |
|
| 176 | + | set store.requests[id.index] = Request { |
|
| 177 | + | source: batch.source, target, action, state: State::Pending, reply: Reply::Value(0), |
|
| 178 | + | }; |
|
| 179 | + | enqueue(store, target, id); |
|
| 180 | + | let live = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(id)); |
|
| 181 | + | } |
|
| 182 | + | return batch; |
|
| 183 | + | }, |
|
| 184 | + | } |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// Append a generation reference to a queue with reserved capacity. |
|
| 188 | + | fn enqueue(store: &mut Store, target: u32, id: abi::Ref) { |
|
| 189 | + | assert store.queues[target].count < limits::REMOTE_PER_HART; |
|
| 190 | + | let index = store.queues[target].write; |
|
| 191 | + | set store.queues[target].entries[index] = id; |
|
| 192 | + | set store.queues[target].write = (index + 1) % limits::REMOTE_PER_HART; |
|
| 193 | + | set store.queues[target].count += 1; |
|
| 194 | + | } |
|
| 195 | + | ||
| 196 | + | /// Initialize all request capacity before secondary harts start. |
|
| 197 | + | export fn initialize(store: &mut Store, online: u32) { |
|
| 198 | + | assert online <> 0 and (online >> limits::HARTS) == 0; |
|
| 199 | + | set store.online = online; |
|
| 200 | + | slots::initialize(&mut store.slots[..]); |
|
| 201 | + | for hart in 0..limits::HARTS { |
|
| 202 | + | set store.queues[hart].read = 0; |
|
| 203 | + | set store.queues[hart].write = 0; |
|
| 204 | + | set store.queues[hart].count = 0; |
|
| 205 | + | } |
|
| 206 | + | } |
|
| 207 | + | ||
| 208 | + | /// Check an online hart before indexing its mailbox. |
|
| 209 | + | fn hart(store: &Store, id: u32) throws (abi::Error) { |
|
| 210 | + | if id >= limits::HARTS or (store.online & (1 << id)) == 0 { |
|
| 211 | + | throw abi::Error::InvalidArg; |
|
| 212 | + | } |
|
| 213 | + | } |
|
| 214 | + | ||
| 215 | + | /// Enqueue a request atomically under metadata serialization. |
|
| 216 | + | export fn send(store: &mut Store, source: u32, target: u32, action: Action) -> abi::Ref throws (abi::Error) { |
|
| 217 | + | try hart(store, source); try hart(store, target); |
|
| 218 | + | if store.queues[target].count == limits::REMOTE_PER_HART { |
|
| 219 | + | throw abi::Error::Busy; |
|
| 220 | + | } |
|
| 221 | + | let pending = try slots::reserve(&mut store.slots[..]) catch { |
|
| 222 | + | throw abi::Error::Busy; |
|
| 223 | + | }; |
|
| 224 | + | let id = slots::reference(&pending); |
|
| 225 | + | set store.requests[id.index] = Request { source, target, action, state: State::Pending, reply: Reply::Value(0) }; |
|
| 226 | + | enqueue(store, target, id); |
|
| 227 | + | return try! slots::commit(&mut store.slots[..], pending); |
|
| 228 | + | } |
|
| 229 | + | ||
| 230 | + | /// Take the next live request, discarding stale or completed queue references. |
|
| 231 | + | export fn take(store: &mut Store, target: u32) -> ?Delivery throws (abi::Error) { |
|
| 232 | + | try hart(store, target); |
|
| 233 | + | for i in 0..limits::REMOTE_PER_HART { |
|
| 234 | + | if store.queues[target].count == 0 { |
|
| 235 | + | return nil; |
|
| 236 | + | } |
|
| 237 | + | let index = store.queues[target].read; |
|
| 238 | + | let id = store.queues[target].entries[index]; |
|
| 239 | + | set store.queues[target].read = (index + 1) % limits::REMOTE_PER_HART; |
|
| 240 | + | set store.queues[target].count -= 1; |
|
| 241 | + | if not slots::matches(&store.slots[..], id, slots::State::Live) { |
|
| 242 | + | continue; |
|
| 243 | + | } |
|
| 244 | + | let request = store.requests[id.index]; |
|
| 245 | + | if request.target <> target or request.state <> State::Pending { |
|
| 246 | + | continue; |
|
| 247 | + | } |
|
| 248 | + | set store.requests[id.index].state = State::Delivered; |
|
| 249 | + | return Delivery { id, request: store.requests[id.index] }; |
|
| 250 | + | } |
|
| 251 | + | return nil; |
|
| 252 | + | } |
|
| 253 | + | ||
| 254 | + | /// Acknowledge one delivered generation exactly once under metadata serialization. |
|
| 255 | + | export fn acknowledge(store: &mut Store, target: u32, id: abi::Ref) -> bool { |
|
| 256 | + | if not slots::matches(&store.slots[..], id, slots::State::Live) { |
|
| 257 | + | return false; |
|
| 258 | + | } |
|
| 259 | + | let request = store.requests[id.index]; |
|
| 260 | + | if request.target <> target or request.state <> State::Delivered { |
|
| 261 | + | return false; |
|
| 262 | + | } |
|
| 263 | + | set store.requests[id.index].state = State::Acknowledged; |
|
| 264 | + | return true; |
|
| 265 | + | } |
|
| 266 | + | ||
| 267 | + | /// Resolve a live request through its issuing hart. |
|
| 268 | + | fn owned(store: &Store, source: u32, id: abi::Ref) -> Request throws (abi::Error) { |
|
| 269 | + | if not slots::matches(&store.slots[..], id, slots::State::Live) { |
|
| 270 | + | throw abi::Error::BadHandle; |
|
| 271 | + | } |
|
| 272 | + | let request = store.requests[id.index]; |
|
| 273 | + | if request.source <> source { |
|
| 274 | + | throw abi::Error::BadHandle; |
|
| 275 | + | } |
|
| 276 | + | return request; |
|
| 277 | + | } |
|
| 278 | + | ||
| 279 | + | /// Test completion while retaining the acknowledgement record. |
|
| 280 | + | export fn completed(store: &Store, source: u32, id: abi::Ref) -> bool throws (abi::Error) { |
|
| 281 | + | return (try owned(store, source, id)).state == State::Acknowledged; |
|
| 282 | + | } |
|
| 283 | + | ||
| 284 | + | /// Store a result before the receiving hart acknowledges its delivery. |
|
| 285 | + | export fn respond(store: &mut Store, target: u32, id: abi::Ref, reply: Reply) { |
|
| 286 | + | assert slots::matches(&store.slots[..], id, slots::State::Live); |
|
| 287 | + | assert store.requests[id.index].target == target and store.requests[id.index].state == State::Delivered; |
|
| 288 | + | set store.requests[id.index].reply = reply; |
|
| 289 | + | } |
|
| 290 | + | ||
| 291 | + | /// Read a completed operation's result without releasing its record. |
|
| 292 | + | export fn result(store: &Store, source: u32, id: abi::Ref) -> u64 throws (abi::Error) { |
|
| 293 | + | let request = try owned(store, source, id); |
|
| 294 | + | if request.state <> State::Acknowledged { |
|
| 295 | + | throw abi::Error::Busy; |
|
| 296 | + | } |
|
| 297 | + | match request.reply { |
|
| 298 | + | case Reply::Value(value) => return value, |
|
| 299 | + | case Reply::Error(error) => throw error, |
|
| 300 | + | } |
|
| 301 | + | } |
|
| 302 | + | ||
| 303 | + | /// Release an acknowledged request and advance its generation. |
|
| 304 | + | export fn release(store: &mut Store, source: u32, id: abi::Ref) throws (abi::Error) { |
|
| 305 | + | if (try owned(store, source, id)).state <> State::Acknowledged { |
|
| 306 | + | throw abi::Error::Busy; |
|
| 307 | + | } |
|
| 308 | + | try! slots::release(&mut store.slots[..], id); |
|
| 309 | + | } |
|
| 310 | + | ||
| 311 | + | /// Cancel queued work or discard a completed acknowledgement. |
|
| 312 | + | export fn cancel(store: &mut Store, source: u32, id: abi::Ref) throws (abi::Error) { |
|
| 313 | + | if (try owned(store, source, id)).state == State::Delivered { |
|
| 314 | + | throw abi::Error::Busy; |
|
| 315 | + | } |
|
| 316 | + | try! slots::release(&mut store.slots[..], id); |
|
| 317 | + | } |
kernel/kernel/tests.rad
+1 -0
| 18 | 18 | export mod domains; |
|
| 19 | 19 | export mod budgets; |
|
| 20 | 20 | export mod dispatch; |
|
| 21 | 21 | export mod calls; |
|
| 22 | 22 | export mod timers; |
|
| 23 | + | export mod remote; |
kernel/kernel/tests/budgets.rad
+1 -1
| 193 | 193 | let child = slots::reference(&childSlot); |
|
| 194 | 194 | capability::initialize(&mut MEMORY.table, child); |
|
| 195 | 195 | let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]); |
|
| 196 | 196 | let context = slots::reference(&contextSlot); |
|
| 197 | 197 | set DOMAINS.contexts[context.index] = domains::Context { |
|
| 198 | - | owner: child, state: domains::ContextState::Ready, hart: nil, |
|
| 198 | + | owner: child, state: domains::ContextState::Ready, hart: nil, pending: nil, |
|
| 199 | 199 | kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 }, |
|
| 200 | 200 | userStack: range::Range { start: 0, end: 0 }, |
|
| 201 | 201 | frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 }, |
|
| 202 | 202 | }; |
|
| 203 | 203 | set DOMAINS.records[child.index] = domains::Domain { |
kernel/kernel/tests/calls.rad
+36 -0
| 10 | 10 | use kernel::slots; |
|
| 11 | 11 | use kernel::registry; |
|
| 12 | 12 | use kernel::limits; |
|
| 13 | 13 | use kernel::trap; |
|
| 14 | 14 | use kernel::budgets; |
|
| 15 | + | use kernel::remote; |
|
| 16 | + | ||
| 17 | + | /// Deferred budget operations recheck authority and expiration at execution time. |
|
| 18 | + | @test unsafe fn deferredBudget() throws (testing::TestError) { |
|
| 19 | + | let owner = initialize(); |
|
| 20 | + | let handle = try! budgets::seed(&mut budgets::STORE, &mut MEMORY.table, 1, 10, 100); |
|
| 21 | + | let entry = try! capability::get(&MEMORY.table, handle); |
|
| 22 | + | let call = remote::Call { owner, context: owner, operation: 70, arguments: [*handle, 50, 0, 0] }; |
|
| 23 | + | let wrongHart = calls::remoteBudget(call, 0, 20); |
|
| 24 | + | assert wrongHart == remote::Reply::Error(abi::Error::BadHandle); |
|
| 25 | + | remote::initialize(&mut remote::STORE, 3); |
|
| 26 | + | let request = try! remote::send(&mut remote::STORE, 0, 1, remote::Action::Budget(call)); |
|
| 27 | + | let delivery = try! remote::take(&mut remote::STORE, 1) else panic "missing expired call"; |
|
| 28 | + | assert delivery.id == request; |
|
| 29 | + | let case remote::Action::Budget(queued) = delivery.request.action else panic "queued budget call"; |
|
| 30 | + | let expired = calls::remoteBudget(queued, 1, 100); |
|
| 31 | + | assert expired == remote::Reply::Error(abi::Error::Busy); |
|
| 32 | + | remote::respond(&mut remote::STORE, 1, request, expired); |
|
| 33 | + | assert remote::acknowledge(&mut remote::STORE, 1, request); |
|
| 34 | + | assert try! remote::completed(&remote::STORE, 0, request); |
|
| 35 | + | try! remote::release(&mut remote::STORE, 0, request); |
|
| 36 | + | let unchanged = try! budgets::get(&budgets::STORE, entry.object); |
|
| 37 | + | assert unchanged.start == 10 and unchanged.end == 100; |
|
| 38 | + | let reply = calls::remoteBudget(call, 1, 20); |
|
| 39 | + | let case remote::Reply::Value(value) = reply else panic "deferred split"; |
|
| 40 | + | let left = try! budgets::query(&budgets::STORE, &MEMORY.table, handle); |
|
| 41 | + | let right = try! budgets::query(&budgets::STORE, &MEMORY.table, abi::Handle(value)); |
|
| 42 | + | assert left.start == 20 and left.end == 50 and right.start == 50 and right.end == 100; |
|
| 43 | + | let duplicate = calls::remoteBudget(call, 1, 25); |
|
| 44 | + | assert duplicate == remote::Reply::Error(abi::Error::InvalidArg); |
|
| 45 | + | let retained = try! budgets::query(&budgets::STORE, &MEMORY.table, handle); |
|
| 46 | + | assert retained.start == 20 and retained.end == 50; |
|
| 47 | + | let dropped = try! calls::invoke(owner, 12, &[*handle, 0, 0, 0], 25); |
|
| 48 | + | assert dropped == 0; |
|
| 49 | + | assert calls::remoteBudget(call, 1, 25) == remote::Reply::Error(abi::Error::BadHandle); |
|
| 50 | + | } |
|
| 15 | 51 | ||
| 16 | 52 | /// Mapped physical storage for four pages plus alignment padding. |
|
| 17 | 53 | static RAM: [u64; 2560] = [0; 2560]; |
|
| 18 | 54 | /// Kernel-only metadata for the calling domain. |
|
| 19 | 55 | unsafe static MEMORY: domains::Memory = undefined; |
kernel/kernel/tests/dispatch.rad
+15 -0
| 40 | 40 | let b = try! capability::get(&TABLE, last); |
|
| 41 | 41 | set BUDGETS.windows[a.object.index].context = owner; |
|
| 42 | 42 | set BUDGETS.windows[b.object.index].context = owner; |
|
| 43 | 43 | } |
|
| 44 | 44 | ||
| 45 | + | /// Wakeup reaches future windows and excludes expired or stopped contexts. |
|
| 46 | + | @test unsafe fn wakeTargets() throws (testing::TestError) { |
|
| 47 | + | initialize(); |
|
| 48 | + | set DOMAINS.contexts[0].state = domains::ContextState::Waiting; |
|
| 49 | + | assert budgets::wakeTargets(&BUDGETS, &DOMAINS, TABLE.owner, 0) == 1; |
|
| 50 | + | assert budgets::wakeTargets(&BUDGETS, &DOMAINS, TABLE.owner, 25) == 1; |
|
| 51 | + | assert budgets::wakeTargets(&BUDGETS, &DOMAINS, TABLE.owner, 40) == 0; |
|
| 52 | + | set DOMAINS.contexts[0].state = domains::ContextState::Stopped; |
|
| 53 | + | assert budgets::wakeTargets(&BUDGETS, &DOMAINS, TABLE.owner, 0) == 0; |
|
| 54 | + | set DOMAINS.contexts[0].state = domains::ContextState::Ready; |
|
| 55 | + | set DOMAINS.contextSlots[0].generation += 1; |
|
| 56 | + | assert budgets::wakeTargets(&BUDGETS, &DOMAINS, TABLE.owner, 0) == 0; |
|
| 57 | + | } |
|
| 58 | + | ||
| 45 | 59 | /// Dispatch selects only the half-open interval and wakes at the next runnable start. |
|
| 46 | 60 | @test unsafe fn boundaries() throws (testing::TestError) { |
|
| 47 | 61 | initialize(); |
|
| 48 | 62 | let before = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 9); |
|
| 49 | 63 | try testing::expect(before.context == nil and before.deadline == 10); |
| 98 | 112 | set PLATFORM.devices[0] = platform::Device { |
|
| 99 | 113 | kind: platform::Kind::Clint, memory: range::Range { start: 0x2000000, end: 0x2010000 }, |
|
| 100 | 114 | }; |
|
| 101 | 115 | let first = try! dispatch::timer(&PLATFORM, 0); |
|
| 102 | 116 | let last = try! dispatch::timer(&PLATFORM, 7); |
|
| 117 | + | assert first.software == 0x2000000 and last.software == first.software + 28; |
|
| 103 | 118 | try testing::expect(first.clock == 0x200bff8 and first.compare == 0x2004000); |
|
| 104 | 119 | try testing::expect(last.clock == first.clock and last.compare == first.compare + 56); |
|
| 105 | 120 | let mut failures: u32 = 0; |
|
| 106 | 121 | try dispatch::timer(&PLATFORM, 1) catch err { |
|
| 107 | 122 | try testing::expect(err == abi::Error::InvalidArg); set failures += 1; |
kernel/kernel/tests/domains.rad
+10 -0
| 213 | 213 | }; |
|
| 214 | 214 | try! domains::returned(&mut DOMAINS, authority.object, context); |
|
| 215 | 215 | try testing::expect(DOMAINS.contexts[context.index].state == domains::ContextState::Stopped); |
|
| 216 | 216 | try testing::expect(DOMAINS.records[authority.object.index].state == domains::Lifecycle::Pending); |
|
| 217 | 217 | set DOMAINS.contexts[context.index].hart = nil; |
|
| 218 | + | set DOMAINS.contexts[context.index].pending = abi::Ref { index: 0, generation: 1 }; |
|
| 219 | + | let mut retained = false; |
|
| 220 | + | try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error { |
|
| 221 | + | assert error == abi::Error::Busy; set retained = true; |
|
| 222 | + | }; |
|
| 223 | + | assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live; |
|
| 224 | + | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 225 | + | assert not PAGES.backings.pool.free[i]; |
|
| 226 | + | } |
|
| 227 | + | set DOMAINS.contexts[context.index].pending = nil; |
|
| 218 | 228 | try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context); |
|
| 219 | 229 | for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count { |
|
| 220 | 230 | try testing::expect(PAGES.backings.pool.free[i]); |
|
| 221 | 231 | } |
|
| 222 | 232 | try domains::context(&DOMAINS, authority.object, context) catch error { |
kernel/kernel/tests/remote.rad
added
+208 -0
| 1 | + | //! Bounded request queues, acknowledgement identity, and capacity recovery. |
|
| 2 | + | use std::testing; |
|
| 3 | + | use kernel::abi; |
|
| 4 | + | use kernel::remote; |
|
| 5 | + | use kernel::limits; |
|
| 6 | + | use kernel::slots; |
|
| 7 | + | ||
| 8 | + | /// Serialized request state shared by two test harts. |
|
| 9 | + | unsafe static STORE: remote::Store = undefined; |
|
| 10 | + | ||
| 11 | + | /// Requests retain source ownership until an explicit acknowledgement is released. |
|
| 12 | + | @test unsafe fn acknowledgement() throws (testing::TestError) { |
|
| 13 | + | remote::initialize(&mut STORE, 3); |
|
| 14 | + | let id = try! remote::send(&mut STORE, 0, 1, remote::Action::InstructionSync); |
|
| 15 | + | assert not try! remote::completed(&STORE, 0, id); |
|
| 16 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "missing request"; |
|
| 17 | + | assert delivery.id == id and delivery.request.source == 0; |
|
| 18 | + | assert not remote::acknowledge(&mut STORE, 0, id); |
|
| 19 | + | assert remote::acknowledge(&mut STORE, 1, id); |
|
| 20 | + | assert not remote::acknowledge(&mut STORE, 1, id); |
|
| 21 | + | assert try! remote::completed(&STORE, 0, id); |
|
| 22 | + | let mut denied = false; |
|
| 23 | + | try remote::release(&mut STORE, 1, id) catch error { |
|
| 24 | + | assert error == abi::Error::BadHandle; set denied = true; |
|
| 25 | + | }; |
|
| 26 | + | assert denied; |
|
| 27 | + | try! remote::release(&mut STORE, 0, id); |
|
| 28 | + | assert not remote::acknowledge(&mut STORE, 1, id); |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Cancellation leaves a harmless stale queue entry until the receiver drains it. |
|
| 32 | + | @test unsafe fn saturation() throws (testing::TestError) { |
|
| 33 | + | remote::initialize(&mut STORE, 3); |
|
| 34 | + | let mut ids: [abi::Ref; 16] = undefined; |
|
| 35 | + | for i in 0..16 { |
|
| 36 | + | set ids[i] = try! remote::send(&mut STORE, 0, 1, remote::Action::Reschedule); |
|
| 37 | + | } |
|
| 38 | + | let mut full = false; |
|
| 39 | + | try remote::send(&mut STORE, 0, 1, remote::Action::Reschedule) catch error { |
|
| 40 | + | assert error == abi::Error::Busy; set full = true; |
|
| 41 | + | }; |
|
| 42 | + | assert full; |
|
| 43 | + | try! remote::cancel(&mut STORE, 0, ids[0]); |
|
| 44 | + | for i in 1..16 { |
|
| 45 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "missing queued request"; |
|
| 46 | + | assert delivery.id == ids[i]; |
|
| 47 | + | assert remote::acknowledge(&mut STORE, 1, delivery.id); |
|
| 48 | + | try! remote::release(&mut STORE, 0, delivery.id); |
|
| 49 | + | } |
|
| 50 | + | assert try! remote::take(&mut STORE, 1) == nil; |
|
| 51 | + | for i in 0..40 { |
|
| 52 | + | let id = try! remote::send(&mut STORE, 0, 1, remote::Action::Reschedule); |
|
| 53 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "request reuse"; |
|
| 54 | + | assert delivery.id == id and id <> ids[0]; |
|
| 55 | + | assert not remote::acknowledge(&mut STORE, 1, ids[0]); |
|
| 56 | + | assert remote::acknowledge(&mut STORE, 1, id); |
|
| 57 | + | try! remote::release(&mut STORE, 0, id); |
|
| 58 | + | } |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Completed records retain capacity until their source releases them. |
|
| 62 | + | @test unsafe fn retainedCapacity() throws (testing::TestError) { |
|
| 63 | + | remote::initialize(&mut STORE, 3); |
|
| 64 | + | let mut ids: [abi::Ref; limits::REMOTE_REQUESTS] = undefined; |
|
| 65 | + | for i in 0..limits::REMOTE_REQUESTS { |
|
| 66 | + | let id = try! remote::send(&mut STORE, 0, 1, remote::Action::Reschedule); |
|
| 67 | + | set ids[i] = id; |
|
| 68 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "missing request"; |
|
| 69 | + | assert delivery.id == id; |
|
| 70 | + | assert remote::acknowledge(&mut STORE, 1, id); |
|
| 71 | + | } |
|
| 72 | + | let mut full = false; |
|
| 73 | + | try remote::send(&mut STORE, 0, 1, remote::Action::Reschedule) catch error { |
|
| 74 | + | assert error == abi::Error::Busy; set full = true; |
|
| 75 | + | }; |
|
| 76 | + | assert full; |
|
| 77 | + | for id in &ids[..] { |
|
| 78 | + | try! remote::release(&mut STORE, 0, id); |
|
| 79 | + | } |
|
| 80 | + | let id = try! remote::send(&mut STORE, 0, 1, remote::Action::InstructionSync); |
|
| 81 | + | try! remote::cancel(&mut STORE, 0, id); |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// A cancelled queue reference cannot deliver a reused slot generation. |
|
| 85 | + | @test unsafe fn cancelledGeneration() throws (testing::TestError) { |
|
| 86 | + | remote::initialize(&mut STORE, 3); |
|
| 87 | + | let stale = try! remote::send(&mut STORE, 0, 1, remote::Action::Reschedule); |
|
| 88 | + | try! remote::cancel(&mut STORE, 0, stale); |
|
| 89 | + | let live = try! remote::send(&mut STORE, 0, 1, remote::Action::InstructionSync); |
|
| 90 | + | assert live.index == stale.index and live <> stale; |
|
| 91 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "missing reused request"; |
|
| 92 | + | assert delivery.id == live and delivery.request.action == remote::Action::InstructionSync; |
|
| 93 | + | assert try! remote::take(&mut STORE, 1) == nil; |
|
| 94 | + | assert not remote::acknowledge(&mut STORE, 1, stale); |
|
| 95 | + | let mut cancelDenied = false; |
|
| 96 | + | try remote::cancel(&mut STORE, 0, live) catch error { |
|
| 97 | + | assert error == abi::Error::Busy; set cancelDenied = true; |
|
| 98 | + | }; |
|
| 99 | + | assert cancelDenied; |
|
| 100 | + | let mut releaseDenied = false; |
|
| 101 | + | try remote::release(&mut STORE, 0, live) catch error { |
|
| 102 | + | assert error == abi::Error::Busy; set releaseDenied = true; |
|
| 103 | + | }; |
|
| 104 | + | assert releaseDenied; |
|
| 105 | + | assert remote::acknowledge(&mut STORE, 1, live); |
|
| 106 | + | try! remote::release(&mut STORE, 0, live); |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | /// Invalid routes and premature release preserve all queued work. |
|
| 110 | + | @test unsafe fn invalidRoute() throws (testing::TestError) { |
|
| 111 | + | remote::initialize(&mut STORE, 1); |
|
| 112 | + | for target in &[1, limits::HARTS] { |
|
| 113 | + | let mut denied = false; |
|
| 114 | + | try remote::send(&mut STORE, 0, target, remote::Action::Reschedule) catch error { |
|
| 115 | + | assert error == abi::Error::InvalidArg; set denied = true; |
|
| 116 | + | }; |
|
| 117 | + | assert denied; |
|
| 118 | + | } |
|
| 119 | + | let mut sourceDenied = false; |
|
| 120 | + | try remote::send(&mut STORE, 1, 0, remote::Action::Reschedule) catch error { |
|
| 121 | + | assert error == abi::Error::InvalidArg; set sourceDenied = true; |
|
| 122 | + | }; |
|
| 123 | + | assert sourceDenied; |
|
| 124 | + | let id = try! remote::send(&mut STORE, 0, 0, remote::Action::Reschedule); |
|
| 125 | + | let mut releaseDenied = false; |
|
| 126 | + | try remote::release(&mut STORE, 0, id) catch error { |
|
| 127 | + | assert error == abi::Error::Busy; set releaseDenied = true; |
|
| 128 | + | }; |
|
| 129 | + | assert releaseDenied; |
|
| 130 | + | assert not remote::acknowledge(&mut STORE, 0, id); |
|
| 131 | + | let delivery = try! remote::take(&mut STORE, 0) else panic "missing local request"; |
|
| 132 | + | assert delivery.id == id; |
|
| 133 | + | assert remote::acknowledge(&mut STORE, 0, id); |
|
| 134 | + | try! remote::release(&mut STORE, 0, id); |
|
| 135 | + | assert try! remote::take(&mut STORE, 0) == nil; |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Replies become observable only after the target acknowledges completion. |
|
| 139 | + | @test unsafe fn replies() throws (testing::TestError) { |
|
| 140 | + | remote::initialize(&mut STORE, 3); |
|
| 141 | + | let id = try! remote::send(&mut STORE, 0, 1, remote::Action::InstructionSync); |
|
| 142 | + | let delivery = try! remote::take(&mut STORE, 1) else panic "missing reply request"; |
|
| 143 | + | assert delivery.id == id; |
|
| 144 | + | remote::respond(&mut STORE, 1, id, remote::Reply::Value(42)); |
|
| 145 | + | let mut pending = false; |
|
| 146 | + | try remote::result(&STORE, 0, id) catch error { |
|
| 147 | + | assert error == abi::Error::Busy; set pending = true; |
|
| 148 | + | }; |
|
| 149 | + | assert pending; |
|
| 150 | + | assert remote::acknowledge(&mut STORE, 1, id); |
|
| 151 | + | assert try! remote::result(&STORE, 0, id) == 42; |
|
| 152 | + | try! remote::release(&mut STORE, 0, id); |
|
| 153 | + | let failed = try! remote::send(&mut STORE, 0, 1, remote::Action::InstructionSync); |
|
| 154 | + | let taken = try! remote::take(&mut STORE, 1) else panic "missing failure request"; |
|
| 155 | + | assert taken.id == failed; |
|
| 156 | + | remote::respond(&mut STORE, 1, failed, remote::Reply::Error(abi::Error::BadHandle)); |
|
| 157 | + | assert remote::acknowledge(&mut STORE, 1, failed); |
|
| 158 | + | let mut rejected = false; |
|
| 159 | + | try remote::result(&STORE, 0, failed) catch error { |
|
| 160 | + | assert error == abi::Error::BadHandle; set rejected = true; |
|
| 161 | + | }; |
|
| 162 | + | assert rejected; |
|
| 163 | + | try! remote::release(&mut STORE, 0, failed); |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | /// Batch failure and cancellation leave every mailbox unchanged. |
|
| 167 | + | @test unsafe fn batchReservation() throws (testing::TestError) { |
|
| 168 | + | remote::initialize(&mut STORE, 7); |
|
| 169 | + | for i in 0..limits::REMOTE_PER_HART { |
|
| 170 | + | let id = try! remote::send(&mut STORE, 0, 2, remote::Action::Reschedule); |
|
| 171 | + | } |
|
| 172 | + | assert batchFull() and STORE.queues[1].count == 0; |
|
| 173 | + | let pending = try! remote::reserve(&mut STORE, 0, 2); |
|
| 174 | + | assert STORE.queues[1].count == 0; |
|
| 175 | + | remote::cancelReservation(&mut STORE, pending); |
|
| 176 | + | assert STORE.queues[1].count == 0; |
|
| 177 | + | let mut live: u32 = 0; |
|
| 178 | + | for slot in &STORE.slots[..] { |
|
| 179 | + | assert slot.state <> slots::State::Reserved; |
|
| 180 | + | if slot.state == slots::State::Live { |
|
| 181 | + | set live += 1; |
|
| 182 | + | } |
|
| 183 | + | } |
|
| 184 | + | assert live == limits::REMOTE_PER_HART; |
|
| 185 | + | for i in 0..limits::REMOTE_PER_HART { |
|
| 186 | + | let delivery = try! remote::take(&mut STORE, 2) else panic "full mailbox lost request"; |
|
| 187 | + | assert remote::acknowledge(&mut STORE, 2, delivery.id); |
|
| 188 | + | try! remote::release(&mut STORE, 0, delivery.id); |
|
| 189 | + | } |
|
| 190 | + | let reserved = try! remote::reserve(&mut STORE, 0, 6); |
|
| 191 | + | let batch = remote::publish(&mut STORE, reserved, remote::Action::Reschedule); |
|
| 192 | + | assert batch.source == 0 and batch.targets == 6; |
|
| 193 | + | for target in 1..3 { |
|
| 194 | + | let delivery = try! remote::take(&mut STORE, target) else panic "missing batch request"; |
|
| 195 | + | assert delivery.id == batch.ids[target]; |
|
| 196 | + | assert remote::acknowledge(&mut STORE, target, delivery.id); |
|
| 197 | + | try! remote::release(&mut STORE, 0, delivery.id); |
|
| 198 | + | } |
|
| 199 | + | } |
|
| 200 | + | ||
| 201 | + | /// Cancel an unexpected reservation so every linear result is consumed. |
|
| 202 | + | unsafe fn batchFull() -> bool { |
|
| 203 | + | let pending = try remote::reserve(&mut STORE, 0, 6) catch error { |
|
| 204 | + | assert error == abi::Error::Busy; return true; |
|
| 205 | + | }; |
|
| 206 | + | remote::cancelReservation(&mut STORE, pending); |
|
| 207 | + | return false; |
|
| 208 | + | } |
kernel/kernel/tests/timers.rad
+32 -9
| 4 | 4 | use kernel::slots; |
|
| 5 | 5 | use kernel::domains; |
|
| 6 | 6 | use kernel::events; |
|
| 7 | 7 | use kernel::timers; |
|
| 8 | 8 | use kernel::limits; |
|
| 9 | + | use kernel::budgets; |
|
| 9 | 10 | ||
| 10 | 11 | /// Timer records under test. |
|
| 11 | 12 | unsafe static TIMERS: timers::Store = undefined; |
|
| 12 | 13 | /// Domain metadata and trusted notification state. |
|
| 13 | 14 | unsafe static DOMAINS: domains::Store = undefined; |
|
| 15 | + | /// CPU authority used to identify remote notification targets. |
|
| 16 | + | unsafe static BUDGETS: budgets::Store = undefined; |
|
| 14 | 17 | /// Receiver ring and private metadata. |
|
| 15 | 18 | unsafe static MEMORY: domains::Memory = undefined; |
|
| 16 | 19 | ||
| 17 | 20 | /// Open an active domain queue without assigning CPU authority. |
|
| 18 | 21 | unsafe fn initialize() -> abi::Ref { |
|
| 19 | 22 | timers::initialize(&mut TIMERS); |
|
| 23 | + | budgets::initialize(&mut BUDGETS); |
|
| 20 | 24 | domains::initialize(&mut DOMAINS); |
|
| 21 | 25 | let pending = try! slots::reserve(&mut DOMAINS.slots[..]); |
|
| 22 | 26 | let owner = try! slots::commit(&mut DOMAINS.slots[..], pending); |
|
| 23 | 27 | set DOMAINS.records[owner.index].state = domains::Lifecycle::Active; |
|
| 24 | 28 | set DOMAINS.records[owner.index].memory = &mut MEMORY; |
|
| 25 | 29 | try! events::open(&mut DOMAINS.events, owner, &mut MEMORY.ring); |
|
| 26 | 30 | return owner; |
|
| 27 | 31 | } |
|
| 28 | 32 | ||
| 33 | + | /// A timeout reports the hart of a receiver whose context moved before delivery. |
|
| 34 | + | @test unsafe fn remoteReceiver() throws (testing::TestError) { |
|
| 35 | + | let owner = initialize(); |
|
| 36 | + | set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 37 | + | let context = abi::Ref { index: 0, generation: 1 }; |
|
| 38 | + | set DOMAINS.contexts[0].owner = owner; |
|
| 39 | + | set DOMAINS.contexts[0].state = domains::ContextState::Waiting; |
|
| 40 | + | set BUDGETS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live }; |
|
| 41 | + | set BUDGETS.windows[0] = budgets::Window { |
|
| 42 | + | hart: 1, start: 10, end: 100, owner, handle: abi::Handle(0), context, |
|
| 43 | + | }; |
|
| 44 | + | try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 15, 17); |
|
| 45 | + | let result = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 15); |
|
| 46 | + | assert result.harts == 2 and result.deadline == 0xffffffffffffffff; |
|
| 47 | + | assert DOMAINS.contexts[0].state == domains::ContextState::Ready; |
|
| 48 | + | let again = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 16); |
|
| 49 | + | assert again.harts == 0; |
|
| 50 | + | } |
|
| 51 | + | ||
| 29 | 52 | /// A timeout is delivered once, at its deadline, on its registered hart. |
|
| 30 | 53 | @test unsafe fn boundaries() throws (testing::TestError) { |
|
| 31 | 54 | let owner = initialize(); |
|
| 32 | 55 | try! timers::request(&mut TIMERS, &DOMAINS, owner, 1, 10, 5, 17); |
|
| 33 | - | assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 15) == 0xffffffffffffffff); |
|
| 34 | - | assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 14) == 15); |
|
| 56 | + | assert ((try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 15)).deadline == 0xffffffffffffffff); |
|
| 57 | + | assert ((try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 1, 14)).deadline == 15); |
|
| 35 | 58 | assert (try! events::pop(&mut MEMORY.ring) == nil); |
|
| 36 | - | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 15); |
|
| 37 | - | assert (next == 0xffffffffffffffff); |
|
| 59 | + | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 1, 15); |
|
| 60 | + | assert (next.deadline == 0xffffffffffffffff); |
|
| 38 | 61 | let event = try! events::pop(&mut MEMORY.ring) else panic "timeout"; |
|
| 39 | 62 | assert (event.kind == events::TIMEOUT and event.code == 17 and event.value == 0); |
|
| 40 | - | let again = try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 16); |
|
| 63 | + | let again = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 1, 16); |
|
| 41 | 64 | assert (try! events::pop(&mut MEMORY.ring) == nil); |
|
| 42 | 65 | } |
|
| 43 | 66 | ||
| 44 | 67 | /// Full queues retain due timeouts without requesting another immediate interrupt. |
|
| 45 | 68 | @test unsafe fn saturation() throws (testing::TestError) { |
| 54 | 77 | let mut full = false; |
|
| 55 | 78 | try timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 1, 99) catch error { |
|
| 56 | 79 | assert (error == abi::Error::Busy); set full = true; |
|
| 57 | 80 | }; |
|
| 58 | 81 | assert (full); |
|
| 59 | - | assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 1) == 0xffffffffffffffff); |
|
| 82 | + | assert ((try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 1)).deadline == 0xffffffffffffffff); |
|
| 60 | 83 | for i in 0..events::ORDINARY { |
|
| 61 | 84 | let event = try! events::pop(&mut MEMORY.ring) else panic "wake"; |
|
| 62 | 85 | } |
|
| 63 | - | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 2); |
|
| 86 | + | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 2); |
|
| 64 | 87 | for i in 0..limits::TIMEOUTS_PER_HART { |
|
| 65 | 88 | let event = try! events::pop(&mut MEMORY.ring) else panic "retained timeout"; |
|
| 66 | 89 | assert (event.kind == events::TIMEOUT and event.code == i); |
|
| 67 | 90 | } |
|
| 68 | 91 | try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 2, 1, 100); |
| 78 | 101 | try timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0xffffffffffffffff, 1, 1) catch error { |
|
| 79 | 102 | assert (error == abi::Error::InvalidArg); set rejected += 1; |
|
| 80 | 103 | }; |
|
| 81 | 104 | try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 5, 1); |
|
| 82 | 105 | set DOMAINS.records[owner.index].state = domains::Lifecycle::Dead; |
|
| 83 | - | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 1); |
|
| 84 | - | assert (next == 0xffffffffffffffff and rejected == 2); |
|
| 106 | + | let next = try! timers::service(&mut TIMERS, &mut DOMAINS, &BUDGETS, 0, 1); |
|
| 107 | + | assert (next.deadline == 0xffffffffffffffff and rejected == 2); |
|
| 85 | 108 | assert (TIMERS.entries[0][0].owner == nil); |
|
| 86 | 109 | } |
kernel/kernel/timers.rad
+16 -6
| 2 | 2 | use super::abi; |
|
| 3 | 3 | use super::limits; |
|
| 4 | 4 | use super::slots; |
|
| 5 | 5 | use super::domains; |
|
| 6 | 6 | use super::events; |
|
| 7 | + | use super::budgets; |
|
| 7 | 8 | ||
| 8 | 9 | /// One armed timeout or retained due notification. |
|
| 9 | 10 | export record Entry: Copy { |
|
| 10 | 11 | /// Receiver generation, or nil for free capacity. |
|
| 11 | 12 | owner: ?abi::Ref, |
| 52 | 53 | return; |
|
| 53 | 54 | } |
|
| 54 | 55 | throw abi::Error::Busy; |
|
| 55 | 56 | } |
|
| 56 | 57 | ||
| 57 | - | /// Deliver due entries and return the next future timeout boundary. |
|
| 58 | + | /// Next timer boundary and dispatch notifications from delivered events. |
|
| 59 | + | export record Result: Copy { |
|
| 60 | + | /// Earliest future timeout, or the maximum tick when none is armed. |
|
| 61 | + | deadline: u64, |
|
| 62 | + | /// Harts with retained receiver authority that must refresh dispatch. |
|
| 63 | + | harts: u32, |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Deliver due entries and report the next boundary and affected dispatch harts. |
|
| 58 | 67 | /// Full queues retain due entries for the next kernel entry without a timer spin. |
|
| 59 | - | export unsafe fn service(store: &mut Store, receivers: &mut domains::Store, hart: u32, now: u64) -> u64 throws (abi::Error) { |
|
| 68 | + | export unsafe fn service(store: &mut Store, receivers: &mut domains::Store, windows: &budgets::Store, hart: u32, now: u64) -> Result throws (abi::Error) { |
|
| 60 | 69 | if hart >= limits::HARTS { |
|
| 61 | 70 | throw abi::Error::InvalidArg; |
|
| 62 | 71 | } |
|
| 63 | - | let mut next: u64 = 0xffffffffffffffff; |
|
| 72 | + | let mut result = Result { deadline: 0xffffffffffffffff, harts: 0 }; |
|
| 64 | 73 | for i in 0..limits::TIMEOUTS_PER_HART { |
|
| 65 | 74 | let entry = store.entries[hart][i]; |
|
| 66 | 75 | let owner = entry.owner else { |
|
| 67 | 76 | continue; |
|
| 68 | 77 | }; |
| 70 | 79 | or receivers.records[owner.index].state == domains::Lifecycle::Dead { |
|
| 71 | 80 | set store.entries[hart][i].owner = nil; |
|
| 72 | 81 | continue; |
|
| 73 | 82 | } |
|
| 74 | 83 | if entry.deadline > now { |
|
| 75 | - | if entry.deadline < next { |
|
| 76 | - | set next = entry.deadline; |
|
| 84 | + | if entry.deadline < result.deadline { |
|
| 85 | + | set result.deadline = entry.deadline; |
|
| 77 | 86 | } |
|
| 78 | 87 | continue; |
|
| 79 | 88 | } |
|
| 80 | 89 | if try domains::notify(receivers, owner, events::Event { kind: events::TIMEOUT, reserved: 0, code: entry.token, value: 0 }) { |
|
| 81 | 90 | set store.entries[hart][i].owner = nil; |
|
| 91 | + | set result.harts |= budgets::wakeTargets(windows, receivers, owner, now); |
|
| 82 | 92 | } |
|
| 83 | 93 | } |
|
| 84 | - | return next; |
|
| 94 | + | return result; |
|
| 85 | 95 | } |
test/smp/kernel/dispatchcheck.rad
+189 -6
| 11 | 11 | use kernel::domains; |
|
| 12 | 12 | use kernel::budgets; |
|
| 13 | 13 | use kernel::dispatch; |
|
| 14 | 14 | use kernel::boot; |
|
| 15 | 15 | use kernel::sync; |
|
| 16 | + | use kernel::remote; |
|
| 17 | + | use kernel::calls; |
|
| 18 | + | use kernel::events; |
|
| 19 | + | use kernel::timers; |
|
| 16 | 20 | use kernel::dispatchinput; |
|
| 17 | 21 | ||
| 18 | 22 | /// Bootstrap resource authority. |
|
| 19 | 23 | unsafe static TABLE: capability::Table = undefined; |
|
| 20 | 24 | /// Release/acquire barrier after fixture publication. |
| 23 | 27 | unsafe static USER: abi::Ref = undefined; |
|
| 24 | 28 | /// Per-hart user execution contexts. |
|
| 25 | 29 | unsafe static CONTEXTS: [abi::Ref; 8] = undefined; |
|
| 26 | 30 | /// Mapped progress counters in the user's private state. |
|
| 27 | 31 | static COUNTERS: u64 = 0; |
|
| 32 | + | /// Per-context migration observations in the user's private state. |
|
| 33 | + | static MIGRATIONS: u64 = 0; |
|
| 34 | + | /// Shared handshake for the independently activated context on hart one. |
|
| 35 | + | static WAKE_STATE: u64 = 0; |
|
| 36 | + | /// Bootstrap management capability for the pending idle-wakeup domain. |
|
| 37 | + | unsafe static IDLE_HANDLE: abi::Handle = undefined; |
|
| 38 | + | /// Context whose Wait call must release the remote hart. |
|
| 39 | + | unsafe static IDLE_CONTEXT: abi::Ref = undefined; |
|
| 40 | + | /// Validated initial stack pointer for the idle-wakeup domain. |
|
| 41 | + | static IDLE_STACK: u64 = 0; |
|
| 42 | + | /// Generation of the idle-wakeup domain. |
|
| 43 | + | unsafe static IDLE_DOMAIN: abi::Ref = undefined; |
|
| 44 | + | /// Completion checker's live domain generation. |
|
| 45 | + | unsafe static CHECKER: abi::Ref = undefined; |
|
| 46 | + | /// Checker's authority to activate and notify the idle-wakeup domain. |
|
| 47 | + | unsafe static RECEIVER: abi::Handle = undefined; |
|
| 48 | + | /// Unbound authority reserved for the idle-wakeup check on hart one. |
|
| 49 | + | unsafe static IDLE_WINDOW: abi::Handle = undefined; |
|
| 28 | 50 | /// Validated physical memory mapping. |
|
| 29 | 51 | fn memory(address: u64) -> *mut u8; |
|
| 30 | 52 | /// Current kernel package-state table. |
|
| 31 | 53 | fn kernelGp() -> u64; |
|
| 32 | 54 | /// M-mode entry of the completion checker. |
| 65 | 87 | let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 66 | 88 | let cap = try! capability::get(&TABLE, handle); |
|
| 67 | 89 | set USER = cap.object; |
|
| 68 | 90 | let mut user = try! domains::get(&domains::STORE, USER); |
|
| 69 | 91 | let self = try! capability::install(&mut user.memory.table, capability::Entry { |
|
| 70 | - | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE), |
|
| 92 | + | kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE | abi::WAKE), |
|
| 71 | 93 | }); |
|
| 72 | 94 | let target = try! registry::exported(®istry::STORE, user.image, &"spin::worker"[..]); |
|
| 73 | 95 | let case shared::Target::Function(entry) = target else panic "worker entry"; |
|
| 74 | - | let counterTarget = try! registry::exported(®istry::STORE, user.image, &"spin::count"[..]); |
|
| 75 | - | let case shared::Target::Data(data) = counterTarget else panic "counter state"; |
|
| 76 | - | set COUNTERS = user.graph.table[data.slot] + data.offset as u64; |
|
| 96 | + | set COUNTERS = dataAddress(&user, &"spin::count"[..]); |
|
| 97 | + | set MIGRATIONS = dataAddress(&user, &"spin::migrations"[..]); |
|
| 98 | + | let notification = @sliceOf(memory(dataAddress(&user, &"spin::notification"[..])), 8); |
|
| 99 | + | for i in 0..8 { |
|
| 100 | + | set notification[i] = ((*self) >> (i as u64 * 8)) as u8; |
|
| 101 | + | } |
|
| 77 | 102 | for hart in 0..8 { |
|
| 78 | 103 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { |
|
| 79 | 104 | continue; |
|
| 80 | 105 | } |
|
| 81 | 106 | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
| 95 | 120 | } |
|
| 96 | 121 | } |
|
| 97 | 122 | let checkerHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, ®istry::STORE, &mut TABLE, authority, image); |
|
| 98 | 123 | let checkerCap = try! capability::get(&TABLE, checkerHandle); |
|
| 99 | 124 | let checker = try! domains::get(&domains::STORE, checkerCap.object); |
|
| 125 | + | set CHECKER = checkerCap.object; |
|
| 100 | 126 | set domains::STORE.records[checkerCap.object.index].state = domains::Lifecycle::Active; |
|
| 101 | 127 | set domains::STORE.contexts[checker.initial.index].frame.pc = completion(); |
|
| 102 | 128 | set domains::STORE.contexts[checker.initial.index].frame.status = 0x1880; |
|
| 103 | 129 | set domains::STORE.contexts[checker.initial.index].frame.registers[2] = domains::STORE.contexts[checker.initial.index].kernelStack.end; |
|
| 104 | 130 | set domains::STORE.contexts[checker.initial.index].frame.registers[3] = kernelGp(); |
|
| 131 | + | idleSetup(authority, image); |
|
| 105 | 132 | let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)); |
|
| 106 | 133 | let start = clock + 1000000; |
|
| 107 | 134 | let mut harts: u64 = 0; |
|
| 108 | 135 | for hart in 0..8 { |
|
| 109 | 136 | if (boot::PLATFORM.harts & (1 << hart)) <> 0 { |
|
| 110 | 137 | set harts += 1; |
|
| 111 | 138 | } |
|
| 112 | 139 | } |
|
| 113 | 140 | let end = start + 10000000 * harts * harts; |
|
| 141 | + | let migrationEnd = end + 10000000 * harts * harts; |
|
| 142 | + | let windowBase = dataAddress(&user, &"spin::windows"[..]); |
|
| 114 | 143 | for hart in 0..8 { |
|
| 115 | 144 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { |
|
| 116 | 145 | continue; |
|
| 117 | 146 | } |
|
| 118 | 147 | let budget = try! budgets::seed(&mut budgets::STORE, &mut TABLE, hart, start, 0xffffffffffffffff); |
|
| 119 | 148 | let final = try! budgets::split(&mut budgets::STORE, &mut TABLE, budget, end, clock); |
|
| 149 | + | let checking = try! budgets::split(&mut budgets::STORE, &mut TABLE, final, migrationEnd, clock); |
|
| 150 | + | let future = try! budgets::split(&mut budgets::STORE, &mut TABLE, checking, migrationEnd + 10000000000, clock); |
|
| 151 | + | let transferred = try! budgets::transfer(&mut budgets::STORE, &mut TABLE, &mut user.memory.table, |
|
| 152 | + | future, budgets::DEFAULT_RIGHTS as u64, clock); |
|
| 153 | + | let caller = (hart as u64 + harts - 1) % harts; |
|
| 154 | + | let output = @sliceOf(memory(windowBase + caller * 8), 8); |
|
| 155 | + | for i in 0..8 { |
|
| 156 | + | set output[i] = ((*transferred) >> (i as u64 * 8)) as u8; |
|
| 157 | + | } |
|
| 120 | 158 | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 121 | 159 | budgets::Binding { budget, domain: handle, context: CONTEXTS[hart] }, clock); |
|
| 160 | + | let rotated = ((hart as u64 + 1) % harts) as u32; |
|
| 161 | + | let moved = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 162 | + | budgets::Binding { budget: final, domain: handle, context: CONTEXTS[rotated] }, clock); |
|
| 122 | 163 | if hart == 0 { |
|
| 123 | 164 | let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 124 | - | budgets::Binding { budget: final, domain: checkerHandle, context: checker.initial }, clock); |
|
| 165 | + | budgets::Binding { budget: checking, domain: checkerHandle, context: checker.initial }, clock); |
|
| 166 | + | } |
|
| 167 | + | if hart == 1 { |
|
| 168 | + | set IDLE_WINDOW = checking; |
|
| 125 | 169 | } |
|
| 126 | 170 | } |
|
| 127 | 171 | } |
|
| 128 | 172 | ||
| 173 | + | /// Resolve one exported private-data address in the fixture's user instance. |
|
| 174 | + | unsafe fn dataAddress(user: &domains::Domain, name: &[u8]) -> u64 { |
|
| 175 | + | let target = try! registry::exported(®istry::STORE, user.image, name); |
|
| 176 | + | let case shared::Target::Data(data) = target else panic "fixture data export"; |
|
| 177 | + | return user.graph.table[data.slot] + data.offset as u64; |
|
| 178 | + | } |
|
| 179 | + | ||
| 180 | + | /// Prepare a pending domain and its stack for activation on an idle hart. |
|
| 181 | + | unsafe fn idleSetup(authority: abi::Handle, image: abi::Handle) { |
|
| 182 | + | set IDLE_HANDLE = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, |
|
| 183 | + | ®istry::STORE, &mut TABLE, authority, image); |
|
| 184 | + | let permit = try! capability::get(&TABLE, IDLE_HANDLE); |
|
| 185 | + | set IDLE_DOMAIN = permit.object; |
|
| 186 | + | let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN); |
|
| 187 | + | set IDLE_CONTEXT = domain.initial; |
|
| 188 | + | set WAKE_STATE = dataAddress(&domain, &"spin::wakeState"[..]); |
|
| 189 | + | sync::storeRelease(memory(dataAddress(&domain, &"spin::idle"[..])) as *mut u64, 1); |
|
| 190 | + | let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1); |
|
| 191 | + | let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut domain.memory.table, stack, (abi::READ | abi::WRITE) as u64); |
|
| 192 | + | let permission = try! capability::get(&TABLE, stack); |
|
| 193 | + | let page = try! pages::get(&pages::STORE, permission.object); |
|
| 194 | + | set IDLE_STACK = page.base + 4096; |
|
| 195 | + | let mut checker = try! domains::get(&domains::STORE, CHECKER); |
|
| 196 | + | set RECEIVER = try! capability::install(&mut checker.memory.table, capability::Entry { |
|
| 197 | + | kind: abi::Kind::Domain, object: IDLE_DOMAIN, rights: abi::Rights(abi::WAKE | abi::EXECUTE), |
|
| 198 | + | }); |
|
| 199 | + | } |
|
| 200 | + | ||
| 129 | 201 | /// Check shared progress and context isolation after the user windows finish. |
|
| 130 | 202 | export unsafe fn verify() { |
|
| 131 | 203 | let guard = sync::enter(); |
|
| 132 | 204 | let mut counts: [u64; 8] = [0; 8]; |
|
| 205 | + | let mut notifications: u32 = 0; |
|
| 206 | + | for hart in 0..8 { |
|
| 207 | + | if (boot::PLATFORM.harts & (1 << hart)) <> 0 { |
|
| 208 | + | set notifications += 1; |
|
| 209 | + | } |
|
| 210 | + | } |
|
| 133 | 211 | for hart in 0..8 { |
|
| 134 | 212 | if (boot::PLATFORM.harts & (1 << hart)) == 0 { |
|
| 135 | 213 | continue; |
|
| 136 | 214 | } |
|
| 137 | 215 | let value = sync::loadAcquire(memory(COUNTERS + hart as u64 * 8) as *u64); |
|
| 138 | 216 | set counts[hart] = value; |
|
| 139 | 217 | let context = try! domains::context(&domains::STORE, USER, CONTEXTS[hart]); |
|
| 140 | 218 | assert context.owner == USER and (context.frame.status & 0x1800) == 0; |
|
| 141 | - | assert context.hart == nil or context.hart == hart; |
|
| 219 | + | let destination = (hart + notifications - 1) % notifications; |
|
| 220 | + | assert context.hart == nil or context.hart == destination; |
|
| 221 | + | assert sync::loadAcquire(memory(MIGRATIONS + hart as u64 * 8) as *u64) == destination as u64 + 1; |
|
| 222 | + | assert context.pending == nil; |
|
| 142 | 223 | } |
|
| 224 | + | assert domains::STORE.events.queues[USER.index].tail == notifications; |
|
| 143 | 225 | sync::leave(guard); |
|
| 144 | 226 | for byte in "smp completed transactions:" { |
|
| 145 | 227 | put(byte); |
|
| 146 | 228 | } |
|
| 147 | 229 | for hart in 0..8 { |
| 154 | 236 | for hart in 0..8 { |
|
| 155 | 237 | if (boot::PLATFORM.harts & (1 << hart)) <> 0 { |
|
| 156 | 238 | assert counts[hart] == 16; |
|
| 157 | 239 | } |
|
| 158 | 240 | } |
|
| 241 | + | for target in 0..8 { |
|
| 242 | + | if (boot::PLATFORM.harts & (1 << target)) == 0 { |
|
| 243 | + | continue; |
|
| 244 | + | } |
|
| 245 | + | for action in &[remote::Action::Reschedule, remote::Action::InstructionSync] { |
|
| 246 | + | let posting = sync::enter(); |
|
| 247 | + | let id = try! dispatch::request(target, action); |
|
| 248 | + | sync::leave(posting); |
|
| 249 | + | let mut acknowledged = false; |
|
| 250 | + | for attempt in 0..100000 { |
|
| 251 | + | let guard = sync::enter(); |
|
| 252 | + | if try! remote::completed(&remote::STORE, 0, id) { |
|
| 253 | + | try! remote::release(&mut remote::STORE, 0, id); |
|
| 254 | + | set acknowledged = true; |
|
| 255 | + | } |
|
| 256 | + | sync::leave(guard); |
|
| 257 | + | if acknowledged { |
|
| 258 | + | break; |
|
| 259 | + | } |
|
| 260 | + | } |
|
| 261 | + | assert acknowledged; |
|
| 262 | + | } |
|
| 263 | + | } |
|
| 264 | + | for byte in "smp request acknowledgements passed\n" { |
|
| 265 | + | put(byte); |
|
| 266 | + | } |
|
| 267 | + | if (boot::PLATFORM.harts & 2) <> 0 { |
|
| 268 | + | idleWakeup(); |
|
| 269 | + | } |
|
| 159 | 270 | let maximum = sync::maximum(); |
|
| 160 | 271 | assert maximum > 0 and maximum < 1000000; |
|
| 161 | 272 | for byte in "smp metadata instructions: 0x" { |
|
| 162 | 273 | put(byte); |
|
| 163 | 274 | } |
|
| 164 | 275 | number(maximum); |
|
| 165 | 276 | put(10); |
|
| 166 | 277 | finish(); |
|
| 167 | 278 | } |
|
| 168 | 279 | ||
| 280 | + | /// Resume an actual U-mode Wait call through an IPI to an idle remote hart. |
|
| 281 | + | unsafe fn idleWakeup() { |
|
| 282 | + | let guard = sync::enter(); |
|
| 283 | + | let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)); |
|
| 284 | + | let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE, |
|
| 285 | + | budgets::Binding { budget: IDLE_WINDOW, domain: IDLE_HANDLE, context: IDLE_CONTEXT }, clock); |
|
| 286 | + | let request = try! dispatch::request(1, remote::Action::Reschedule); |
|
| 287 | + | sync::leave(guard); |
|
| 288 | + | let acknowledged = try! dispatch::awaitRequest(0, request); |
|
| 289 | + | assert acknowledged == 0; |
|
| 290 | + | let activated = try! calls::synchronized(CHECKER, 21, &[*RECEIVER, IDLE_STACK, 0, 0], clock); |
|
| 291 | + | assert activated == 0 and waiting(); |
|
| 292 | + | let now = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)); |
|
| 293 | + | let result = try! calls::synchronized(CHECKER, 50, &[*RECEIVER, 0x55, 0, 0], now); |
|
| 294 | + | assert result == 0; |
|
| 295 | + | let mut resumed = false; |
|
| 296 | + | for attempt in 0..100000 { |
|
| 297 | + | if sync::loadAcquire(memory(WAKE_STATE) as *u64) == 3 { |
|
| 298 | + | set resumed = true; |
|
| 299 | + | break; |
|
| 300 | + | } |
|
| 301 | + | } |
|
| 302 | + | assert resumed; |
|
| 303 | + | for byte in "smp idle-hart activation and wakeup passed\n" { |
|
| 304 | + | put(byte); |
|
| 305 | + | } |
|
| 306 | + | remoteTimeout(); |
|
| 307 | + | } |
|
| 308 | + | ||
| 309 | + | /// Deliver a timeout from hart zero to a waiting user context on idle hart one. |
|
| 310 | + | unsafe fn remoteTimeout() { |
|
| 311 | + | let guard = sync::enter(); |
|
| 312 | + | let mut domain = try! domains::get(&domains::STORE, IDLE_DOMAIN); |
|
| 313 | + | let event = try! events::pop(&mut domain.memory.ring) else panic "missing wakeup"; |
|
| 314 | + | assert event.kind == events::WAKEUP and event.code == 0x55; |
|
| 315 | + | sync::storeRelease(memory(WAKE_STATE) as *mut u64, 4); |
|
| 316 | + | sync::leave(guard); |
|
| 317 | + | assert waiting(); |
|
| 318 | + | let posting = sync::enter(); |
|
| 319 | + | let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)); |
|
| 320 | + | try! timers::request(&mut timers::STORE, &domains::STORE, IDLE_DOMAIN, 0, clock, 100000, 0x66); |
|
| 321 | + | let request = try! dispatch::request(0, remote::Action::Reschedule); |
|
| 322 | + | sync::leave(posting); |
|
| 323 | + | let acknowledged = try! dispatch::awaitRequest(0, request); |
|
| 324 | + | assert acknowledged == 0; |
|
| 325 | + | let mut resumed = false; |
|
| 326 | + | for attempt in 0..100000 { |
|
| 327 | + | if sync::loadAcquire(memory(WAKE_STATE) as *u64) == 6 { |
|
| 328 | + | set resumed = true; |
|
| 329 | + | break; |
|
| 330 | + | } |
|
| 331 | + | } |
|
| 332 | + | assert resumed; |
|
| 333 | + | for byte in "smp remote timeout passed\n" { |
|
| 334 | + | put(byte); |
|
| 335 | + | } |
|
| 336 | + | } |
|
| 337 | + | ||
| 338 | + | /// Confirm the waiting context has released hart ownership before its wakeup. |
|
| 339 | + | unsafe fn waiting() -> bool { |
|
| 340 | + | for attempt in 0..100000 { |
|
| 341 | + | let guard = sync::enter(); |
|
| 342 | + | let context = try! domains::context(&domains::STORE, IDLE_DOMAIN, IDLE_CONTEXT); |
|
| 343 | + | let stopped = context.state == domains::ContextState::Waiting and context.hart == nil; |
|
| 344 | + | sync::leave(guard); |
|
| 345 | + | if stopped { |
|
| 346 | + | return true; |
|
| 347 | + | } |
|
| 348 | + | } |
|
| 349 | + | return false; |
|
| 350 | + | } |
|
| 351 | + | ||
| 169 | 352 | /// Print one full-width hexadecimal diagnostic value. |
|
| 170 | 353 | fn number(value: u64) { |
|
| 171 | 354 | let digits = "0123456789abcdef"; |
|
| 172 | 355 | for i in 0..16 { |
|
| 173 | 356 | put(digits[((value >> ((15 - i) as u64 * 4)) & 15) as u32]); |
test/smp/spin.rad
+45 -0
| 3 | 3 | export mod sys; |
|
| 4 | 4 | ||
| 5 | 5 | /// Completed allocation transactions, indexed by the executing hart. |
|
| 6 | 6 | export static count: [u64; 8] = [0; 8]; |
|
| 7 | 7 | ||
| 8 | + | /// Future budget handles assigned to callers on the preceding online hart. |
|
| 9 | + | export static windows: [u64; 8] = [0; 8]; |
|
| 10 | + | ||
| 11 | + | /// Capability used to notify the shared domain from each online hart. |
|
| 12 | + | export static notification: u64 = 0; |
|
| 13 | + | ||
| 14 | + | /// Observed destination hart plus one after each context changes budget. |
|
| 15 | + | export static migrations: [u64; 8] = [0; 8]; |
|
| 16 | + | ||
| 17 | + | /// Wait handshake: 2/3 bracket wakeup; 4 requests a second wait, bracketed by 5/6. |
|
| 18 | + | export static wakeState: u64 = 0; |
|
| 19 | + | ||
| 20 | + | /// Nonzero selects the independently activated idle-wakeup context. |
|
| 21 | + | export static idle: u64 = 0; |
|
| 22 | + | ||
| 8 | 23 | /// Check hart identity and shared-table allocation through repeated direct calls. |
|
| 9 | 24 | fn run(hart: u64) { |
|
| 10 | 25 | let original = sys::currentContext(); |
|
| 11 | 26 | assert original.hart == hart; |
|
| 27 | + | let budget = abi::Handle(windows[hart as u32]); |
|
| 28 | + | let before = try! sys::queryBudget(budget); |
|
| 29 | + | let right = try! sys::budgetSplit(budget, before.start + 1000); |
|
| 30 | + | let leftInfo = try! sys::queryBudget(budget); |
|
| 31 | + | let rightInfo = try! sys::queryBudget(right); |
|
| 32 | + | assert leftInfo.start == before.start and leftInfo.end == rightInfo.start; |
|
| 33 | + | assert rightInfo.end == before.end and rightInfo.hart == before.hart; |
|
| 34 | + | let merged = try! sys::budgetMerge(budget, right); |
|
| 35 | + | assert merged == budget; |
|
| 36 | + | let after = try! sys::queryBudget(merged); |
|
| 37 | + | assert after.start == before.start and after.end == before.end; |
|
| 38 | + | try! sys::capabilityDrop(merged); |
|
| 39 | + | try! sys::wakeup(abi::Handle(notification), hart as u32); |
|
| 12 | 40 | for i in 0..16 { |
|
| 13 | 41 | let page = try! sys::pageAllocate(abi::Handle(0), 1); |
|
| 14 | 42 | let info = sys::queryPage(page); |
|
| 15 | 43 | assert info.count == 1 and info.base <> 0; |
|
| 16 | 44 | try! sys::capabilityDrop(page); |
|
| 17 | 45 | let current = sys::currentContext(); |
|
| 18 | 46 | assert current.context == original.context and current.hart == hart; |
|
| 19 | 47 | set count[hart as u32] += 1; |
|
| 20 | 48 | } |
|
| 21 | 49 | while true { |
|
| 50 | + | let current = sys::currentContext(); |
|
| 51 | + | assert current.context == original.context; |
|
| 52 | + | if current.budget <> original.budget { |
|
| 53 | + | set migrations[hart as u32] = current.hart + 1; |
|
| 54 | + | } |
|
| 22 | 55 | } |
|
| 23 | 56 | } |
|
| 24 | 57 | ||
| 25 | 58 | /// Start an additional context with its physical hart index in validated memory. |
|
| 26 | 59 | export fn worker(args: *u64, size: u64) { |
| 28 | 61 | run(*args); |
|
| 29 | 62 | } |
|
| 30 | 63 | ||
| 31 | 64 | /// The initial context owns hart zero's window. |
|
| 32 | 65 | @default fn main(env: *opaque) { |
|
| 66 | + | if idle <> 0 { |
|
| 67 | + | set wakeState = 2; |
|
| 68 | + | sys::wait(); |
|
| 69 | + | set wakeState = 3; |
|
| 70 | + | while wakeState <> 4 { |
|
| 71 | + | } |
|
| 72 | + | set wakeState = 5; |
|
| 73 | + | sys::wait(); |
|
| 74 | + | set wakeState = 6; |
|
| 75 | + | while true { |
|
| 76 | + | } |
|
| 77 | + | } |
|
| 33 | 78 | run(0); |
|
| 34 | 79 | } |