kernel: Implement wait, timeout, wakeup, and yield

34415325910752c63af2fcdbbf3219bb0d299846bb1d52b1c403a2e2429751e6
Add fixed timeout storage, event-driven context wakeup, and exclusive
targeted budget handoff. Exercise wait ordering, saturation, authority
failures, native yield, and later caller resumption.

Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 638682ed
kernel/kernel.rad +1 -0
20 20
export mod loader;
21 21
export mod instances;
22 22
export mod domains;
23 23
export mod budgets;
24 24
export mod dispatch;
25 +
export mod timers;
25 26
export mod calls;
26 27
export mod sys;
27 28
export mod boot;
28 29
@test export mod tests;
kernel/kernel/abi.rad +1 -1
77 77
    domain: u64,
78 78
    /// First byte of the reserved user stack.
79 79
    stackBase: u64,
80 80
    /// Exclusive upper bound of the reserved user stack.
81 81
    stackTop: u64,
82 -
    /// Ready or Stopped encoded as zero or one.
82 +
    /// Ready, Stopped, or Waiting encoded as zero, one, or two.
83 83
    state: u64,
84 84
    /// Executing hart, or all ones while no hart owns execution.
85 85
    hart: u64,
86 86
}
87 87
kernel/kernel/boot.rad +2 -0
9 9
use super::pages;
10 10
use super::registry;
11 11
use super::domains;
12 12
use super::budgets;
13 13
use super::dispatch;
14 +
use super::timers;
14 15
use std::arch::rv64::shared::catalog;
15 16
16 17
/// Platform data published by hart zero before secondary initialization.
17 18
export unsafe static PLATFORM: platform::Platform = undefined;
18 19
/// Release/acquire publication flag for PLATFORM.
81 82
        assert platform::inRam(&PLATFORM, treeRange);
82 83
        try! platform::protect(&mut PLATFORM, treeRange);
83 84
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
84 85
        domains::initialize(&mut domains::STORE);
85 86
        budgets::initialize(&mut budgets::STORE);
87 +
        timers::initialize(&mut timers::STORE);
86 88
        assert entryCount == 2;
87 89
        registry::initialize(&mut registry::STORE);
88 90
        let bootCatalog = @sliceOf(entries, entryCount);
89 91
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
90 92
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
kernel/kernel/budgets.rad +33 -0
257 257
    set store.windows[object.index].start = candidate.start;
258 258
    set store.windows[object.index].context = request.context;
259 259
    return result;
260 260
}
261 261
262 +
/// Transfer the active remainder to an available target's initial context.
263 +
/// Failure leaves the source window, binding, and capability unchanged.
264 +
export unsafe fn handoff(store: &mut Store, domainStore: &domains::Store, table: &mut capability::Table,
265 +
    current: abi::CurrentContextInfo, target: abi::Handle, now: u64) throws (abi::Error)
266 +
{
267 +
    let handle = abi::Handle(current.budget);
268 +
    let object = try owned(store, table, handle, abi::TRANSFER);
269 +
    let window = try get(store, object);
270 +
    if window.hart as u64 <> current.hart or window.context <> abi::reference(current.context)
271 +
        or now < window.start or now >= window.end {
272 +
            throw abi::Error::Busy;
273 +
        }
274 +
    let permit = try capability::lookup(table, target, abi::Kind::Domain, abi::Rights(abi::WAKE));
275 +
    let receiver = try domains::get(domainStore, permit.object) catch {
276 +
        throw abi::Error::Busy;
277 +
    };
278 +
    if receiver.state <> domains::Lifecycle::Active or abi::id(receiver.initial) == current.context {
279 +
        throw abi::Error::Busy;
280 +
    }
281 +
    let context = try domains::context(domainStore, permit.object, receiver.initial) catch {
282 +
        throw abi::Error::Busy;
283 +
    };
284 +
    if context.state <> domains::ContextState::Ready or context.hart <> nil {
285 +
        throw abi::Error::Busy;
286 +
    }
287 +
    let result = try bind(store, domainStore, table, Binding { budget: handle, domain: target, context: receiver.initial }, now) catch error {
288 +
        if error == abi::Error::InvalidArg {
289 +
            throw abi::Error::Busy;
290 +
        }
291 +
        throw error;
292 +
    };
293 +
}
294 +
262 295
/// Test half-open execution authority on the selected hart at the current tick.
263 296
export fn active(store: &Store, object: abi::Ref, hart: u32, now: u64) -> bool throws (abi::Error) {
264 297
    let window = try get(store, object);
265 298
    return window.context <> nil and window.hart == hart and window.start <= now and now < window.end;
266 299
}
kernel/kernel/calls.rad +31 -0
11 11
use super::trap;
12 12
use super::events;
13 13
use super::transactions;
14 14
use super::loader;
15 15
use super::dispatch;
16 +
use super::timers;
16 17
17 18
/// Validate resident object generations for capabilities without page ownership.
18 19
unsafe fn resident(entry: capability::Entry) throws (abi::Error) {
19 20
    match entry.kind {
20 21
        case abi::Kind::Domain, abi::Kind::Events => {
145 146
                handle, arguments[1], arguments[2], arguments[3]);
146 147
            return 0;
147 148
        },
148 149
        case 30 => return *try pages::allocate(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]),
