kernel: dispatch the complete direct-call ABI
1279b22552a4c3a31240a398c356f577c2ab62df5c236a329e8272761c8ff6e7
Verified: make -C kernel check; hosted ABI validation and native execution through calls::dispatch pass.
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); |