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.
Alexis Sellier committed ago 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 +
}