149 150
        case 31 => return *try pages::split(&mut pages::STORE, &mut domain.memory.table, handle, arguments[1]),
151 +
        case 40 => {
152 +
            if arguments[1] <> 0 or arguments[2] <> 0 or arguments[3] <> 0 {
153 +
                throw abi::Error::InvalidArg;
154 +
            }
155 +
            try budgets::handoff(&mut budgets::STORE, &domains::STORE, &mut domain.memory.table, dispatch::current(), handle, now);
156 +
            return 0;
157 +
        },
158 +
        case 41 => {
159 +
            if arguments[1] > 0xffffffff or arguments[2] <> 0 or arguments[3] <> 0 {
160 +
                throw abi::Error::InvalidArg;
161 +
            }
162 +
            let current = dispatch::current();
163 +
            try timers::request(&mut timers::STORE, &domains::STORE, owner, current.hart as u32, now, arguments[0], arguments[1] as u32);
164 +
            return 0;
165 +
        },
166 +
        case 42 => {
167 +
            if (arguments[0] | arguments[1] | arguments[2] | arguments[3]) <> 0 {
168 +
                throw abi::Error::InvalidArg;
169 +
            }
170 +
            let current = dispatch::current();
171 +
            try domains::wait(&mut domains::STORE, owner, abi::reference(current.context));
172 +
            return 0;
173 +
        },
150 174
        case 44 => {
151 175
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Page, abi::Rights(0));
152 176
            let page = try pages::get(&pages::STORE, entry.object);
153 177
            try reply(&domain, arguments, &[page.base, page.count as u64]);
154 178
            return 0;
169 193
            try reply(&domain, arguments, &[events::CAPACITY as u64]);
170 194
            return 0;
171 195
        },
172 196
        case 51 => return *try loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut domain.memory.table,
173 197
            loader::Request { authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3] }),
198 +
        case 50 => {
199 +
            if arguments[1] > 0xffffffff or arguments[2] <> 0 or arguments[3] <> 0 {
200 +
                throw abi::Error::InvalidArg;
201 +
            }
202 +
            try domains::wakeup(&mut domains::STORE, &domain.memory.table, handle, arguments[1] as u32);
203 +
            return 0;
204 +
        },
