kernel: dispatch the complete direct-call ABI

1279b22552a4c3a31240a398c356f577c2ab62df5c236a329e8272761c8ff6e7
Verified: make -C kernel check; hosted ABI validation and native execution through calls::dispatch pass.
Alexis Sellier committed ago 1 parent dde478e3
kernel/NOTES.md +18 -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 19 of the 22-step plan.
5 +
record the contracts established through step 20 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.
278 278
- Checks cover running-context retention, cascades through dead creators,
279 279
  exhausted reparenting credits, stale names, and malformed parent progress.
280 280
  Destroying an image instance must preserve its sibling and immutable initializer
281 281
  so a newly created instance starts from the same value.
282 282
283 +
## Complete direct-call dispatch
284 +
285 +
- core::calls validates arguments and authority and returns explicit effects for
286 +
  architectural integration. Ordinary failures and invalid metadata queries
287 +
  return Error. QueryContext requires an explicit Domain handle with Execute,
288 +
  not merely the self-sentinel.
289 +
- Calls 70 through 76 are BudgetSplit, BudgetBind, ContextCreate, ContextRun,
290 +
  QueryBudget, QueryContext, and Delegate. Splits conserve ticks and hart identity;
291 +
  a context binding retains its budget after the handle is dropped.
292 +
- Hosted dispatcher checks cover ordinary errors, metadata values, query errors,
293 +
  authority attenuation, and returned execution/lifecycle effects. Machine
294 +
  scheduling of these effects is separate from their validation.
295 +
- Public Page counts, Interrupt numbers, and Events capacities use u32. Public
296 +
  domain identifiers use u16, with Pending, Active, and Dead lifecycle values.
297 +
  The register transport uses u64 words.
298 +
283 299
## Validation
284 300
285 301
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
286 302
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
287 303
checks compiler dependencies. From the repository root, run:
288 304
289 305
```sh
290 306
make -C kernel check
291 307
```
292 308
293 -
Exercise deferred resource retention, creation cascades, reparenting capacity, first terminal reports, surviving Page recipients, and stale identities. Destroy an instance, preserve its sibling, then create and execute a fresh instance from the unchanged initializer.
309 +
Run hosted direct-call coverage for argument and authority errors, metadata, query errors, and execution/lifecycle effects. Keep the existing native, context, IRQ, activation, and lifecycle checks.
294 310
295 311
Run the context reservation probe with an emulator that retains LR/SC
296 312
reservations across traps. This checks the kernel's reservation invalidation.
kernel/check.rad +2 -0
13 13
mod timers;
14 14
mod notifications;
15 15
mod devices;
16 16
mod activation;
17 17
mod lifecycle;
18 +
mod calls;
18 19
19 20
/// Run the available kernel mechanism checks.
20 21
@default fn main() -> u32 {
21 22
    frames::run();
22 23
    boot::run();
30 31
    timers::run();
31 32
    notifications::run();
32 33
    devices::run();
33 34
    activation::run();
34 35
    lifecycle::run();
36 +
    calls::run();
35 37
    return 0;
36 38
}
kernel/check/calls.rad added +147 -0
1 +
//! Dispatcher boundary regressions using real capability and accounting mechanisms.
2 +
3 +
use core::abi;
4 +
use core::budget_caps;
5 +
use core::budgets;
6 +
use core::calls;
7 +
use core::capabilities;
8 +
use core::contexts;
9 +
use core::domains;
10 +
use core::fdt;
11 +
use core::frames;
12 +
use core::handles;
13 +
use core::lifecycle;
14 +
use core::memory;
15 +
use core::platform;
16 +
use core::resources;
17 +
use core::state;
18 +
19 +
static DOMAINS: [domains::Domain; 2] = undefined;
20 +
static OBJECTS: [resources::Slot; 2] = undefined;
21 +
static CONTEXTS: [contexts::Context; 1] = undefined;
22 +
static POOL: frames::Pool = undefined;
23 +
static PINS: [u16; 1] = undefined;
24 +
static ASSIGNED: [bool; 1] = undefined;
25 +
static GRANTS: [u64; 2] = undefined;
26 +
static KERNEL: state::State = undefined;
27 +
28 +
/// Exercise a returning operation through exactly the public seven-word input.
29 +
fn reply(caller: u32, number: u64, args: [u64; 7], now: u64) -> [u64; 4] {
30 +
    let result = try! calls::dispatch(&mut KERNEL, caller, number, &args[..], now);
31 +
    match result {
32 +
        case calls::Outcome::Reply { values } => return values,
33 +
        else => panic "reply: expected completed direct call",
34 +
    }
35 +
}
36 +
37 +
/// Observe a boundary rejection without substituting a lower-level helper.
38 +
fn reject(caller: u32, number: u64, args: [u64; 7], expected: abi::Error) {
39 +
    let _result = try calls::dispatch(&mut KERNEL, caller, number, &args[..], 100) catch error {
40 +
        assert error == expected;
41 +
        return;
42 +
    };
43 +
    panic "reject: invalid direct call succeeded";
44 +
}
45 +
46 +
/// Slot kinds, reserved bits, and MMIO widths/rights remain full-width inputs.
47 +
fn materialization(root: abi::Handle) {
48 +
    let device = try! resources::create(&mut OBJECTS[..], resources::Value::Device(fdt::Range { base: 0x1000, size: 8 }));
49 +
    resources::retain(&mut OBJECTS[..], device);
50 +
    let handle = handles::install(&mut DOMAINS[0].handles, 3, device, abi::READ);
51 +
    let readonly = reply(0, 10, [handle.bits, 0, 0, 0, 0, 0, 0], 0)[0];
52 +
    let info = reply(0, 45, [readonly, 0, 0, 0, 0, 0, 0], 0);
53 +
    assert info[0] == 0x1000 and info[1] == 8;
54 +
    reject(0, 61, [readonly, 0, 1, 1, 0, 0, 0], abi::Error::Denied);
55 +
    assert reply(0, 62, [3, 3, 0, 0, 0, 0, 0], 0)[0] == handle.bits;
56 +
    reject(0, 62, [3, 0x100000003, 0, 0, 0, 0, 0], abi::Error::BadHandle);
57 +
    reject(0, 62, [0x100000003, 3, 0, 0, 0, 0, 0], abi::Error::BadHandle);
58 +
    reject(0, 62, [3, 1, 0, 0, 0, 0, 0], abi::Error::BadHandle);
59 +
    reject(0, 45, [handle.bits | 0x1000000, 0, 0, 0, 0, 0, 0], abi::Error::BadHandle);
60 +
    reject(0, 61, [handle.bits, 0, 0x100000001, 1, 0, 0, 0], abi::Error::InvalidArg);
61 +
    reject(0, 61, [handle.bits, 0, 1, 0x10001, 0, 0, 0], abi::Error::InvalidArg);
62 +
    reject(0, 60, [root.bits, 0x10001, 0, 0, 0, 0, 0], abi::Error::InvalidArg);
63 +
    assert reply(0, 61, [handle.bits, 0, 8, 1, 0, 0, 0], 0)[0] == 0x1000;
64 +
    let _drop = reply(0, 12, [handle.bits, 0, 0, 0, 0, 0, 0], 0);
65 +
    let replacement = handles::install(&mut DOMAINS[0].handles, 3, device, abi::READ);
66 +
    resources::retain(&mut OBJECTS[..], device);
67 +
    reject(0, 45, [handle.bits, 0, 0, 0, 0, 0, 0], abi::Error::BadHandle);
68 +
    assert reply(0, 62, [3, 3, 0, 0, 0, 0, 0], 0)[0] == replacement.bits;
69 +
    // A valid slot generation is insufficient when its resource incarnation dies.
70 +
    set OBJECTS[device.index].epoch += 1;
71 +
    reject(0, 62, [3, 3, 0, 0, 0, 0, 0], abi::Error::BadHandle);
72 +
    set OBJECTS[device.index].epoch = device.epoch;
73 +
}
74 +
75 +
/// Running metadata is conservatively debited; Run keeps admission transactional.
76 +
fn execution(target: abi::Handle, child: abi::Object) {
77 +
    set DOMAINS[child.index].state = domains::Lifecycle::Active;
78 +
    try! contexts::prepare(&mut CONTEXTS[0], child, 0, 0, DOMAINS[child.index].env);
79 +
    let id = contexts::identity(&CONTEXTS[0]);
80 +
    set DOMAINS[child.index].env.context = id;
81 +
    let resource = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 1000)));
