kernel: create bounded domains with private event state

f126a2b75baefc37668be7668848af8d87d469702419a9a0b05a4c2dcffc8e2c
Verified: make -C kernel check with the machine-capable emulator; all pass.
Alexis Sellier committed ago 1 parent 2fa6ad33
kernel/Makefile +3 -2
1 1
# Freestanding kernel and hosted mechanism checks.
2 2
EMU ?= $(or $(RAD_EMULATOR),emulator)
3 3
HOST_EMU ?= $(EMU)
4 4
COMPILER := ../bin/radiance.rv64.dev
5 5
COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER)
6 -
MODULES := core/fdt.rad core/platform.rad core/frames.rad core/abi.rad core/handles.rad
6 +
MODULES := core/fdt.rad core/platform.rad core/frames.rad core/abi.rad core/handles.rad \
7 +
	core/events.rad core/domains.rad
7 8
CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES))
8 -
CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad check/handles.rad
9 +
CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad check/handles.rad check/domains.rad
9 10
10 11
.PHONY: all check clean compiler-check
11 12
all: kernel.rv64
12 13
13 14
compiler-check:
kernel/NOTES.md +13 -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 4 of the 22-step plan.
5 +
record the contracts established through step 5 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.
54 54
- Rights are Read=1, Write=2, Execute=4, Grant=8, Transfer=16, Create=32,
55 55
  Allocate=64, Wake=128, Destroy=256; higher bits are reserved. New Page rights
56 56
  are Read|Write|Grant|Transfer. New Domain rights are
57 57
  Destroy|Execute|Grant|Transfer|Wake; root additionally has Create|Allocate.
58 58
59 +
## Domain storage and initial authority
60 +
61 +
- Mechanisms borrow bounded caller-owned storage. Each domain has private
62 +
  capability and event-producer state. Lookup accepts only live Pending or
63 +
  Active domain identities.
64 +
- Zero as a self-sentinel identifies the caller but supplies no resource right.
65 +
  Allocate and Create require a live capability to that caller with the right.
66 +
  A newly created domain has only its installed Read|Write Events handle.
67 +
- Accepting a child reserves one terminal event. At most 128 credits can be
68 +
  outstanding in a 256-entry Events queue.
69 +
59 70
## Validation
60 71
61 72
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
62 73
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
63 74
checks compiler dependencies. From the repository root, run:
64 75
65 76
```sh
66 77
make -C kernel check
67 78
```
68 79
69 -
Exercise invented and stale names, reserved bits, wrong kinds, rights checks, and generation retirement.
80 +
Exercise empty startup authority, self-sentinel checks, bounded domain storage, relationship credits, and identity reuse.
70 81
71 82
The entry probe uses explicit M-mode success/fault finish writes; secondary
72 83
harts idle. This checks machine entry, not user-domain execution. Finish writes
73 84
are a check protocol, not a domain-exit operation.
kernel/check.rad +2 -0
2 2
3 3
mod boot;
4 4
export mod fixture;
5 5
mod frames;
6 6
mod handles;
7 +
mod domains;
7 8
8 9
/// Run the available kernel mechanism checks.
9 10
@default fn main() -> u32 {
10 11
    frames::run();
11 12
    boot::run();
12 13
    handles::run();
14 +
    domains::run();
13 15
    return 0;
14 16
}
kernel/check/domains.rad added +40 -0
1 +
//! Pending-domain authority and bounded creation checks.
2 +
3 +
use core::abi;
4 +
use core::domains;
5 +
use core::handles;
6 +
7 +
/// Small domain pool used to exercise resource exhaustion.
8 +
static DOMAINS: [domains::Domain; 4] = undefined;
9 +
10 +
/// Require the self sentinel to fail without explicit resource authority.
11 +
fn denied(caller: u32) {
12 +
    let _object = try domains::authority(&DOMAINS[..], caller, abi::Handle { bits: 0 }, abi::ALLOCATE) catch error {
13 +
        assert error == abi::Error::Denied;
14 +
        return;
15 +
    };
16 +
    panic "denied: self sentinel created authority";
17 +
}
18 +
19 +
/// Check initial root rights and the empty authority of pending children.
20 +
export fn run() {
21 +
    domains::init(&mut DOMAINS[..]);
22 +
    let rootHandle = domains::root(&mut DOMAINS[..], 0);
23 +
    let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE | abi::ALLOCATE);