174 205
        case 52 => {
175 206
            let object = try registry::image(&registry::STORE, &domain.memory.table, handle, abi::Rights(abi::READ));
176 207
            let package = try! registry::get(&registry::STORE, object);
177 208
            let mut entry: u64 = 0;
178 209
            if let address = package.entry {
kernel/kernel/dispatch.rad +7 -1
7 7
use super::domains;
8 8
use super::platform;
9 9
use super::trap;
10 10
use super::range;
11 11
use super::calls;
12 +
use super::timers;
12 13
13 14
/// Dispatch ownership and idle state retained by one physical hart.
14 15
export record State: Copy {
15 16
    /// Physical hart whose windows this state selects.
16 17
    hart: u32,
66 67
/// Reevaluate authority after a timer interrupt with shared metadata serialized.
67 68
export unsafe fn interrupt(frame: &mut trap::Frame, anchor: &mut trap::Hart) {
68 69
    let id = hart();
69 70
    assert id < limits::HARTS;
70 71
    let clock = now(HARTS[id].timer);
72 +
    let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, id, clock);
71 73
    let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock);
72 -
    arm(HARTS[id].timer, choice.deadline);
74 +
    let mut deadline = choice.deadline;
75 +
    if timeout < deadline {
76 +
        set deadline = timeout;
77 +
    }
78 +
    arm(HARTS[id].timer, deadline);
73 79
}
74 80
75 81
/// Read the running context's identity and domain-relative CPU authority.
76 82
export unsafe fn current() -> abi::CurrentContextInfo {
77 83
    let id = hart();
kernel/kernel/domains.rad +45 -0
44 44
export union ContextState: Copy {
45 45
    /// The context can execute with CPU authority in an active domain.
46 46
    Ready,
47 47
    /// The context has returned and cannot execute again.
48 48
    Stopped,
49 +
    /// The context has no unread notification and waits for one.
50 +
    Waiting,
49 51
}
50 52
51 53
/// Saved integer state owned by one execution context.
52 54
export record Context: Copy {
53 55
    /// Domain that owns this context.
150 152
    } else {
151 153
        set store.contexts[object.index].state = ContextState::Stopped;
152 154
    }
153 155
}
154 156
157 +
/// Atomically check notifications and suspend the caller if its queue is empty.
158 +
/// The caller serializes this check with notification publication.
159 +
export unsafe fn wait(store: &mut Store, owner: abi::Ref, object: abi::Ref) throws (abi::Error) {
160 +
    let current = try context(store, owner, object);
161 +
    let mut domain = try get(store, owner);
162 +
    if domain.state <> Lifecycle::Active or current.state <> ContextState::Ready {
163 +
        throw abi::Error::Busy;
164 +
    }
165 +
    let flushed = try events::flush(&mut store.events, owner, &mut domain.memory.ring);
166 +
    if store.events.queues[owner.index].head == store.events.queues[owner.index].tail {
167 +
        set store.contexts[object.index].state = ContextState::Waiting;
168 +
    }
169 +
}
170 +
171 +
/// Publish an ordinary event and ready all waiting contexts in the receiver.
172 +
/// The caller serializes event publication and context state transitions.
173 +
export unsafe fn notify(store: &mut Store, receiver: abi::Ref, event: events::Event) -> bool throws (abi::Error) {
174 +
    let mut domain = try get(store, receiver);
175 +
    if domain.state == Lifecycle::Dead {
176 +
        throw abi::Error::BadHandle;
177 +
    }
178 +
    if not try events::send(&mut store.events, receiver, &mut domain.memory.ring, event) {
179 +
        return false;
180 +
    }
181 +
    for i in 0..limits::CONTEXTS {
182 +
        if store.contextSlots[i].state == slots::State::Live and store.contexts[i].owner == receiver
183 +
            and store.contexts[i].state == ContextState::Waiting {
184 +
            set store.contexts[i].state = ContextState::Ready;
185 +
        }
186 +
    }
187 +
    return true;
188 +
}
189 +
190 +
/// Send a token through a nonzero Domain capability with Wake authority.
191 +
export unsafe fn wakeup(store: &mut Store, table: &capability::Table, handle: abi::Handle, token: u32) throws (abi::Error) {
192 +
    let permit = try capability::lookup(table, handle, abi::Kind::Domain, abi::Rights(abi::WAKE));
193 +
    if not try notify(store, permit.object, events::Event {
194 +
        kind: events::WAKEUP, reserved: 0, code: token, value: abi::id(table.owner),
195 +
    }) {
196 +
        throw abi::Error::Busy;
197 +
    }
198 +
}
199 +
155 200
/// Reclaim an additional context after all harts have released execution ownership.
156 201
export fn contextDestroy(store: &mut Store, memory: &mut pages::Store, table: &capability::Table,
157 202
    authority: abi::Handle, object: abi::Ref) throws (abi::Error)
158 203
{
159 204
    let permit = try capability::authority(table, authority, abi::Rights(abi::DESTROY));
kernel/kernel/limits.rad +2 -0
20 20
export constant BUDGETS: u32 = 512;
21 21
/// Maximum execution contexts across all domains.
22 22
export constant CONTEXTS: u32 = 2048;
23 23
/// Maximum hardware threads.
24 24
export constant HARTS: u32 = 8;
25 +
/// One-shot timeouts retained by each hart, including pending delivery.
26 +
export constant TIMEOUTS_PER_HART: u32 = 64;
25 27
/// Maximum resident package records and gp table entries.
26 28
export constant PACKAGES: u32 = 256;
27 29
/// Generation values never wrap.
28 30
export constant LAST_GENERATION: u32 = 0xffffffff;
kernel/kernel/sys.rad +20 -0
74 74
/// Relinquish a handle and its object-specific authority.
75 75
export fn capabilityDrop(handle: abi::Handle) throws (abi::Error) {
76 76
    try result(ecall(abi::Operation::CapabilityDrop as u32, *handle as i64, 0, 0, 0));
77 77
}
78 78
79 +
/// Give the active window's remainder to a target and resume with later authority.
80 +
export fn yield(target: abi::Handle) throws (abi::Error) {
81 +
    try result(ecall(abi::Operation::Yield as u32, *target as i64, 0, 0, 0));
82 +
}
83 +
84 +
/// Request a one-shot notification after a positive delay in mtime ticks.
85 +
export fn timeout(delay: u64, token: u32) throws (abi::Error) {
86 +
    try result(ecall(abi::Operation::Timeout as u32, delay as i64, token as i64, 0, 0));
87 +
}
88 +
89 +
/// Suspend until a notification is pending and CPU authority permits resumption.
90 +
export fn wait() {
91 +
    try! result(ecall(abi::Operation::Wait as u32, 0, 0, 0, 0));
92 +
}
93 +
94 +
/// Send a token to a domain with Wake authority without transferring CPU time.
95 +
export fn wakeup(handle: abi::Handle, token: u32) throws (abi::Error) {
96 +
    try result(ecall(abi::Operation::Wakeup as u32, *handle as i64, token as i64, 0, 0));
97 +
}
98 +
79 99
/// Allocate contiguous zeroed physical frames with self Allocate authority.
80 100
export fn pageAllocate(handle: abi::Handle, count: u64) -> abi::Handle throws (abi::Error) {
81 101
    return abi::Handle(try result(ecall(abi::Operation::PageAllocate as u32, *handle as i64, count as i64, 0, 0)));
82 102
}
83 103
kernel/kernel/tests.rad +1 -0
17 17
export mod instances;
18 18
export mod domains;
19 19
export mod budgets;
20 20
export mod dispatch;
21 21
export mod calls;
22 +
export mod timers;
kernel/kernel/tests/budgets.rad +61 -0
20 20
/// Domain and context lifetimes used for binding admission.
21 21
unsafe static DOMAINS: domains::Store = undefined;
22 22
/// Capability storage for a pending receiver domain.
23 23
unsafe static MEMORY: domains::Memory = undefined;
24 24
25 +
/// Targeted yield moves exactly the active remainder and preserves failures.
26 +
@test unsafe fn targetedYield() throws (testing::TestError) {
27 +
    initialize();
28 +
    let target = receiver();
29 +
    let permission = try! capability::get(&TABLE, target);
30 +
    let initial = DOMAINS.records[permission.object.index].initial;
31 +
    let budget = try! budgets::seed(&mut STORE, &mut TABLE, 0, 10, 100);
32 +
    let entry = try! capability::get(&TABLE, budget);
33 +
    let source = abi::Ref { index: 7, generation: 1 };
34 +
    set STORE.windows[entry.object.index].context = source;
35 +
    let current = abi::CurrentContextInfo { context: abi::id(source), hart: 0, budget: *budget };
36 +
    let before = STORE.windows[entry.object.index];
37 +
    let mut rejected: u32 = 0;
38 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
39 +
        assert (error == abi::Error::Busy); set rejected += 1;
40 +
    };
41 +
    set DOMAINS.records[permission.object.index].state = domains::Lifecycle::Active;
42 +
    set DOMAINS.contexts[initial.index].state = domains::ContextState::Waiting;
43 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
44 +
        assert (error == abi::Error::Busy); set rejected += 1;
45 +
    };
46 +
    set DOMAINS.contexts[initial.index].state = domains::ContextState::Ready;
47 +
    set DOMAINS.contexts[initial.index].hart = 1;
48 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
49 +
        assert error == abi::Error::Busy; set rejected += 1;
50 +
    };
