kernel: activate private image instances and independent contexts

04ed5958ed7ad2a3599dbc975e8709c98e9da258a66bf9232b968980154cd5ac
Verified: make -C kernel check; activation checks pass and native contexts return 42, 44, and 42 across shared and independent instances.
Alexis Sellier committed ago 1 parent 14a97c54
kernel/Makefile +3 -3
7 7
CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras arch/physical.ras
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 -
BASE_IMAGES := sample_root sample_control sample_scalars sample_memory sample_overflow sample_events sample_alias
12 +
BASE_IMAGES := sample_root sample_control sample_scalars sample_memory sample_overflow sample_events sample_alias sample_instance
13 13
BASE_RIL := $(addprefix build/,$(addsuffix .ril,$(BASE_IMAGES)))
14 14
IMAGE_MODULES_sample_control := user/sample_control/math.rad
15 15
BASE_CATALOG := -zero-bss -pkg images -mod build/baseline/images.rad -mod user/sys.ras $(addprefix -image ,$(BASE_RIL))
16 16
MACHINE := $(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions
17 17
44 44
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@
45 45
46 46
interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
47 47
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@
48 48
49 -
native.rv64: native.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
50 -
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg native -start arch/entry.ras -mod native.rad -entry native -o $@
49 +
native.rv64: native.rad native/instances.rad arch/entry.ras core.rad $(MODULES) $(CORE_ASM) build/baseline/images.rad user/sys.ras $(COMPILER)
50 +
	$(COMPILE) $(BASE_CATALOG) $(CORE) -pkg native -start arch/entry.ras -mod native.rad -mod native/instances.rad -entry native -o $@
51 51
52 52
check: all check.rv64 context.rv64 interrupt.rv64 native.rv64
53 53
	$(HOST_EMU) -run check.rv64
54 54
	$(MACHINE) -run kernel.rv64
55 55
	$(MACHINE) -max-steps=1000000 -run context.rv64
kernel/NOTES.md +20 -3
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 17 of the 22-step plan.
5 +
record the contracts established through step 18 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.
239 239
- -zero-bss includes zero-initialized kernel storage in native data sections so
240 240
  the image header and firmware reservations cover globals. Physical clear/copy
241 241
  callbacks accept validated owned ranges; constructing physical byte views is
242 242
  unsafe.
243 243
244 +
## Complete image instances
245 +
246 +
- DomainCreate preflights handle, domain, and terminal-event capacity. It copies
247 +
  only the immutable initializer into owned private frames. Failed reservations
248 +
  return provisional frames without installing authority.
249 +
- Activation requires Execute, a writable target Page stack, and readable
250 +
  coverage of the complete nonempty argument range. A descending stack may start
251 +
  at a Page's exclusive end with 16-byte alignment.
252 +
- Activation owns setup of each context's immutable 80-byte Env and private
253 +
  64 KiB native stack, rather than relying on machine-fixture setup.
254 +
- Contexts in one domain share writable image state and Events; different domains
255 +
  have independent state. Creation and activation grant no budget or hart.
256 +
- Creation ancestry survives intermediate creator death. Slot reuse clears the
257 +
  old incarnation's ancestry bit without erasing surviving ancestors. Private
258 +
  frame ownership prevents reuse before reclamation.
259 +
- The eighth native image checks instance state: initial value 41 becomes 42
260 +
  and 44 across two contexts in one domain, while another domain reports 42.
261 +
244 262
## Validation
245 263
246 264
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
247 265
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
248 266
checks compiler dependencies. From the repository root, run:
249 267
250 268
```sh
251 269
make -C kernel check
252 -
make std-test bin-test
253 270
```
254 271
255 -
Run seven source-compiled binary images through native machine traps. The fixture handles PageAllocate (30), QueryPage (44), checked Page materialization (60), and Exit (49), plus breakpoint fault entry. Check control flow, scalar operations, Page bounds, Events consumption, and overlapping memory. Fixture setup supplies private state, Env, and stack; this is not the complete calls dispatcher.
272 +
Run the eight-image native workload and hosted activation checks. Check complete argument/stack coverage, independent private instances, and two contexts sharing one instance with reported values 42 and 44 versus 42 in another.
256 273
257 274
Run the context reservation probe with an emulator that retains LR/SC
258 275
reservations across traps. This checks the kernel's reservation invalidation.
kernel/check.rad +2 -0
11 11
mod budgets;
12 12
mod budget_caps;
13 13
mod timers;
14 14
mod notifications;
15 15
mod devices;
16 +
mod activation;
16 17
17 18
/// Run the available kernel mechanism checks.
18 19
@default fn main() -> u32 {
19 20
    frames::run();
20 21
    boot::run();
26 27
    budgets::run();
27 28
    budget_caps::run();
28 29
    timers::run();
29 30
    notifications::run();
30 31
    devices::run();
32 +
    activation::run();
31 33
    return 0;
32 34
}
kernel/check/activation.rad added +174 -0
1 +
//! Transactional activation, live Page bounds, and private context ownership.
2 +
3 +
use core::abi;
4 +
use core::activation;
5 +
use core::capabilities;
6 +
use core::contexts;
7 +
use core::domains;
8 +
use core::events;
9 +
use core::fdt;
10 +
use core::frames;
11 +
use core::handles;
12 +
use core::memory;
13 +
use core::pages;
14 +
use core::platform;
15 +
use core::resources;
16 +
use core::state;
17 +
use images;
18 +
19 +
/// Test-owned physical memory for Page and private-stack allocations.
20 +
static RAM: [u8; 64 * frames::PAGE_SIZE] = undefined;
21 +
/// Domain slots include spare creation capacity unless explicitly exhausted.
22 +
static DOMAINS: [domains::Domain; 3] = undefined;
23 +
/// Page resource metadata.
24 +
static OBJECTS: [resources::Slot; 8] = undefined;
25 +
/// Two incarnations let us distinguish initial and additional contexts.
26 +
static CONTEXTS: [contexts::Context; 2] = undefined;
27 +
/// Allocation and persistent Page lifetime metadata.
28 +
static POOL: frames::Pool = undefined;
29 +
static PINS: [u16; 64] = undefined;
30 +
static ASSIGNED: [bool; 64] = undefined;
31 +
static GRANTS: [u64; 3] = undefined;
32 +
/// Shared state for the exercised public operations.
33 +
static KERNEL: state::State = undefined;
34 +
35 +
/// Zero only the supplied simulated physical range.
36 +
fn clear(base: u64, size: u32) {
37 +
    assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base;
38 +
    for i in base as u32..base as u32 + size { set RAM[i] = 0; }
39 +
}
40 +
41 +
/// Creation rejection must happen before any initializer can be copied.
42 +
fn unexpectedCopy(destination: u64, source: u64, size: u32) {
43 +
    panic "unexpectedCopy: rejected creation reached initializer";
44 +
}
45 +
46 +
/// Fresh state; the low-level child supplies an admitted entry for bounds tests.
47 +
fn setup() -> abi::Handle {
48 +
    let mut machine: platform::Platform = undefined;
49 +
    set machine.memoryCount = 1;
50 +
    set machine.memory[0] = fdt::Range { base: 0, size: RAM.len as u64 };
51 +
    set machine.reservedCount = 0;
52 +
    try! frames::init(&mut POOL, &machine);
53 +
    let mut ram: memory::Memory = undefined;
54 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 3);
55 +
    domains::init(&mut DOMAINS[..]);
56 +
    resources::init(&mut OBJECTS[..]);
57 +
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
58 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
59 +
    let handle = domains::root(&mut DOMAINS[..], images::ROOT);
60 +
    let root = try! domains::resolve(&DOMAINS[..], 0, handle, abi::CREATE);
61 +
    let child = try! domains::create(&mut DOMAINS[..], root, images::ROOT);
62 +
    return handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
63 +
}
64 +
65 +
/// Read a rejected call's public error without accepting accidental success.
66 +
fn createError(authority: abi::Handle, image: u64) -> abi::Error {
67 +
    let _handle = try activation::create(&mut KERNEL, 0, authority, image, clear, unexpectedCopy) catch error {
68 +
        return error;
69 +
    };
70 +
    panic "createError: creation unexpectedly succeeded";
71 +
}
72 +
73 +
/// Read activation errors for the initial or an additional context.
74 +
fn startError(target: abi::Handle, stack: u64, args: u64, size: u64, initial: bool) -> abi::Error {
75 +
    if initial {
76 +
        let _id = try activation::activate(&mut KERNEL, 0, target, stack, args, size, clear) catch error {
77 +
            return error;
78 +
        };
79 +
    } else {
80 +
        let _id = try activation::context(&mut KERNEL, 0, target, stack, args, size, clear) catch error {
81 +
            return error;
82 +
        };
83 +
    }
84 +
    panic "startError: context unexpectedly started";
85 +
}
86 +
87 +
/// A rejected image/capacity request cannot consume authority or an incarnation.
88 +
fn creation() {
89 +
    let target = setup();
90 +
    let self = abi::Handle { bits: 0 };
91 +
    assert createError(self, images::COUNT as u64) == abi::Error::VerifyFailed;
92 +
    assert createError(self, 0x100000000) == abi::Error::VerifyFailed;
93 +
    assert createError(target, images::ROOT as u64) == abi::Error::Denied;
94 +
    let child = try! domains::resolve(&DOMAINS[..], 0, target, abi::EXECUTE);
95 +
    let foreign = handles::install(&mut DOMAINS[0].handles, 3, child, abi::CREATE);
96 +
    assert createError(foreign, images::ROOT as u64) == abi::Error::Denied;
97 +
    set DOMAINS[0].events.reserved = events::CRITICAL;
98 +
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
99 +
    assert DOMAINS[0].events.reserved == events::CRITICAL;
100 +
    set DOMAINS[0].events.reserved = 1;
101 +
    let root = try! domains::authority(&DOMAINS[..], 0, self, abi::CREATE);
102 +
    for i in 4..abi::MAX_HANDLES {
103 +
        let _handle = handles::install(&mut DOMAINS[0].handles, i, root, abi::EXECUTE);
104 +
    }
105 +
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
106 +
    for i in 4..abi::MAX_HANDLES { let _entry = handles::remove(&mut DOMAINS[0].handles, i); }
107 +
    assert DOMAINS[2].epoch == 0 and DOMAINS[2].state == domains::Lifecycle::Dead;
108 +
    let _last = try! domains::create(&mut DOMAINS[..], root, images::ROOT);
109 +
    assert createError(self, images::ROOT as u64) == abi::Error::Exhausted;
110 +
    assert POOL.available == 64;
111 +
    assert domains::live(&DOMAINS[..], try! domains::resolve(&DOMAINS[..], 0, target, abi::EXECUTE));
112 +
}
113 +
114 +
/// Verify stack edges, adjacent args, allocation rollback, and context isolation.
115 +
export fn run() {
116 +
    creation();
117 +
    let target = setup();
118 +
    let whole = try! pages::allocate(&mut KERNEL, 0, abi::Handle { bits: 0 }, 4, clear);
119 +
    let right = try! pages::split(&mut KERNEL, 0, whole, 2);
120 +
    let _leftGrant = try! capabilities::grant(&mut KERNEL, 0, whole, target, (abi::READ | abi::WRITE) as u64);
121 +
    let rightGrant = try! capabilities::grant(&mut KERNEL, 0, right, target, abi::READ as u64);
122 +
    let restricted = handles::install(&mut DOMAINS[0].handles, try! handles::vacant(&DOMAINS[0].handles),
123 +
        try! domains::resolve(&DOMAINS[..], 0, target, abi::EXECUTE), abi::WAKE);
124 +
    assert startError(restricted, 8192, 4096, 8192, true) == abi::Error::Denied;
125 +
    assert startError(target, 8192, 4096, 8192, false) == abi::Error::Busy;
126 +
    assert startError(target, 0, 0, 0, true) == abi::Error::InvalidArg;
127 +
    assert startError(target, 8191, 0, 0, true) == abi::Error::InvalidArg;
128 +
    assert startError(target, 8208, 0, 0, true) == abi::Error::InvalidArg;
129 +
    assert startError(target, 8192, 4096, 12289, true) == abi::Error::InvalidArg;
130 +
    assert startError(target, 8192, 0xfffffffffffffff0, 32, true) == abi::Error::InvalidArg;
131 +
    // A dropped readable handle no longer authorizes args, even while its
132 +
    // persistent lifetime pin keeps the physical bytes from being reused.
133 +
    try! capabilities::drop(&mut KERNEL, 1, rightGrant);
134 +
    assert startError(target, 8192, 4096, 8192, true) == abi::Error::InvalidArg;
135 +
    let _readAgain = try! capabilities::grant(&mut KERNEL, 0, right, target, abi::READ as u64);
136 +
    set CONTEXTS[0].epoch = 0xffffffff;
137 +
    set CONTEXTS[1].epoch = 0xffffffff;
138 +
    assert startError(target, 8192, 4096, 8192, true) == abi::Error::Exhausted;
139 +
    set CONTEXTS[0].epoch = 0;
140 +
    set CONTEXTS[1].epoch = 0;
141 +
    let held = try! frames::allocate(&mut POOL, POOL.available, clear);
142 +
    assert startError(target, 8192, 4096, 8192, true) == abi::Error::OutOfMemory;
143 +
    assert DOMAINS[1].state == domains::Lifecycle::Pending and DOMAINS[1].env.context == 0;
144 +
    assert CONTEXTS[0].status == contexts::Status::Vacant and CONTEXTS[0].epoch == 0;
145 +
    assert CONTEXTS[1].status == contexts::Status::Vacant and CONTEXTS[1].epoch == 0;
146 +
    frames::discard(&mut POOL, held);
147 +
    let first = try! activation::activate(&mut KERNEL, 0, target, 8192, 4096, 8192, clear);
148 +
    assert DOMAINS[1].state == domains::Lifecycle::Active;
149 +
    let a = try! contexts::resolve(&CONTEXTS[..], first);
150 +
    assert DOMAINS[1].env.context == first;
151 +
    assert CONTEXTS[a].env.stackBase == 0 and CONTEXTS[a].env.stackTop == 8192;
152 +
    assert CONTEXTS[a].env.argsPointer == 4096 and CONTEXTS[a].env.argsSize == 8192;
153 +
    assert CONTEXTS[a].env.eventsPointer == &DOMAINS[1].events.ring as u64;
154 +
    assert CONTEXTS[a].env.privateStackTop - CONTEXTS[a].env.privateStackBase == activation::PRIVATE_STACK_SIZE as u64;
155 +
    assert CONTEXTS[a].frame.pc == images::get(images::ROOT).entry;
156 +
    assert startError(target, 8192, 0, 0, true) == abi::Error::NotPending;
157 +
    // Empty args need no Page, even for an otherwise invalid pointer.
158 +
    let second = try! activation::context(&mut KERNEL, 0, target, 4096, 0xffffffffffffffff, 0, clear);
159 +
    let b = try! contexts::resolve(&CONTEXTS[..], second);
160 +
    assert first <> second and a <> b;
161 +
    assert CONTEXTS[b].env.stackTop == 4096 and DOMAINS[1].env.stackTop == 8192;
162 +
    assert DOMAINS[1].env.context == first;
163 +
    assert CONTEXTS[a].env.privateStackTop <= CONTEXTS[b].env.privateStackBase
164 +
        or CONTEXTS[b].env.privateStackTop <= CONTEXTS[a].env.privateStackBase;
165 +
    assert CONTEXTS[a].budget.kind == abi::Kind::Empty and CONTEXTS[b].budget.kind == abi::Kind::Empty;
166 +
    assert CONTEXTS[a].hart == contexts::NO_HART and CONTEXTS[b].hart == contexts::NO_HART;
167 +
    assert startError(target, 8192, 0, 0, false) == abi::Error::Exhausted;
168 +
    assert try! contexts::resolve(&CONTEXTS[..], first) == a;
169 +
    assert try! contexts::resolve(&CONTEXTS[..], second) == b;
170 +
    assert POOL.available == 64 - 4 - 2 * (activation::PRIVATE_STACK_SIZE / frames::PAGE_SIZE);
171 +
    for i in 4..64 {
172 +
        assert not ASSIGNED[i] and PINS[i] == 0;
173 +
    }
174 +
}
kernel/core.rad +1 -0
22 22
export mod notifications;
23 23
export mod devices;
24 24
export mod interrupts;
25 25
export mod mmio;
26 26
export mod physical;
27 +
export mod activation;
kernel/core/activation.rad added +182 -0
1 +
//! Capability-mediated image instances and private execution startup state.
2 +
//!
3 +
//! Callers hold the kernel critical section across each operation, including
4 +
//! callbacks, and supply a live caller index. Domain/context storage must not
5 +
//! move while live environments or Events pointers refer into it. Callbacks
6 +
//! cannot fail, reenter the kernel, mutate kernel metadata, or retain a range.
7 +
8 +
use core::abi;
9 +
use core::contexts;
10 +
use core::domains;
11 +
use core::events;
12 +
use core::frames;
13 +
use core::handles;
14 +
use core::pages;
15 +
use core::resources;
16 +
use core::state;
17 +
use images;
18 +
19 +
/// Fixed private native stack per context: 64 KiB for locals and saved registers.
20 +
/// The authorized Page stack has separate bounds in Env.
21 +
export constant PRIVATE_STACK_SIZE: u32 = 64 * 1024;
22 +
23 +
/// Exclusive private initializer storage for one domain instance.
24 +
export record ImageState: Copy {
25 +
    /// Owned frames, or an empty run for a stateless graph.
26 +
    run: frames::Run,
27 +
    /// Aligned mutable-state base within the owned run.
28 +
    base: u64,
29 +
}
30 +
31 +
/// Allocate and copy a checked catalog initializer before domain installation.
32 +
/// The caller installs the returned ownership or reclaims it on failure.
33 +
export fn initialize(pool: *mut frames::Pool, image: u32, clear: fn(u64, u32), copy: fn(u64, u64, u32)) -> ImageState throws (abi::Error) {
34 +
    let descriptor = images::get(image);
35 +
    let mut run = frames::Run { first: 0, count: 0 };
36 +
    let mut base: u64 = 0;
37 +
    if descriptor.stateSize <> 0 {
38 +
        let alignment = descriptor.stateAlignment as u64;
39 +
        // These are trusted compiler facts, not caller-provided metadata.
40 +
        assert alignment <> 0 and alignment & (alignment - 1) == 0;
41 +
        let mut padding: u64 = 0;
42 +
        if alignment > frames::PAGE_SIZE as u64 {
43 +
            // Every run already starts on a frame boundary.
44 +
            set padding = alignment - frames::PAGE_SIZE as u64;
45 +
        }
46 +
        let bytes = descriptor.stateSize as u64 + padding;
47 +
        let count = (bytes + frames::PAGE_SIZE as u64 - 1) / frames::PAGE_SIZE as u64;
48 +
        if count > frames::MAX_FRAMES as u64 { throw abi::Error::OutOfMemory; }
49 +
        let allocation = try frames::allocate(pool, count as u32, clear) catch {
50 +
            throw abi::Error::OutOfMemory;
51 +
        };
52 +
        set run = frames::install(allocation);
53 +
        let first = run.first as u64 * frames::PAGE_SIZE as u64;
54 +
        set base = (first + alignment - 1) & ~(alignment - 1);
55 +
        copy(base, descriptor.initial, descriptor.stateSize);
56 +
        descriptor.relocate(base);
57 +
    }
58 +
    return ImageState { run, base };
59 +
}
60 +
61 +
/// Instantiate an admitted catalog graph under the caller's Create authority.
62 +
/// image is only a catalog index, never an entry address or runtime descriptor.
63 +
/// clear(base, size) zeros exactly the supplied physical allocation;
64 +
/// copy(destination, source, size) copies exactly the immutable initializer.
65 +
/// Both callbacks obey the serialization and lifetime rules of this module.
66 +
/// All capacity checks precede allocation; failure installs no authority or
67 +
/// domain incarnation and returns any provisionally allocated private frames.
68 +
export fn create(kernel: *mut state::State, caller: u32, authority: abi::Handle, image: u64, clear: fn(u64, u32), copy: fn(u64, u64, u32)) -> abi::Handle throws (abi::Error) {
69 +
    let creator = try domains::authority(kernel.domains, caller, authority, abi::CREATE);
70 +
    if creator.index <> caller { throw abi::Error::Denied; }
71 +
    if image >= images::COUNT as u64 { throw abi::Error::VerifyFailed; }
72 +
    let slot = try handles::vacant(&kernel.domains[caller].handles);
73 +
    let _domainSlot = try domains::vacant(kernel.domains);
74 +
    // Refresh only consumed event credits; do not reserve a child until commit.
75 +
    try events::refresh(&mut kernel.domains[caller].events);
76 +
    if kernel.domains[caller].events.reserved == events::CRITICAL {
77 +
        throw abi::Error::Exhausted;
78 +
    }
79 +
    let initial = try initialize(kernel.memory.pool, image as u32, clear, copy);
80 +
    // The consumer can publish another head during allocation. Revalidate it
81 +
    // when reserving the terminal event; even that late failure returns RAM.
82 +
    let object = try domains::create(kernel.domains, creator, image as u32) catch error {
83 +
        if initial.run.count <> 0 { frames::reclaim(kernel.memory.pool, initial.run); }
84 +
        throw error;
85 +
    };
86 +
    set kernel.domains[object.index].private = initial.run;
87 +
    set kernel.domains[object.index].env.stateBase = initial.base;
88 +
    return handles::install(&mut kernel.domains[caller].handles, slot, object, abi::DOMAIN_RIGHTS);
89 +
}
90 +
91 +
/// Return the base of a live writable Page containing bytes below stack.
92 +
/// A descending stack may start at the Page's exclusive end, but not its base.
93 +
fn stackBase(kernel: *state::State, domain: u32, stack: u64) -> u64 throws (abi::Error) {
94 +
    if stack & 15 <> 0 { throw abi::Error::InvalidArg; }
95 +
    for i in 0..abi::MAX_HANDLES {
96 +
        let entry = &kernel.domains[domain].handles.entries[i];
97 +
        if entry.object.kind <> abi::Kind::Page or entry.rights & abi::WRITE == 0
98 +
            or not resources::live(kernel.resources, kernel.domains, entry.object) { continue; }
99 +
        let case resources::Value::Page(page) = kernel.resources[entry.object.index].value else { panic "stackBase: page kind mismatch"; };
100 +
        let base = page.run.first as u64 * frames::PAGE_SIZE as u64;
101 +
        let end = base + page.run.count as u64 * frames::PAGE_SIZE as u64;
102 +
        if stack > base and stack <= end { return base; }
103 +
    }
104 +
    throw abi::Error::InvalidArg;
105 +
}
106 +
107 +
/// Prepare one context without altering the domain until all fallible work ends.
108 +
fn start(kernel: *mut state::State, owner: abi::Object, stack: u64, args: u64, size: u64, clear: fn(u64, u32), initial: bool) -> u64 throws (abi::Error) {
109 +
    let lifecycle = kernel.domains[owner.index].state;
110 +
    if initial {
111 +
        if lifecycle <> domains::Lifecycle::Pending { throw abi::Error::NotPending; }
112 +
    } else {
113 +
        if lifecycle <> domains::Lifecycle::Active { throw abi::Error::Busy; }
114 +
    }
115 +
    let base = try stackBase(kernel, owner.index, stack);
116 +
    if not pages::covers(kernel, owner.index, args, size, abi::READ) {
117 +
        throw abi::Error::InvalidArg;
118 +
    }
119 +
    let mut slot = kernel.contexts.len;
120 +
    for i in 0..kernel.contexts.len {
121 +
        let context = &kernel.contexts[i];
122 +
        if context.status == contexts::Status::Vacant and context.epoch < 0xffffffff {
123 +
            set slot = i;
124 +
            break;
125 +
        }
126 +
    }
127 +
    if slot == kernel.contexts.len { throw abi::Error::Exhausted; }
128 +
    let domain = &kernel.domains[owner.index];
129 +
    assert domain.image < images::COUNT;
130 +
    let descriptor = images::get(domain.image);
131 +
    let allocation = try frames::allocate(kernel.memory.pool, PRIVATE_STACK_SIZE / frames::PAGE_SIZE, clear) catch {
132 +
        throw abi::Error::OutOfMemory;
133 +
    };
134 +
    let private = frames::install(allocation);
135 +
    let privateBase = private.first as u64 * frames::PAGE_SIZE as u64;
136 +
    let env = domains::Env {
137 +
        argsPointer: args, argsSize: size,
138 +
        eventsHandle: domain.env.eventsHandle,
139 +
        eventsPointer: &domain.events.ring as u64,
140 +
        stateBase: domain.env.stateBase,
141 +
        stackBase: base, stackTop: stack, context: 0,
142 +
        privateStackBase: privateBase,
143 +
        privateStackTop: privateBase + PRIVATE_STACK_SIZE as u64,
144 +
    };
145 +
    // Architectural sp starts at the authorized Page stack. The image entry
146 +
    // selects env.privateStackTop for native locals and saved registers.
147 +
    try contexts::prepare(&mut kernel.contexts[slot], owner, descriptor.entry, stack, env) catch error {
148 +
        frames::reclaim(kernel.memory.pool, private);
149 +
        throw error;
150 +
    };
151 +
    set kernel.contexts[slot].private = private;
152 +
    let id = contexts::identity(&kernel.contexts[slot]);
153 +
    if initial {
154 +
        set kernel.domains[owner.index].env = kernel.contexts[slot].env;
155 +
        set kernel.domains[owner.index].state = domains::Lifecycle::Active;
156 +
    }
157 +
    return id;
158 +
}
159 +
160 +
/// Activate a Pending domain at its admitted entry and return its context ID.
161 +
/// Execute is required on target. stack is 16-aligned and above the base of a
162 +
/// live writable Page held by target, at most its exclusive end. Nonempty args
163 +
/// must be wholly covered by target's live readable Pages, including adjacency.
164 +
/// Failure leaves the domain Pending and context slots unchanged. No budget is
165 +
/// minted or bound and no hart is chosen. The dispatcher may discard this ID
166 +
/// for the void DomainActivate ABI. clear zeros exactly its supplied physical
167 +
/// byte range and obeys this module's caller serialization/callback invariants.
168 +
export fn activate(kernel: *mut state::State, caller: u32, target: abi::Handle, stack: u64, args: u64, size: u64, clear: fn(u64, u32)) -> u64 throws (abi::Error) {
169 +
    let owner = try domains::resolve(kernel.domains, caller, target, abi::EXECUTE);
170 +
    return try start(kernel, owner, stack, args, size, clear, true);
171 +
}
172 +
173 +
/// Add a context to an Active domain, always at that domain's admitted entry.
174 +
/// Requires Execute and the same stack/args and serialized clear contract as
175 +
/// activate; a Pending target returns Busy. Each context owns a distinct Env
176 +
/// and private stack but shares image state and Events with its domain. The
177 +
/// first environment/identity stays in Domain.env. No budget or hart is bound.
178 +
/// Failure preserves every existing context and the target's Active state.
179 +
export fn context(kernel: *mut state::State, caller: u32, target: abi::Handle, stack: u64, args: u64, size: u64, clear: fn(u64, u32)) -> u64 throws (abi::Error) {
180 +
    let owner = try domains::resolve(kernel.domains, caller, target, abi::EXECUTE);
181 +
    return try start(kernel, owner, stack, args, size, clear, false);
182 +
}
kernel/core/domains.rad +48 -12
1 1
//! Domain identities, initial authority, and private startup state.
2 2
3 3
use core::abi;
4 4
use core::events;
5 +
use core::frames;
5 6
use core::handles;
6 7
7 8
/// Domain state; Dying is an internal quiescence phase.
8 9
export union Lifecycle: Copy {
9 10
    /// Startup capabilities can be installed before activation.
26 27
    eventsHandle: u64,
27 28
    /// Physical address of this domain's shared event ring.
28 29
    eventsPointer: u64,
29 30
    /// Physical base of this image instance's mutable data.
30 31
    stateBase: u64,
31 -
    /// Lowest address available to Page-backed local stack frames.
32 +
    /// Lowest address in the authorized Page stack.
32 33
    stackBase: u64,
33 -
    /// Exclusive upper bound of the addressable local stack.
34 +
    /// Exclusive upper bound of the authorized Page stack.
34 35
    stackTop: u64,
35 36
    /// Kernel execution-context identity.
36 37
    context: u64,
37 -
    /// Lowest private address for SSA values and saved registers.
38 +
    /// Lowest native stack address for locals and saved registers.
38 39
    privateStackBase: u64,
39 40
    /// Exclusive upper bound of the never-granted private stack.
40 41
    privateStackTop: u64,
41 42
}
42 43
50 51
    epoch: u32,