82 +
    resources::retain(&mut OBJECTS[..], resource);
83 +
    let handleSlot = try! handles::vacant(&DOMAINS[0].handles);
84 +
    let handle = handles::install(&mut DOMAINS[0].handles, handleSlot, resource, abi::EXECUTE);
85 +
    let _bound = reply(0, 71, [handle.bits, target.bits, id, 0, 0, 0, 0], 0);
86 +
    let budget = budget_caps::value(&mut KERNEL, resource);
87 +
    let running = try! contexts::begin(&mut CONTEXTS[0], budget, 100, 0);
88 +
    let query = reply(0, 74, [handle.bits, 0, 0, 0, 0, 0, 0], 300);
89 +
    assert query[1] == 800 and query[2] == id;
90 +
    let current = reply(0, 75, [target.bits, 0, 0, 0, 0, 0, 0], 300);
91 +
    assert current[0] == id and current[2] == contexts::Status::Running as u64 and current[3] == 800;
92 +
    assert reply(0, 74, [handle.bits, 0, 0, 0, 0, 0, 0], 1200)[1] == 0;
93 +
    reject(0, 73, [target.bits, id, 0, 0, 0, 0, 0], abi::Error::Busy);
94 +
    assert contexts::finish(&mut CONTEXTS[0], budget, running, 350) == 750;
95 +
    assert reply(0, 74, [handle.bits, 0, 0, 0, 0, 0, 0], 1000)[1] == 750;
96 +
    let _drop = reply(0, 12, [handle.bits, 0, 0, 0, 0, 0, 0], 1000);
97 +
    set CONTEXTS[0].status = contexts::Status::Stopped;
98 +
    let args = [target.bits, id, 0, 0, 0, 0, 0];
99 +
    match try! calls::dispatch(&mut KERNEL, 0, 73, &args[..], 1000) {
100 +
        case calls::Outcome::Run { context } => { assert context == id; },
101 +
        else => panic "execution: retained binding could not run",
102 +
    }
103 +
    assert CONTEXTS[0].status == contexts::Status::Stopped;
104 +
    set CONTEXTS[0].status = contexts::Status::Waiting;
105 +
    reject(0, 73, args, abi::Error::Busy);
106 +
    set CONTEXTS[0].status = contexts::Status::Stopped;
107 +
    reject(0, 75, [target.bits, id + (1 as u64 << 32), 0, 0, 0, 0, 0], abi::Error::InvalidArg);
108 +
}
109 +
110 +
/// Independent target-local administrative authority survives source death.
111 +
export fn run() {
112 +
    domains::init(&mut DOMAINS[..]);
113 +
    contexts::init(&mut CONTEXTS[0], 0);
114 +
    resources::init(&mut OBJECTS[..]);
115 +
    let mut machine: platform::Platform = undefined;
116 +
    set machine.memoryCount = 0;
117 +
    set machine.reservedCount = 0;
118 +
    try! frames::init(&mut POOL, &machine);
119 +
    let mut ram: memory::Memory = undefined;
120 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
121 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
122 +
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
123 +
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE);
124 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
125 +
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
126 +
    materialization(rootHandle);
127 +
    execution(target, child);
128 +
    reject(0, 76, [rootHandle.bits, target.bits, 0x100000020, 0, 0, 0, 0], abi::Error::InvalidArg);
129 +
    reject(0, 76, [rootHandle.bits, target.bits, abi::DESTROY as u64, 0, 0, 0, 0], abi::Error::InvalidArg);
130 +
    let createGrant = (abi::CREATE | abi::GRANT) as u64;
131 +
    let narrow = reply(0, 10, [rootHandle.bits, 0, createGrant, 0, 0, 0, 0], 0)[0];
132 +
    reject(0, 76, [narrow, target.bits, abi::ALLOCATE as u64, 0, 0, 0, 0], abi::Error::Denied);
133 +
    reject(0, 76, [target.bits, target.bits, abi::CREATE as u64, 0, 0, 0, 0], abi::Error::Denied);
134 +
    let delegated = reply(0, 76, [narrow, target.bits, createGrant, 0, 0, 0, 0], 0)[0];
135 +
    let local = abi::Handle { bits: delegated };
136 +
    let authority = try! capabilities::lookup(&KERNEL, child.index, local);
137 +
    assert authority.object.index == child.index and authority.object.epoch == child.epoch;
138 +
    assert authority.rights as u64 == createGrant;
139 +
    lifecycle::terminate(&mut KERNEL, root, 4, 0);
140 +
    assert lifecycle::poll(&mut KERNEL) == nil;
141 +
    assert DOMAINS[0].state == domains::Lifecycle::Dead;
142 +
    let survivor = try! domains::authority(&DOMAINS[..], child.index, local, abi::CREATE);
143 +
    assert survivor.index == child.index and survivor.epoch == child.epoch;
144 +
    let info = reply(child.index, 47, [local.bits, 0, 0, 0, 0, 0, 0], 0);
145 +
    assert info[0] == child.index as u64 and info[1] == root.index as u64;
146 +
    assert info[2] == abi::INVALID_DOMAIN as u64;