51 +
    set DOMAINS.contexts[initial.index].hart = nil;
52 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 100) catch error {
53 +
        assert error == abi::Error::Busy; set rejected += 1;
54 +
    };
55 +
    let budgetSlot = try! abi::decode(budget);
56 +
    set TABLE.entries[budgetSlot.object.index].rights = abi::Rights(abi::READ);
57 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
58 +
        assert error == abi::Error::Denied; set rejected += 1;
59 +
    };
60 +
    set TABLE.entries[budgetSlot.object.index].rights = entry.rights;
61 +
    let targetSlot = try! abi::decode(target);
62 +
    set TABLE.entries[targetSlot.object.index].rights = abi::Rights(abi::READ);
63 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
64 +
        assert error == abi::Error::Denied; set rejected += 1;
65 +
    };
66 +
    set TABLE.entries[targetSlot.object.index].rights = permission.rights;
67 +
    for i in 0..MEMORY.table.slots.len {
68 +
        set MEMORY.table.slots[i].state = slots::State::Retired;
69 +
    }
70 +
    try budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30) catch error {
71 +
        assert (error == abi::Error::Busy); set rejected += 1;
72 +
    };
73 +
    assert (rejected == 7 and STORE.windows[entry.object.index] == before);
74 +
    assert (try! capability::get(&TABLE, budget) == entry);
75 +
    capability::initialize(&mut MEMORY.table, permission.object);
76 +
    try! budgets::handoff(&mut STORE, &DOMAINS, &mut TABLE, current, target, 30);
77 +
    let after = STORE.windows[entry.object.index];
78 +
    assert (after.start == 30 and after.end == 100 and after.hart == 0);
79 +
    assert (after.owner == permission.object and after.context == initial);
80 +
    let moved = try! capability::get(&MEMORY.table, after.handle);
81 +
    assert (moved.object == entry.object);
82 +
    let old = try! abi::decode(budget);
83 +
    assert (TABLE.slots[old.object.index].state == slots::State::Free);
84 +
}
85 +
25 86
/// Start with one live owner and no issued hart intervals.
26 87
unsafe fn initialize() {
27 88
    budgets::initialize(&mut STORE);
28 89
    capability::initialize(&mut TABLE, abi::Ref { index: 0, generation: 1 });
29 90
    capability::initialize(&mut TARGET, abi::Ref { index: 1, generation: 1 });
kernel/kernel/tests/domains.rad +40 -0
8 8
use kernel::capability;
9 9
use kernel::registry;
10 10
use kernel::domains;
11 11
use kernel::transactions;
12 12
use kernel::pages;
13 +
use kernel::events;
13 14
14 15
/// Mapped physical memory for child metadata and package instances.
15 16
static RAM: [u64; 131072] = [0; 131072];
16 17
/// Domain and context metadata under test.
17 18
unsafe static DOMAINS: domains::Store = undefined;
206 207
    try testing::expect(DOMAINS.contexts[next.index].state == domains::ContextState::Stopped);
207 208
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, next);
208 209
    try testing::expect(rejected == 11);