51 52
    /// Protection-domain lifecycle.
52 53
    state: Lifecycle,
53 54
    /// Historical creator identity; independent of the current parent.
54 55
    creator: abi::Object,
56 +
    /// Creation ancestry by slot; reuse clears only that slot's historical bit.
57 +
    /// Surviving ancestor bits outlive intermediate creator incarnations.
58 +
    ancestors: [u64; abi::MAX_DOMAINS / 64],
55 59
    /// Current receiver of terminal lifecycle notifications.
56 60
    parent: abi::Object,
61 +
    /// First terminal event kind: Fault=3 or ChildExit=4; zero while live.
62 +
    terminalKind: u16,
63 +
    /// First terminal fault code or exit status; immutable while Dying.
64 +
    terminalCode: u32,
57 65
    /// Trusted immutable image registry index.
58 66
    image: u32,
67 +
    /// Exclusive image-state frames, never Page claims or lifetime pins.
68 +
    /// A zero count means empty state; reclaim and zero after quiescence.
69 +
    /// env.stateBase may be aligned inside this run rather than at its base.
70 +
    private: frames::Run,
59 71
    /// Initial execution environment.
60 72
    env: Env,
61 73
}
62 74
63 75
/// Return the identity used for a missing parent or creator.
73 85
        handles::init(&mut domain.handles);