147 +
}
kernel/core.rad +1 -0
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;
kernel/core/calls.rad added +272 -0
1 +
//! Serialized direct-call control plane; architectural handoff belongs to runtime.
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::contexts;
9 +
use core::devices;
10 +
use core::domains;
11 +
use core::events;
12 +
use core::frames;
13 +
use core::handles;
14 +
use core::lifecycle;
15 +
use core::notifications;
16 +
use core::pages;
17 +
use core::physical;
18 +
use core::platform;
19 +
use core::resources;
20 +
use core::state;
21 +
22 +
/// A completed reply or an obligation the runtime must complete before replying.
23 +
/// Control outcomes never authorize touching another hart's architectural frame.
24 +
export union Outcome: Copy {
25 +
    /// Successful value words for a1..a4; runtime writes Error::Ok to a0.
26 +
    Reply { values: [u64; 4] },
27 +
    /// Relinquish execution through the current continuation/scheduler protocol.
28 +
    Yield,
29 +
    /// Validate Events progress and wait for an event with retained budget.
30 +
    Wait,
31 +
    /// Arm the current hart's bounded timer table before returning success.
32 +
    /// ticks is a monotonic delay; token is returned in the Timeout event.
33 +
    Timeout { ticks: u64, token: u32 },
34 +
    /// Record terminal cause and drive quiescence; never return to this domain.
35 +
    /// kind is Fault=3 or ChildExit=4; code is the first terminal status.
36 +
    Terminate { kind: u16, code: u32 },
37 +
    /// Snapshot the selected incarnations, then call lifecycle::request and wait
38 +
    /// for their reclamation. This raw request has not yet been validated.
39 +
    /// handle selects a Domain; flags bit zero includes creation descendants.
40 +
    Destroy { handle: abi::Handle, flags: u64 },
41 +
    /// Validated execution request. Runtime must admit the target hart before
42 +
    /// resuming a Stopped context or installing any continuation/handoff state.
43 +
    /// context is the incarnation-qualified execution identity.
44 +
    Run { context: u64 },
45 +
}
46 +
47 +
/// Resolve metadata authority without imposing extra Read rights.
48 +
fn object(kernel: *state::State, caller: u32, handle: abi::Handle, kind: abi::Kind) -> abi::Object throws (abi::Error) {
49 +
    let entry = try capabilities::lookup(kernel, caller, handle);
50 +
    if entry.object.kind <> kind { throw abi::Error::BadHandle; }
51 +
    return entry.object;
52 +
}
53 +
54 +
/// Match a context incarnation to an explicitly authorized domain identity.
55 +
/// Only QueryContext permits zero to select the domain's initial environment.
56 +
fn context(kernel: *state::State, owner: abi::Object, id: u64) -> u32 throws (abi::Error) {
57 +
    let index = try contexts::resolve(kernel.contexts, id);
58 +
    let actual = kernel.contexts[index].owner;
59 +
    if actual.kind <> abi::Kind::Domain or actual.index <> owner.index or actual.epoch <> owner.epoch {
60 +
        throw abi::Error::Denied;
61 +
    }
62 +
    return index;
63 +
}
64 +
65 +
/// Read accounting without touching frames or charging another hart's interval.
66 +
/// The deadline bounds a Running context's balance at the supplied monotonic now.
67 +
fn remaining(kernel: *state::State, identity: abi::Object, budget: *budgets::Budget, now: u64) -> u64 throws (abi::Error) {
68 +
    if budget.context == 0 { return budget.remaining; }
69 +
    let index = try contexts::resolve(kernel.contexts, budget.context);
70 +
    let bound = &kernel.contexts[index];
71 +
    if bound.budget.kind <> abi::Kind::Budget or bound.budget.index <> identity.index
72 +
        or bound.budget.epoch <> identity.epoch or bound.hart <> budget.hart {
73 +
        throw abi::Error::BadHandle;
74 +
    }
75 +
    if bound.status <> contexts::Status::Running { return budget.remaining; }
76 +
    if now >= bound.deadline { return 0; }
77 +
    let ticks = bound.deadline - now;
78 +
    if ticks < budget.remaining { return ticks; }
79 +
    return budget.remaining;
80 +
}
81 +
82 +
/// Read a retained binding, returning zero for a context without budget.
83 +
fn contextRemaining(kernel: *state::State, index: u32, now: u64) -> u64 throws (abi::Error) {
84 +
    let bound = &kernel.contexts[index];
85 +
    if bound.budget.kind == abi::Kind::Empty { return 0; }
86 +
    if bound.budget.kind <> abi::Kind::Budget
87 +
        or not resources::live(kernel.resources, kernel.domains, bound.budget) {
88 +
        throw abi::Error::BadHandle;
89 +
    }
90 +
    let case resources::Value::Budget(budget) = &kernel.resources[bound.budget.index].value else {
91 +
        panic "contextRemaining: budget kind mismatch";
92 +
    };
93 +
    if budget.context <> contexts::identity(bound) { throw abi::Error::BadHandle; }
94 +
    return try remaining(kernel, bound.budget, &budget, now);
95 +
}
96 +
97 +
/// Preflight execution without changing context status or remote admission state.
98 +
fn runnable(kernel: *state::State, caller: u32, target: abi::Handle, id: u64) throws (abi::Error) {
99 +
    let owner = try domains::resolve(kernel.domains, caller, target, abi::EXECUTE);
100 +
    let index = try context(kernel, owner, id);
101 +
    if kernel.domains[owner.index].state <> domains::Lifecycle::Active { throw abi::Error::InvalidArg; }
102 +
    let bound = &kernel.contexts[index];
103 +
    if bound.status <> contexts::Status::Ready and bound.status <> contexts::Status::Stopped {
104 +
        throw abi::Error::Busy;
105 +
    }
106 +
    if bound.budget.kind <> abi::Kind::Budget
107 +
        or not resources::live(kernel.resources, kernel.domains, bound.budget) {
108 +
        throw abi::Error::Denied;
109 +
    }
110 +
    let slot = &kernel.resources[bound.budget.index];
111 +
    let case resources::Value::Budget(budget) = &slot.value else { panic "runnable: budget kind mismatch"; };
112 +
    if slot.references == 0 or budget.context <> id or bound.hart <> budget.hart
113 +
        or bound.hart >= platform::MAX_HARTS { throw abi::Error::Denied; }
114 +
    if budget.remaining == 0 { throw abi::Error::Exhausted; }
115 +
}
116 +
117 +
/// Install independent administrative authority on target's own incarnation.
118 +
/// Only existing Create/Allocate/Grant/Transfer rights can be delegated; Grant
119 +
/// on source is mandatory. All checks, including destination capacity, precede
120 +
/// installation. No existing handle or source-domain lifetime is carried over.
121 +
fn delegate(kernel: *mut state::State, caller: u32, source: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) {
122 +
    let allowed = (abi::CREATE | abi::ALLOCATE | abi::GRANT | abi::TRANSFER) as u64;
123 +
    if rights & ~allowed <> 0 or rights & (abi::CREATE | abi::ALLOCATE) as u64 == 0 {
124 +
        throw abi::Error::InvalidArg;
125 +
    }
126 +
    let entry = try capabilities::lookup(kernel, caller, source);
127 +
    if entry.object.kind <> abi::Kind::Domain { throw abi::Error::BadHandle; }
128 +
    if entry.rights & abi::GRANT == 0 or entry.rights as u64 & rights <> rights {
129 +
        throw abi::Error::Denied;
130 +
    }
131 +
    let owner = try domains::resolve(kernel.domains, caller, target, 0);
132 +
    let slot = try handles::vacant(&kernel.domains[owner.index].handles);
133 +
    return handles::install(&mut kernel.domains[owner.index].handles, slot, owner, rights as u16);
134 +
}
135 +
136 +
/// Materialize an existing live slot only after checking full-width index/kind.
137 +
fn slot(kernel: *state::State, caller: u32, index: u64, kind: u64) -> abi::Handle throws (abi::Error) {
138 +
    if index >= abi::MAX_HANDLES as u64 { throw abi::Error::BadHandle; }
139 +
    let mut wanted = abi::Kind::Empty;
140 +
    match kind {
141 +
        case 1 => set wanted = abi::Kind::Page,
142 +
        case 2 => set wanted = abi::Kind::Interrupt,
143 +
        case 3 => set wanted = abi::Kind::Device,
144 +
        case 4 => set wanted = abi::Kind::Events,
145 +
        case 5 => set wanted = abi::Kind::Domain,
146 +
        case 6 => set wanted = abi::Kind::Budget,
147 +
        else => throw abi::Error::BadHandle,
148 +
    }
149 +
    let handle = try handles::get(&kernel.domains[caller].handles, index as u32, wanted);
150 +
    if not resources::live(kernel.resources, kernel.domains, kernel.domains[caller].handles.entries[index as u32].object) {
151 +
        throw abi::Error::BadHandle;
152 +
    }
153 +
    return handle;
154 +
}
155 +
156 +
/// Reject high bits before narrowing loader-requested Page or Device rights.
157 +
fn accessRights(bits: u64) -> u16 throws (abi::Error) {
158 +
    if bits == 0 or bits & ~((abi::READ | abi::WRITE) as u64) <> 0 {
159 +
        throw abi::Error::InvalidArg;
160 +
    }
161 +
    return bits as u16;
162 +
}
163 +
164 +
/// Dispatch a7 using the seven a0..a6 input words while holding the global lock.
165 +
/// caller is a live domain index; arguments has at least seven words. now is a
166 +
/// monotonic CLINT timestamp used only to conservatively query running budgets.
167 +
/// Returning effects finish here. Runtime completes control obligations before
168 +
/// replying, advances returning ecalls, and faults query/slot errors (44..48,
169 +
/// 62, 74, 75) and undefined calls rather than exposing their error return.
170 +
/// Calls 60/61/62 support trusted runtime wrappers: Page bounds, one transient
171 +
/// Device access, and a typed existing-slot lookup. RIL verification is deferred.
172 +
export fn dispatch(kernel: *mut state::State, caller: u32, number: u64, arguments: *[u64], now: u64) -> Outcome throws (abi::Error) {
173 +
    assert caller < kernel.domains.len and arguments.len >= 7;
174 +
    let first = abi::Handle { bits: arguments[0] };
175 +
    let second = abi::Handle { bits: arguments[1] };
176 +
    let mut values: [u64; 4] = [0; 4];
177 +
    match number {
178 +
        case 10 => set values[0] = (try capabilities::grant(kernel, caller, first, second, arguments[2])).bits,
179 +
        case 11 => set values[0] = (try capabilities::transfer(kernel, caller, first, second, arguments[2])).bits,
180 +
        case 12 => try capabilities::drop(kernel, caller, first),
181 +
        case 20 => set values[0] = (try activation::create(kernel, caller, first, arguments[1], physical::clear, physical::copy)).bits,
182 +
        case 21 => {
183 +
            let _initial = try activation::activate(kernel, caller, first, arguments[1], arguments[2], arguments[3], physical::clear);
184 +
        },
185 +
        case 22 => return Outcome::Destroy { handle: first, flags: arguments[1] },
186 +
        case 23 => try lifecycle::reparent(kernel, caller, first, second),
187 +
        case 30 => set values[0] = (try pages::allocate(kernel, caller, first, arguments[1], physical::clear)).bits,
188 +
        case 31 => set values[0] = (try pages::split(kernel, caller, first, arguments[1])).bits,
189 +
        case 40 => return Outcome::Yield,
190 +
        case 41 => {
191 +
            if arguments[1] > 0xffffffff { throw abi::Error::InvalidArg; }
192 +
            return Outcome::Timeout { ticks: arguments[0], token: arguments[1] as u32 };
193 +
        },
194 +
        case 42 => return Outcome::Wait,
195 +
        case 43 => return Outcome::Terminate { kind: 3, code: 0x100 },
196 +
        case 44 => {
197 +
            let range = try pages::access(kernel, caller, first, 0);
198 +
            set values[0] = range.base;
199 +
            set values[1] = range.size / frames::PAGE_SIZE as u64;
200 +
        },
201 +
        case 45 => {
202 +
            let identity = try object(kernel, caller, first, abi::Kind::Device);
203 +
            let case resources::Value::Device(region) = kernel.resources[identity.index].value else { panic "dispatch: device kind mismatch"; };
204 +
            set values[0] = region.base;
205 +
            set values[1] = region.size;
206 +
        },
207 +
        case 46 => {
208 +
            let identity = try object(kernel, caller, first, abi::Kind::Interrupt);
209 +
            let case resources::Value::Interrupt(irq) = kernel.resources[identity.index].value else { panic "dispatch: interrupt kind mismatch"; };
210 +
            set values[0] = irq.number as u64;
211 +
        },
212 +
        case 47 => {
213 +
            let identity = try object(kernel, caller, first, abi::Kind::Domain);
214 +
            let domain = &kernel.domains[identity.index];
215 +
            set values[0] = identity.index as u64;
216 +
            set values[1] = domain.creator.index as u64;
217 +
            set values[2] = abi::INVALID_DOMAIN as u64;
218 +
            if domains::live(kernel.domains, domain.parent) { set values[2] = domain.parent.index as u64; }
219 +
            set values[3] = domain.state as u64;
220 +
        },
221 +
        case 48 => {
222 +
            let _identity = try object(kernel, caller, first, abi::Kind::Events);
223 +
            set values[0] = events::CAPACITY as u64;
224 +
        },
225 +
        case 49 => {
226 +
            if arguments[0] > 0xffffffff { throw abi::Error::InvalidArg; }
227 +
            return Outcome::Terminate { kind: 4, code: arguments[0] as u32 };
228 +
        },
229 +
        case 50 => {
230 +
            if arguments[1] > 0xffffffff { throw abi::Error::InvalidArg; }
231 +
            try notifications::wakeup(kernel, caller, first, arguments[1] as u32);
232 +
        },
233 +
        case 60 => {
234 +
            let rights = try accessRights(arguments[1]);
235 +
            let range = try pages::access(kernel, caller, first, rights);
236 +
            set values[0] = range.base;
237 +
            set values[1] = range.size;
238 +
        },
239 +
        case 61 => {
240 +
            let rights = try accessRights(arguments[3]);
241 +
            set values[0] = try devices::access(kernel, caller, first, arguments[1], arguments[2], rights);
242 +
        },
243 +
        case 62 => set values[0] = (try slot(kernel, caller, arguments[0], arguments[1])).bits,
244 +
        case 70 => set values[0] = (try budget_caps::split(kernel, caller, first, arguments[1])).bits,
245 +
        case 71 => try budget_caps::bind(kernel, caller, first, second, arguments[2]),
246 +
        case 72 => set values[0] = try activation::context(kernel, caller, first, arguments[1], arguments[2], arguments[3], physical::clear),
247 +
        case 73 => {
248 +
            try runnable(kernel, caller, first, arguments[1]);
249 +
            return Outcome::Run { context: arguments[1] };
250 +
        },
251 +
        case 74 => {
252 +
            let identity = try object(kernel, caller, first, abi::Kind::Budget);
253 +
            let case resources::Value::Budget(budget) = &kernel.resources[identity.index].value else { panic "dispatch: budget kind mismatch"; };
254 +
            set values[0] = budget.hart as u64;
255 +
            set values[1] = try remaining(kernel, identity, &budget, now);
256 +
            set values[2] = budget.context;
257 +
        },
258 +
        case 75 => {
259 +
            let owner = try domains::resolve(kernel.domains, caller, first, abi::EXECUTE);
260 +
            let mut id = arguments[1];
261 +
            if id == 0 { set id = kernel.domains[owner.index].env.context; }
262 +
            let index = try context(kernel, owner, id);
263 +
            set values[0] = id;
264 +
            set values[1] = kernel.contexts[index].hart as u64;
265 +
            set values[2] = kernel.contexts[index].status as u64;
266 +
            set values[3] = try contextRemaining(kernel, index, now);
267 +
        },
268 +
        case 76 => set values[0] = (try delegate(kernel, caller, first, second, arguments[2])).bits,
269 +
        else => throw abi::Error::InvalidArg,
270 +
    }
271 +
    return Outcome::Reply { values };
272 +
}
kernel/native.rad +13 -18
1 1
//! Real user-mode execution of admitted graphs and their memory-safety checks.
2 2
3 3
use core::abi;
4 4
use core::atomic;
5 +
use core::calls;
5 6
use core::cpu;
6 7
use core::contexts;
7 8
use core::domains;
8 9
use core::events;
9 10
use core::fdt;
72 73
            assert frame.cause == 3;
