kernel: run budgeted domains across independent harts
408a18439b39d1bfdf32c158adccbf0d54c555c937d0b96c1ef4607d4ca361fc
Verified: make -C kernel check; make std-test bin-test; complete runtime/control workloads pass on 1, 2, and 8 harts.
1 parent
0b52e1ef
kernel/Makefile
+15 -6
| 8 | 8 | CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM)) |
|
| 9 | 9 | CHECK_MODULES := $(wildcard check/*.rad) |
|
| 10 | 10 | USER_INPUTS := user.rad $(wildcard user/*.rad user/*/*.rad) core/abi.rad |
|
| 11 | 11 | USER_BASE := -pkg abi -mod core/abi.rad -pkg user -mod user.rad -mod user/sys.rad -pkg probe -mod user/probe.rad |
|
| 12 | 12 | BASE_IMAGES := sample_root sample_control sample_scalars sample_memory sample_overflow sample_events sample_alias sample_instance |
|
| 13 | + | CONTROL_IMAGES := control flow exiting faulting spinning admin remote |
|
| 13 | 14 | BASE_RIL := $(addprefix build/,$(addsuffix .ril,$(BASE_IMAGES))) |
|
| 15 | + | CONTROL_RIL := $(addprefix build/,$(addsuffix .ril,$(CONTROL_IMAGES))) |
|
| 14 | 16 | IMAGE_MODULES_sample_control := user/sample_control/math.rad |
|
| 17 | + | IMAGE_MODULES_control := user/control/lifecycle.rad user/control/authority.rad |
|
| 15 | 18 | BASE_CATALOG := -zero-bss -pkg images -mod build/baseline/images.rad -mod user/sys.ras $(addprefix -image ,$(BASE_RIL)) |
|
| 16 | - | MACHINE := $(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions |
|
| 19 | + | CONTROL_CATALOG := -zero-bss -pkg images -mod build/control/images.rad -mod user/sys.ras $(addprefix -image ,$(CONTROL_RIL)) |
|
| 20 | + | MACHINE := $(EMU) -machine -no-guard-stack -max-steps=1000000000 -count-instructions |
|
| 17 | 21 | ||
| 18 | 22 | .PHONY: all check clean compiler-check |
|
| 19 | 23 | .DELETE_ON_ERROR: |
|
| 20 | 24 | all: kernel.rv64 |
|
| 21 | 25 |
| 32 | 36 | ||
| 33 | 37 | build/baseline/images.rad: $(BASE_RIL) $(COMPILER) |
|
| 34 | 38 | mkdir -p $(@D) |
|
| 35 | 39 | $(COMPILE) -catalog $@ $(addprefix -image ,$(BASE_RIL)) |
|
| 36 | 40 | ||
| 37 | - | kernel.rv64: main.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 38 | - | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg kernel -start arch/entry.ras -mod main.rad -entry kernel -o $@ |
|
| 41 | + | build/control/images.rad: $(CONTROL_RIL) $(COMPILER) |
|
| 42 | + | mkdir -p $(@D) |
|
| 43 | + | $(COMPILE) -catalog $@ $(addprefix -image ,$(CONTROL_RIL)) |
|
| 44 | + | ||
| 45 | + | kernel.rv64: main.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/control/images.rad user/sys.ras $(COMPILER) |
|
| 46 | + | $(COMPILE) $(CONTROL_CATALOG) $(CORE) -pkg kernel -start arch/start.ras -mod main.rad -entry kernel -o $@ |
|
| 39 | 47 | ||
| 40 | 48 | check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 41 | 49 | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@ |
|
| 42 | 50 | ||
| 43 | 51 | context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
| 47 | 55 | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@ |
|
| 48 | 56 | ||
| 49 | 57 | native.rv64: native.rad native/instances.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 50 | 58 | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg native -start arch/entry.ras -mod native.rad -mod native/instances.rad -entry native -o $@ |
|
| 51 | 59 | ||
| 52 | - | parallel.rv64: parallel.rad parallel/mailboxes.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER) |
|
| 53 | - | $(COMPILE) $(BASE_CATALOG) $(CORE) -pkg parallel -start arch/start.ras -mod parallel.rad -mod parallel/mailboxes.rad -entry parallel -o $@ |
|
| 60 | + | parallel.rv64: parallel.rad parallel/mailboxes.rad arch/start.ras core.rad $(MODULES) $(CORE_ASM) build/control/images.rad user/sys.ras $(COMPILER) |
|
| 61 | + | $(COMPILE) $(CONTROL_CATALOG) $(CORE) -pkg parallel -start arch/start.ras -mod parallel.rad -mod parallel/mailboxes.rad -entry parallel -o $@ |
|
| 54 | 62 | ||
| 55 | 63 | check: all check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64 |
|
| 56 | 64 | $(HOST_EMU) -run check.rv64 |
|
| 57 | - | $(MACHINE) -run kernel.rv64 |
|
| 58 | 65 | $(MACHINE) -max-steps=1000000 -run context.rv64 |
|
| 59 | 66 | $(MACHINE) -harts=2 -irq=3 -irq-at=4000000 -uart-rx=52 -uart-rx-at=8000000 -run interrupt.rv64 |
|
| 60 | 67 | $(MACHINE) -run native.rv64 |
|
| 61 | 68 | $(MACHINE) -harts=1 -run parallel.rv64 |
|
| 62 | 69 | $(MACHINE) -harts=2 -run parallel.rv64 |
|
| 63 | 70 | $(MACHINE) -harts=8 -run parallel.rv64 |
|
| 71 | + | @output="$$( $(MACHINE) -harts=2 -run kernel.rv64 2>build/boot.log )"; status=$$?; \ |
|
| 72 | + | if test "$$status" -ne 2 || test "$$output" != CONTROL-OK; then cat build/boot.log; exit 1; fi |
|
| 64 | 73 | ||
| 65 | 74 | clean: |
|
| 66 | 75 | rm -rf build |
|
| 67 | 76 | rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64 native.rv64 parallel.rv64 |
kernel/NOTES.md
+47 -2
| 1 | 1 | # Kernel implementation decisions |
|
| 2 | 2 | ||
| 3 | 3 | The specification at https://radiant.computer/system/kernel takes precedence |
|
| 4 | 4 | for fixed call numbers, handle layout, rights, and object behavior. These notes |
|
| 5 | - | record the contracts established through step 21 of the 22-step plan. |
|
| 5 | + | record the contracts established through step 22 of the 22-step plan. |
|
| 6 | 6 | ||
| 7 | 7 | ## Source and trust boundary |
|
| 8 | 8 | ||
| 9 | 9 | - Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register |
|
| 10 | 10 | state, atomics, and MMIO. Hosted checks exercise the same mechanism modules. |
| 311 | 311 | Queue drain and software-interrupt acknowledgement share the publication lock. |
|
| 312 | 312 | - Machine probes cover ticket wraparound, mailbox capacity, coherent increments, |
|
| 313 | 313 | and software-interrupt wakeup after payload publication on 1, 2, and 8 harts. |
|
| 314 | 314 | RV64A checks run in machine mode, not the hosted emulator. |
|
| 315 | 315 | ||
| 316 | + | ## Budgeted multicore execution and boot |
|
| 317 | + | ||
| 318 | + | - core::control applies trap effects under the shared lock. Each hart owns one |
|
| 319 | + | architectural frame and one exact-use accounting interval outside that lock. |
|
| 320 | + | Charge completed U-mode execution before processing its trap. Queries observe |
|
| 321 | + | retained state and conservative balances without charging another hart's live |
|
| 322 | + | interval. |
|
| 323 | + | - At the architectural boundary, failed metadata queries terminate the caller |
|
| 324 | + | with Fault code 512 + Error. Ordinary call failures return Error to U-mode. |
|
| 325 | + | - Run grants explicit admission. Local Run suspends its caller until the callee |
|
| 326 | + | yields, waits, exhausts its budget, or terminates. Remote Run uses the bounded |
|
| 327 | + | request queue. Events may resume an admitted waiting context with positive |
|
| 328 | + | retained budget. Yield requires another explicit Run. |
|
| 329 | + | - Dying domains lose lookup and dispatch authority. Each active architectural |
|
| 330 | + | owner receives a reserved Stop request. Reclaim only after every frame returns |
|
| 331 | + | and accounting completes. Destroy waits for every selected domain incarnation, |
|
| 332 | + | including creation descendants, before replying. Stale requests cannot target |
|
| 333 | + | a reused identity. |
|
| 334 | + | - Boot reserves bounded kernel tables from physical RAM before user allocation. |
|
| 335 | + | Root receives device, interrupt, per-hart reserve-budget, startup-Page, and stack |
|
| 336 | + | authority; a separate finite budget starts it on hart zero. Root Exit reclaims |
|
| 337 | + | root normally and leaves the machine running. |
|
| 338 | + | - Root Device handles have Read, Write, Grant, and Transfer rights. Root can |
|
| 339 | + | share device authority or transfer it to a driver domain. |
|
| 340 | + | - Control workloads cover local continuations, Page sharing, delegation after |
|
| 341 | + | issuer death, reparenting, query faults, budget exhaustion, and cross-hart event |
|
| 342 | + | handshakes. Two contexts in one domain change and observe shared state on |
|
| 343 | + | different harts. After Destroy returns, a sentinel must remain unchanged |
|
| 344 | + | through a timeout, exercising remote quiescence before reclamation. |
|
| 345 | + | ||
| 346 | + | ## Source size |
|
| 347 | + | ||
| 348 | + | - Kernel mechanisms and machine entry contain 3,449 nonblank, non-comment source |
|
| 349 | + | lines. All kernel Radiance/RAS files, including checks and workloads, contain |
|
| 350 | + | 6,977 such lines. The largest module has 458 lines. |
|
| 351 | + | - These are source-size metrics, not evidence of hostile-code isolation, hardware |
|
| 352 | + | timing, or cache-coherence behavior. |
|
| 353 | + | ||
| 316 | 354 | ## Validation |
|
| 317 | 355 | ||
| 318 | 356 | Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass |
|
| 319 | 357 | `EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build |
|
| 320 | 358 | checks compiler dependencies. From the repository root, run: |
|
| 321 | 359 | ||
| 322 | 360 | ```sh |
|
| 323 | 361 | make -C kernel check |
|
| 362 | + | make std-test bin-test |
|
| 324 | 363 | ``` |
|
| 325 | 364 | ||
| 326 | - | Run machine mailbox/startup probes on 1, 2, and 8 harts, including wraparound, full queues, coherent shared increments, and CLINT wakeup after payload publication. |
|
| 365 | + | Run hosted mechanisms, context/timer entry, two-hart PLIC/UART delivery, and the eight-image native workload. Run the complete control workload on 1, 2, and 8 harts. Production boot must print CONTROL-OK, then reach the emulator tick limit with the machine still running after root exit. The check expects that deliberate limit exit rather than firmware shutdown. |
|
| 327 | 366 | ||
| 328 | 367 | Run the context reservation probe with an emulator that retains LR/SC |
|
| 329 | 368 | reservations across traps. This checks the kernel's reservation invalidation. |
|
| 369 | + | ||
| 370 | + | The shared-backend smoke graph also exercises repeated, unaligned, and |
|
| 371 | + | transitive private pointers in independent instances. Oversized writable |
|
| 372 | + | addresses must return a capacity error before backend address patching. Machine |
|
| 373 | + | checks use emulator tick limits and deterministic interleaving; they are not |
|
| 374 | + | real-hardware tests. The trusted-input verification exclusions still apply. |
kernel/core.rad
+6 -1
| 24 | 24 | export mod interrupts; |
|
| 25 | 25 | export mod mmio; |
|
| 26 | 26 | export mod physical; |
|
| 27 | 27 | export mod activation; |
|
| 28 | 28 | export mod lifecycle; |
|
| 29 | - | export mod calls; |
|
| 30 | 29 | export mod smp; |
|
| 30 | + | export mod boot; |
|
| 31 | + | export mod calls; |
|
| 32 | + | export mod execution; |
|
| 33 | + | export mod control; |
|
| 34 | + | export mod teardown; |
|
| 35 | + | export mod runtime; |
kernel/core/boot.rad
added
+211 -0
| 1 | + | //! One-shot physical provisioning of kernel tables and explicit root authority. |
|
| 2 | + | //! |
|
| 3 | + | //! Kernel tables consume permanent frame allocations, never Page resources or |
|
| 4 | + | //! domain-lifetime grants. Their slices remain stable until the machine resets. |
|
| 5 | + | ||
| 6 | + | use core::abi; |
|
| 7 | + | use core::activation; |
|
| 8 | + | use core::atomic; |
|
| 9 | + | use core::budget_caps; |
|
| 10 | + | use core::budgets; |
|
| 11 | + | use core::contexts; |
|
| 12 | + | use core::domains; |
|
| 13 | + | use core::execution; |
|
| 14 | + | use core::fdt; |
|
| 15 | + | use core::frames; |
|
| 16 | + | use core::handles; |
|
| 17 | + | use core::interrupts; |
|
| 18 | + | use core::memory; |
|
| 19 | + | use core::pages; |
|
| 20 | + | use core::physical; |
|
| 21 | + | use core::platform; |
|
| 22 | + | use core::resources; |
|
| 23 | + | use core::smp; |
|
| 24 | + | use core::state; |
|
| 25 | + | use images; |
|
| 26 | + | ||
| 27 | + | /// Space for one Page per tracked frame plus all bounded non-Page resources. |
|
| 28 | + | constant RESOURCE_CAPACITY: u32 = frames::MAX_FRAMES + 512 + platform::MAX_DEVICES + platform::MAX_IRQS; |
|
| 29 | + | /// Root's authorized Page stack; activation allocates a separate native stack. |
|
| 30 | + | constant ROOT_STACK_FRAMES: u32 = 16; |
|
| 31 | + | /// Initial budget handles permit inspection, splitting, binding and delegation. |
|
| 32 | + | constant BUDGET_RIGHTS: u16 = abi::READ | abi::WRITE | abi::EXECUTE | abi::GRANT | abi::TRANSFER; |
|
| 33 | + | /// Release-published only after root admission and every shared table is ready. |
|
| 34 | + | static READY: u32 = 0; |
|
| 35 | + | /// Decoder output must not consume the hart's private 64 KiB machine stack. |
|
| 36 | + | /// Node properties borrow the firmware blob, which stays in reserved boot RAM. |
|
| 37 | + | static TREE: fdt::Tree = undefined; |
|
| 38 | + | /// Validated topology is immutable after publication. |
|
| 39 | + | static PLATFORM: platform::Platform = undefined; |
|
| 40 | + | /// Machine-lifetime frame ownership, never copied after tables borrow its address. |
|
| 41 | + | static POOL: frames::Pool = undefined; |
|
| 42 | + | /// Stable roots of permanently allocated capability and context tables. |
|
| 43 | + | static KERNEL: state::State = undefined; |
|
| 44 | + | /// Global interrupt state, protected by RUNTIME.lock after publication. |
|
| 45 | + | static CONTROLLER: interrupts::Controller = undefined; |
|
| 46 | + | /// Stable lock and pointers to permanent hardware-hart and admission tables. |
|
| 47 | + | static RUNTIME: execution::State = undefined; |
|
| 48 | + | ||
| 49 | + | /// Provision shared state once and acquire its publication on every online hart. |
|
| 50 | + | /// Firmware reserves the image, machine stacks and borrowed FDT for machine life. |
|
| 51 | + | /// Each caller must initialize its local runtime interrupt state before dispatch. |
|
| 52 | + | export unsafe fn start(hardware: u64, description: *u8) -> *mut execution::State { |
|
| 53 | + | assert hardware < platform::MAX_HARTS as u64; |
|
| 54 | + | let hart = hardware as u32; |
|
| 55 | + | assert smp::hart() == hart; |
|
| 56 | + | if hart == 0 { |
|
| 57 | + | let size = try! fdt::word(@sliceOf(description, 40), 4); |
|
| 58 | + | assert size >= 40 and size <= fdt::MAX_BYTES; |
|
| 59 | + | try! fdt::decode(@sliceOf(description, size), &mut TREE); |
|
| 60 | + | try! platform::discover(&TREE, &mut PLATFORM); |
|
| 61 | + | try! frames::init(&mut POOL, &PLATFORM); |
|
| 62 | + | let root = init(&mut KERNEL, &mut POOL, &PLATFORM, &mut CONTROLLER); |
|
| 63 | + | initRuntime(&mut RUNTIME, &mut KERNEL, &PLATFORM, &mut CONTROLLER); |
|
| 64 | + | // Activation and binding alone grant no dispatch admission. |
|
| 65 | + | execution::root(&mut RUNTIME, root); |
|
| 66 | + | atomic::store(&mut READY, 1); |
|
| 67 | + | } else { |
|
| 68 | + | // This is the only shared access permitted before acquiring publication. |
|
| 69 | + | while atomic::load(&READY) == 0 {} |
|
| 70 | + | } |
|
| 71 | + | return &mut RUNTIME; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Allocate permanent, cleared kernel storage without a stack-local backing array. |
|
| 75 | + | /// The caller supplies a concrete type layout and retains the resulting slice. |
|
| 76 | + | unsafe fn storage(pool: *mut frames::Pool, count: u32, size: u32, alignment: u32) -> *mut opaque { |
|
| 77 | + | assert count > 0 and size > 0; |
|
| 78 | + | assert alignment > 0 and alignment <= frames::PAGE_SIZE; |
|
| 79 | + | assert alignment & (alignment - 1) == 0; |
|
| 80 | + | let bytes = count as u64 * size as u64; |
|
| 81 | + | let frameCount = (bytes + frames::PAGE_SIZE as u64 - 1) / frames::PAGE_SIZE as u64; |
|
| 82 | + | assert frameCount <= frames::MAX_FRAMES as u64; |
|
| 83 | + | let run = frames::install(try! frames::allocate(pool, frameCount as u32, physical::clear)); |
|
| 84 | + | // No resource object or grant ever owns these frames. Only the availability |
|
| 85 | + | // bitmap and permanent kernel slices retain their machine-lifetime ownership. |
|
| 86 | + | return physical::bytes(run.first as u64 * frames::PAGE_SIZE as u64, run.count * frames::PAGE_SIZE).ptr as *mut opaque; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Initialize full-capacity tables directly in owned physical storage. |
|
| 90 | + | unsafe fn tables(kernel: *mut state::State, pool: *mut frames::Pool) { |
|
| 91 | + | let domainStore = storage(pool, abi::MAX_DOMAINS, @sizeOf(domains::Domain), @alignOf(domains::Domain)); |
|
| 92 | + | let resourceStore = storage(pool, RESOURCE_CAPACITY, @sizeOf(resources::Slot), @alignOf(resources::Slot)); |
|
| 93 | + | let contextStore = storage(pool, contexts::CAPACITY, @sizeOf(contexts::Context), @alignOf(contexts::Context)); |
|
| 94 | + | let pinStore = storage(pool, frames::MAX_FRAMES, @sizeOf(u16), @alignOf(u16)); |
|
| 95 | + | let objectStore = storage(pool, frames::MAX_FRAMES, @sizeOf(bool), @alignOf(bool)); |
|
| 96 | + | let grantCount = frames::MAX_FRAMES / 64 * abi::MAX_DOMAINS; |
|
| 97 | + | let grantStore = storage(pool, grantCount, @sizeOf(u64), @alignOf(u64)); |
|
| 98 | + | set kernel.domains = @sliceOf(domainStore as *mut domains::Domain, abi::MAX_DOMAINS); |
|
| 99 | + | set kernel.resources = @sliceOf(resourceStore as *mut resources::Slot, RESOURCE_CAPACITY); |
|
| 100 | + | set kernel.contexts = @sliceOf(contextStore as *mut contexts::Context, contexts::CAPACITY); |
|
| 101 | + | domains::init(kernel.domains); |
|
| 102 | + | resources::init(kernel.resources); |
|
| 103 | + | for i in 0..kernel.contexts.len { contexts::init(&mut kernel.contexts[i], i); } |
|
| 104 | + | memory::init(&mut kernel.memory, pool, |
|
| 105 | + | @sliceOf(pinStore as *mut u16, frames::MAX_FRAMES), |
|
| 106 | + | @sliceOf(objectStore as *mut bool, frames::MAX_FRAMES), |
|
| 107 | + | @sliceOf(grantStore as *mut u64, grantCount), abi::MAX_DOMAINS); |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Allocate machine-lifetime routing and hart-owned frames before publication. |
|
| 111 | + | /// kernel has been initialized by init. runtime and its lock must never move; |
|
| 112 | + | /// no domain Page, private-image allocation, or teardown owns these table frames. |
|
| 113 | + | /// Root still needs an explicit execution::root admission after this returns. |
|
| 114 | + | export unsafe fn initRuntime(runtime: *mut execution::State, kernel: *mut state::State, |
|
| 115 | + | machine: *platform::Platform, controller: *mut interrupts::Controller) { |
|
| 116 | + | let pool = kernel.memory.pool; |
|
| 117 | + | let hartStore = storage(pool, platform::MAX_HARTS, @sizeOf(execution::Hart), @alignOf(execution::Hart)); |
|
| 118 | + | let routeStore = storage(pool, kernel.contexts.len, @sizeOf(execution::Route), @alignOf(execution::Route)); |
|
| 119 | + | execution::init(runtime, kernel, machine, controller, |
|
| 120 | + | @sliceOf(hartStore as *mut execution::Hart, platform::MAX_HARTS), |
|
| 121 | + | @sliceOf(routeStore as *mut execution::Route, kernel.contexts.len)); |
|
| 122 | + | } |
|
| 123 | + | ||
| 124 | + | /// Publish one boot resource with its first reference in an exact root slot. |
|
| 125 | + | fn install(kernel: *mut state::State, slot: u32, value: resources::Value, rights: u16) -> abi::Handle { |
|
| 126 | + | let object = try! resources::create(kernel.resources, value); |
|
| 127 | + | resources::retain(kernel.resources, object); |
|
| 128 | + | return handles::install(&mut kernel.domains[0].handles, slot, object, rights); |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | /// Provision a validated machine once, before secondary or user kernel entry. |
|
| 132 | + | /// pool is already initialized, excluding kernel/image/boot reservations; kernel, |
|
| 133 | + | /// pool, machine and controller remain kernel-owned and mapped throughout use. |
|
| 134 | + | /// Initialize the PLIC on hardware hart 0 and return root's activated context, |
|
| 135 | + | /// bound to a distinct finite bootstrap Budget. Allocation failure halts boot. |
|
| 136 | + | /// Startup is consecutive u64 words: version, deviceCount, irqCount, hartCount, |
|
| 137 | + | /// budgetBaseSlot, bootstrapBudgetSlot, timebaseFrequency, imageCount; then |
|
| 138 | + | /// (hardwareHartId, reserveBudgetSlot) pairs and catalog Image indices in order. |
|
| 139 | + | export unsafe fn init(kernel: *mut state::State, pool: *mut frames::Pool, machine: *platform::Platform, controller: *mut interrupts::Controller) -> u64 { |
|
| 140 | + | assert pool.limit > 0 and pool.limit <= frames::MAX_FRAMES; |
|
| 141 | + | assert machine.deviceCount <= platform::MAX_DEVICES; |
|
| 142 | + | assert machine.irqCount > 0 and machine.irqCount < platform::MAX_IRQS; |
|
| 143 | + | assert machine.hartCount > 0 and machine.hartCount <= platform::MAX_HARTS; |
|
| 144 | + | assert machine.frequency > 0 and images::ROOT < images::COUNT; |
|
| 145 | + | let budgetBaseSlot = 2 + machine.deviceCount + machine.irqCount; |
|
| 146 | + | let bootstrapBudgetSlot = budgetBaseSlot + machine.hartCount; |
|
| 147 | + | assert bootstrapBudgetSlot + 3 <= abi::MAX_HANDLES; |
|
| 148 | + | let mut bootHartIndex = machine.hartCount; |
|
| 149 | + | for i in 0..machine.hartCount { |
|
| 150 | + | assert machine.harts[i].id < platform::MAX_HARTS; |
|
| 151 | + | if machine.harts[i].id == 0 { set bootHartIndex = i; } |
|
| 152 | + | } |
|
| 153 | + | assert bootHartIndex < machine.hartCount; |
|
| 154 | + | tables(kernel, pool); |
|
| 155 | + | interrupts::init(controller, machine, bootHartIndex); |
|
| 156 | + | let root = domains::root(kernel.domains, images::ROOT); |
|
| 157 | + | let owner = try! domains::resolve(kernel.domains, 0, root, 0); |
|
| 158 | + | let initial = try! activation::initialize(pool, images::ROOT, physical::clear, physical::copy); |
|
| 159 | + | set kernel.domains[0].private = initial.run; |
|
| 160 | + | set kernel.domains[0].env.stateBase = initial.base; |
|
| 161 | + | ||
| 162 | + | // Fixed slots contain no implicit discovery or ambient resource authority. |
|
| 163 | + | for i in 0..machine.deviceCount { |
|
| 164 | + | let _device = install(kernel, 2 + i, resources::Value::Device(machine.devices[i].region), abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER); |
|
| 165 | + | } |
|
| 166 | + | for source in 1..machine.irqCount + 1 { |
|
| 167 | + | let slot = 2 + machine.deviceCount + source - 1; |
|
| 168 | + | let irq = install(kernel, slot, resources::Value::Interrupt(resources::Interrupt { |
|
| 169 | + | number: source, target: owner, |
|
| 170 | + | }), abi::RIGHTS); |
|
| 171 | + | let entry = try! handles::require(&kernel.domains[0].handles, irq, abi::Kind::Interrupt, 0); |
|
| 172 | + | interrupts::register(controller, source, entry.object); |
|
| 173 | + | } |
|
| 174 | + | let frequency = machine.frequency as u64; |
|
| 175 | + | for i in 0..machine.hartCount { |
|
| 176 | + | let reserve = try! budgets::init(machine.harts[i].id, frequency * 3600); |
|
| 177 | + | let _budget = install(kernel, budgetBaseSlot + i, resources::Value::Budget(reserve), BUDGET_RIGHTS); |
|
| 178 | + | } |
|
| 179 | + | let bootstrap = try! budgets::init(0, frequency * 60); |
|
| 180 | + | let bootBudget = install(kernel, bootstrapBudgetSlot, resources::Value::Budget(bootstrap), BUDGET_RIGHTS); |
|
| 181 | + | ||
| 182 | + | // These two Page objects follow all fixed authority slots: startup, then stack. |
|
| 183 | + | // Large catalogs may span several frames but still occupy one startup Page. |
|
| 184 | + | let wordCount = 8 as u64 + machine.hartCount as u64 * 2 + images::COUNT as u64; |
|
| 185 | + | let byteCount = wordCount * 8; |
|
| 186 | + | let startupFrames = (byteCount + frames::PAGE_SIZE as u64 - 1) / frames::PAGE_SIZE as u64; |
|
| 187 | + | let startup = try! pages::allocate(kernel, 0, root, startupFrames, physical::clear); |
|
| 188 | + | let args = try! pages::access(kernel, 0, startup, abi::READ | abi::WRITE); |
|
| 189 | + | let words = @sliceOf(physical::bytes(args.base, byteCount as u32).ptr as *mut u64, wordCount as u32); |
|
| 190 | + | set words[0] = 1; |
|
| 191 | + | set words[1] = machine.deviceCount as u64; |
|
| 192 | + | set words[2] = machine.irqCount as u64; |
|
| 193 | + | set words[3] = machine.hartCount as u64; |
|
| 194 | + | set words[4] = budgetBaseSlot as u64; |
|
| 195 | + | set words[5] = bootstrapBudgetSlot as u64; |
|
| 196 | + | set words[6] = frequency; |
|
| 197 | + | set words[7] = images::COUNT as u64; |
|
| 198 | + | for i in 0..machine.hartCount { |
|
| 199 | + | set words[8 + i * 2] = machine.harts[i].id as u64; |
|
| 200 | + | set words[9 + i * 2] = (budgetBaseSlot + i) as u64; |
|
| 201 | + | } |
|
| 202 | + | for i in 0..images::COUNT { |
|
| 203 | + | set words[8 + machine.hartCount * 2 + i] = i as u64; |
|
| 204 | + | } |
|
| 205 | + | let stack = try! pages::allocate(kernel, 0, root, ROOT_STACK_FRAMES as u64, physical::clear); |
|
| 206 | + | let stackRange = try! pages::access(kernel, 0, stack, abi::WRITE); |
|
| 207 | + | let id = try! activation::activate(kernel, 0, root, stackRange.base + stackRange.size, |
|
| 208 | + | args.base, byteCount, physical::clear); |
|
| 209 | + | try! budget_caps::bind(kernel, 0, bootBudget, root, id); |
|
| 210 | + | return id; |
|
| 211 | + | } |
kernel/core/control.rad
added
+141 -0
| 1 | + | //! Direct-call effects completed under the shared kernel critical section. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::calls; |
|
| 6 | + | use core::contexts; |
|
| 7 | + | use core::cpu; |
|
| 8 | + | use core::domains; |
|
| 9 | + | use core::events; |
|
| 10 | + | use core::execution; |
|
| 11 | + | use core::lifecycle; |
|
| 12 | + | use core::notifications; |
|
| 13 | + | use core::smp; |
|
| 14 | + | use core::teardown; |
|
| 15 | + | use core::timers; |
|
| 16 | + | ||
| 17 | + | /// Query faults preserve the exact ABI error above the architectural fault range. |
|
| 18 | + | export constant QUERY_FAULT: u32 = 0x200; |
|
| 19 | + | /// An undefined direct-call number is a program fault. |
|
| 20 | + | export constant UNKNOWN_CALL: u32 = 0x102; |
|
| 21 | + | ||
| 22 | + | /// Determine whether this receiver has an admitted, budgeted waiter on a hart. |
|
| 23 | + | fn waiting(runtime: *mut execution::State, owner: abi::Object, hart: u32) -> bool { |
|
| 24 | + | for i in 0..runtime.kernel.contexts.len { |
|
| 25 | + | let context = &runtime.kernel.contexts[i]; |
|
| 26 | + | if context.hart <> hart or context.status <> contexts::Status::Waiting |
|
| 27 | + | or not execution::same(context.owner, owner) or context.budget.kind <> abi::Kind::Budget { continue; } |
|
| 28 | + | if execution::route(runtime, i).admitted and budget_caps::value(runtime.kernel, context.budget).remaining > 0 { |
|
| 29 | + | return true; |
|
| 30 | + | } |
|
| 31 | + | } |
|
| 32 | + | return false; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Construct a domain-wide notification hint; it contains no execution authority. |
|
| 36 | + | fn wake(owner: abi::Object) -> smp::Request { |
|
| 37 | + | return smp::Request { kind: smp::RequestKind::Wake, domain: owner, context: 0 }; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Check every required remote slot before committing a Wakeup event. |
|
| 41 | + | fn prepareWake(runtime: *mut execution::State, index: u32, arguments: *[u64]) -> abi::Object throws (abi::Error) { |
|
| 42 | + | if arguments[1] > 0xffffffff { throw abi::Error::InvalidArg; } |
|
| 43 | + | let source = &runtime.kernel.contexts[index]; |
|
| 44 | + | let owner = try domains::resolve(runtime.kernel.domains, source.owner.index, |
|
| 45 | + | abi::Handle { bits: arguments[0] }, abi::WAKE); |
|
| 46 | + | try events::refresh(&mut runtime.kernel.domains[owner.index].events) catch { |
|
| 47 | + | lifecycle::terminate(runtime.kernel, owner, 3, teardown::BAD_EVENTS); |
|
| 48 | + | throw abi::Error::BadHandle; |
|
| 49 | + | }; |
|
| 50 | + | for hart in 0..runtime.harts.len { |
|
| 51 | + | if hart <> source.hart and runtime.harts[hart].online and waiting(runtime, owner, hart) |
|
| 52 | + | and not smp::canPost(&runtime.harts[hart].mailbox, wake(owner)) { throw abi::Error::Busy; } |
|
| 53 | + | } |
|
| 54 | + | return owner; |
|
| 55 | + | } |
|
| 56 | + | ||
| 57 | + | /// Publish all preflighted wake requests in the same critical section as the event. |
|
| 58 | + | unsafe fn publishWake(runtime: *mut execution::State, source: u32, owner: abi::Object) { |
|
| 59 | + | for hart in 0..runtime.harts.len { |
|
| 60 | + | if hart == source or not runtime.harts[hart].online or not waiting(runtime, owner, hart) { continue; } |
|
| 61 | + | try! smp::post(&mut runtime.harts[hart].mailbox, wake(owner)); |
|
| 62 | + | smp::kick(runtime.machine.clint.base, runtime.harts[hart].hardware.clintIndex); |
|
| 63 | + | } |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Identify direct calls whose typed interface cannot expose an error result. |
|
| 67 | + | fn query(number: u64) -> bool { |
|
| 68 | + | return (number >= 44 and number <= 48) or number == 62 or number == 74 or number == 75; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Distinguish undefined calls from errors returned by defined control operations. |
|
| 72 | + | fn defined(number: u64) -> bool { |
|
| 73 | + | return (number >= 10 and number <= 12) or (number >= 20 and number <= 23) |
|
| 74 | + | or number == 30 or number == 31 or (number >= 40 and number <= 50) |
|
| 75 | + | or (number >= 60 and number <= 62) or (number >= 70 and number <= 76); |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | /// Commit one call. Architectural return and budget charging must precede this. |
|
| 79 | + | unsafe fn apply(runtime: *mut execution::State, index: u32, now: u64) throws (abi::Error) { |
|
| 80 | + | let kernel = runtime.kernel; |
|
| 81 | + | let context = &mut kernel.contexts[index]; |
|
| 82 | + | let frame = &mut context.frame; |
|
| 83 | + | let number = frame.registers[17]; |
|
| 84 | + | let arguments = &frame.registers[10..17]; |
|
| 85 | + | let mut receiver = domains::none(); |
|
| 86 | + | if number == 50 { set receiver = try prepareWake(runtime, index, arguments); } |
|
| 87 | + | let outcome = try calls::dispatch(kernel, context.owner.index, number, arguments, now) catch error { |
|
| 88 | + | // A remote consumer can publish its head between preflight and push. |
|
| 89 | + | // Invalid progress faults that receiver, not the authorized sender. |
|
| 90 | + | if number == 50 and error == abi::Error::InvalidArg { |
|
| 91 | + | lifecycle::terminate(kernel, receiver, 3, teardown::BAD_EVENTS); |
|
| 92 | + | throw abi::Error::BadHandle; |
|
| 93 | + | } |
|
| 94 | + | throw error; |
|
| 95 | + | }; |
|
| 96 | + | match outcome { |
|
| 97 | + | case calls::Outcome::Reply { values } => { |
|
| 98 | + | if number == 50 { publishWake(runtime, context.hart, receiver); } |
|
| 99 | + | cpu::reply(frame, abi::Error::Ok, values[0], values[1], values[2], values[3]); |
|
| 100 | + | }, |
|
| 101 | + | case calls::Outcome::Yield => { |
|
| 102 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 103 | + | set context.status = contexts::Status::Stopped; |
|
| 104 | + | set execution::route(runtime, index).admitted = false; |
|
| 105 | + | execution::relinquish(runtime, index); |
|
| 106 | + | }, |
|
| 107 | + | case calls::Outcome::Wait => { |
|
| 108 | + | try notifications::wait(kernel, index); |
|
| 109 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 110 | + | if context.status == contexts::Status::Waiting { execution::relinquish(runtime, index); } |
|
| 111 | + | }, |
|
| 112 | + | case calls::Outcome::Timeout { ticks, token } => { |
|
| 113 | + | try timers::arm(&mut runtime.harts[context.hart].timers, context.owner, now, ticks, token); |
|
| 114 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 115 | + | }, |
|
| 116 | + | case calls::Outcome::Terminate { kind, code } => lifecycle::terminate(kernel, context.owner, kind, code), |
|
| 117 | + | case calls::Outcome::Destroy { handle, flags } => try teardown::request(runtime, index, handle, flags), |
|
| 118 | + | case calls::Outcome::Run { context: target } => { |
|
| 119 | + | try execution::run(runtime, index, target); |
|
| 120 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 121 | + | }, |
|
| 122 | + | } |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Dispatch a returned U-mode ecall, faulting queries and undefined operations. |
|
| 126 | + | export unsafe fn invoke(runtime: *mut execution::State, index: u32, now: u64) { |
|
| 127 | + | let context = &mut runtime.kernel.contexts[index]; |
|
| 128 | + | let number = context.frame.registers[17]; |
|
| 129 | + | assert context.frame.cause == 8 and context.status == contexts::Status::Ready; |
|
| 130 | + | try apply(runtime, index, now) catch error { |
|
| 131 | + | if query(number) { |
|
| 132 | + | lifecycle::terminate(runtime.kernel, context.owner, 3, QUERY_FAULT + error as u32); |
|
| 133 | + | } else if not defined(number) { |
|
| 134 | + | lifecycle::terminate(runtime.kernel, context.owner, 3, UNKNOWN_CALL); |
|
| 135 | + | } else if number == 42 { |
|
| 136 | + | lifecycle::terminate(runtime.kernel, context.owner, 3, teardown::BAD_EVENTS); |
|
| 137 | + | } else { |
|
| 138 | + | cpu::reply(&mut context.frame, error, 0, 0, 0, 0); |
|
| 139 | + | } |
|
| 140 | + | }; |
|
| 141 | + | } |
kernel/core/execution.rad
added
+253 -0
| 1 | + | //! Explicit execution admission, continuations, and hart-owned remote requests. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::contexts; |
|
| 6 | + | use core::cpu; |
|
| 7 | + | use core::domains; |
|
| 8 | + | use core::interrupts; |
|
| 9 | + | use core::platform; |
|
| 10 | + | use core::smp; |
|
| 11 | + | use core::state; |
|
| 12 | + | use core::timers; |
|
| 13 | + | ||
| 14 | + | /// Private admission state for one incarnation-qualified context. |
|
| 15 | + | export record Route { |
|
| 16 | + | /// Context identity to which all remaining fields belong. |
|
| 17 | + | identity: u64, |
|
| 18 | + | /// An explicit Run or boot admission remains in force. |
|
| 19 | + | admitted: bool, |
|
| 20 | + | /// The owning hart has not consumed its Run request. |
|
| 21 | + | queued: bool, |
|
| 22 | + | /// Same-hart callee that must relinquish execution before this caller resumes. |
|
| 23 | + | suspended: u64, |
|
| 24 | + | /// A successful Destroy reply awaits all snapshotted incarnations. |
|
| 25 | + | destroying: bool, |
|
| 26 | + | /// Selected domain epochs; zero excludes a slot. Read only while destroying. |
|
| 27 | + | targets: [u32; abi::MAX_DOMAINS], |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Private architectural routing and bounded asynchronous state for one hart. |
|
| 31 | + | export record Hart { |
|
| 32 | + | /// Firmware admitted this hardware hart. |
|
| 33 | + | online: bool, |
|
| 34 | + | /// Validated CLINT and PLIC routing. |
|
| 35 | + | hardware: platform::Hart, |
|
| 36 | + | /// Context whose architectural frame this hart owns outside the lock. |
|
| 37 | + | current: u64, |
|
| 38 | + | /// Explicit continuation or interrupted context preferred at the next dispatch. |
|
| 39 | + | preferred: u64, |
|
| 40 | + | /// Bounded ordinary Run and Wake transport. |
|
| 41 | + | mailbox: smp::Mailbox, |
|
| 42 | + | /// Reserved stop request for this hart's single architectural owner. |
|
| 43 | + | stop: ?smp::Request, |
|
| 44 | + | /// Accepted one-shot timers, serialized by the global lock. |
|
| 45 | + | timers: timers::Store, |
|
| 46 | + | /// Private trap frame used only by this hart's idle entry. |
|
| 47 | + | idle: cpu::Frame, |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Stable shared runtime storage; every field except owned frames needs lock. |
|
| 51 | + | export record State { |
|
| 52 | + | /// Capability, physical-memory, and context mechanisms. |
|
| 53 | + | kernel: *mut state::State, |
|
| 54 | + | /// Immutable firmware description. |
|
| 55 | + | machine: *platform::Platform, |
|
| 56 | + | /// Global PLIC service state. |
|
| 57 | + | controller: *mut interrupts::Controller, |
|
| 58 | + | /// Hardware-ID-indexed hart records. |
|
| 59 | + | harts: *mut [Hart], |
|
| 60 | + | /// Context-indexed admission records. |
|
| 61 | + | routes: *mut [Route], |
|
| 62 | + | /// Exact-use critical-section ownership. |
|
| 63 | + | lock: smp::Lock, |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Initialize one unshared admission record or discard an expired incarnation. |
|
| 67 | + | fn reset(route: *mut Route, identity: u64) { |
|
| 68 | + | set route.identity = identity; |
|
| 69 | + | set route.admitted = false; |
|
| 70 | + | set route.queued = false; |
|
| 71 | + | set route.suspended = 0; |
|
| 72 | + | set route.destroying = false; |
|
| 73 | + | set route.targets = [0; abi::MAX_DOMAINS]; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Initialize stable storage once before publication to secondary harts. |
|
| 77 | + | export fn init(runtime: *mut State, kernel: *mut state::State, machine: *platform::Platform, |
|
| 78 | + | controller: *mut interrupts::Controller, harts: *mut [Hart], routes: *mut [Route]) { |
|
| 79 | + | assert harts.len == platform::MAX_HARTS and routes.len == kernel.contexts.len; |
|
| 80 | + | set runtime.kernel = kernel; |
|
| 81 | + | set runtime.machine = machine; |
|
| 82 | + | set runtime.controller = controller; |
|
| 83 | + | set runtime.harts = harts; |
|
| 84 | + | set runtime.routes = routes; |
|
| 85 | + | smp::init(&mut runtime.lock); |
|
| 86 | + | for i in 0..routes.len { reset(&mut routes[i], 0); } |
|
| 87 | + | for i in 0..harts.len { |
|
| 88 | + | let hart = &mut harts[i]; |
|
| 89 | + | set hart.online = false; |
|
| 90 | + | set hart.hardware = platform::Hart { id: i, controller: 0, clintIndex: 0, plicContext: 0 }; |
|
| 91 | + | set hart.current = 0; |
|
| 92 | + | set hart.preferred = 0; |
|
| 93 | + | set hart.stop = nil; |
|
| 94 | + | smp::initMailbox(&mut hart.mailbox); |
|
| 95 | + | timers::init(&mut hart.timers); |
|
| 96 | + | cpu::init(&mut hart.idle, 0, 0, 0, 0); |
|
| 97 | + | } |
|
| 98 | + | for i in 0..machine.hartCount { |
|
| 99 | + | let hardware = machine.harts[i]; |
|
| 100 | + | assert hardware.id < harts.len; |
|
| 101 | + | set harts[hardware.id].online = true; |
|
| 102 | + | set harts[hardware.id].hardware = hardware; |
|
| 103 | + | } |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Borrow admission state for the current live context incarnation under lock. |
|
| 107 | + | export fn route(runtime: *mut State, index: u32) -> *mut Route { |
|
| 108 | + | let id = contexts::identity(&runtime.kernel.contexts[index]); |
|
| 109 | + | let entry = &mut runtime.routes[index]; |
|
| 110 | + | if entry.identity <> id { reset(entry, id); } |
|
| 111 | + | return entry; |
|
| 112 | + | } |
|
| 113 | + | ||
| 114 | + | /// Check complete domain identity without treating a transport message as authority. |
|
| 115 | + | export fn same(left: abi::Object, right: abi::Object) -> bool { |
|
| 116 | + | return left.kind == right.kind and left.index == right.index and left.epoch == right.epoch; |
|
| 117 | + | } |
|
| 118 | + | ||
| 119 | + | /// Release a same-hart caller after yield, wait, exhaustion, or termination. |
|
| 120 | + | /// Skip dead or exhausted callers without depending on context-table ordering. |
|
| 121 | + | /// Every link names both context incarnations, and Run cannot form a cycle. |
|
| 122 | + | export fn relinquish(runtime: *mut State, index: u32) { |
|
| 123 | + | let mut child = index; |
|
| 124 | + | for depth in 0..runtime.kernel.contexts.len { |
|
| 125 | + | let context = &mut runtime.kernel.contexts[child]; |
|
| 126 | + | assert context.status <> contexts::Status::Running; |
|
| 127 | + | let caller = context.continuation; |
|
| 128 | + | set context.continuation = 0; |
|
| 129 | + | if caller == 0 { return; } |
|
| 130 | + | let parent = try contexts::resolve(runtime.kernel.contexts, caller) catch { return; }; |
|
| 131 | + | let entry = route(runtime, parent); |
|
| 132 | + | if entry.suspended <> contexts::identity(context) { return; } |
|
| 133 | + | set entry.suspended = 0; |
|
| 134 | + | let resumed = &mut runtime.kernel.contexts[parent]; |
|
| 135 | + | assert resumed.hart == context.hart and resumed.status == contexts::Status::Stopped; |
|
| 136 | + | if domains::live(runtime.kernel.domains, resumed.owner) and entry.admitted { |
|
| 137 | + | assert not entry.queued and not entry.destroying; |
|
| 138 | + | if resumed.budget.kind == abi::Kind::Budget |
|
| 139 | + | and budget_caps::value(runtime.kernel, resumed.budget).remaining > 0 { |
|
| 140 | + | set resumed.status = contexts::Status::Ready; |
|
| 141 | + | set runtime.harts[resumed.hart].preferred = caller; |
|
| 142 | + | return; |
|
| 143 | + | } |
|
| 144 | + | } |
|
| 145 | + | set entry.admitted = false; |
|
| 146 | + | set child = parent; |
|
| 147 | + | } |
|
| 148 | + | } |
|
| 149 | + | ||
| 150 | + | /// Admit boot's already activated, budget-bound root without an implicit caller. |
|
| 151 | + | export fn root(runtime: *mut State, id: u64) { |
|
| 152 | + | let index = try! contexts::resolve(runtime.kernel.contexts, id); |
|
| 153 | + | let context = &runtime.kernel.contexts[index]; |
|
| 154 | + | assert context.hart == 0 and context.status == contexts::Status::Ready; |
|
| 155 | + | assert runtime.harts[0].online and domains::live(runtime.kernel.domains, context.owner); |
|
| 156 | + | assert runtime.kernel.domains[context.owner.index].state == domains::Lifecycle::Active; |
|
| 157 | + | let budget = budget_caps::value(runtime.kernel, context.budget); |
|
| 158 | + | assert budget.context == id and budget.hart == 0 and budget.remaining > 0; |
|
| 159 | + | set route(runtime, index).admitted = true; |
|
| 160 | + | set runtime.harts[0].preferred = id; |
|
| 161 | + | } |
|
| 162 | + | ||
| 163 | + | /// Admit an authorized Run atomically with transport capacity and continuation state. |
|
| 164 | + | /// The control dispatcher has checked Execute rights and the retained budget. |
|
| 165 | + | export unsafe fn run(runtime: *mut State, caller: u32, id: u64) throws (abi::Error) { |
|
| 166 | + | let index = try contexts::resolve(runtime.kernel.contexts, id); |
|
| 167 | + | let context = &mut runtime.kernel.contexts[index]; |
|
| 168 | + | let entry = route(runtime, index); |
|
| 169 | + | if entry.admitted or entry.destroying or entry.suspended <> 0 { throw abi::Error::Busy; } |
|
| 170 | + | let hardware = context.hart; |
|
| 171 | + | if hardware >= runtime.harts.len or not runtime.harts[hardware].online { throw abi::Error::Denied; } |
|
| 172 | + | let source = &mut runtime.kernel.contexts[caller]; |
|
| 173 | + | assert source.status == contexts::Status::Ready and context.continuation == 0; |
|
| 174 | + | let sourceRoute = route(runtime, caller); |
|
| 175 | + | assert sourceRoute.admitted and sourceRoute.suspended == 0 and not sourceRoute.destroying; |
|
| 176 | + | let message = smp::Request { kind: smp::RequestKind::Run, domain: context.owner, context: id }; |
|
| 177 | + | if source.hart <> hardware and not smp::canPost(&runtime.harts[hardware].mailbox, message) { |
|
| 178 | + | throw abi::Error::Busy; |
|
| 179 | + | } |
|
| 180 | + | set entry.admitted = true; |
|
| 181 | + | set context.status = contexts::Status::Ready; |
|
| 182 | + | if source.hart == hardware { |
|
| 183 | + | set context.continuation = contexts::identity(source); |
|
| 184 | + | set sourceRoute.suspended = id; |
|
| 185 | + | set source.status = contexts::Status::Stopped; |
|
| 186 | + | set runtime.harts[hardware].preferred = id; |
|
| 187 | + | } else { |
|
| 188 | + | set entry.queued = true; |
|
| 189 | + | try! smp::post(&mut runtime.harts[hardware].mailbox, message); |
|
| 190 | + | smp::kick(runtime.machine.clint.base, runtime.harts[hardware].hardware.clintIndex); |
|
| 191 | + | } |
|
| 192 | + | } |
|
| 193 | + | ||
| 194 | + | /// Test whether explicit admission and finite retained authority permit dispatch. |
|
| 195 | + | fn ready(runtime: *mut State, index: u32, hart: u32) -> bool { |
|
| 196 | + | let context = &runtime.kernel.contexts[index]; |
|
| 197 | + | if context.status <> contexts::Status::Ready or context.hart <> hart |
|
| 198 | + | or not domains::live(runtime.kernel.domains, context.owner) { return false; } |
|
| 199 | + | if runtime.kernel.domains[context.owner.index].state <> domains::Lifecycle::Active { return false; } |
|
| 200 | + | let entry = route(runtime, index); |
|
| 201 | + | if not entry.admitted or entry.queued or entry.destroying or entry.suspended <> 0 { return false; } |
|
| 202 | + | if context.budget.kind <> abi::Kind::Budget { return false; } |
|
| 203 | + | return budget_caps::value(runtime.kernel, context.budget).remaining > 0; |
|
| 204 | + | } |
|
| 205 | + | /// Resolve a preferred incarnation without admitting any other table occupant. |
|
| 206 | + | fn preference(runtime: *mut State, hart: u32, id: u64) -> ?u32 { |
|
| 207 | + | let index = try contexts::resolve(runtime.kernel.contexts, id) catch { return nil; }; |
|
| 208 | + | if ready(runtime, index, hart) { return index; } |
|
| 209 | + | return nil; |
|
| 210 | + | } |
|
| 211 | + | ||
| 212 | + | ||
| 213 | + | /// Select an explicit continuation first, then retained ready admissions on this hart. |
|
| 214 | + | /// This adds no authority, quantum, replenishment, or priority to a context. |
|
| 215 | + | export fn select(runtime: *mut State, hart: u32) -> ?u32 { |
|
| 216 | + | let preferred = runtime.harts[hart].preferred; |
|
| 217 | + | set runtime.harts[hart].preferred = 0; |
|
| 218 | + | if preferred <> 0 { |
|
| 219 | + | if let index = preference(runtime, hart, preferred) { return index; } |
|
| 220 | + | } |
|
| 221 | + | for i in 0..runtime.kernel.contexts.len { |
|
| 222 | + | if ready(runtime, i, hart) { return i; } |
|
| 223 | + | } |
|
| 224 | + | return nil; |
|
| 225 | + | } |
|
| 226 | + | ||
| 227 | + | /// Acknowledge under the publication lock, then reject stale request identities. |
|
| 228 | + | export unsafe fn drain(runtime: *mut State, hart: u32) { |
|
| 229 | + | let local = &mut runtime.harts[hart]; |
|
| 230 | + | assert local.current == 0; |
|
| 231 | + | smp::acknowledge(runtime.machine.clint.base, local.hardware.clintIndex); |
|
| 232 | + | // The owning hart reaches drain only after consuming its Running obligation. |
|
| 233 | + | // A stale stop can be discarded, but a matching frame must be quiescent. |
|
| 234 | + | if let request = local.stop { |
|
| 235 | + | assert request.kind == smp::RequestKind::Stop; |
|
| 236 | + | let index = request.context as u32; |
|
| 237 | + | if index < runtime.kernel.contexts.len { |
|
| 238 | + | let context = &runtime.kernel.contexts[index]; |
|
| 239 | + | if contexts::identity(context) == request.context and same(context.owner, request.domain) { |
|
| 240 | + | assert context.hart == hart and context.status <> contexts::Status::Running; |
|
| 241 | + | } |
|
| 242 | + | } |
|
| 243 | + | } |
|
| 244 | + | set local.stop = nil; |
|
| 245 | + | while let request = smp::take(&mut local.mailbox) { |
|
| 246 | + | if not domains::live(runtime.kernel.domains, request.domain) { continue; } |
|
| 247 | + | if request.kind == smp::RequestKind::Wake { continue; } |
|
| 248 | + | let index = try contexts::resolve(runtime.kernel.contexts, request.context) catch { continue; }; |
|
| 249 | + | let context = &runtime.kernel.contexts[index]; |
|
| 250 | + | if context.hart <> hart or not same(context.owner, request.domain) { continue; } |
|
| 251 | + | if request.kind == smp::RequestKind::Run { set route(runtime, index).queued = false; } |
|
| 252 | + | } |
|
| 253 | + | } |
kernel/core/runtime.rad
added
+153 -0
| 1 | + | //! Reactive trap processing with hart-local architectural and accounting ownership. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::clock; |
|
| 6 | + | use core::contexts; |
|
| 7 | + | use core::control; |
|
| 8 | + | use core::cpu; |
|
| 9 | + | use core::domains; |
|
| 10 | + | use core::events; |
|
| 11 | + | use core::execution; |
|
| 12 | + | use core::interrupts; |
|
| 13 | + | use core::lifecycle; |
|
| 14 | + | use core::notifications; |
|
| 15 | + | use core::smp; |
|
| 16 | + | use core::teardown; |
|
| 17 | + | use core::timers; |
|
| 18 | + | ||
| 19 | + | /// All hardware causes accepted by the machine idle path. |
|
| 20 | + | fn interrupt(cause: u64) -> bool { |
|
| 21 | + | return cause == 0x8000000000000003 or cause == 0x8000000000000007 or cause == 0x800000000000000b; |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Stop exhausted quiescent admissions and release their explicit caller chains. |
|
| 25 | + | fn exhausted(runtime: *mut execution::State) { |
|
| 26 | + | for i in 0..runtime.kernel.contexts.len { |
|
| 27 | + | let context = &mut runtime.kernel.contexts[i]; |
|
| 28 | + | if context.status <> contexts::Status::Ready or context.budget.kind <> abi::Kind::Budget |
|
| 29 | + | or not domains::live(runtime.kernel.domains, context.owner) { continue; } |
|
| 30 | + | let entry = execution::route(runtime, i); |
|
| 31 | + | if not entry.admitted or entry.destroying or budget_caps::value(runtime.kernel, context.budget).remaining <> 0 { continue; } |
|
| 32 | + | set context.status = contexts::Status::Stopped; |
|
| 33 | + | set entry.admitted = false; |
|
| 34 | + | set entry.queued = false; |
|
| 35 | + | execution::relinquish(runtime, i); |
|
| 36 | + | } |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Retain ready admissions when ordinary remote slots are full. |
|
| 40 | + | /// A full mailbox already owns a kick; active harts have a finite budget interrupt. |
|
| 41 | + | unsafe fn nudge(runtime: *mut execution::State, source: u32) { |
|
| 42 | + | for i in 0..runtime.kernel.contexts.len { |
|
| 43 | + | let context = &runtime.kernel.contexts[i]; |
|
| 44 | + | if context.status <> contexts::Status::Ready or context.hart == source |
|
| 45 | + | or context.hart >= runtime.harts.len or not domains::live(runtime.kernel.domains, context.owner) { continue; } |
|
| 46 | + | let entry = execution::route(runtime, i); |
|
| 47 | + | if not entry.admitted or entry.queued or entry.destroying or entry.suspended <> 0 { continue; } |
|
| 48 | + | let hart = &mut runtime.harts[context.hart]; |
|
| 49 | + | if not hart.online or hart.current <> 0 { continue; } |
|
| 50 | + | let request = smp::Request { kind: smp::RequestKind::Wake, domain: context.owner, context: 0 }; |
|
| 51 | + | try smp::post(&mut hart.mailbox, request) catch { continue; }; |
|
| 52 | + | smp::kick(runtime.machine.clint.base, hart.hardware.clintIndex); |
|
| 53 | + | } |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Finish bounded event and lifecycle work while holding the global state lock. |
|
| 57 | + | unsafe fn pending(runtime: *mut execution::State, hart: u32, now: u64) { |
|
| 58 | + | for i in 0..runtime.harts.len { |
|
| 59 | + | if runtime.harts[i].online { timers::deliver(&mut runtime.harts[i].timers, now, runtime.kernel.domains); } |
|
| 60 | + | } |
|
| 61 | + | for attempt in 0..runtime.kernel.domains.len + 1 { |
|
| 62 | + | // Both producers can publish to a sleeping context. Poll its predicate |
|
| 63 | + | // after publication, or this hart could idle with an unread event. |
|
| 64 | + | teardown::poll(runtime); |
|
| 65 | + | interrupts::poll(runtime.controller, runtime.kernel); |
|
| 66 | + | if let bad = notifications::poll(runtime.kernel) { |
|
| 67 | + | lifecycle::terminate(runtime.kernel, bad, 3, teardown::BAD_EVENTS); |
|
| 68 | + | } else { break; } |
|
| 69 | + | } |
|
| 70 | + | exhausted(runtime); |
|
| 71 | + | nudge(runtime, hart); |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | /// Initialize local interrupt state after acquiring the published boot image. |
|
| 75 | + | /// Global M-mode interrupts remain disabled outside the architectural entry routines. |
|
| 76 | + | export unsafe fn initializeHart(runtime: *mut execution::State, hart: u32) { |
|
| 77 | + | assert hart < runtime.harts.len and runtime.harts[hart].online and smp::hart() == hart; |
|
| 78 | + | let mut mask: u64 = 8 | 128; |
|
| 79 | + | if runtime.harts[hart].hardware.plicContext == runtime.controller.context { set mask |= 2048; } |
|
| 80 | + | clock::interrupts(mask); |
|
| 81 | + | clock::arm(runtime.machine.clint.base, runtime.harts[hart].hardware.clintIndex, 0xffffffffffffffff); |
|
| 82 | + | smp::syncInstructions(); |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Execute one dispatch-to-trap interval or one machine idle-to-interrupt interval. |
|
| 86 | + | /// Only this hart reads/writes its architectural frame outside the global lock. |
|
| 87 | + | /// No domain termination or exhausted budget returns from this function as shutdown. |
|
| 88 | + | export unsafe fn step(runtime: *mut execution::State, hart: u32) { |
|
| 89 | + | assert hart < runtime.harts.len and smp::hart() == hart; |
|
| 90 | + | let guard = smp::acquire(&mut runtime.lock); |
|
| 91 | + | let local = &mut runtime.harts[hart]; |
|
| 92 | + | assert local.online and local.current == 0; |
|
| 93 | + | execution::drain(runtime, hart); |
|
| 94 | + | let now = clock::read(runtime.machine.clint.base); |
|
| 95 | + | pending(runtime, hart, now); |
|
| 96 | + | if let index = execution::select(runtime, hart) { |
|
| 97 | + | let context = &mut runtime.kernel.contexts[index]; |
|
| 98 | + | let authority = context.budget; |
|
| 99 | + | let budget = budget_caps::value(runtime.kernel, authority); |
|
| 100 | + | let started = clock::read(runtime.machine.clint.base); |
|
| 101 | + | let running = try contexts::begin(context, budget, started, hart) catch { |
|
| 102 | + | set context.status = contexts::Status::Stopped; |
|
| 103 | + | set execution::route(runtime, index).admitted = false; |
|
| 104 | + | execution::relinquish(runtime, index); |
|
| 105 | + | smp::release(guard); |
|
| 106 | + | return; |
|
| 107 | + | }; |
|
| 108 | + | let id = contexts::identity(context); |
|
| 109 | + | set local.current = id; |
|
| 110 | + | let mut deadline = context.deadline; |
|
| 111 | + | // Only deadlines already attempted by pending may be ignored as backlog. |
|
| 112 | + | // A timer that became due during dispatch must still cause an interrupt. |
|
| 113 | + | let timeout = timers::next(&local.timers, now); |
|
| 114 | + | if timeout < deadline { set deadline = timeout; } |
|
| 115 | + | clock::arm(runtime.machine.clint.base, local.hardware.clintIndex, deadline); |
|
| 116 | + | smp::release(guard); |
|
| 117 | + | cpu::enter(&mut context.frame); |
|
| 118 | + | let stopped = clock::read(runtime.machine.clint.base); |
|
| 119 | + | let returned = smp::acquire(&mut runtime.lock); |
|
| 120 | + | assert local.current == id and contexts::identity(context) == id; |
|
| 121 | + | assert execution::same(context.budget, authority); |
|
| 122 | + | // The retained binding cannot move while Running, even after domain death. |
|
| 123 | + | // Consume the exact interval and accounting object borrowed before entry. |
|
| 124 | + | let _remaining = contexts::finish(context, budget, running, stopped); |
|
| 125 | + | set local.current = 0; |
|
| 126 | + | if local.preferred == 0 { set local.preferred = id; } |
|
| 127 | + | execution::drain(runtime, hart); |
|
| 128 | + | let cause = context.frame.cause; |
|
| 129 | + | assert context.frame.status & (3 << 11) == 0; |
|
| 130 | + | if domains::live(runtime.kernel.domains, context.owner) { |
|
| 131 | + | try events::refresh(&mut runtime.kernel.domains[context.owner.index].events) catch { |
|
| 132 | + | lifecycle::terminate(runtime.kernel, context.owner, 3, teardown::BAD_EVENTS); |
|
| 133 | + | }; |
|
| 134 | + | } |
|
| 135 | + | if domains::live(runtime.kernel.domains, context.owner) { |
|
| 136 | + | if cause == 8 { control::invoke(runtime, index, clock::read(runtime.machine.clint.base)); } |
|
| 137 | + | else if not interrupt(cause) { lifecycle::terminate(runtime.kernel, context.owner, 3, cause as u32); } |
|
| 138 | + | } |
|
| 139 | + | if cause == 0x800000000000000b { interrupts::handle(runtime.controller, runtime.kernel); } |
|
| 140 | + | pending(runtime, hart, clock::read(runtime.machine.clint.base)); |
|
| 141 | + | smp::release(returned); |
|
| 142 | + | } else { |
|
| 143 | + | clock::arm(runtime.machine.clint.base, local.hardware.clintIndex, timers::next(&local.timers, now)); |
|
| 144 | + | smp::release(guard); |
|
| 145 | + | cpu::idle(&mut local.idle); |
|
| 146 | + | let returned = smp::acquire(&mut runtime.lock); |
|
| 147 | + | assert interrupt(local.idle.cause); |
|
| 148 | + | execution::drain(runtime, hart); |
|
| 149 | + | if local.idle.cause == 0x800000000000000b { interrupts::handle(runtime.controller, runtime.kernel); } |
|
| 150 | + | pending(runtime, hart, clock::read(runtime.machine.clint.base)); |
|
| 151 | + | smp::release(returned); |
|
| 152 | + | } |
|
| 153 | + | } |
kernel/core/teardown.rad
added
+102 -0
| 1 | + | //! Terminal quiescence and completion of incarnation-qualified Destroy requests. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::contexts; |
|
| 6 | + | use core::cpu; |
|
| 7 | + | use core::domains; |
|
| 8 | + | use core::execution; |
|
| 9 | + | use core::lifecycle; |
|
| 10 | + | use core::smp; |
|
| 11 | + | ||
| 12 | + | /// Bad Events progress is a domain fault, not a hardware trap code. |
|
| 13 | + | export constant BAD_EVENTS: u32 = 0x101; |
|
| 14 | + | ||
| 15 | + | /// Accept destruction and snapshot its exact closure before any slot can be reused. |
|
| 16 | + | /// The caller holds the global lock. A successful return still awaits poll. |
|
| 17 | + | export fn request(runtime: *mut execution::State, index: u32, handle: abi::Handle, flags: u64) throws (abi::Error) { |
|
| 18 | + | let kernel = runtime.kernel; |
|
| 19 | + | let caller = &mut kernel.contexts[index]; |
|
| 20 | + | let owner = try lifecycle::request(kernel, caller.owner.index, handle, flags); |
|
| 21 | + | let entry = execution::route(runtime, index); |
|
| 22 | + | set entry.targets = [0; abi::MAX_DOMAINS]; |
|
| 23 | + | let word = owner.index / 64; |
|
| 24 | + | let bit = 1 as u64 << (owner.index as u64 & 63); |
|
| 25 | + | for i in 0..kernel.domains.len { |
|
| 26 | + | let domain = &kernel.domains[i]; |
|
| 27 | + | if domain.state <> domains::Lifecycle::Dying { continue; } |
|
| 28 | + | if i == owner.index or (flags & 1 <> 0 and domain.ancestors[word] & bit <> 0) { |
|
| 29 | + | set entry.targets[i] = domain.epoch; |
|
| 30 | + | } |
|
| 31 | + | } |
|
| 32 | + | set entry.destroying = true; |
|
| 33 | + | set caller.status = contexts::Status::Stopped; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Stop only architectural owners; a separate reserved slot cannot be displaced. |
|
| 37 | + | /// The receiver charges its own interval before clearing current and its stop slot. |
|
| 38 | + | unsafe fn stop(runtime: *mut execution::State) { |
|
| 39 | + | let kernel = runtime.kernel; |
|
| 40 | + | for i in 0..kernel.contexts.len { |
|
| 41 | + | let context = &kernel.contexts[i]; |
|
| 42 | + | if context.status == contexts::Status::Vacant or domains::live(kernel.domains, context.owner) { continue; } |
|
| 43 | + | if context.status <> contexts::Status::Running { |
|
| 44 | + | execution::relinquish(runtime, i); |
|
| 45 | + | let entry = execution::route(runtime, i); |
|
| 46 | + | set entry.admitted = false; |
|
| 47 | + | set entry.queued = false; |
|
| 48 | + | set entry.suspended = 0; |
|
| 49 | + | set entry.destroying = false; |
|
| 50 | + | continue; |
|
| 51 | + | } |
|
| 52 | + | let hart = &mut runtime.harts[context.hart]; |
|
| 53 | + | let id = contexts::identity(context); |
|
| 54 | + | assert hart.current == id; |
|
| 55 | + | if let pending = hart.stop { |
|
| 56 | + | assert pending.context == id and execution::same(pending.domain, context.owner); |
|
| 57 | + | } else { |
|
| 58 | + | set hart.stop = smp::Request { kind: smp::RequestKind::Stop, domain: context.owner, context: id }; |
|
| 59 | + | smp::kick(runtime.machine.clint.base, hart.hardware.clintIndex); |
|
| 60 | + | } |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | /// Check all selected epochs, including descendants whose ancestor slot was reused. |
|
| 65 | + | fn complete(runtime: *execution::State, entry: *execution::Route) -> bool { |
|
| 66 | + | for i in 0..runtime.kernel.domains.len { |
|
| 67 | + | let epoch = entry.targets[i]; |
|
| 68 | + | if epoch == 0 { continue; } |
|
| 69 | + | let domain = &runtime.kernel.domains[i]; |
|
| 70 | + | if domain.epoch == epoch and domain.state <> domains::Lifecycle::Dead { return false; } |
|
| 71 | + | } |
|
| 72 | + | return true; |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Reclaim only returned frames and release Destroy replies only after completion. |
|
| 76 | + | /// Faulting an invalid parent strictly reduces the number of live queue owners. |
|
| 77 | + | export unsafe fn poll(runtime: *mut execution::State) { |
|
| 78 | + | let kernel = runtime.kernel; |
|
| 79 | + | for attempt in 0..kernel.domains.len + 1 { |
|
| 80 | + | stop(runtime); |
|
| 81 | + | if let bad = lifecycle::poll(kernel) { |
|
| 82 | + | lifecycle::terminate(kernel, bad, 3, BAD_EVENTS); |
|
| 83 | + | } else { break; } |
|
| 84 | + | } |
|
| 85 | + | for i in 0..kernel.contexts.len { |
|
| 86 | + | let context = &mut kernel.contexts[i]; |
|
| 87 | + | if context.status == contexts::Status::Vacant or not domains::live(kernel.domains, context.owner) { continue; } |
|
| 88 | + | let entry = execution::route(runtime, i); |
|
| 89 | + | if not entry.destroying or not complete(runtime, entry) { continue; } |
|
| 90 | + | assert entry.admitted and entry.suspended == 0 and not entry.queued; |
|
| 91 | + | assert context.status == contexts::Status::Stopped; |
|
| 92 | + | set entry.destroying = false; |
|
| 93 | + | cpu::reply(&mut context.frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 94 | + | if context.budget.kind == abi::Kind::Budget and budget_caps::value(kernel, context.budget).remaining > 0 { |
|
| 95 | + | set context.status = contexts::Status::Ready; |
|
| 96 | + | set runtime.harts[context.hart].preferred = contexts::identity(context); |
|
| 97 | + | } else { |
|
| 98 | + | set entry.admitted = false; |
|
| 99 | + | execution::relinquish(runtime, i); |
|
| 100 | + | } |
|
| 101 | + | } |
|
| 102 | + | } |
kernel/main.rad
+12 -22
| 1 | - | //! Freestanding machine initialization. |
|
| 1 | + | //! One-shot shared provisioning followed by perpetual hart-local kernel service. |
|
| 2 | 2 | ||
| 3 | - | use core::fdt; |
|
| 4 | - | use core::frames; |
|
| 5 | - | use core::platform; |
|
| 6 | - | /// Maximum online harts in this kernel build. |
|
| 7 | - | constant MAX_HARTS: u64 = 8; |
|
| 3 | + | use core::boot; |
|
| 4 | + | use core::runtime; |
|
| 8 | 5 | ||
| 9 | - | /// Decode trusted firmware memory before any resource can reach user space. |
|
| 10 | - | /// Firmware supplies a mapped FDT header; subsequent lengths are validated. |
|
| 11 | - | @default unsafe fn main(hart: u64, description: *u8) -> u32 { |
|
| 12 | - | assert hart < MAX_HARTS; |
|
| 13 | - | let header = @sliceOf(description, 40); |
|
| 14 | - | let size = try! fdt::word(header, 4); |
|
| 15 | - | assert size >= 40 and size <= fdt::MAX_BYTES; |
|
| 16 | - | let bytes = @sliceOf(description, size); |
|
| 17 | - | let mut tree: fdt::Tree = undefined; |
|
| 18 | - | try! fdt::decode(bytes, &mut tree); |
|
| 19 | - | let mut machine: platform::Platform = undefined; |
|
| 20 | - | try! platform::discover(&tree, &mut machine); |
|
| 21 | - | let mut pool: frames::Pool = undefined; |
|
| 22 | - | try! frames::init(&mut pool, &machine); |
|
| 23 | - | assert pool.available > 0; |
|
| 24 | - | return 0; |
|
| 6 | + | ||
| 7 | + | /// Firmware enters each hardware hart on arch/start.ras's distinct machine stack. |
|
| 8 | + | /// Its reservations must cover the loaded image, these statics, all boot stacks, |
|
| 9 | + | /// and the FDT. No normal domain transition returns to the firmware finish path. |
|
| 10 | + | @default unsafe fn main(hardware: u64, description: *u8) -> u32 { |
|
| 11 | + | let execution = boot::start(hardware, description); |
|
| 12 | + | let hart = hardware as u32; |
|
| 13 | + | runtime::initializeHart(execution, hart); |
|
| 14 | + | loop { runtime::step(execution, hart); } |
|
| 25 | 15 | } |
kernel/parallel.rad
+19 -1
| 1 | 1 | //! Machine proof of per-hart stacks, coherent ticket locking, and CLINT requests. |
|
| 2 | 2 | ||
| 3 | 3 | use core::abi; |
|
| 4 | 4 | use core::atomic; |
|
| 5 | + | use core::boot; |
|
| 6 | + | use core::domains; |
|
| 7 | + | use core::runtime; |
|
| 5 | 8 | use core::clock; |
|
| 6 | 9 | use core::cpu; |
|
| 7 | 10 | use core::fdt; |
|
| 8 | 11 | use core::platform; |
|
| 9 | 12 | use core::smp; |
| 115 | 118 | set OBSERVED[hart] = PAYLOAD[hart]; |
|
| 116 | 119 | smp::acknowledge(PLATFORM.clint.base, routing.clintIndex); |
|
| 117 | 120 | smp::release(guard); |
|
| 118 | 121 | atomic::store(&mut DONE[hart], 2); |
|
| 119 | 122 | } |
|
| 120 | - | return 0; |
|
| 123 | + | let execution = boot::start(hardware, description); |
|
| 124 | + | runtime::initializeHart(execution, hart); |
|
| 125 | + | loop { |
|
| 126 | + | runtime::step(execution, hart); |
|
| 127 | + | if hart == 0 { |
|
| 128 | + | let guard = smp::acquire(&mut execution.lock); |
|
| 129 | + | let root = &execution.kernel.domains[0]; |
|
| 130 | + | let finished = root.state == domains::Lifecycle::Dead; |
|
| 131 | + | if finished { |
|
| 132 | + | assert root.terminalKind == 4 and root.terminalCode == 0; |
|
| 133 | + | assert root.private.count == 0; |
|
| 134 | + | } |
|
| 135 | + | smp::release(guard); |
|
| 136 | + | if finished { return 0; } |
|
| 137 | + | } |
|
| 138 | + | } |
|
| 121 | 139 | } |
kernel/user/admin.rad
added
+37 -0
| 1 | + | //! Distinguish copied root authority from delegated independent self authority. |
|
| 2 | + | use user::sys; |
|
| 3 | + | use probe; |
|
| 4 | + | ||
| 5 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 6 | + | let args = probe::args(env); |
|
| 7 | + | let authority = probe::handle(args[1]); |
|
| 8 | + | if args[0] == 1 { |
|
| 9 | + | let target = probe::handle(args[2]); |
|
| 10 | + | let root = probe::handle(args[3]); |
|
| 11 | + | let shared = probe::handle(args[4]); |
|
| 12 | + | probe::allocationDenied(probe::handle(args[5])); |
|
| 13 | + | let independent = try! sys::delegate(authority, target, 104); |
|
| 14 | + | set probe::words(shared)[0] = independent.bits; |
|
| 15 | + | try! sys::domainReparent(target, root); |
|
| 16 | + | probe::empty(env); |
|
| 17 | + | try! sys::yield(); |
|
| 18 | + | sys::abort(); |
|
| 19 | + | return 100; |
|
| 20 | + | } |
|
| 21 | + | probe::equal(args[0], 2); |
|
| 22 | + | let ownId = probe::id(authority); |
|
| 23 | + | probe::equal(ownId as u64, args[3]); |
|
| 24 | + | let allocated = probe::allocate(authority, 1); |
|
| 25 | + | let words = probe::words(allocated); |
|
| 26 | + | set words[0] = 909; |
|
| 27 | + | probe::equal(words[0], 909); |
|
| 28 | + | assert sys::queryPage(allocated).count == 1; |
|
| 29 | + | let child = probe::create(authority, args[2]); |
|
| 30 | + | let childId = probe::id(child); |
|
| 31 | + | probe::relationship(child, ownId, ownId, sys::DomainLifecycle::Pending); |
|
| 32 | + | try! sys::domainDestroy(child, 0); |
|
| 33 | + | probe::event(env, 4, 0, childId as u64); |
|
| 34 | + | try! sys::capabilityDrop(allocated); |
|
| 35 | + | probe::empty(env); |
|
| 36 | + | return 73; |
|
| 37 | + | } |
kernel/user/control.rad
added
+167 -0
| 1 | + | //! Exercise the boot authority manifest and real local and remote domain control. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | use probe; |
|
| 5 | + | mod lifecycle; |
|
| 6 | + | mod authority; |
|
| 7 | + | ||
| 8 | + | /// Select the emulator UART from explicit Device capabilities. |
|
| 9 | + | fn uart(count: u64) -> abi::Handle { |
|
| 10 | + | for i in 0..count { |
|
| 11 | + | let device = sys::handleSlot(i + 2, abi::Kind::Device); |
|
| 12 | + | let info = sys::queryDevice(device); |
|
| 13 | + | if info.base == 0x10000000 { |
|
| 14 | + | assert info.size >= 8; |
|
| 15 | + | return device; |
|
| 16 | + | } |
|
| 17 | + | } |
|
| 18 | + | sys::abort(); |
|
| 19 | + | return sys::domainSelf(); |
|
| 20 | + | } |
|
| 21 | + | fn print(device: abi::Handle) { |
|
| 22 | + | let message = "CONTROL-OK\n"; |
|
| 23 | + | for i in 0..message.len { |
|
| 24 | + | while (try! sys::deviceRead8(device, 5)) & 32 == 0 {} |
|
| 25 | + | try! sys::deviceWrite8(device, 0, message[i]); |
|
| 26 | + | } |
|
| 27 | + | } |
|
| 28 | + | /// Verify every fixed boot slot and per-hart reserve budget. |
|
| 29 | + | fn slots(words: *[u64], devices: u64, irqs: u64, harts: u64, frequency: u64) { |
|
| 30 | + | for i in 0..devices { |
|
| 31 | + | probe::positive(sys::queryDevice(sys::handleSlot(i + 2, abi::Kind::Device)).size); |
|
| 32 | + | } |
|
| 33 | + | let irqBase = devices + 2; |
|
| 34 | + | for i in 0..irqs { |
|
| 35 | + | probe::equal(sys::queryInterrupt(sys::handleSlot(irqBase + i, abi::Kind::Interrupt)).number as u64, i + 1); |
|
| 36 | + | } |
|
| 37 | + | let budgetBase = irqBase + irqs; |
|
| 38 | + | probe::equal(words[4], budgetBase); |
|
| 39 | + | for i in 0..harts { |
|
| 40 | + | let pair = (8 + i * 2) as u32; |
|
| 41 | + | let slot = words[pair + 1]; |
|
| 42 | + | probe::equal(slot, budgetBase + i); |
|
| 43 | + | let budget = sys::queryBudget(sys::handleSlot(slot, abi::Kind::Budget)); |
|
| 44 | + | probe::equal(budget.hart, words[pair]); |
|
| 45 | + | probe::equal(budget.remaining, frequency * 3600); |
|
| 46 | + | probe::equal(budget.context, 0); |
|
| 47 | + | } |
|
| 48 | + | } |
|
| 49 | + | /// Exercise remote handshakes, concurrent contexts, and acknowledged teardown. |
|
| 50 | + | fn remote(env: *sys::Env, root: abi::Handle, reserve: abi::Handle, localReserve: abi::Handle, ticks: u64, image: u64, delay: u64) { |
|
| 51 | + | let rootId = probe::id(root); |
|
| 52 | + | let child = probe::create(root, image); |
|
| 53 | + | let childId = probe::id(child); |
|
| 54 | + | let remoteRoot = probe::grant(root, child, 128); |
|
| 55 | + | let args = probe::allocate(root, 1); |
|
| 56 | + | let words = probe::words(args); |
|
| 57 | + | set words[0] = remoteRoot.bits; |
|
| 58 | + | set words[1] = rootId as u64; |
|
| 59 | + | let context = probe::activate(root, child, args, 16); |
|
| 60 | + | let budget = probe::fund(reserve, child, context, ticks); |
|
| 61 | + | probe::run(child, context); |
|
| 62 | + | probe::event(env, 5, 8001, childId as u64); |
|
| 63 | + | try! sys::wakeup(child, 8002); |
|
| 64 | + | probe::event(env, 5, 8003, childId as u64); |
|
| 65 | + | probe::event(env, 4, 61, childId as u64); |
|
| 66 | + | try! sys::capabilityDrop(args); |
|
| 67 | + | try! sys::capabilityDrop(budget); |
|
| 68 | + | probe::empty(env); |
|
| 69 | + | ||
| 70 | + | // The destroy reply must follow remote stop acknowledgement, not admission. |
|
| 71 | + | let spinner = probe::create(root, image); |
|
| 72 | + | let spinnerId = probe::id(spinner); |
|
| 73 | + | let spinnerRoot = probe::grant(root, spinner, 128); |
|
| 74 | + | let markerPage = probe::allocate(root, 1); |
|
| 75 | + | let markerBytes = try! sys::pageSliceMut(markerPage, 0, 4); |
|
| 76 | + | let marker = markerBytes.ptr as *mut u32; |
|
| 77 | + | sys::storeRelease(marker, 0); |
|
| 78 | + | let remoteMarker = probe::grant(markerPage, spinner, 3); |
|
| 79 | + | let spinnerArgs = probe::allocate(root, 1); |
|
| 80 | + | let spinnerWords = probe::words(spinnerArgs); |
|
| 81 | + | set spinnerWords[0] = spinnerRoot.bits; |
|
| 82 | + | set spinnerWords[1] = rootId as u64; |
|
| 83 | + | set spinnerWords[2] = remoteMarker.bits; |
|
| 84 | + | let spinnerContext = probe::activate(root, spinner, spinnerArgs, 32); |
|
| 85 | + | set spinnerWords[3] = spinnerContext; |
|
| 86 | + | let spinnerBudget = probe::fund(reserve, spinner, spinnerContext, ticks); |
|
| 87 | + | probe::run(spinner, spinnerContext); |
|
| 88 | + | probe::event(env, 5, 8101, spinnerId as u64); |
|
| 89 | + | probe::positive(sys::loadAcquire(marker) as u64); |
|
| 90 | + | let watcher = probe::context(root, spinner, spinnerArgs, 32); |
|
| 91 | + | let watcherBudget = probe::fund(localReserve, spinner, watcher, ticks); |
|
| 92 | + | probe::run(spinner, watcher); |
|
| 93 | + | probe::event(env, 5, 8103, spinnerId as u64); |
|
| 94 | + | try! sys::domainDestroy(spinner, 0); |
|
| 95 | + | probe::event(env, 4, 0, spinnerId as u64); |
|
| 96 | + | // Persistent views survive recipient reclamation; no remote instruction may |
|
| 97 | + | // overwrite this sentinel after the acknowledged teardown. |
|
| 98 | + | sys::storeRelease(marker, 0xdecafbad); |
|
| 99 | + | try! sys::timeout(delay, 8102); |
|
| 100 | + | probe::event(env, 2, 8102, 0); |
|
| 101 | + | probe::equal(sys::loadAcquire(marker) as u64, 0xdecafbad); |
|
| 102 | + | try! sys::capabilityDrop(markerPage); |
|
| 103 | + | try! sys::capabilityDrop(spinnerArgs); |
|
| 104 | + | try! sys::capabilityDrop(spinnerBudget); |
|
| 105 | + | try! sys::capabilityDrop(watcherBudget); |
|
| 106 | + | probe::empty(env); |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 110 | + | let startup = sys::envArgs(env); |
|
| 111 | + | let words = probe::args(env); |
|
| 112 | + | probe::equal(words[0], 1); |
|
| 113 | + | let devices = words[1]; |
|
| 114 | + | let irqs = words[2]; |
|
| 115 | + | let harts = words[3]; |
|
| 116 | + | probe::positive(harts); |
|
| 117 | + | let budgetBase = words[4]; |
|
| 118 | + | let bootstrapSlot = words[5]; |
|
| 119 | + | probe::equal(bootstrapSlot, budgetBase + harts); |
|
| 120 | + | let frequency = words[6]; |
|
| 121 | + | probe::positive(frequency); |
|
| 122 | + | let imageCount = words[7]; |
|
| 123 | + | probe::equal(imageCount, 7); |
|
| 124 | + | let catalogBase = 8 + harts * 2; |
|
| 125 | + | probe::equal(startup.len as u64, (catalogBase + imageCount) * 8); |
|
| 126 | + | slots(words, devices, irqs, harts, frequency); |
|
| 127 | + | let root = sys::handleSlot(0, abi::Kind::Domain); |
|
| 128 | + | let context = sys::envContext(env); |
|
| 129 | + | probe::positive(context); |
|
| 130 | + | let events = sys::handleSlot(1, abi::Kind::Events); |
|
| 131 | + | let capacity = sys::queryEvents(events).capacity; |
|
| 132 | + | assert capacity > 0; |
|
| 133 | + | assert sys::queryEvents(sys::envEvents(env)).capacity == capacity; |
|
| 134 | + | let info = sys::queryContext(root, context); |
|
| 135 | + | probe::equal(info.id, context); |
|
| 136 | + | probe::equal(info.hart, words[8]); |
|
| 137 | + | let bootstrap = sys::queryBudget(sys::handleSlot(bootstrapSlot, abi::Kind::Budget)); |
|
| 138 | + | probe::equal(bootstrap.hart, info.hart); |
|
| 139 | + | probe::positive(bootstrap.remaining); |
|
| 140 | + | assert bootstrap.remaining <= frequency * 60; |
|
| 141 | + | probe::equal(bootstrap.context, context); |
|
| 142 | + | assert sys::queryPage(sys::handleSlot(bootstrapSlot + 1, abi::Kind::Page)).count > 0; |
|
| 143 | + | let noAllocate = probe::grant(root, root, 0); |
|
| 144 | + | probe::allocationDenied(noAllocate); |
|
| 145 | + | probe::empty(env); |
|
| 146 | + | try! sys::timeout(1, 6001); |
|
| 147 | + | probe::event(env, 2, 6001, 0); |
|
| 148 | + | probe::empty(env); |
|
| 149 | + | let reserve = sys::handleSlot(words[9], abi::Kind::Budget); |
|
| 150 | + | let ticks = frequency * 5; |
|
| 151 | + | let catalog = &words[catalogBase as u32..]; |
|
| 152 | + | for i in 0..catalog.len { probe::equal(catalog[i], i as u64); } |
|
| 153 | + | let output = lifecycle::flow(env, root, reserve, ticks, catalog[1], uart(devices)); |
|
| 154 | + | lifecycle::terminal(env, root, reserve, ticks, catalog[2], 37, 4, 37); |
|
| 155 | + | lifecycle::terminal(env, root, reserve, ticks, catalog[3], 0, 3, 256); |
|
| 156 | + | lifecycle::terminal(env, root, reserve, ticks, catalog[3], 1, 3, 513); |
|
| 157 | + | let shortTicks = frequency / 10; |
|
| 158 | + | probe::positive(shortTicks); |
|
| 159 | + | lifecycle::exhaust(env, root, reserve, shortTicks, catalog[4]); |
|
| 160 | + | authority::check(env, root, reserve, ticks, catalog[5], catalog[2]); |
|
| 161 | + | if harts > 1 { |
|
| 162 | + | remote(env, root, sys::handleSlot(words[11], abi::Kind::Budget), reserve, ticks, catalog[6], shortTicks); |
|
| 163 | + | } |
|
| 164 | + | probe::empty(env); |
|
| 165 | + | print(output); |
|
| 166 | + | return 0; |
|
| 167 | + | } |
kernel/user/control/authority.rad
added
+55 -0
| 1 | + | //! Delegated self authority survives the issuer's reclamation. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | use probe; |
|
| 5 | + | ||
| 6 | + | /// Exercise reparenting and delegated authority after issuer destruction. |
|
| 7 | + | export fn check(env: *sys::Env, root: abi::Handle, reserve: abi::Handle, ticks: u64, adminImage: u64, exitImage: u64) { |
|
| 8 | + | let rootId = probe::id(root); |
|
| 9 | + | let donor = probe::create(root, adminImage); |
|
| 10 | + | let donorId = probe::id(donor); |
|
| 11 | + | let recipient = probe::create(root, adminImage); |
|
| 12 | + | let recipientId = probe::id(recipient); |
|
| 13 | + | try! sys::domainReparent(recipient, donor); |
|
| 14 | + | probe::relationship(recipient, rootId, donorId, sys::DomainLifecycle::Pending); |
|
| 15 | + | let donorAuthority = try! sys::delegate(root, donor, 120); |
|
| 16 | + | let donorRecipient = probe::grant(recipient, donor, 256); |
|
| 17 | + | let donorRoot = probe::grant(root, donor, 128); |
|
| 18 | + | let ordinaryRoot = probe::grant(root, donor, 120); |
|
| 19 | + | let shared = probe::allocate(root, 1); |
|
| 20 | + | let donorShared = probe::grant(shared, donor, 3); |
|
| 21 | + | let donorArgs = probe::allocate(root, 1); |
|
| 22 | + | let donorWords = probe::words(donorArgs); |
|
| 23 | + | set donorWords[0] = 1; |
|
| 24 | + | set donorWords[1] = donorAuthority.bits; |
|
| 25 | + | set donorWords[2] = donorRecipient.bits; |
|
| 26 | + | set donorWords[3] = donorRoot.bits; |
|
| 27 | + | set donorWords[4] = donorShared.bits; |
|
| 28 | + | set donorWords[5] = ordinaryRoot.bits; |
|
| 29 | + | let donorContext = probe::activate(root, donor, donorArgs, 48); |
|
| 30 | + | let donorBudget = probe::fund(reserve, donor, donorContext, ticks); |
|
| 31 | + | probe::run(donor, donorContext); |
|
| 32 | + | probe::status(donor, donorContext, 4); |
|
| 33 | + | probe::relationship(recipient, rootId, rootId, sys::DomainLifecycle::Pending); |
|
| 34 | + | let recipientAuthority = probe::words(shared)[0]; |
|
| 35 | + | try! sys::domainDestroy(donor, 0); |
|
| 36 | + | probe::event(env, 4, 0, donorId as u64); |
|
| 37 | + | probe::empty(env); |
|
| 38 | + | let recipientArgs = probe::allocate(root, 1); |
|
| 39 | + | let recipientWords = probe::words(recipientArgs); |
|
| 40 | + | set recipientWords[0] = 2; |
|
| 41 | + | set recipientWords[1] = recipientAuthority; |
|
| 42 | + | set recipientWords[2] = exitImage; |
|
| 43 | + | set recipientWords[3] = recipientId as u64; |
|
| 44 | + | let recipientContext = probe::activate(root, recipient, recipientArgs, 32); |
|
| 45 | + | probe::relationship(recipient, rootId, rootId, sys::DomainLifecycle::Active); |
|
| 46 | + | let recipientBudget = probe::fund(reserve, recipient, recipientContext, ticks); |
|
| 47 | + | probe::run(recipient, recipientContext); |
|
| 48 | + | probe::event(env, 4, 73, recipientId as u64); |
|
| 49 | + | try! sys::capabilityDrop(shared); |
|
| 50 | + | try! sys::capabilityDrop(donorArgs); |
|
| 51 | + | try! sys::capabilityDrop(recipientArgs); |
|
| 52 | + | try! sys::capabilityDrop(donorBudget); |
|
| 53 | + | try! sys::capabilityDrop(recipientBudget); |
|
| 54 | + | probe::empty(env); |
|
| 55 | + | } |
kernel/user/control/lifecycle.rad
added
+128 -0
| 1 | + | //! Page lifetimes, context transitions, terminal reports and finite budgets. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | use probe; |
|
| 5 | + | ||
| 6 | + | /// Verify that a transferred Device no longer permits MMIO through its old handle. |
|
| 7 | + | fn staleDevice(device: abi::Handle) { |
|
| 8 | + | let _value = try sys::deviceRead8(device, 5) catch error { |
|
| 9 | + | assert error == abi::Error::BadHandle; |
|
| 10 | + | return; |
|
| 11 | + | }; |
|
| 12 | + | sys::abort(); |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Exercise shared Page lifetimes and explicit Yield/Wait continuations. |
|
| 16 | + | /// Grant then transfer the boot UART to the child; retain a root output handle. |
|
| 17 | + | export fn flow(env: *sys::Env, root: abi::Handle, reserve: abi::Handle, ticks: u64, image: u64, device: abi::Handle) -> abi::Handle { |
|
| 18 | + | let rootId = probe::id(root); |
|
| 19 | + | let child = probe::create(root, image); |
|
| 20 | + | let childId = probe::id(child); |
|
| 21 | + | let left = probe::allocate(root, 2); |
|
| 22 | + | let original = sys::queryPage(left); |
|
| 23 | + | assert original.count == 2; |
|
| 24 | + | let right = try! sys::pageSplit(left, 1); |
|
| 25 | + | assert sys::queryPage(left).count == 1; |
|
| 26 | + | let rightInfo = sys::queryPage(right); |
|
| 27 | + | probe::equal(rightInfo.base, original.base + 4096); |
|
| 28 | + | assert rightInfo.count == 1; |
|
| 29 | + | let leftWords = probe::words(left); |
|
| 30 | + | let rightWords = probe::words(right); |
|
| 31 | + | set leftWords[0] = 101; |
|
| 32 | + | set rightWords[0] = 202; |
|
| 33 | + | let remoteLeft = probe::grant(left, child, 3); |
|
| 34 | + | let remoteRight = try! sys::capabilityTransfer(right, child, 27); |
|
| 35 | + | probe::staleDrop(right); |
|
| 36 | + | try! sys::capabilityDrop(left); |
|
| 37 | + | let remoteRoot = probe::grant(root, child, 128); |
|
| 38 | + | let remoteDevice = probe::grant(device, child, (abi::READ | abi::WRITE) as u64); |
|
| 39 | + | let output = probe::grant(device, root, (abi::READ | abi::WRITE) as u64); |
|
| 40 | + | let args = probe::allocate(root, 1); |
|
| 41 | + | let words = probe::words(args); |
|
| 42 | + | set words[0] = 1; |
|
| 43 | + | set words[1] = remoteLeft.bits; |
|
| 44 | + | set words[2] = remoteRight.bits; |
|
| 45 | + | set words[3] = rootId as u64; |
|
| 46 | + | set words[4] = remoteRoot.bits; |
|
| 47 | + | set words[5] = remoteDevice.bits; |
|
| 48 | + | let context = probe::activate(root, child, args, 48); |
|
| 49 | + | let budget = probe::fund(reserve, child, context, ticks); |
|
| 50 | + | probe::run(child, context); |
|
| 51 | + | probe::status(child, context, 4); |
|
| 52 | + | probe::equal(leftWords[0], 301); |
|
| 53 | + | probe::equal(rightWords[0], 302); |
|
| 54 | + | let movedDevice = try! sys::capabilityTransfer(device, child, (abi::READ | abi::WRITE) as u64); |
|
| 55 | + | staleDevice(device); |
|
| 56 | + | set rightWords[1] = movedDevice.bits; |
|
| 57 | + | set leftWords[0] = 401; |
|
| 58 | + | probe::run(child, context); |
|
| 59 | + | probe::status(child, context, 3); |
|
| 60 | + | probe::equal(rightWords[0], 402); |
|
| 61 | + | try! sys::wakeup(child, 7001); |
|
| 62 | + | probe::event(env, 5, 7002, childId as u64); |
|
| 63 | + | probe::status(child, context, 4); |
|
| 64 | + | probe::equal(rightWords[0], 502); |
|
| 65 | + | let secondArgs = probe::allocate(root, 1); |
|
| 66 | + | set probe::words(secondArgs)[0] = 2; |
|
| 67 | + | let secondContext = probe::context(root, child, secondArgs, 8); |
|
| 68 | + | assert secondContext <> context; |
|
| 69 | + | let secondBudget = probe::fund(reserve, child, secondContext, ticks); |
|
| 70 | + | try! sys::capabilityDrop(secondBudget); |
|
| 71 | + | probe::run(child, secondContext); |
|
| 72 | + | probe::status(child, secondContext, 4); |
|
| 73 | + | try! sys::domainDestroy(child, 0); |
|
| 74 | + | probe::event(env, 4, 0, childId as u64); |
|
| 75 | + | probe::equal(rightWords[0], 502); |
|
| 76 | + | set rightWords[0] = 602; |
|
| 77 | + | probe::equal(rightWords[0], 602); |
|
| 78 | + | try! sys::capabilityDrop(args); |
|
| 79 | + | try! sys::capabilityDrop(secondArgs); |
|
| 80 | + | try! sys::capabilityDrop(budget); |
|
| 81 | + | probe::empty(env); |
|
| 82 | + | return output; |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Verify the first terminal report from an exit or fault workload. |
|
| 86 | + | export fn terminal(env: *sys::Env, root: abi::Handle, reserve: abi::Handle, ticks: u64, image: u64, mode: u64, kind: u16, code: u32) { |
|
| 87 | + | let child = probe::create(root, image); |
|
| 88 | + | let id = probe::id(child); |
|
| 89 | + | let args = probe::allocate(root, 1); |
|
| 90 | + | let victim = probe::allocate(root, 1); |
|
| 91 | + | let remoteVictim = probe::grant(victim, child, 1); |
|
| 92 | + | let words = probe::words(args); |
|
| 93 | + | set words[0] = mode; |
|
| 94 | + | set words[1] = remoteVictim.bits; |
|
| 95 | + | let context = probe::activate(root, child, args, 16); |
|
| 96 | + | let budget = probe::fund(reserve, child, context, ticks); |
|
| 97 | + | probe::run(child, context); |
|
| 98 | + | probe::event(env, kind, code, id as u64); |
|
| 99 | + | try! sys::capabilityDrop(args); |
|
| 100 | + | try! sys::capabilityDrop(victim); |
|
| 101 | + | try! sys::capabilityDrop(budget); |
|
| 102 | + | probe::empty(env); |
|
| 103 | + | } |
|
| 104 | + | ||
| 105 | + | /// Verify timer preemption and rejection after budget exhaustion. |
|
| 106 | + | export fn exhaust(env: *sys::Env, root: abi::Handle, reserve: abi::Handle, ticks: u64, image: u64) { |
|
| 107 | + | let child = probe::create(root, image); |
|
| 108 | + | let id = probe::id(child); |
|
| 109 | + | let flag = probe::allocate(root, 1); |
|
| 110 | + | let flagWords = probe::words(flag); |
|
| 111 | + | set flagWords[0] = 0; |
|
| 112 | + | let remoteFlag = probe::grant(flag, child, 3); |
|
| 113 | + | let args = probe::allocate(root, 1); |
|
| 114 | + | set probe::words(args)[0] = remoteFlag.bits; |
|
| 115 | + | let context = probe::activate(root, child, args, 8); |
|
| 116 | + | let budget = probe::fund(reserve, child, context, ticks); |
|
| 117 | + | probe::run(child, context); |
|
| 118 | + | probe::status(child, context, 4); |
|
| 119 | + | probe::equal(flagWords[0], context); |
|
| 120 | + | probe::equal(sys::queryBudget(budget).remaining, 0); |
|
| 121 | + | probe::exhausted(child, context); |
|
| 122 | + | try! sys::domainDestroy(child, 0); |
|
| 123 | + | probe::event(env, 4, 0, id as u64); |
|
| 124 | + | try! sys::capabilityDrop(flag); |
|
| 125 | + | try! sys::capabilityDrop(args); |
|
| 126 | + | try! sys::capabilityDrop(budget); |
|
| 127 | + | probe::empty(env); |
|
| 128 | + | } |
kernel/user/exiting.rad
added
+9 -0
| 1 | + | //! Return the exact little-endian u32 status supplied in startup memory. |
|
| 2 | + | use user::sys; |
|
| 3 | + | ||
| 4 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 5 | + | let args = sys::envArgs(env); |
|
| 6 | + | assert args.len >= 4; |
|
| 7 | + | return args[0] as u32 | (args[1] as u32 << 8) |
|
| 8 | + | | (args[2] as u32 << 16) | (args[3] as u32 << 24); |
|
| 9 | + | } |
kernel/user/faulting.rad
added
+13 -0
| 1 | + | //! Explicit Abort and stale metadata queries have distinct terminal fault codes. |
|
| 2 | + | use user::sys; |
|
| 3 | + | use probe; |
|
| 4 | + | ||
| 5 | + | @default fn main(env: *sys::Env) { |
|
| 6 | + | let args = probe::args(env); |
|
| 7 | + | if args[0] == 0 { sys::abort(); return; } |
|
| 8 | + | probe::equal(args[0], 1); |
|
| 9 | + | let page = probe::handle(args[1]); |
|
| 10 | + | try! sys::capabilityDrop(page); |
|
| 11 | + | let _info = sys::queryPage(page); |
|
| 12 | + | sys::abort(); |
|
| 13 | + | } |
kernel/user/flow.rad
added
+56 -0
| 1 | + | //! Two contexts share one instance while Page views outlive capability drops. |
|
| 2 | + | use abi; |
|
| 3 | + | use user::sys; |
|
| 4 | + | use probe; |
|
| 5 | + | ||
| 6 | + | static COUNT: u64 = 41; |
|
| 7 | + | ||
| 8 | + | /// Exercise UART Read and Write without changing the output or configuration. |
|
| 9 | + | /// The 16550 scratch register has no device effect; restore its previous value. |
|
| 10 | + | fn device(handle: abi::Handle, value: u8) { |
|
| 11 | + | let saved = try! sys::deviceRead8(handle, 7); |
|
| 12 | + | try! sys::deviceWrite8(handle, 7, value); |
|
| 13 | + | probe::equal((try! sys::deviceRead8(handle, 7)) as u64, value as u64); |
|
| 14 | + | try! sys::deviceWrite8(handle, 7, saved); |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | @default fn main(env: *sys::Env) { |
|
| 18 | + | let args = probe::args(env); |
|
| 19 | + | let old = COUNT; |
|
| 20 | + | if args[0] == 2 { |
|
| 21 | + | probe::equal(old, 42); |
|
| 22 | + | set COUNT = 43; |
|
| 23 | + | try! sys::yield(); |
|
| 24 | + | sys::abort(); |
|
| 25 | + | return; |
|
| 26 | + | } |
|
| 27 | + | probe::equal(args[0], 1); |
|
| 28 | + | probe::equal(old, 41); |
|
| 29 | + | set COUNT = 42; |
|
| 30 | + | let left = probe::handle(args[1]); |
|
| 31 | + | let right = probe::handle(args[2]); |
|
| 32 | + | let parent = args[3]; |
|
| 33 | + | let root = probe::handle(args[4]); |
|
| 34 | + | let leftWords = probe::words(left); |
|
| 35 | + | let rightWords = probe::words(right); |
|
| 36 | + | probe::equal(leftWords[0], 101); |
|
| 37 | + | probe::equal(rightWords[0], 202); |
|
| 38 | + | try! sys::capabilityDrop(left); |
|
| 39 | + | try! sys::capabilityDrop(right); |
|
| 40 | + | set leftWords[0] = 301; |
|
| 41 | + | set rightWords[0] = 302; |
|
| 42 | + | device(probe::handle(args[5]), 0x5a); |
|
| 43 | + | probe::empty(env); |
|
| 44 | + | try! sys::yield(); |
|
| 45 | + | probe::equal(leftWords[0], 401); |
|
| 46 | + | device(probe::handle(rightWords[1]), 0xa5); |
|
| 47 | + | set rightWords[0] = 402; |
|
| 48 | + | probe::empty(env); |
|
| 49 | + | try! sys::wait(); |
|
| 50 | + | probe::event(env, 5, 7001, parent); |
|
| 51 | + | probe::empty(env); |
|
| 52 | + | set rightWords[0] = 502; |
|
| 53 | + | try! sys::wakeup(root, 7002); |
|
| 54 | + | try! sys::yield(); |
|
| 55 | + | sys::abort(); |
|
| 56 | + | } |
kernel/user/remote.rad
added
+32 -0
| 1 | + | //! Cross-hart event handshakes and a continuously mutating teardown witness. |
|
| 2 | + | use user::sys; |
|
| 3 | + | use probe; |
|
| 4 | + | ||
| 5 | + | @default fn main(env: *sys::Env) -> u32 { |
|
| 6 | + | let args = probe::args(env); |
|
| 7 | + | let root = probe::handle(args[0]); |
|
| 8 | + | probe::empty(env); |
|
| 9 | + | if args.len == 4 { |
|
| 10 | + | let bytes = try! sys::pageSliceMut(probe::handle(args[2]), 0, 4); |
|
| 11 | + | let marker = bytes.ptr as *mut u32; |
|
| 12 | + | if sys::envContext(env) <> args[3] { |
|
| 13 | + | let before = sys::loadAcquire(marker); |
|
| 14 | + | while sys::loadAcquire(marker) == before {} |
|
| 15 | + | try! sys::wakeup(root, 8103); |
|
| 16 | + | try! sys::yield(); |
|
| 17 | + | return 0; |
|
| 18 | + | } |
|
| 19 | + | sys::storeRelease(marker, 1); |
|
| 20 | + | try! sys::wakeup(root, 8101); |
|
| 21 | + | let mut value: u32 = 1; |
|
| 22 | + | loop { |
|
| 23 | + | set value = ((value as u64 + 1) & 0xffffffff) as u32; |
|
| 24 | + | sys::storeRelease(marker, value); |
|
| 25 | + | } |
|
| 26 | + | } |
|
| 27 | + | try! sys::wakeup(root, 8001); |
|
| 28 | + | probe::event(env, 5, 8002, args[1]); |
|
| 29 | + | probe::empty(env); |
|
| 30 | + | try! sys::wakeup(root, 8003); |
|
| 31 | + | return 61; |
|
| 32 | + | } |
kernel/user/spinning.rad
added
+10 -0
| 1 | + | //! Publish entry before a syscall-free loop stopped only by budget preemption. |
|
| 2 | + | use user::sys; |
|
| 3 | + | use probe; |
|
| 4 | + | ||
| 5 | + | @default fn main(env: *sys::Env) { |
|
| 6 | + | let args = probe::args(env); |
|
| 7 | + | let words = probe::words(probe::handle(args[0])); |
|
| 8 | + | set words[0] = sys::envContext(env); |
|
| 9 | + | loop {} |
|
| 10 | + | } |