209 210
}
210 211
212 +
/// Both serialized wait/wakeup orders preserve notification and runnable state.
213 +
@test unsafe fn waitWakeup() throws (testing::TestError) {
214 +
    initialize(abi::CREATE);
215 +
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
216 +
    let authority = try! capability::get(&TABLE, handle);
217 +
    let mut child = try! domains::get(&DOMAINS, authority.object);
218 +
    set DOMAINS.records[authority.object.index].state = domains::Lifecycle::Active;
219 +
    try! domains::wait(&mut DOMAINS, authority.object, child.initial);
220 +
    assert (DOMAINS.contexts[child.initial.index].state == domains::ContextState::Waiting);
221 +
    try! domains::wakeup(&mut DOMAINS, &TABLE, handle, 17);
222 +
    assert (DOMAINS.contexts[child.initial.index].state == domains::ContextState::Ready);
223 +
    try! domains::wait(&mut DOMAINS, authority.object, child.initial);
224 +
    assert (DOMAINS.contexts[child.initial.index].state == domains::ContextState::Ready);
225 +
    let event = try! events::pop(&mut child.memory.ring) else panic "wake event";
226 +
    assert (event.kind == events::WAKEUP and event.code == 17 and event.value == abi::id(TABLE.owner));
227 +
    try! domains::wakeup(&mut DOMAINS, &TABLE, handle, 23);
228 +
    try! domains::wait(&mut DOMAINS, authority.object, child.initial);
229 +
    assert (DOMAINS.contexts[child.initial.index].state == domains::ContextState::Ready);
230 +
    let second = try! events::pop(&mut child.memory.ring) else panic "second wake event";
231 +
    assert (second.code == 23);
232 +
    for i in 0..events::ORDINARY {
233 +
        try! domains::wakeup(&mut DOMAINS, &TABLE, handle, i);
234 +
    }
235 +
    let tail = child.memory.ring.tail;
236 +
    let mut rejected: u32 = 0;
237 +
    try domains::wakeup(&mut DOMAINS, &TABLE, handle, 999) catch error {
238 +
        assert (error == abi::Error::Busy); set rejected += 1;
239 +
    };
240 +
    assert (child.memory.ring.tail == tail);
241 +
    try domains::wakeup(&mut DOMAINS, &TABLE, abi::Handle(0), 0) catch error {
242 +
        assert (error == abi::Error::BadHandle); set rejected += 1;
243 +
    };
244 +
    set DOMAINS.records[authority.object.index].state = domains::Lifecycle::Dead;
245 +
    try domains::wakeup(&mut DOMAINS, &TABLE, handle, 0) catch error {
246 +
        assert (error == abi::Error::BadHandle); set rejected += 1;
247 +
    };
248 +
    assert (rejected == 3);
249 +
}
250 +
211 251
/// Creation installs only Events in the child and preserves selected authorizing rights.
212 252
@test unsafe fn pending() throws (testing::TestError) {
213 253
    initialize(abi::CREATE | abi::ALLOCATE);
214 254
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
215 255
    let authority = try! capability::get(&TABLE, handle);
kernel/kernel/tests/timers.rad added +86 -0
1 +
//! Timeout boundaries, fixed capacity, and retained delivery.
2 +
use std::testing;
3 +
use kernel::abi;
4 +
use kernel::slots;
5 +
use kernel::domains;
6 +
use kernel::events;
7 +
use kernel::timers;
8 +
use kernel::limits;
9 +
10 +
/// Timer records under test.
11 +
unsafe static TIMERS: timers::Store = undefined;
12 +
/// Domain metadata and trusted notification state.
13 +
unsafe static DOMAINS: domains::Store = undefined;
14 +
/// Receiver ring and private metadata.
15 +
unsafe static MEMORY: domains::Memory = undefined;
16 +
17 +
/// Open an active domain queue without assigning CPU authority.
18 +
unsafe fn initialize() -> abi::Ref {
19 +
    timers::initialize(&mut TIMERS);
20 +
    domains::initialize(&mut DOMAINS);
21 +
    let pending = try! slots::reserve(&mut DOMAINS.slots[..]);
22 +
    let owner = try! slots::commit(&mut DOMAINS.slots[..], pending);
23 +
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Active;
24 +
    set DOMAINS.records[owner.index].memory = &mut MEMORY;
25 +
    try! events::open(&mut DOMAINS.events, owner, &mut MEMORY.ring);
26 +
    return owner;
27 +
}
28 +
29 +
/// A timeout is delivered once, at its deadline, on its registered hart.
30 +
@test unsafe fn boundaries() throws (testing::TestError) {
31 +
    let owner = initialize();
32 +
    try! timers::request(&mut TIMERS, &DOMAINS, owner, 1, 10, 5, 17);
33 +
    assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 15) == 0xffffffffffffffff);
34 +
    assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 14) == 15);
35 +
    assert (try! events::pop(&mut MEMORY.ring) == nil);
36 +
    let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 15);
37 +
    assert (next == 0xffffffffffffffff);
38 +
    let event = try! events::pop(&mut MEMORY.ring) else panic "timeout";
39 +
    assert (event.kind == events::TIMEOUT and event.code == 17 and event.value == 0);
40 +
    let again = try! timers::service(&mut TIMERS, &mut DOMAINS, 1, 16);
41 +
    assert (try! events::pop(&mut MEMORY.ring) == nil);