74 86
        events::init(&mut domain.events);
75 87
        set domain.epoch = 0;
76 88
        set domain.state = Lifecycle::Dead;
77 89
        set domain.creator = none();
90 +
        set domain.ancestors = [0; abi::MAX_DOMAINS / 64];
78 91
        set domain.parent = none();
92 +
        set domain.terminalKind = 0;
93 +
        set domain.terminalCode = 0;
79 94
        set domain.image = 0;
95 +
        set domain.private = frames::Run { first: 0, count: 0 };
80 96
        set domain.env = Env {
81 97
            argsPointer: 0, argsSize: 0, eventsHandle: 0, eventsPointer: 0,
82 98
            stateBase: 0, stackBase: 0, stackTop: 0, context: 0,
83 99
            privateStackBase: 0, privateStackTop: 0,
84 100
        };
118 134
    throw abi::Error::Denied;
119 135
}
120 136
121 137
/// Prepare a free slot with an Events handle and no ambient authority.
122 138
fn prepare(domains: *mut [Domain], index: u32, creator: abi::Object, image: u32) -> abi::Object {
139 +
    // Index bits name only the current incarnation. Remove the old incarnation
140 +
    // without erasing surviving ancestry through an exited intermediate creator.
141 +
    let word = index / 64;
142 +
    let bit = 1 as u64 << (index as u64 & 63);
143 +
    for i in 0..domains.len { set domains[i].ancestors[word] &= ~bit; }
123 144
    let domain = &mut domains[index];
124 145
    assert domain.state == Lifecycle::Dead and domain.epoch < 0xffffffff;
146 +
    assert domain.private.count == 0;
125 147
    for i in 0..abi::MAX_HANDLES {
126 148
        assert domain.handles.entries[i].object.kind == abi::Kind::Empty;
127 149
    }
128 150
    set domain.epoch += 1;
129 151
    set domain.creator = creator;
152 +
    set domain.ancestors = [0; abi::MAX_DOMAINS / 64];
153 +
    if creator.kind == abi::Kind::Domain {
154 +
        set domain.ancestors = domains[creator.index].ancestors;
155 +
        set domain.ancestors[creator.index / 64] |= 1 as u64 << (creator.index as u64 & 63);
156 +
    }
130 157
    set domain.parent = creator;
158 +
    set domain.terminalKind = 0;
159 +
    set domain.terminalCode = 0;
131 160
    set domain.image = image;
132 161
    events::init(&mut domain.events);
133 162
    let object = abi::Object { kind: abi::Kind::Domain, index, epoch: domain.epoch };
134 163
    let eventObject = abi::Object { kind: abi::Kind::Events, index, epoch: domain.epoch };
135 164
    let eventHandle = handles::install(&mut domain.handles, 1, eventObject, abi::READ | abi::WRITE);
136 165
    set domain.env = Env {
137 -
        argsPointer: 0, argsSize: 0, eventsHandle: eventHandle.bits, eventsPointer: 0,
166 +
        argsPointer: 0, argsSize: 0, eventsHandle: eventHandle.bits,
167 +
        eventsPointer: &domain.events.ring as u64,
138 168
        stateBase: 0, stackBase: 0, stackTop: 0, context: 0,
139 169
        privateStackBase: 0, privateStackTop: 0,
140 170
    };
141 171
    set domain.state = Lifecycle::Pending;
142 172
    return object;
147 177
    assert domains[0].epoch == 0;
148 178
    let object = prepare(domains, 0, none(), image);
149 179
    return handles::install(&mut domains[0].handles, 0, object, abi::ROOT_RIGHTS);
150 180
}
151 181
152 -
/// Create a pending domain after the caller has authorized image admission.
153 -
/// Reserve terminal event capacity before accepting the parent relationship.
154 -
export fn create(domains: *mut [Domain], creator: abi::Object, image: u32) -> abi::Object throws (abi::Error) {
155 -
    if not live(domains, creator) { throw abi::Error::BadHandle; }
182 +
/// Find a reusable domain slot without changing its incarnation or authority.
183 +
/// Dead slots still owning private state must be reclaimed before reuse.
184 +
export fn vacant(domains: *[Domain]) -> u32 throws (abi::Error) {
156 185
    for index in 0..domains.len {
157 186
        let domain = &domains[index];
158 187
        if domain.state == Lifecycle::Dead and domain.epoch < 0xffffffff
159 -
            and domain.handles.entries[1].generation <> 0 {
160 -
            try events::reserve(&mut domains[creator.index].events);
161 -
            let result = prepare(domains, index, creator, image);
162 -
            return result;
188 +
            and domain.private.count == 0 and domain.handles.entries[1].generation <> 0 {
189 +
            return index;
163 190
        }
164 191
    }
165 192
    throw abi::Error::Exhausted;
166 193
}
194 +
195 +
/// Create a pending domain after the caller has authorized image admission.
196 +
/// Reserve terminal event capacity before accepting the parent relationship.
197 +
export fn create(domains: *mut [Domain], creator: abi::Object, image: u32) -> abi::Object throws (abi::Error) {
198 +
    if not live(domains, creator) { throw abi::Error::BadHandle; }
199 +
    let index = try vacant(domains);
200 +
    try events::reserve(&mut domains[creator.index].events);
201 +
    return prepare(domains, index, creator, image);
202 +
}
kernel/native.rad +10 -7
14 14
use core::platform;
15 15
use core::resources;
16 16
use core::state;
17 17
use images;
18 18
19 +
mod instances;
20 +
19 21
/// Isolated consumer used by the native graph regressions.
20 -
static DOMAINS: [domains::Domain; 1] = undefined;
22 +
static DOMAINS: [domains::Domain; 3] = undefined;
21 23
/// Page objects exercised by checked materialization calls.
22 24
static OBJECTS: [resources::Slot; 16] = undefined;
23 25
/// Storage for the execution-state view.
24 -
static CONTEXTS: [contexts::Context; 1] = undefined;
26 +
static CONTEXTS: [contexts::Context; 3] = undefined;
25 27
/// Physical frame ownership from the firmware reservations.
26 28
static POOL: frames::Pool = undefined;
27 29
/// Persistent physical Page pins.
28 30
static PINS: [u16; frames::MAX_FRAMES] = undefined;
29 31
/// Live Page claims.
30 32
static CLAIMS: [bool; frames::MAX_FRAMES] = undefined;
31 33
/// Domain-lifetime frame bitmap.
32 -
static GRANTS: [u64; frames::MAX_FRAMES / 64] = undefined;
34 +
static GRANTS: [u64; 3 * frames::MAX_FRAMES / 64] = undefined;
33 35
/// Actual capability and Page mechanisms used by the fixture calls.
34 36
static KERNEL: state::State = undefined;
35 37
36 38
/// Run one graph against actual Page authority and check its terminal call.
37 39
unsafe fn run(image: u32, expected: u32, own: abi::Handle, queued: bool) {
100 102
    frames::reclaim(&mut POOL, data);
101 103
}
102 104
103 105
/// Check graph linking, native control flow, bounded memory, and protected spills.
104 106
@default unsafe fn main(hart: u64, description: *u8) -> u32 {
105 -
    assert hart == 0 and images::COUNT == 7;
107 +
    assert hart == 0 and images::COUNT == 8;
106 108
    let size = try! fdt::word(@sliceOf(description, 40), 4);
107 109
    assert size >= 40 and size <= fdt::MAX_BYTES;
108 110
    let mut tree: fdt::Tree = undefined;
109 111
    try! fdt::decode(@sliceOf(description, size), &mut tree);
110 112
    let mut machine: platform::Platform = undefined;
111 113
    try! platform::discover(&tree, &mut machine);
112 114
    try! frames::init(&mut POOL, &machine);
113 115
    domains::init(&mut DOMAINS[..]);
114 116
    resources::init(&mut OBJECTS[..]);
115 -
    contexts::init(&mut CONTEXTS[0], 0);
117 +
    for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); }
116 118
    let mut ram: memory::Memory = undefined;
117 -
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut CLAIMS[..], &mut GRANTS[..], 1);
119 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut CLAIMS[..], &mut GRANTS[..], 3);
118 120
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
119 121
    let own = domains::root(&mut DOMAINS[..], images::ROOT);