24 +
    let child = try! domains::create(&mut DOMAINS[..], root, 1);
25 +
    assert domains::live(&DOMAINS[..], child);
26 +
    denied(child.index);
27 +
    let queue = try! handles::get(&DOMAINS[child.index].handles, 1, abi::Kind::Events);
28 +
    let _events = try! handles::require(&DOMAINS[child.index].handles, queue, abi::Kind::Events, abi::READ | abi::WRITE);
29 +
    let _invalid = try handles::get(&DOMAINS[child.index].handles, 0, abi::Kind::Domain) catch error {
30 +
        assert error == abi::Error::BadHandle;
31 +
        let _second = try! domains::create(&mut DOMAINS[..], root, 1);
32 +
        let _third = try! domains::create(&mut DOMAINS[..], root, 1);
33 +
        let _fourth = try domains::create(&mut DOMAINS[..], root, 1) catch full {
34 +
            assert full == abi::Error::Exhausted;
35 +
            return;
36 +
        };
37 +
        panic "run: exhausted domain pool accepted a child";
38 +
    };
39 +
    panic "run: child received ambient Domain authority";
40 +
}
kernel/core.rad +2 -0
3 3
export mod fdt;
4 4
export mod platform;
5 5
export mod frames;
6 6
export mod abi;
7 7
export mod handles;
8 +
export mod events;
9 +
export mod domains;
kernel/core/domains.rad added +161 -0
1 +
//! Domain identities, initial authority, and private startup state.
2 +
3 +
use core::abi;
4 +
use core::events;
5 +
use core::handles;
6 +
7 +
/// Domain state; Dying is an internal quiescence phase.
8 +
export union Lifecycle: Copy {
9 +
    /// Startup capabilities can be installed before activation.
10 +
    Pending,
11 +
    /// Authorized execution contexts can run.
12 +
    Active,
13 +
    /// No execution or handle lookup is permitted.
14 +
    Dead,
15 +
    /// Contexts must stop before resources can be reclaimed.
16 +
    Dying,
17 +
}
18 +
19 +
/// Fixed startup environment; each field occupies eight bytes.
20 +
export record Env: Copy {
21 +
    /// Readable startup argument range base.
22 +
    argsPointer: u64,
23 +
    /// Number of argument bytes.
24 +
    argsSize: u64,
25 +
    /// Packed Events handle in this domain's table.
26 +
    eventsHandle: u64,
27 +
    /// Physical address of this domain's shared event ring.
28 +
    eventsPointer: u64,
29 +
    /// Physical base of this image instance's mutable data.
30 +
    stateBase: u64,
31 +
    /// Lowest address available to this context's stack frames.
32 +
    stackBase: u64,
33 +
    /// Exclusive upper bound of this context's stack.
34 +
    stackTop: u64,
35 +
    /// Kernel execution-context identity.
36 +
    context: u64,
37 +
}
38 +
39 +
/// One protection domain and its private authority boundary.
40 +
export record Domain: Copy {
41 +
    /// Domain-relative capability names.
42 +
    handles: handles::Table,
43 +
    /// Notification storage and private producer state.
44 +
    events: events::Queue,
45 +
    /// Live incarnation, or zero before the first allocation.
46 +
    epoch: u32,
47 +
    /// Protection-domain lifecycle.
48 +
    state: Lifecycle,
49 +
    /// Historical creator identity; independent of the current parent.
50 +
    creator: abi::Object,
51 +
    /// Current receiver of terminal lifecycle notifications.
52 +
    parent: abi::Object,
53 +
    /// Trusted immutable image registry index.
54 +
    image: u32,
55 +
    /// Initial execution environment.
56 +
    env: Env,
57 +
}
58 +
59 +
/// Return the identity used for a missing parent or creator.
60 +
export fn none() -> abi::Object {
61 +
    return abi::Object { kind: abi::Kind::Empty, index: abi::INVALID_DOMAIN, epoch: 0 };
62 +
}
63 +
64 +
/// Initialize caller-owned domain storage once at boot.
65 +
export fn init(domains: *mut [Domain]) {
66 +
    assert domains.len > 0 and domains.len <= abi::MAX_DOMAINS;
67 +
    for i in 0..domains.len {
68 +
        let domain = &mut domains[i];
69 +
        handles::init(&mut domain.handles);
70 +
        events::init(&mut domain.events);
71 +
        set domain.epoch = 0;
72 +
        set domain.state = Lifecycle::Dead;
73 +
        set domain.creator = none();
74 +
        set domain.parent = none();
75 +
        set domain.image = 0;
76 +
        set domain.env = Env {
77 +
            argsPointer: 0, argsSize: 0, eventsHandle: 0, eventsPointer: 0,
78 +
            stateBase: 0, stackBase: 0, stackTop: 0, context: 0,
79 +
        };
80 +
    }
81 +
}
82 +
83 +
/// Check that an object identifies a live, non-dying domain incarnation.
84 +
export fn live(domains: *[Domain], object: abi::Object) -> bool {
85 +
    if object.kind <> abi::Kind::Domain or object.index >= domains.len or object.epoch == 0 {
86 +
        return false;
87 +
    }
88 +
    let domain = &domains[object.index];
89 +
    return domain.epoch == object.epoch
90 +
        and (domain.state == Lifecycle::Pending or domain.state == Lifecycle::Active);
91 +
}
92 +
93 +
/// Get a live domain identity from a capability with all required rights.
94 +
export fn resolve(domains: *[Domain], caller: u32, handle: abi::Handle, rights: u16) -> abi::Object throws (abi::Error) {
95 +
    assert caller < domains.len;
96 +
    let entry = try handles::require(&domains[caller].handles, handle, abi::Kind::Domain, rights);
97 +
    if not live(domains, entry.object) { throw abi::Error::BadHandle; }
98 +
    return entry.object;
99 +
}
100 +
101 +
/// Resolve self authority by finding a live capability to the caller.
102 +
/// The zero sentinel does not supply Allocate or Create rights itself.
103 +
export fn authority(domains: *[Domain], caller: u32, handle: abi::Handle, rights: u16) -> abi::Object throws (abi::Error) {
104 +
    if handle.bits <> 0 { return try resolve(domains, caller, handle, rights); }
105 +
    let domain = &domains[caller];
106 +
    for i in 0..abi::MAX_HANDLES {
107 +
        let entry = &domain.handles.entries[i];
108 +
        if entry.object.kind == abi::Kind::Domain and entry.object.index == caller
109 +
            and entry.object.epoch == domain.epoch and entry.rights & rights == rights {
110 +
            return entry.object;
111 +
        }
112 +
    }
113 +
    throw abi::Error::Denied;
114 +
}
115 +
116 +
/// Prepare a free slot with an Events handle and no ambient authority.
117 +
fn prepare(domains: *mut [Domain], index: u32, creator: abi::Object, image: u32) -> abi::Object {
118 +
    let domain = &mut domains[index];
119 +
    assert domain.state == Lifecycle::Dead and domain.epoch < 0xffffffff;
120 +
    for i in 0..abi::MAX_HANDLES {
121 +
        assert domain.handles.entries[i].object.kind == abi::Kind::Empty;
122 +
    }
123 +
    set domain.epoch += 1;
124 +
    set domain.creator = creator;
125 +
    set domain.parent = creator;
126 +
    set domain.image = image;
127 +
    events::init(&mut domain.events);
128 +
    let object = abi::Object { kind: abi::Kind::Domain, index, epoch: domain.epoch };
129 +
    let eventObject = abi::Object { kind: abi::Kind::Events, index, epoch: domain.epoch };
130 +
    let eventHandle = handles::install(&mut domain.handles, 1, eventObject, abi::READ | abi::WRITE);
131 +
    set domain.env = Env {
132 +
        argsPointer: 0, argsSize: 0, eventsHandle: eventHandle.bits, eventsPointer: 0,
133 +
        stateBase: 0, stackBase: 0, stackTop: 0, context: 0,
134 +
    };
135 +
    set domain.state = Lifecycle::Pending;
136 +
    return object;
137 +
}
138 +
139 +
/// Install root's initial Domain and Events authority into slot zero.
140 +
export fn root(domains: *mut [Domain], image: u32) -> abi::Handle {
141 +
    assert domains[0].epoch == 0;
142 +
    let object = prepare(domains, 0, none(), image);
143 +
    return handles::install(&mut domains[0].handles, 0, object, abi::ROOT_RIGHTS);
144 +
}
145 +
146 +
/// Create a pending domain after the caller has authorized image admission.
147 +
/// Reserve terminal event capacity before accepting the parent relationship.
148 +
export fn create(domains: *mut [Domain], creator: abi::Object, image: u32) -> abi::Object throws (abi::Error) {
149 +
    if not live(domains, creator) { throw abi::Error::BadHandle; }
150 +
    if domains[creator.index].events.reserved == events::CRITICAL { throw abi::Error::Exhausted; }
151 +
    for index in 0..domains.len {
152 +
        let domain = &domains[index];
153 +
        if domain.state == Lifecycle::Dead and domain.epoch < 0xffffffff
154 +
            and domain.handles.entries[1].generation <> 0 {
155 +
            let result = prepare(domains, index, creator, image);
156 +
            set domains[creator.index].events.reserved += 1;
157 +
            return result;
158 +
        }
159 +
    }
160 +
    throw abi::Error::Exhausted;
161 +
}
kernel/core/events.rad added +61 -0
1 +
//! Fixed per-domain notification memory and producer state.
2 +
3 +
/// Shared notification ring depth; a power of two.
4 +
export constant CAPACITY: u32 = 256;
5 +
/// Notification slots reserved for terminal lifecycle events.
6 +
export constant CRITICAL: u32 = 128;
7 +
8 +
/// A kernel-to-domain asynchronous notification.
9 +
export record Event: Copy {
10 +
    /// Interrupt=1, Timeout=2, Fault=3, ChildExit=4, Wakeup=5.
11 +
    kind: u16,
12 +
    /// Reserved ABI field, always zero.
13 +
    reserved: u16,
14 +
    /// Event-specific token, source, status, or fault code.
15 +
    code: u32,
16 +
    /// Event-specific payload or sender identifier.
17 +
    value: u64,
18 +
}
19 +
20 +
/// Shared ring layout; head and tail require acquire/release access.
21 +
export record Ring: Copy {
22 +
    /// Notifications indexed by the low bits of head or tail.
23 +
    data: [Event; CAPACITY],
24 +
    /// Consumer progress, published by serialized user consumers.
25 +
    head: u32,
26 +
    /// Producer progress, published by the kernel.
27 +
    tail: u32,
28 +
    /// CAPACITY - 1, supplied by the kernel.
29 +
    mask: u32,
30 +
}
31 +
32 +
/// Private kernel state for a domain's ring.
33 +
export record Queue: Copy {
34 +
    /// Shared ABI storage exposed to this domain.
35 +
    ring: Ring,
36 +
    /// Last validated consumer progress.
37 +
    head: u32,
38 +
    /// Authoritative producer progress.
39 +
    tail: u32,
40 +
    /// Number of outstanding ordinary notifications.
41 +
    ordinary: u32,
42 +
    /// Live-child and queued-lifecycle delivery reservations.
43 +
    reserved: u32,
44 +
    /// Slots that release a lifecycle reservation when consumed.
45 +
    critical: [bool; CAPACITY],
46 +
}
47 +
48 +
/// Clear event memory before it is exposed to a domain incarnation.
49 +
export fn init(queue: *mut Queue) {
50 +
    for i in 0..CAPACITY {
51 +
        set queue.ring.data[i] = Event { kind: 0, reserved: 0, code: 0, value: 0 };
52 +
        set queue.critical[i] = false;
53 +
    }
54 +
    set queue.ring.head = 0;
55 +
    set queue.ring.tail = 0;
56 +
    set queue.ring.mask = CAPACITY - 1;
57 +
    set queue.head = 0;
58 +
    set queue.tail = 0;
59 +
    set queue.ordinary = 0;
60 +
    set queue.reserved = 0;
61 +
}