42 +
}
43 +
44 +
/// Full queues retain due timeouts without requesting another immediate interrupt.
45 +
@test unsafe fn saturation() throws (testing::TestError) {
46 +
    let owner = initialize();
47 +
    for i in 0..events::ORDINARY {
48 +
        assert (try! domains::notify(&mut DOMAINS, owner,
49 +
            events::Event { kind: events::WAKEUP, reserved: 0, code: i, value: 0 }));
50 +
    }
51 +
    for i in 0..limits::TIMEOUTS_PER_HART {
52 +
        try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 1, i);
53 +
    }
54 +
    let mut full = false;
55 +
    try timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 1, 99) catch error {
56 +
        assert (error == abi::Error::Busy); set full = true;
57 +
    };
58 +
    assert (full);
59 +
    assert (try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 1) == 0xffffffffffffffff);
60 +
    for i in 0..events::ORDINARY {
61 +
        let event = try! events::pop(&mut MEMORY.ring) else panic "wake";
62 +
    }
63 +
    let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 2);
64 +
    for i in 0..limits::TIMEOUTS_PER_HART {
65 +
        let event = try! events::pop(&mut MEMORY.ring) else panic "retained timeout";
66 +
        assert (event.kind == events::TIMEOUT and event.code == i);
67 +
    }
68 +
    try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 2, 1, 100);
69 +
}
70 +
71 +
/// Invalid delays fail, and dead receivers release timer capacity.
72 +
@test unsafe fn lifetime() throws (testing::TestError) {
73 +
    let owner = initialize();
74 +
    let mut rejected: u32 = 0;
75 +
    try timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 0, 1) catch error {
76 +
        assert (error == abi::Error::InvalidArg); set rejected += 1;
77 +
    };
78 +
    try timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0xffffffffffffffff, 1, 1) catch error {
79 +
        assert (error == abi::Error::InvalidArg); set rejected += 1;
80 +
    };
81 +
    try! timers::request(&mut TIMERS, &DOMAINS, owner, 0, 0, 5, 1);
82 +
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Dead;
83 +
    let next = try! timers::service(&mut TIMERS, &mut DOMAINS, 0, 1);
84 +
    assert (next == 0xffffffffffffffff and rejected == 2);
85 +
    assert (TIMERS.entries[0][0].owner == nil);
86 +
}
kernel/kernel/timers.rad added +85 -0
1 +
//! Fixed per-hart timeout storage and lossless deferred notification.
2 +
use super::abi;
3 +
use super::limits;
4 +
use super::slots;
5 +
use super::domains;
6 +
use super::events;
7 +
8 +
/// One armed timeout or retained due notification.
9 +
export record Entry: Copy {
10 +
    /// Receiver generation, or nil for free capacity.
11 +
    owner: ?abi::Ref,
12 +
    /// Earliest permitted delivery time in mtime ticks.
13 +
    deadline: u64,
14 +
    /// User token echoed in the timeout event.
15 +
    token: u32,
16 +
}
17 +
18 +
/// Fixed timeout slots partitioned by the hart that accepted each request.
19 +
export record Store: Copy {
20 +
    /// Armed or pending entries owned by each physical hart.
21 +
    entries: [[Entry; limits::TIMEOUTS_PER_HART]; limits::HARTS],
22 +
}
23 +
24 +
/// Timeout state serialized with domain and notification metadata.
25 +
export unsafe static STORE: Store = undefined;
26 +
27 +
/// Clear all timeout capacity before secondary harts start.
28 +
export fn initialize(store: &mut Store) {
29 +
    for hart in 0..limits::HARTS {
30 +
        for i in 0..limits::TIMEOUTS_PER_HART {
31 +
            set store.entries[hart][i].owner = nil;
32 +
        }
33 +
    }
34 +
}
35 +
36 +
/// Reserve one timeout without blocking or assigning CPU authority.
37 +
export fn request(store: &mut Store, receivers: &domains::Store, owner: abi::Ref,
38 +
    hart: u32, now: u64, delay: u64, token: u32) throws (abi::Error)