120 122
    let expected: [u32; 7] = [42, 21, 73, 77, 0, 42, 81];
121 -
    for i in 0..images::COUNT { run(i, expected[i], own, i == 5); }
123 +
    for i in 0..expected.len { run(i, expected[i], own, i == 5); }
122 124
    run(5, 42, own, false);
125 +
    instances::run(&mut KERNEL, own, machine.clint.base);
123 126
    return 0;
124 127
}
kernel/native/instances.rad added +61 -0
1 +
//! Native proof of independent domain state and shared same-domain contexts.
2 +
3 +
use core::abi;
4 +
use core::activation;
5 +
use core::budget_caps;
6 +
use core::budgets;
7 +
use core::capabilities;
8 +
use core::clock;
9 +
use core::contexts;
10 +
use core::cpu;
11 +
12 +
use core::handles;
13 +
use core::pages;
14 +
15 +
use core::physical;
16 +
use core::resources;
17 +
use core::state;
18 +
19 +
/// Give one explicit context its own finite budget and observe its native Exit.
20 +
unsafe fn execute(kernel: *mut state::State, target: abi::Handle, id: u64, clint: u64, expected: u32) {
21 +
    let object = try! resources::create(kernel.resources, resources::Value::Budget(try! budgets::init(0, 1000000)));
22 +
    resources::retain(kernel.resources, object);
23 +
    let handle = handles::install(&mut kernel.domains[0].handles,
24 +
        try! handles::vacant(&kernel.domains[0].handles), object, abi::EXECUTE);
25 +
    try! budget_caps::bind(kernel, 0, handle, target, id);
26 +
    let index = try! contexts::resolve(kernel.contexts, id);
27 +
    let context = &mut kernel.contexts[index];
28 +
    let budget = budget_caps::value(kernel, object);
29 +
    let running = try! contexts::begin(context, budget, clock::read(clint), 0);
30 +
    clock::arm(clint, 0, context.deadline);
31 +
    clock::interrupts(128);
32 +
    cpu::enter(&mut context.frame);
33 +
    let stopped = clock::read(clint);
34 +
    clock::interrupts(0);
35 +
    clock::arm(clint, 0, 0xffffffffffffffff);
36 +
    assert contexts::finish(context, budget, running, stopped) > 0;
37 +
    assert context.frame.cause == 8 and context.frame.registers[17] == 49;
38 +
    assert context.frame.registers[10] == expected as u64;
39 +
    set context.status = contexts::Status::Stopped;
40 +
}
41 +
42 +
/// Instantiate one graph twice, then run two contexts in the first instance.
43 +
export unsafe fn run(kernel: *mut state::State, own: abi::Handle, clint: u64) {
44 +
    let image: u64 = 7;
45 +
    let first = try! activation::create(kernel, 0, own, image, physical::clear, physical::copy);
46 +
    let second = try! activation::create(kernel, 0, own, image, physical::clear, physical::copy);
47 +
    let page = try! pages::allocate(kernel, 0, own, 1, physical::clear);
48 +
    let range = try! pages::access(kernel, 0, page, abi::READ | abi::WRITE);
49 +
    let words = @sliceOf(physical::bytes(range.base, 8).ptr as *mut u32, 2);
50 +
    set words[0] = 1;
51 +
    set words[1] = 2;
52 +
    let _firstPage = try! capabilities::grant(kernel, 0, page, first, (abi::READ | abi::WRITE) as u64);
53 +
    let _secondPage = try! capabilities::grant(kernel, 0, page, second, (abi::READ | abi::WRITE) as u64);
54 +
    let top = range.base + range.size;
55 +
    let firstId = try! activation::activate(kernel, 0, first, top, range.base, 4, physical::clear);
56 +
    let extraId = try! activation::context(kernel, 0, first, top, range.base + 4, 4, physical::clear);
57 +
    let secondId = try! activation::activate(kernel, 0, second, top, range.base, 4, physical::clear);
58 +
    execute(kernel, first, firstId, clint, 42);
59 +
    execute(kernel, first, extraId, clint, 44);
60 +
    execute(kernel, second, secondId, clint, 42);
61 +
}
kernel/user/sample_instance.rad added +14 -0
1 +
//! Independent domains initialize at 41; contexts share their domain's counter.
2 +
use user::sys;
3 +
4 +
/// Mutable state shared by contexts in one image instance.
5 +
static COUNT: u32 = 41;
6 +
7 +
@default fn main(env: *sys::Env) -> u32 {
8 +
    let args = sys::envArgs(env);
9 +
    assert args.len >= 4;
10 +
    let increment = args[0] as u32 | (args[1] as u32 << 8)
11 +
        | (args[2] as u32 << 16) | (args[3] as u32 << 24);
12 +
    set COUNT += increment;
13 +
    return COUNT;
14 +
}