73 74
            break;
74 75
        }
75 76
        assert frame.cause == 8;
76 77
        let args = &frame.registers;
77 -
        match args[17] {
78 -
            case 30 => {
79 -
                let page = try! pages::allocate(&mut KERNEL, 0, abi::Handle { bits: args[10] }, args[11], physical::clear);
80 -
                cpu::reply(&mut frame, abi::Error::Ok, page.bits, 0, 0, 0);
78 +
        match try! calls::dispatch(&mut KERNEL, 0, args[17], &args[10..17], 0) {
79 +
            case calls::Outcome::Reply { values } => {
80 +
                cpu::reply(&mut frame, abi::Error::Ok, values[0], values[1], values[2], values[3]);
81 81
            },
82 -
            case 44 => {
83 -
                let area = try! pages::access(&KERNEL, 0, abi::Handle { bits: args[10] }, 0);
84 -
                cpu::reply(&mut frame, abi::Error::Ok, area.base, area.size / frames::PAGE_SIZE as u64, 0, 0);
85 -
            },
86 -
            case 60 => {
87 -
                assert args[11] <= 3;
88 -
                let area = try! pages::access(&KERNEL, 0, abi::Handle { bits: args[10] }, args[11] as u16);
89 -
                cpu::reply(&mut frame, abi::Error::Ok, area.base, area.size, 0, 0);
90 -
            },
91 -
            case 49 => {
92 -
                assert args[10] == expected as u64;
82 +
            case calls::Outcome::Terminate { kind, code } => {
83 +
                assert kind == 4 and code == expected;
93 84
                break;
94 85
            },
95 -
            else => panic "native: unexpected kernel call",
86 +
            else => panic "native: unexpected control effect",
96 87
        }
97 88
    }
89 +
    if image == 0 {
90 +
        assert *(physical::bytes(dataBase, 8).ptr as *u64) == 42;
91 +
        assert *(physical::bytes(descriptor.initial, 8).ptr as *u64) == 0;
92 +
    }
98 93
    if image == 5 {
99 94
        assert atomic::load(&DOMAINS[0].events.ring.head) == atomic::load(&DOMAINS[0].events.ring.tail);
100 95
    }
101 96
    frames::reclaim(&mut POOL, private);
102 97
    frames::reclaim(&mut POOL, data);
103 98
}
104 99
105 100
/// Check graph linking, native control flow, bounded memory, and protected spills.
106 101
@default unsafe fn main(hart: u64, description: *u8) -> u32 {
107 -
    assert hart == 0 and images::COUNT == 8;
102 +
    assert hart == 0;
108 103
    let size = try! fdt::word(@sliceOf(description, 40), 4);
109 104
    assert size >= 40 and size <= fdt::MAX_BYTES;
110 105
    let mut tree: fdt::Tree = undefined;
111 106
    try! fdt::decode(@sliceOf(description, size), &mut tree);
112 107
    let mut machine: platform::Platform = undefined;
122 117
    let expected: [u32; 7] = [42, 21, 73, 77, 0, 42, 81];
123 118
    for i in 0..expected.len { run(i, expected[i], own, i == 5); }
124 119
    run(5, 42, own, false);
125 120
    instances::run(&mut KERNEL, own, machine.clint.base);
126 121
    return 0;
127 -
}
122 +
}
kernel/user/probe.rad +118 -1
1 -
//! Shared argument and Page views for source image probes.
1 +
//! Shared assertions and explicit capability setup for the control workloads.
2 2
use abi;
3 3
use user::sys;
4 4
5 +
/// Abort unless the two values are equal.
6 +
export fn equal(actual: u64, expected: u64) { if actual <> expected { sys::abort(); } }
7 +
/// Abort if the value is zero.
8 +
export fn positive(value: u64) { if value == 0 { sys::abort(); } }
5 9
/// Decode a caller-local handle supplied in the workload arguments.
6 10
export fn handle(bits: u64) -> abi::Handle { return abi::Handle { bits }; }
7 11
/// View aligned startup arguments as complete u64 words.
8 12
export fn args(env: *sys::Env) -> *[u64] {
9 13
    let bytes = sys::envArgs(env);
16 20
    let info = sys::queryPage(page);
17 21
    assert info.count <= 0xffffffff / 4096;
18 22
    let bytes = try! sys::pageSliceMut(page, 0, info.count * 4096);
19 23
    return @sliceOf(bytes.ptr as *mut u64, bytes.len / 8);
20 24
}
25 +
/// Allocate the requested contiguous frames or fail the workload.
26 +
export fn allocate(authority: abi::Handle, count: u64) -> abi::Handle {
27 +
    return try! sys::pageAllocate(authority, count);
28 +
}
29 +
/// Create a domain and verify its pending lifecycle.
30 +
export fn create(authority: abi::Handle, image: u64) -> abi::Handle {
31 +
    let child = try! sys::domainCreate(authority, image);
32 +
    assert sys::queryDomain(child).state == sys::DomainLifecycle::Pending;
33 +
    return child;
34 +
}
35 +
/// Read a domain's public identifier.
36 +
export fn id(domain: abi::Handle) -> u16 { return sys::queryDomain(domain).id; }
37 +
/// Install attenuated authority in the target domain.
38 +
export fn grant(source: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle {
39 +
    return try! sys::capabilityGrant(source, target, rights);
40 +
}
41 +
/// Grant a writable stack and readable arguments before context creation.
42 +
fn stack(authority: abi::Handle, target: abi::Handle, args: abi::Handle) -> u64 {
43 +
    let page = allocate(authority, 2);
44 +
    let _stack = grant(page, target, 3);
45 +
    let _args = grant(args, target, 1);
46 +
    let top = sys::queryPage(page).base + 8192;
47 +
    try! sys::capabilityDrop(page);
48 +
    return top;
49 +
}
50 +
/// Activate the first context and verify that it has no CPU budget.
51 +
export fn activate(authority: abi::Handle, target: abi::Handle, args: abi::Handle, size: u32) -> u64 {
52 +
    let top = stack(authority, target, args);
53 +
    try! sys::domainActivate(target, top, try! sys::pageSlice(args, 0, size));
54 +
    let info = sys::queryContext(target, 0);
55 +
    positive(info.id);
56 +
    equal(info.hart, 0xffffffff);
57 +
    equal(info.status, 1);
58 +
    equal(info.remaining, 0);
59 +
    return info.id;
60 +
}
61 +
/// Create another context with its own authorized stack.
62 +
export fn context(authority: abi::Handle, target: abi::Handle, args: abi::Handle, size: u32) -> u64 {
63 +
    let top = stack(authority, target, args);
64 +
    let context = try! sys::contextCreate(target, top, try! sys::pageSlice(args, 0, size));
65 +
    positive(context);
66 +
    return context;
67 +
}
68 +
/// Bind a new budget and verify conservation and hart assignment.
69 +
export fn fund(reserve: abi::Handle, target: abi::Handle, context: u64, ticks: u64) -> abi::Handle {
70 +
    let before = sys::queryBudget(reserve);
71 +
    equal(before.context, 0);
72 +
    let budget = try! sys::budgetSplit(reserve, ticks);
73 +
    equal(before.remaining - sys::queryBudget(reserve).remaining, ticks);
74 +
    let split = sys::queryBudget(budget);
75 +
    equal(split.hart, before.hart);
76 +
    equal(split.remaining, ticks);
77 +
    equal(split.context, 0);
78 +
    try! sys::budgetBind(budget, target, context);
79 +
    equal(sys::queryBudget(budget).context, context);
80 +
    equal(sys::queryContext(target, context).hart, before.hart);
81 +
    return budget;
82 +
}
83 +
/// Admit an explicitly authorized context for execution.
84 +
export fn run(target: abi::Handle, context: u64) { try! sys::contextRun(target, context); }
85 +
/// Verify the state of an incarnation-qualified context.
86 +
export fn status(target: abi::Handle, context: u64, expected: u64) {
87 +
    let info = sys::queryContext(target, context);
88 +
    equal(info.id, context);
89 +
    equal(info.status, expected);
90 +
}
91 +
/// Wait for and consume exactly the expected next notification.
92 +
export fn event(env: *sys::Env, kind: u16, code: u32, value: u64) {
93 +
    loop {
94 +
        if let event = sys::eventsPop(env) {
95 +
            equal(event.kind as u64, kind as u64);
96 +
            equal(event.code as u64, code as u64);
97 +
            equal(event.value, value);
98 +
            return;
99 +
        }
100 +
        try! sys::wait();
101 +
    }
102 +
}
103 +
/// Fail if an unexpected notification is pending.
104 +
export fn empty(env: *sys::Env) {
105 +
    if let _event = sys::eventsPop(env) { sys::abort(); }
106 +
}
107 +
/// Verify that insufficient Allocate authority is denied.
108 +
export fn allocationDenied(authority: abi::Handle) {
109 +
    let _page = try sys::pageAllocate(authority, 1) catch error {
110 +
        assert error == abi::Error::Denied;
111 +
        return;
112 +
    };
113 +
    sys::abort();
114 +
}
115 +
/// Verify that a stale handle cannot be dropped again.
116 +
export fn staleDrop(handle: abi::Handle) {
117 +
    try sys::capabilityDrop(handle) catch error {
118 +
        assert error == abi::Error::BadHandle;
119 +
        return;
120 +
    };
121 +
    sys::abort();
122 +
}
123 +
/// Verify that an exhausted context cannot resume.
124 +
export fn exhausted(target: abi::Handle, context: u64) {
125 +
    try sys::contextRun(target, context) catch error {
126 +
        assert error == abi::Error::Exhausted;
127 +
        return;
128 +
    };
129 +
    sys::abort();
130 +
}
131 +
/// Verify creation ancestry, notification parent, and lifecycle.
132 +
export fn relationship(child: abi::Handle, creator: u16, parent: u16, state: sys::DomainLifecycle) {
133 +
    let info = sys::queryDomain(child);
134 +
    assert info.creator == creator;
135 +
    assert info.parent == parent;
136 +
    assert info.state == state;
137 +
}
kernel/user/sys.rad +278 -0
54 54
export fn storeRelease(address: *mut u32, value: u32);
55 55
/// Exchange the private consumer lock; never expose producer state as an API.
56 56
fn lockAcquire(address: *mut u32) -> u32;
57 57
/// Release the shared consumer lock after copying an event.
58 58
fn lockRelease(address: *mut u32);
59 +
/// One naturally aligned access with full memory/I/O fences on both sides.
60 +
unsafe fn read8(address: u64) -> u8;
61 +
/// Read one aligned 16-bit device register with memory/I/O fences.
62 +
unsafe fn read16(address: u64) -> u16;
63 +
/// Read one aligned 32-bit device register with memory/I/O fences.
64 +
unsafe fn read32(address: u64) -> u32;
65 +
/// Read one aligned 64-bit device register with memory/I/O fences.
66 +
unsafe fn read64(address: u64) -> u64;
67 +
/// Write one 8-bit device register with memory/I/O fences.
68 +
unsafe fn write8(address: u64, value: u8);
69 +
/// Write one aligned 16-bit device register with memory/I/O fences.
70 +
unsafe fn write16(address: u64, value: u16);
71 +
/// Write one aligned 32-bit device register with memory/I/O fences.
72 +
unsafe fn write32(address: u64, value: u32);
73 +
/// Write one aligned 64-bit device register with memory/I/O fences.
74 +
unsafe fn write64(address: u64, value: u64);
59 75
60 76
/// All input registers are initialized, including unused argument words.
61 77
fn invoke(number: u64, a: u64, b: u64, c: u64, d: u64) -> Reply {
62 78
    let arguments = Arguments { registers: [a, b, c, d, 0, 0, 0] };
63 79
    let mut reply: Reply = undefined;
79 95
        case 8 => throw abi::Error::Exhausted,
80 96
        else => panic "sys: unknown ABI error",
81 97
    }
82 98
}
83 99
100 +
/// Copy attenuated authority into target's table; result is target-local.
101 +
/// Kernel rules decide which resource kinds may be delegated.
102 +
export fn capabilityGrant(source: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) {
103 +
    let reply = invoke(10, source.bits, target.bits, rights, 0);
104 +
    try check(reply.error);
105 +
    return abi::Handle { bits: reply.values[0] };
106 +
}
107 +
108 +
/// Move attenuated authority into target's table. Failure leaves source live.
109 +
export fn capabilityTransfer(source: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) {
110 +
    let reply = invoke(11, source.bits, target.bits, rights, 0);
111 +
    try check(reply.error);
112 +
    return abi::Handle { bits: reply.values[0] };
113 +
}
114 +
115 +
/// Drop only this local capability, not another domain's copy or its object.
116 +
export fn capabilityDrop(handle: abi::Handle) throws (abi::Error) {
117 +
    try check(invoke(12, handle.bits, 0, 0, 0).error);
118 +
}
119 +
120 +
/// Create a pending domain using explicit Create authority and a catalog index.
121 +
/// image is a u64 image-catalog index, not a handle or a source-code address.
122 +
export fn domainCreate(authority: abi::Handle, image: u64) -> abi::Handle throws (abi::Error) {
123 +
    let reply = invoke(20, authority.bits, image, 0, 0);
124 +
    try check(reply.error);
125 +
    return abi::Handle { bits: reply.values[0] };
126 +
}
127 +
128 +
/// Activate at the admitted entry; args is the exact readable byte range.
129 +
/// The kernel checks Page authority and stack ownership. Pass args.ptr, NEVER
130 +
/// the address of its fat-slice descriptor. Subslice first to pass fewer bytes.
131 +
export fn domainActivate(domain: abi::Handle, stackTop: u64, args: *[u8]) throws (abi::Error) {
132 +
    try check(invoke(21, domain.bits, stackTop, args.ptr as u64, args.len as u64).error);
133 +
}
134 +
135 +
/// Destroy only target (flags=0), or its creation descendants too (flags=1).
136 +
/// Self destruction never returns and never implies machine shutdown.
137 +
export fn domainDestroy(domain: abi::Handle, flags: u64) throws (abi::Error) {
138 +
    try check(invoke(22, domain.bits, flags, 0, 0).error);
139 +
}
140 +
141 +
/// Move the child's terminal-notification parent, subject to kernel authority.
142 +
export fn domainReparent(domain: abi::Handle, parent: abi::Handle) throws (abi::Error) {
143 +
    try check(invoke(23, domain.bits, parent.bits, 0, 0).error);
144 +
}
145 +
146 +
/// Delegate independent target-self Create/Allocate authority; result is local
147 +
/// to target. Source needs Grant and every bit. Only Create, Allocate, Grant,
148 +
/// Transfer are accepted, including at least Create or Allocate.
149 +
export fn delegate(source: abi::Handle, target: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error) {
150 +
    let reply = invoke(76, source.bits, target.bits, rights, 0);
151 +
    try check(reply.error);
152 +
    return abi::Handle { bits: reply.values[0] };
153 +
}
154 +
84 155
/// Allocate count contiguous zeroed frames using explicit Allocate authority.
85 156
export fn pageAllocate(authority: abi::Handle, count: u64) -> abi::Handle throws (abi::Error) {
86 157
    let reply = invoke(30, authority.bits, count, 0, 0);
87 158
    try check(reply.error);
88 159
    return abi::Handle { bits: reply.values[0] };
89 160
}
90 161
162 +
/// Retain leftCount frames in source and return its exclusive remainder.
163 +
export fn pageSplit(source: abi::Handle, leftCount: u64) -> abi::Handle throws (abi::Error) {
164 +
    let reply = invoke(31, source.bits, leftCount, 0, 0);
165 +
    try check(reply.error);
166 +
    return abi::Handle { bits: reply.values[0] };
167 +
}
168 +
169 +
/// Relinquish execution; scheduling policy and finite budget remain unchanged.
170 +
export fn yield() throws (abi::Error) { try check(invoke(40, 0, 0, 0, 0).error); }
171 +
/// Arm one bounded timer with a u32 token, without allocating execution budget.
172 +
export fn timeout(delay: u64, token: u32) throws (abi::Error) {
173 +
    try check(invoke(41, delay, token as u64, 0, 0).error);
174 +
}
175 +
/// Wait for Events with retained valid budget. There is no timeout argument.
176 +
export fn wait() throws (abi::Error) { try check(invoke(42, 0, 0, 0, 0).error); }
177 +
/// Terminate abnormally. A returned terminal call is a fatal ABI violation.
178 +
export fn abort() {
179 +
    let _reply = invoke(43, 0, 0, 0, 0);
180 +
    panic "sys: abort returned";
181 +
}
182 +
/// Terminate normally with a parent-visible u32 status; never shut down hardware.
183 +
export fn exit(status: u32) {
184 +
    let _reply = invoke(49, status as u64, 0, 0, 0);
185 +
    panic "sys: exit returned";
186 +
}
187 +
/// Append a wakeup token using Wake authority; never transfer or mint ticks.
188 +
export fn wakeup(domain: abi::Handle, token: u32) throws (abi::Error) {
189 +
    try check(invoke(50, domain.bits, token as u64, 0, 0).error);
190 +
}
191 +
91 192
/// Page metadata only; base alone is not dereferenceable authority.
92 193
export record PageInfo: Copy {
93 194
    /// Physical address of the first frame.
94 195
    base: u64,
95 196
    /// Number of contiguous 4 KiB frames.
96 197
    count: u32,
97 198
}
199 +
/// Device metadata only; use the one-shot deviceRead/Write operations for I/O.
200 +
export record DeviceInfo: Copy {
201 +
    /// Physical address of the first device byte.
202 +
    base: u64,
203 +
    /// Number of device bytes.
204 +
    size: u64,
205 +
}
206 +
/// Hardware interrupt source number.
207 +
export record InterruptInfo: Copy {
208 +
    /// PLIC source number.
209 +
    number: u32,
210 +
}
211 +
/// Public protection-domain lifecycle.
212 +
export union DomainLifecycle: Copy {
213 +
    /// Domain exists and is ready to be activated.
214 +
    Pending,
215 +
    /// Domain exists and may be scheduled by the kernel.
216 +
    Active,
217 +
    /// Domain was destroyed and cannot be scheduled or activated.
218 +
    Dead,
219 +
}
220 +
/// Domain identity and lifecycle; parent=abi::INVALID_DOMAIN means no live parent.
221 +
export record DomainInfo: Copy {
222 +
    /// Public domain identifier.
223 +
    id: u16,
224 +
    /// Historical creator identifier.
225 +
    creator: u16,
226 +
    /// Current terminal-event receiver, or abi::INVALID_DOMAIN.
227 +
    parent: u16,
228 +
    /// Public lifecycle state.
229 +
    state: DomainLifecycle,
230 +
}
231 +
/// Shared ring capacity, without an additional pointer or capability.
232 +
export record EventsInfo: Copy {
233 +
    /// Maximum number of unread ring entries.
234 +
    capacity: u32,
235 +
}
236 +
/// Finite conservative balance; context=0 means unbound.
237 +
export record BudgetInfo: Copy {
238 +
    /// Hardware hart authorized by this budget.
239 +
    hart: u64,
240 +
    /// Conservative remaining timer ticks.
241 +
    remaining: u64,
242 +
    /// Bound context identity, or zero.
243 +
    context: u64,
244 +
}
245 +
/// Exact incarnation; unbound hart=0xffffffff. Status: Ready=1, Running=2,
246 +
/// Waiting=3, Stopped=4. remaining is a conservative finite balance.
247 +
export record ContextInfo: Copy {
248 +
    /// Incarnation-qualified context identity.
249 +
    id: u64,
250 +
    /// Bound hardware hart, or 0xffffffff.
251 +
    hart: u64,
252 +
    /// Ready=1, Running=2, Waiting=3, Stopped=4.
253 +
    status: u64,
254 +
    /// Conservative remaining timer ticks.
255 +
    remaining: u64,
256 +
}
98 257
99 258
/// Invalid metadata queries fault the domain in the kernel, never return Error.
100 259
export fn queryPage(handle: abi::Handle) -> PageInfo {
101 260
    let reply = invoke(44, handle.bits, 0, 0, 0);
102 261
    return PageInfo { base: reply.values[0], count: reply.values[1] as u32 };
103 262
}
263 +
/// Query a Device without requiring Read or exposing access authority.
264 +
export fn queryDevice(handle: abi::Handle) -> DeviceInfo {
265 +
    let reply = invoke(45, handle.bits, 0, 0, 0);
266 +
    return DeviceInfo { base: reply.values[0], size: reply.values[1] };
267 +
}
268 +
/// Query an Interrupt; invalid handles fault the calling domain.
269 +
export fn queryInterrupt(handle: abi::Handle) -> InterruptInfo {
270 +
    let reply = invoke(46, handle.bits, 0, 0, 0);
271 +
    return InterruptInfo { number: reply.values[0] as u32 };
272 +
}
273 +
/// Query a Domain; invalid handles fault the calling domain.
274 +
export fn queryDomain(handle: abi::Handle) -> DomainInfo {
275 +
    let reply = invoke(47, handle.bits, 0, 0, 0);
276 +
    let mut state: DomainLifecycle = undefined;
277 +
    match reply.values[3] {
278 +
        case 0 => set state = DomainLifecycle::Pending,
279 +
        case 1 => set state = DomainLifecycle::Active,
280 +
        case 2 => set state = DomainLifecycle::Dead,
281 +
        else => panic "sys: unknown domain lifecycle",
282 +
    }
283 +
    return DomainInfo { id: reply.values[0] as u16, creator: reply.values[1] as u16, parent: reply.values[2] as u16, state };
284 +
}
285 +
/// Query Events shape; invalid handles fault the calling domain.
286 +
export fn queryEvents(handle: abi::Handle) -> EventsInfo {
287 +
    let reply = invoke(48, handle.bits, 0, 0, 0);
288 +
    return EventsInfo { capacity: reply.values[0] as u32 };
289 +
}
290 +
/// Query bounded CPU authority without refreshing or transferring it.
291 +
export fn queryBudget(handle: abi::Handle) -> BudgetInfo {
292 +
    let reply = invoke(74, handle.bits, 0, 0, 0);
293 +
    return BudgetInfo { hart: reply.values[0], remaining: reply.values[1], context: reply.values[2] };
294 +
}
295 +
/// Query an owned context using Execute authority; context=0 selects initial Env.
296 +
export fn queryContext(domain: abi::Handle, context: u64) -> ContextInfo {
297 +
    let reply = invoke(75, domain.bits, context, 0, 0);
298 +
    return ContextInfo { id: reply.values[0], hart: reply.values[1], status: reply.values[2], remaining: reply.values[3] };
299 +
}
300 +
301 +
/// Split ticks from an unbound finite budget; never create additional time.
302 +
export fn budgetSplit(source: abi::Handle, ticks: u64) -> abi::Handle throws (abi::Error) {
303 +
    let reply = invoke(70, source.bits, ticks, 0, 0);
304 +
    try check(reply.error);
305 +
    return abi::Handle { bits: reply.values[0] };
306 +
}
307 +
/// Bind the exact context using Execute authority on budget and domain.
308 +
export fn budgetBind(budget: abi::Handle, domain: abi::Handle, context: u64) throws (abi::Error) {
309 +
    try check(invoke(71, budget.bits, domain.bits, context, 0).error);
310 +
}
311 +
/// Create another admitted-entry context. Arguments carry their real byte extent,
312 +
/// just as in domainActivate; no implicit budget or scheduling policy is added.
313 +
export fn contextCreate(domain: abi::Handle, stackTop: u64, args: *[u8]) -> u64 throws (abi::Error) {
314 +
    let reply = invoke(72, domain.bits, stackTop, args.ptr as u64, args.len as u64);
315 +
    try check(reply.error);
316 +
    return reply.values[0];
317 +
}
318 +
/// Run a Ready/Stopped context with positive retained matching-hart budget.
319 +
export fn contextRun(domain: abi::Handle, context: u64) throws (abi::Error) {
320 +
    try check(invoke(73, domain.bits, context, 0, 0).error);
321 +
}
104 322
105 323
/// Materialize a checked byte subrange, never a metadata-derived arbitrary pointer.
106 324
/// Subtraction-based checks avoid overflow before any pointer is constructed.
107 325
fn pageRange(handle: abi::Handle, offset: u64, size: u32, rights: u16) -> u64 throws (abi::Error) {
108 326
    let reply = invoke(60, handle.bits, rights as u64, 0, 0);
116 334
    let address = base + offset;
117 335
    if address > 0xffffffffffffffff - size as u64 { throw abi::Error::InvalidArg; }
118 336
    return address;
119 337
}
120 338
339 +
/// Read-only Page bytes. size is u32 because Radiance slice lengths are u32.
340 +
/// The handle must carry Read; offset and the entire byte extent are checked.
341 +
export fn pageSlice(handle: abi::Handle, offset: u64, size: u32) -> *[u8] throws (abi::Error) {
342 +
    let address = try pageRange(handle, offset, size, abi::READ);
343 +
    return @sliceOf(pointer(address) as *u8, size);
344 +
}
121 345
/// Mutable slices permit both loads and stores, therefore require Read AND Write.
122 346
export fn pageSliceMut(handle: abi::Handle, offset: u64, size: u32) -> *mut [u8] throws (abi::Error) {
123 347
    let address = try pageRange(handle, offset, size, abi::READ | abi::WRITE);
124 348
    return @sliceOf(pointer(address) as *mut u8, size);
125 349
}
126 350
351 +
/// Validate one access: Device kind, requested rights, width, range and alignment.
352 +
/// Keep the address private and consume it immediately in exactly one MMIO access.
353 +
fn deviceAddress(handle: abi::Handle, offset: u64, width: u64, rights: u16) -> u64 throws (abi::Error) {
354 +
    let reply = invoke(61, handle.bits, offset, width, rights as u64);
355 +
    try check(reply.error);
356 +
    return reply.values[0];
357 +
}
358 +
/// Read one checked, ordered 8-bit device register.
359 +
export fn deviceRead8(handle: abi::Handle, offset: u64) -> u8 throws (abi::Error) {
360 +
    return read8(try deviceAddress(handle, offset, 1, abi::READ));
361 +
}
362 +
/// Read one checked, ordered 16-bit device register.
363 +
export fn deviceRead16(handle: abi::Handle, offset: u64) -> u16 throws (abi::Error) {
364 +
    return read16(try deviceAddress(handle, offset, 2, abi::READ));
365 +
}
366 +
/// Read one checked, ordered 32-bit device register.
367 +
export fn deviceRead32(handle: abi::Handle, offset: u64) -> u32 throws (abi::Error) {
368 +
    return read32(try deviceAddress(handle, offset, 4, abi::READ));
369 +
}
370 +
/// Read one checked, ordered 64-bit device register.
371 +
export fn deviceRead64(handle: abi::Handle, offset: u64) -> u64 throws (abi::Error) {
372 +
    return read64(try deviceAddress(handle, offset, 8, abi::READ));
373 +
}
374 +
/// Write one checked, ordered 8-bit device register.
375 +
export fn deviceWrite8(handle: abi::Handle, offset: u64, value: u8) throws (abi::Error) {
376 +
    write8(try deviceAddress(handle, offset, 1, abi::WRITE), value);
377 +
}
378 +
/// Write one checked, ordered 16-bit device register.
379 +
export fn deviceWrite16(handle: abi::Handle, offset: u64, value: u16) throws (abi::Error) {
380 +
    write16(try deviceAddress(handle, offset, 2, abi::WRITE), value);
381 +
}
382 +
/// Write one checked, ordered 32-bit device register.
383 +
export fn deviceWrite32(handle: abi::Handle, offset: u64, value: u32) throws (abi::Error) {
384 +
    write32(try deviceAddress(handle, offset, 4, abi::WRITE), value);
385 +
}
386 +
/// Write one checked, ordered 64-bit device register.
387 +
export fn deviceWrite64(handle: abi::Handle, offset: u64, value: u64) throws (abi::Error) {
388 +
    write64(try deviceAddress(handle, offset, 8, abi::WRITE), value);
389 +
}
390 +
391 +
/// Look up a live local slot of exactly kind. Empty/wrong/stale slots fault in
392 +
/// the kernel. No truncation of slot and no packed-handle arithmetic is used.
393 +
export fn handleSlot(slot: u64, kind: abi::Kind) -> abi::Handle {
394 +
    let reply = invoke(62, slot, kind as u64, 0, 0);
395 +
    return abi::Handle { bits: reply.values[0] };
396 +
}
397 +
398 +
/// Self selector for PageAllocate, DomainCreate, DomainDestroy, and grant targets.
399 +
/// Allocate and Create still require an installed capability with that right.
400 +
export fn domainSelf() -> abi::Handle { return abi::Handle { bits: 0 }; }
401 +
/// Current entry's exact context identity; pass the entry's own Env.
402 +
export fn envContext(env: *Env) -> u64 { return env.context; }
403 +
/// Installed local Events authority, not a newly manufactured capability.
404 +
export fn envEvents(env: *Env) -> abi::Handle { return abi::Handle { bits: env.eventsHandle }; }
127 405
/// Immutable startup bytes; fail rather than truncate an unrepresentable extent.
128 406
export fn envArgs(env: *Env) -> *[u8] {
129 407
    assert env.argsSize <= 0xffffffff;
130 408
    assert env.argsPointer <= 0xffffffffffffffff - env.argsSize;
131 409
    return @sliceOf(pointer(env.argsPointer) as *u8, env.argsSize as u32);