39 +
{
40 +
    if hart >= limits::HARTS or delay == 0 or delay > 0xffffffffffffffff - now {
41 +
        throw abi::Error::InvalidArg;
42 +
    }
43 +
    let domain = try domains::get(receivers, owner);
44 +
    if domain.state <> domains::Lifecycle::Active {
45 +
        throw abi::Error::BadHandle;
46 +
    }
47 +
    for i in 0..limits::TIMEOUTS_PER_HART {
48 +
        if store.entries[hart][i].owner <> nil {
49 +
            continue;
50 +
        }
51 +
        set store.entries[hart][i] = Entry { owner, deadline: now + delay, token };
52 +
        return;
53 +
    }
54 +
    throw abi::Error::Busy;
55 +
}
56 +
57 +
/// Deliver due entries and return the next future timeout boundary.
58 +
/// Full queues retain due entries for the next kernel entry without a timer spin.
59 +
export unsafe fn service(store: &mut Store, receivers: &mut domains::Store, hart: u32, now: u64) -> u64 throws (abi::Error) {
60 +
    if hart >= limits::HARTS {
61 +
        throw abi::Error::InvalidArg;
62 +
    }
63 +
    let mut next: u64 = 0xffffffffffffffff;
64 +
    for i in 0..limits::TIMEOUTS_PER_HART {
65 +
        let entry = store.entries[hart][i];
66 +
        let owner = entry.owner else {
67 +
            continue;
68 +
        };
69 +
        if not slots::matches(&receivers.slots[..], owner, slots::State::Live)
70 +
            or receivers.records[owner.index].state == domains::Lifecycle::Dead {
71 +
            set store.entries[hart][i].owner = nil;
72 +
            continue;
73 +
        }
74 +
        if entry.deadline > now {
75 +
            if entry.deadline < next {
76 +
                set next = entry.deadline;
77 +
            }
78 +
            continue;
79 +
        }
80 +
        if try domains::notify(receivers, owner, events::Event { kind: events::TIMEOUT, reserved: 0, code: entry.token, value: 0 }) {
81 +
            set store.entries[hart][i].owner = nil;
82 +
        }
83 +
    }
84 +
    return next;
85 +
}
test/dispatch/kernel/dispatchcheck.rad +15 -3
10 10
use kernel::loader;
11 11
use kernel::domains;
12 12
use kernel::budgets;
13 13
use kernel::dispatch;
14 14
use kernel::boot;
15 +
use kernel::events;
15 16
use kernel::dispatchinput;
16 17
17 18
/// Bootstrap authority used to create fixture domains and CPU windows.
18 19
unsafe static TABLE: capability::Table = undefined;
19 20
/// Domain handles used for budget binding.
28 29
fn worker() -> u64;
29 30
/// Native entry for the final assertion callback.
30 31
fn completion() -> u64;
31 32
/// Complete the machine fixture with a passing status.
32 33
fn finish();
34 +
/// Complete the fixture with a distinct failed invariant code.
35 +
fn failed(code: u64);
33 36
34 37
/// Prepare real private domains before enabling dispatch on hart zero.
35 38
export unsafe fn setup() {
36 39
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
37 40
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
119 122
export unsafe fn verify() {
120 123
    let user = domains::STORE.contexts[CONTEXTS[0].index].frame;
121 124
    let machine = domains::STORE.contexts[CONTEXTS[1].index].frame;
122 125
    assert (user.status & 0x1800) == 0 and (machine.status & 0x1800) == 0x1800;
123 126
    assert machine.registers[8] > 0 and machine.registers[9] == 1 and machine.registers[18] > 0;
124 -
    let domain = try! domains::get(&domains::STORE, domains::STORE.contexts[CONTEXTS[0].index].owner);
127 +
    let mut domain = try! domains::get(&domains::STORE, domains::STORE.contexts[CONTEXTS[0].index].owner);
125 128
    let target = try! registry::exported(&registry::STORE, domain.image, &"spin::extraCount"[..]);
126 129
    let case shared::Target::Data(data) = target else panic "extra counter";
127 130
    let bytes = @sliceOf(memory(domain.graph.table[data.slot] + data.offset as u64), 8);
128 131
    let mut progress: u64 = 0;
129 132
    for byte in 0..8 {
130 133
        set progress |= bytes[byte] as u64 << (byte as u64 * 8);
131 134
    }
132 -
    assert progress > 0;
135 +
    if progress == 0 {
136 +
        failed(6);
137 +
    }
133 138
    let checked = try! registry::exported(&registry::STORE, domain.image, &"spin::destroyed"[..]);
134 139
    let case shared::Target::Data(flag) = checked else panic "destruction flag";
135 -
    assert *memory(domain.graph.table[flag.slot] + flag.offset as u64) == 1;
140 +
    if *memory(domain.graph.table[flag.slot] + flag.offset as u64) <> 1 {
141 +
        failed(7);
142 +
    }
143 +
    let wake = try! events::pop(&mut domain.memory.ring) else panic "wake token";
144 +
    assert wake.kind == events::WAKEUP and wake.code == 123;
145 +
    let timeout = try! events::pop(&mut domain.memory.ring) else panic "timeout token";
146 +
    assert timeout.kind == events::TIMEOUT and timeout.code == 77 and timeout.value == 0;
147 +
    assert try! events::pop(&mut domain.memory.ring) == nil;
136 148
    finish();
137 149
}
test/dispatch/machine.ras +3 -0
26 26
.export @kernel::dispatchcheck::memory;
27 27
.export @kernel::dispatchcheck::kernelGp;
28 28
.export @kernel::dispatchcheck::worker;
29 29
.export @kernel::dispatchcheck::completion;
30 30
.export @kernel::dispatchcheck::finish;
31 +
.export @kernel::dispatchcheck::failed;
32 +
@kernel::dispatchcheck::failed
33 +
    j @failure;
31 34
@kernel::dispatchcheck::memory
32 35
    ret;
33 36
@kernel::dispatchcheck::kernelGp
34 37
    mv %a0 %gp;
35 38
    ret;
test/dispatch/spin.rad +33 -4
1 -
//! Non-yielding user code preempted by hardware timer boundaries.
1 +
//! User calls, targeted handoff, and timer preemption of busy loops.
2 2
export mod abi;
3 3
export mod sys;
4 4
/// RV64 direct-call intrinsic used to check the machine ABI.
5 5
@intrinsic fn ecall(number: u32, a0: i64, a1: i64, a2: i64, a3: i64) -> i64;
6 6
/// Local capabilities supplied to the user fixture.
32 32
export static count: u64 = 0;
33 33
/// Additional-context progress in the same private package state.
34 34
export static extraCount: u64 = 0;
35 35
/// Successful user-side return and destruction checks.
36 36
export static destroyed: u64 = 0;
37 +
/// Local self-Domain authority shared by both user contexts.
38 +
static SELF: abi::Handle = abi::Handle(0);
37 39
38 40
/// Update shared private state and return from an additional context.
39 41
export fn worker(args: *opaque, size: u64) {
40 42
    assert size == 0;
41 43
    set extraCount += 1;
44 +
    try! sys::timeout(1000000, 77);
45 +
    sys::wait();
46 +
    set extraCount += 1;
42 47
    set count += 1;
43 48
}
44 -
/// Check direct calls, then consume each window without yielding.
49 +
/// Check direct calls and handoff, then exercise independently bound contexts.
45 50
@default unsafe fn main(env: *Env) {
46 51
    if env.argsSize == 0 {
52 +
        try! sys::timeout(10000, 88);
53 +
        sys::wait();
47 54
        return;
48 55
    }
49 56
    assert env.argsSize == 32;
57 +
    set SELF = abi::Handle(env.argsPointer.domain);
50 58
    let handle = env.argsPointer.stack as i64;
51 59
    let info = sys::queryPage(abi::Handle(handle as u64));
52 60
    assert info.base <> 0 and info.count == 1;
53 61
    let mut rejected = false;
54 62
    try sys::pageAllocate(abi::Handle(0), 0) catch error {
97 105
    assert context.context == extra and context.domain == domain.id;
98 106
    assert context.stackBase == extraMemory.base and context.stackTop == extraMemory.base + 4096;
99 107
    let future = abi::Handle(env.argsPointer.future);
100 108
    let later = try! sys::queryBudget(future);
101 109
    let tail = try! sys::budgetSplit(future, later.start + (later.end - later.start) / 2);
102 -
    let childWindow = try! sys::budgetSplit(tail, later.start + (later.end - later.start) * 3 / 4);
103 -
    let resumption = try! sys::budgetSplit(childWindow, later.start + (later.end - later.start) * 7 / 8);
110 +
    let childWindow = try! sys::budgetSplit(tail, later.start + (later.end - later.start) * 5 / 8);
111 +
    let resumption = try! sys::budgetSplit(childWindow, later.start + (later.end - later.start) * 11 / 16);
112 +
    let awakened = try! sys::budgetSplit(resumption, later.start + (later.end - later.start) * 3 / 4);
113 +
    let finalWindow = try! sys::budgetSplit(awakened, later.start + (later.end - later.start) * 13 / 16);
104 114
    let bound = try! sys::budgetBind(tail, abi::Handle(env.argsPointer.domain), extra);
115 +
    let resumed = try! sys::budgetBind(awakened, SELF, extra);
105 116
    let initial = try! sys::queryContext(child, 0);
106 117
    let childBound = try! sys::budgetBind(childWindow, child, initial.context);
107 118
    let mut local: [u64; 2] = [0; 2];
108 119
    assert ecall(44, handle, (&mut local[0]) as i64, 16, 0) == 0;
109 120
    assert local[0] <> 0 and local[1] == 1;
110 121
    assert ecall(44, handle, (&mut INFO[0]) as i64, 16, 0) == 0;
111 122
    assert INFO[0] == local[0] and INFO[1] == local[1];
112 123
    assert ecall(44, handle, 0, 16, 0) == -4;
113 124
    assert ecall(0xffffffff, 0, 0, 0, 0) == -4;
125 +
    set rejected = false;
126 +
    try sys::yield(SELF) catch error {
127 +
        assert error == abi::Error::Busy;
128 +
        set rejected = true;
129 +
    };
130 +
    assert rejected and sys::currentContext().budget == current.budget;
131 +
    set count = 1;
132 +
    try! sys::yield(child);
133 +
    let continued = sys::currentContext();
134 +
    assert continued.context == current.context and continued.budget == *future;
135 +
    assert sys::queryDomain(child).state == 2;
114 136
    while extraCount == 0 {
115 137
        set count += 1;
116 138
    }
139 +
    let waiting = try! sys::queryContext(SELF, extra);
140 +
    assert waiting.state == 2 and waiting.hart == 0xffffffffffffffff;
141 +
    try! sys::wakeup(SELF, 123);
142 +
    assert extraCount == 1;
143 +
    while extraCount == 1 {
144 +
        set count += 1;
145 +
    }
117 146
    let stopped = try! sys::queryContext(abi::Handle(env.argsPointer.domain), extra);
118 147
    assert stopped.state == 1 and stopped.hart == 0xffffffffffffffff;
119 148
    try! sys::contextDestroy(abi::Handle(env.argsPointer.domain), extra);
120 149
    set rejected = false;
121 150
    try sys::queryContext(abi::Handle(env.argsPointer.domain), extra) catch error {