kernel: wait for events with bounded one-shot timeouts
e7e57c61bb88a4c566b9c9b6f82d47b9ab22041ed130b1b4ed7fb3c4012c9e24
Verified: make -C kernel check with the machine-capable emulator; all pass.
1 parent
8c203f66
kernel/Makefile
+5 -4
| 4 | 4 | COMPILER := ../bin/radiance.rv64.dev |
|
| 5 | 5 | COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER) |
|
| 6 | 6 | MODULES := core/fdt.rad core/platform.rad core/frames.rad core/abi.rad core/handles.rad \ |
|
| 7 | 7 | core/events.rad core/domains.rad core/resources.rad core/capabilities.rad \ |
|
| 8 | 8 | core/memory.rad core/state.rad core/pages.rad core/atomic.rad core/cpu.rad \ |
|
| 9 | - | core/budgets.rad core/contexts.rad core/clock.rad core/budget_caps.rad |
|
| 9 | + | core/budgets.rad core/contexts.rad core/clock.rad core/budget_caps.rad \ |
|
| 10 | + | core/timers.rad core/notifications.rad |
|
| 10 | 11 | CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras |
|
| 11 | 12 | CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM)) |
|
| 12 | 13 | CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad check/handles.rad \ |
|
| 13 | 14 | check/domains.rad check/capabilities.rad check/pages.rad check/events.rad \ |
|
| 14 | - | check/budgets.rad check/budget_caps.rad |
|
| 15 | + | check/budgets.rad check/budget_caps.rad check/timers.rad check/notifications.rad |
|
| 15 | 16 | ||
| 16 | 17 | .PHONY: all check clean compiler-check |
|
| 17 | 18 | all: kernel.rv64 |
|
| 18 | 19 | ||
| 19 | 20 | compiler-check: |
| 25 | 26 | $(COMPILE) $(CORE) -pkg kernel -start arch/entry.ras -mod main.rad -entry kernel -o $@ |
|
| 26 | 27 | ||
| 27 | 28 | check.rv64: check.rad core.rad $(MODULES) $(CORE_ASM) $(CHECK_MODULES) $(COMPILER) |
|
| 28 | 29 | $(COMPILE) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@ |
|
| 29 | 30 | ||
| 30 | - | context.rv64: context.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER) |
|
| 31 | - | $(COMPILE) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod check/context.ras -entry context -o $@ |
|
| 31 | + | context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER) |
|
| 32 | + | $(COMPILE) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@ |
|
| 32 | 33 | ||
| 33 | 34 | check: all check.rv64 context.rv64 |
|
| 34 | 35 | $(HOST_EMU) -run check.rv64 |
|
| 35 | 36 | $(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions -run kernel.rv64 |
|
| 36 | 37 | $(EMU) -machine -no-guard-stack -max-steps=1000000 -count-instructions -run context.rv64 |
kernel/NOTES.md
+15 -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 12 of the 22-step plan. |
|
| 5 | + | record the contracts established through step 13 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. |
| 160 | 160 | charges the monotonic interval once, including transition overhead, saturating |
|
| 161 | 161 | at zero. Empty, wrong-hart, incorrectly bound, or non-ready contexts cannot run. |
|
| 162 | 162 | - Creating a context or publishing an event supplies no ticks. Checks exercise |
|
| 163 | 163 | conservation, failed operations, retained bindings, and actual CLINT preemption. |
|
| 164 | 164 | ||
| 165 | + | ## Waits and one-shot timeouts |
|
| 166 | + | ||
| 167 | + | - Each hart owns 64 timeout slots. An accepted timeout remains allocated until |
|
| 168 | + | publication succeeds or its exact owner incarnation dies. Busy cannot erase |
|
| 169 | + | accepted work. Rotating delivery prevents low-slot reuse from starving it. |
|
| 170 | + | - Program the earlier of the current budget deadline and a future timeout. |
|
| 171 | + | Already-due backpressured timeouts do not cause interrupt spin; kernel entry |
|
| 172 | + | retries publication. |
|
| 173 | + | - Wait validates consumer progress before blocking. Resumption requires an |
|
| 174 | + | unread event and positive retained budget. Wakeup requires an explicit Wake |
|
| 175 | + | handle and records the sender identity. Idle waiting does not consume ticks |
|
| 176 | + | from the suspended context's budget. |
|
| 177 | + | ||
| 165 | 178 | ## Validation |
|
| 166 | 179 | ||
| 167 | 180 | Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass |
|
| 168 | 181 | `EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build |
|
| 169 | 182 | checks compiler dependencies. From the repository root, run: |
|
| 170 | 183 | ||
| 171 | 184 | ```sh |
|
| 172 | 185 | make -C kernel check |
|
| 173 | 186 | ``` |
|
| 174 | 187 | ||
| 175 | - | Exercise budget conservation and failed split/bind operations; preempt finite computation through CLINT and reject execution after exhaustion. |
|
| 188 | + | Exercise Timeout, Wait, machine idle, timer expiry, event consumption, retained notifications, and unchanged budget during idle. |
|
| 176 | 189 | ||
| 177 | 190 | Run the context reservation probe with an emulator that retains LR/SC |
|
| 178 | 191 | reservations across traps. This checks the kernel's reservation invalidation. |
kernel/check.rad
+4 -0
| 8 | 8 | mod capabilities; |
|
| 9 | 9 | mod pages; |
|
| 10 | 10 | mod events; |
|
| 11 | 11 | mod budgets; |
|
| 12 | 12 | mod budget_caps; |
|
| 13 | + | mod timers; |
|
| 14 | + | mod notifications; |
|
| 13 | 15 | ||
| 14 | 16 | /// Run the available kernel mechanism checks. |
|
| 15 | 17 | @default fn main() -> u32 { |
|
| 16 | 18 | frames::run(); |
|
| 17 | 19 | boot::run(); |
| 20 | 22 | capabilities::run(); |
|
| 21 | 23 | pages::run(); |
|
| 22 | 24 | events::run(); |
|
| 23 | 25 | budgets::run(); |
|
| 24 | 26 | budget_caps::run(); |
|
| 27 | + | timers::run(); |
|
| 28 | + | notifications::run(); |
|
| 25 | 29 | return 0; |
|
| 26 | 30 | } |
kernel/check/context.ras
+39 -0
| 95 | 95 | li %t0 1; |
|
| 96 | 96 | sd %t0 0(%a0); |
|
| 97 | 97 | lr.d %a1 0(%a0); |
|
| 98 | 98 | ecall; |
|
| 99 | 99 | ||
| 100 | + | .export @"context::wait::entry"; |
|
| 101 | + | @"context::wait::entry" |
|
| 102 | + | la %a0 @wait_entry; |
|
| 103 | + | ret; |
|
| 104 | + | @wait_entry |
|
| 105 | + | li %a0 10000; |
|
| 106 | + | li %a1 7; |
|
| 107 | + | li %a7 41; |
|
| 108 | + | ecall; |
|
| 109 | + | li %a7 42; |
|
| 110 | + | ecall; |
|
| 111 | + | ld %t0 24(%tp); |
|
| 112 | + | li %t2 4096; |
|
| 113 | + | add %t2 %t0 %t2; |
|
| 114 | + | lwu %t1 0(%t2); |
|
| 115 | + | lwu %t3 4(%t2); |
|
| 116 | + | fence; |
|
| 117 | + | beq %t1 %t3 @wait_failed; |
|
| 118 | + | andi %t4 %t1 255; |
|
| 119 | + | slli %t4 %t4 4; |
|
| 120 | + | add %t4 %t0 %t4; |
|
| 121 | + | lhu %t5 0(%t4); |
|
| 122 | + | li %t6 2; |
|
| 123 | + | bne %t5 %t6 @wait_failed; |
|
| 124 | + | lwu %t5 4(%t4); |
|
| 125 | + | li %t6 7; |
|
| 126 | + | bne %t5 %t6 @wait_failed; |
|
| 127 | + | ld %t5 8(%t4); |
|
| 128 | + | bnez %t5 @wait_failed; |
|
| 129 | + | addi %t1 %t1 1; |
|
| 130 | + | fence; |
|
| 131 | + | sw %t1 0(%t2); |
|
| 132 | + | li %a0 42; |
|
| 133 | + | li %a7 49; |
|
| 134 | + | ecall; |
|
| 135 | + | @wait_failed |
|
| 136 | + | li %a0 100; |
|
| 137 | + | li %a7 49; |
|
| 138 | + | ecall; |
kernel/check/notifications.rad
added
+105 -0
| 1 | + | //! Wait predicates, explicit Wake authority, and budget-neutral notification. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::atomic; |
|
| 5 | + | use core::budget_caps; |
|
| 6 | + | use core::budgets; |
|
| 7 | + | use core::capabilities; |
|
| 8 | + | use core::contexts; |
|
| 9 | + | use core::domains; |
|
| 10 | + | use core::events; |
|
| 11 | + | use core::frames; |
|
| 12 | + | use core::handles; |
|
| 13 | + | use core::memory; |
|
| 14 | + | use core::notifications; |
|
| 15 | + | use core::platform; |
|
| 16 | + | use core::resources; |
|
| 17 | + | use core::state; |
|
| 18 | + | ||
| 19 | + | /// Independent sender and receiver domain storage. |
|
| 20 | + | static DOMAINS: [domains::Domain; 2] = undefined; |
|
| 21 | + | /// Reserve and execution-loan resource slots. |
|
| 22 | + | static OBJECTS: [resources::Slot; 3] = undefined; |
|
| 23 | + | /// Waiting receiver context. |
|
| 24 | + | static CONTEXTS: [contexts::Context; 1] = undefined; |
|
| 25 | + | /// Empty, valid frame metadata for this notification-only fixture. |
|
| 26 | + | static POOL: frames::Pool = undefined; |
|
| 27 | + | /// Empty-pool pin counts. |
|
| 28 | + | static PINS: [u16; 1] = undefined; |
|
| 29 | + | /// Empty-pool claims. |
|
| 30 | + | static ASSIGNED: [bool; 1] = undefined; |
|
| 31 | + | /// Empty-pool grants. |
|
| 32 | + | static GRANTS: [u64; 2] = undefined; |
|
| 33 | + | /// Kernel mechanism storage. |
|
| 34 | + | static KERNEL: state::State = undefined; |
|
| 35 | + | ||
| 36 | + | /// Consume the receiver's next Wakeup with acquire/release progress. |
|
| 37 | + | fn consume(token: u32, sender: u32) { |
|
| 38 | + | let queue = &mut DOMAINS[1].events; |
|
| 39 | + | let head = atomic::load(&queue.ring.head); |
|
| 40 | + | assert head <> atomic::load(&queue.ring.tail); |
|
| 41 | + | let event = queue.ring.data[head & (events::CAPACITY - 1)]; |
|
| 42 | + | assert event.kind == 5 and event.code == token and event.value == sender as u64; |
|
| 43 | + | atomic::store(&mut queue.ring.head, head + 1); |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | /// Reject the identity-only sentinel where an explicit Wake capability is required. |
|
| 47 | + | fn noAuthority() { |
|
| 48 | + | try notifications::wakeup(&mut KERNEL, 0, abi::Handle { bits: 0 }, 99) catch error { |
|
| 49 | + | assert error == abi::Error::BadHandle; |
|
| 50 | + | return; |
|
| 51 | + | }; |
|
| 52 | + | panic "noAuthority: sentinel authorized a wakeup"; |
|
| 53 | + | } |
|
| 54 | + | ||
| 55 | + | /// A retained event cannot wake an exhausted context until authority is assigned. |
|
| 56 | + | export fn run() { |
|
| 57 | + | domains::init(&mut DOMAINS[..]); |
|
| 58 | + | contexts::init(&mut CONTEXTS[0], 0); |
|
| 59 | + | resources::init(&mut OBJECTS[..]); |
|
| 60 | + | let mut machine: platform::Platform = undefined; |
|
| 61 | + | set machine.memoryCount = 0; |
|
| 62 | + | set machine.reservedCount = 0; |
|
| 63 | + | try! frames::init(&mut POOL, &machine); |
|
| 64 | + | let mut ram: memory::Memory = undefined; |
|
| 65 | + | memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2); |
|
| 66 | + | set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram }; |
|
| 67 | + | let rootHandle = domains::root(&mut DOMAINS[..], 0); |
|
| 68 | + | let root = try! domains::resolve(&DOMAINS[..], 0, rootHandle, abi::CREATE); |
|
| 69 | + | let child = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 70 | + | let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS); |
|
| 71 | + | set DOMAINS[0].state = domains::Lifecycle::Active; |
|
| 72 | + | set DOMAINS[1].state = domains::Lifecycle::Active; |
|
| 73 | + | try! contexts::prepare(&mut CONTEXTS[0], child, 0, 0, DOMAINS[1].env); |
|
| 74 | + | let id = contexts::identity(&CONTEXTS[0]); |
|
| 75 | + | let object = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 2000))); |
|
| 76 | + | resources::retain(&mut OBJECTS[..], object); |
|
| 77 | + | let reserve = handles::install(&mut DOMAINS[0].handles, 3, object, abi::READ | abi::WRITE | abi::EXECUTE); |
|
| 78 | + | let loan = try! budget_caps::split(&mut KERNEL, 0, reserve, 1000); |
|
| 79 | + | try! budget_caps::bind(&mut KERNEL, 0, loan, target, id); |
|
| 80 | + | let own = try! capabilities::grant(&mut KERNEL, 0, target, target, abi::WAKE as u64); |
|
| 81 | + | noAuthority(); |
|
| 82 | + | try! notifications::wait(&mut KERNEL, 0); |
|
| 83 | + | assert CONTEXTS[0].status == contexts::Status::Waiting; |
|
| 84 | + | try! notifications::wakeup(&mut KERNEL, 1, own, 77); |
|
| 85 | + | assert notifications::poll(&mut KERNEL) == nil; |
|
| 86 | + | assert CONTEXTS[0].status == contexts::Status::Ready; |
|
| 87 | + | consume(77, 1); |
|
| 88 | + | let budget = budget_caps::value(&mut KERNEL, CONTEXTS[0].budget); |
|
| 89 | + | let running = try! contexts::begin(&mut CONTEXTS[0], budget, 1, 0); |
|
| 90 | + | assert contexts::finish(&mut CONTEXTS[0], budget, running, 1001) == 0; |
|
| 91 | + | try! notifications::wait(&mut KERNEL, 0); |
|
| 92 | + | try! notifications::wakeup(&mut KERNEL, 0, target, 42); |
|
| 93 | + | assert notifications::poll(&mut KERNEL) == nil; |
|
| 94 | + | assert CONTEXTS[0].status == contexts::Status::Waiting and budget.remaining == 0; |
|
| 95 | + | let more = try! budget_caps::split(&mut KERNEL, 0, reserve, 500); |
|
| 96 | + | try! budget_caps::bind(&mut KERNEL, 0, more, target, id); |
|
| 97 | + | assert notifications::poll(&mut KERNEL) == nil; |
|
| 98 | + | try! notifications::wait(&mut KERNEL, 0); |
|
| 99 | + | assert CONTEXTS[0].status == contexts::Status::Ready; |
|
| 100 | + | consume(42, 0); |
|
| 101 | + | try! notifications::wait(&mut KERNEL, 0); |
|
| 102 | + | atomic::store(&mut DOMAINS[1].events.ring.head, 3); |
|
| 103 | + | let bad = notifications::poll(&mut KERNEL) else { panic "run: corrupt wait queue was accepted"; }; |
|
| 104 | + | assert bad.index == child.index and bad.epoch == child.epoch; |
|
| 105 | + | } |
kernel/check/timers.rad
added
+232 -0
| 1 | + | //! One-shot timeout delivery, bounded retention, and incarnation isolation. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::atomic; |
|
| 5 | + | use core::domains; |
|
| 6 | + | use core::events; |
|
| 7 | + | use core::handles; |
|
| 8 | + | use core::timers; |
|
| 9 | + | ||
| 10 | + | /// Real domain storage kept off the boot check stack. |
|
| 11 | + | static DOMAINS: [domains::Domain; 2] = undefined; |
|
| 12 | + | ||
| 13 | + | /// Reset private stores and return root's live domain identity. |
|
| 14 | + | fn setup(store: *mut timers::Store) -> abi::Object { |
|
| 15 | + | timers::init(store); |
|
| 16 | + | domains::init(&mut DOMAINS[..]); |
|
| 17 | + | let root = domains::root(&mut DOMAINS[..], 0); |
|
| 18 | + | return try! domains::resolve(&DOMAINS[..], 0, root, abi::CREATE); |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Acquire a complete notification and release the consumer's new position. |
|
| 22 | + | fn pop(queue: *mut events::Queue) -> events::Event { |
|
| 23 | + | let head = atomic::load(&queue.ring.head); |
|
| 24 | + | assert head <> atomic::load(&queue.ring.tail); |
|
| 25 | + | let event = queue.ring.data[head & queue.ring.mask]; |
|
| 26 | + | atomic::store(&mut queue.ring.head, ((head as u64 + 1) & 0xffffffff) as u32); |
|
| 27 | + | return event; |
|
| 28 | + | } |
|
| 29 | + | ||
| 30 | + | /// Observe published producer progress without touching private queue counters. |
|
| 31 | + | fn empty(queue: *events::Queue) -> bool { |
|
| 32 | + | return atomic::load(&queue.ring.head) == atomic::load(&queue.ring.tail); |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Require the complete public Timeout payload, including its reserved fields. |
|
| 36 | + | fn timeout(queue: *mut events::Queue, token: u32) { |
|
| 37 | + | let event = pop(queue); |
|
| 38 | + | assert event.kind == 2 and event.reserved == 0; |
|
| 39 | + | assert event.code == token and event.value == 0; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Require rejection instead of accepting an additional timeout. |
|
| 43 | + | fn rejected(store: *mut timers::Store, owner: abi::Object, now: u64, delay: u64, expected: abi::Error) { |
|
| 44 | + | try timers::arm(store, owner, now, delay, 0xffffffff) catch error { |
|
| 45 | + | assert error == expected; |
|
| 46 | + | return; |
|
| 47 | + | }; |
|
| 48 | + | panic "rejected: invalid or full timer store accepted a timeout"; |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Future deadlines do not publish early, and late entries still publish once. |
|
| 52 | + | fn timing() { |
|
| 53 | + | let mut store: timers::Store = undefined; |
|
| 54 | + | let root = setup(&mut store); |
|
| 55 | + | let queue = &mut DOMAINS[0].events; |
|
| 56 | + | assert timers::next(&store, 100) == 0xffffffffffffffff; |
|
| 57 | + | try! timers::arm(&mut store, root, 100, 10, 0xfedcba98); |
|
| 58 | + | try! timers::arm(&mut store, root, 100, 5, 0); |
|
| 59 | + | assert timers::next(&store, 100) == 105; |
|
| 60 | + | timers::deliver(&mut store, 104, &mut DOMAINS[..]); |
|
| 61 | + | assert empty(queue); |
|
| 62 | + | timers::deliver(&mut store, 105, &mut DOMAINS[..]); |
|
| 63 | + | timeout(queue, 0); |
|
| 64 | + | assert empty(queue) and timers::next(&store, 105) == 110; |
|
| 65 | + | timers::deliver(&mut store, 109, &mut DOMAINS[..]); |
|
| 66 | + | assert empty(queue); |
|
| 67 | + | timers::deliver(&mut store, 120, &mut DOMAINS[..]); |
|
| 68 | + | timeout(queue, 0xfedcba98); |
|
| 69 | + | timers::deliver(&mut store, 121, &mut DOMAINS[..]); |
|
| 70 | + | assert empty(queue) and timers::next(&store, 121) == 0xffffffffffffffff; |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Full-store rejection leaves every accepted token intact and frees no slot. |
|
| 74 | + | fn capacity() { |
|
| 75 | + | let mut store: timers::Store = undefined; |
|
| 76 | + | let root = setup(&mut store); |
|
| 77 | + | let queue = &mut DOMAINS[0].events; |
|
| 78 | + | for i in 0..timers::CAPACITY { try! timers::arm(&mut store, root, 0, 10, i); } |
|
| 79 | + | rejected(&mut store, root, 0, 1, abi::Error::Busy); |
|
| 80 | + | assert timers::next(&store, 0) == 10; |
|
| 81 | + | timers::deliver(&mut store, 10, &mut DOMAINS[..]); |
|
| 82 | + | for i in 0..timers::CAPACITY { timeout(queue, i); } |
|
| 83 | + | assert empty(queue); |
|
| 84 | + | try! timers::arm(&mut store, root, 10, 1, 64); |
|
| 85 | + | timers::deliver(&mut store, 11, &mut DOMAINS[..]); |
|
| 86 | + | timeout(queue, 64); |
|
| 87 | + | timers::deliver(&mut store, 12, &mut DOMAINS[..]); |
|
| 88 | + | assert empty(queue); |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// A completely full real ring retains an expired timer until consumption. |
|
| 92 | + | fn backpressure() { |
|
| 93 | + | let mut store: timers::Store = undefined; |
|
| 94 | + | let root = setup(&mut store); |
|
| 95 | + | let queue = &mut DOMAINS[0].events; |
|
| 96 | + | for i in 0..events::CAPACITY - events::CRITICAL { |
|
| 97 | + | try! events::push(queue, events::Event { kind: 5, reserved: 0, code: i, value: i as u64 }, events::Class::Ordinary); |
|
| 98 | + | } |
|
| 99 | + | for i in 0..events::CRITICAL { |
|
| 100 | + | try! events::reserve(queue); |
|
| 101 | + | try! events::push(queue, events::Event { kind: 4, reserved: 0, code: i, value: i as u64 }, events::Class::Critical); |
|
| 102 | + | } |
|
| 103 | + | try! timers::arm(&mut store, root, 0, 5, 0x12345678); |
|
| 104 | + | try! timers::arm(&mut store, root, 0, 20, 20); |
|
| 105 | + | timers::deliver(&mut store, 5, &mut DOMAINS[..]); |
|
| 106 | + | timers::deliver(&mut store, 6, &mut DOMAINS[..]); |
|
| 107 | + | assert atomic::load(&queue.ring.tail) == events::CAPACITY; |
|
| 108 | + | assert timers::next(&store, 6) == 20; |
|
| 109 | + | assert pop(queue).kind == 5; |
|
| 110 | + | timers::deliver(&mut store, 7, &mut DOMAINS[..]); |
|
| 111 | + | timers::deliver(&mut store, 8, &mut DOMAINS[..]); |
|
| 112 | + | assert atomic::load(&queue.ring.tail) == events::CAPACITY + 1; |
|
| 113 | + | for i in 1..events::CAPACITY - events::CRITICAL { |
|
| 114 | + | let event = pop(queue); |
|
| 115 | + | assert event.kind == 5 and event.code == i and event.value == i as u64; |
|
| 116 | + | } |
|
| 117 | + | for i in 0..events::CRITICAL { |
|
| 118 | + | let event = pop(queue); |
|
| 119 | + | assert event.kind == 4 and event.code == i and event.value == i as u64; |
|
| 120 | + | } |
|
| 121 | + | timeout(queue, 0x12345678); |
|
| 122 | + | assert empty(queue); |
|
| 123 | + | timers::deliver(&mut store, 20, &mut DOMAINS[..]); |
|
| 124 | + | timeout(queue, 20); |
|
| 125 | + | timers::deliver(&mut store, 21, &mut DOMAINS[..]); |
|
| 126 | + | assert empty(queue) and timers::next(&store, 21) == 0xffffffffffffffff; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | /// A reused low slot cannot steal the next free event position from a survivor. |
|
| 130 | + | fn rotation() { |
|
| 131 | + | let mut store: timers::Store = undefined; |
|
| 132 | + | let root = setup(&mut store); |
|
| 133 | + | let queue = &mut DOMAINS[0].events; |
|
| 134 | + | for i in 0..events::CAPACITY - events::CRITICAL - 1 { |
|
| 135 | + | try! events::push(queue, events::Event { kind: 5, reserved: 0, code: i, value: 0 }, events::Class::Ordinary); |
|
| 136 | + | } |
|
| 137 | + | try! timers::arm(&mut store, root, 0, 1, 10); |
|
| 138 | + | try! timers::arm(&mut store, root, 0, 1, 11); |
|
| 139 | + | timers::deliver(&mut store, 1, &mut DOMAINS[..]); |
|
| 140 | + | assert timers::next(&store, 1) == 0xffffffffffffffff; |
|
| 141 | + | assert pop(queue).kind == 5; |
|
| 142 | + | try! timers::arm(&mut store, root, 1, 1, 12); |
|
| 143 | + | timers::deliver(&mut store, 2, &mut DOMAINS[..]); |
|
| 144 | + | for i in 1..events::CAPACITY - events::CRITICAL - 1 { assert pop(queue).code == i; } |
|
| 145 | + | timeout(queue, 10); |
|
| 146 | + | timeout(queue, 11); |
|
| 147 | + | assert empty(queue); |
|
| 148 | + | timers::deliver(&mut store, 3, &mut DOMAINS[..]); |
|
| 149 | + | timeout(queue, 12); |
|
| 150 | + | timers::deliver(&mut store, 4, &mut DOMAINS[..]); |
|
| 151 | + | assert empty(queue); |
|
| 152 | + | } |
|
| 153 | + | ||
| 154 | + | /// Delay arithmetic rejects wraparound but accepts the last representable tick. |
|
| 155 | + | fn deadlines() { |
|
| 156 | + | let mut store: timers::Store = undefined; |
|
| 157 | + | let root = setup(&mut store); |
|
| 158 | + | let queue = &mut DOMAINS[0].events; |
|
| 159 | + | rejected(&mut store, root, 0, 0, abi::Error::InvalidArg); |
|
| 160 | + | rejected(&mut store, root, 0xffffffffffffffff, 1, abi::Error::InvalidArg); |
|
| 161 | + | rejected(&mut store, root, 0xfffffffffffffffe, 2, abi::Error::InvalidArg); |
|
| 162 | + | try! timers::arm(&mut store, root, 0xfffffffffffffffe, 1, 99); |
|
| 163 | + | timers::deliver(&mut store, 0xfffffffffffffffe, &mut DOMAINS[..]); |
|
| 164 | + | assert empty(queue); |
|
| 165 | + | timers::deliver(&mut store, 0xffffffffffffffff, &mut DOMAINS[..]); |
|
| 166 | + | timeout(queue, 99); |
|
| 167 | + | timers::deliver(&mut store, 0xffffffffffffffff, &mut DOMAINS[..]); |
|
| 168 | + | assert empty(queue); |
|
| 169 | + | } |
|
| 170 | + | ||
| 171 | + | /// Cancellation compares kind, index, and epoch, never merely a domain slot. |
|
| 172 | + | fn cancellation() { |
|
| 173 | + | let mut store: timers::Store = undefined; |
|
| 174 | + | let root = setup(&mut store); |
|
| 175 | + | let child = try! domains::create(&mut DOMAINS[..], root, 1); |
|
| 176 | + | try! timers::arm(&mut store, root, 0, 5, 1); |
|
| 177 | + | try! timers::arm(&mut store, child, 0, 5, 2); |
|
| 178 | + | timers::cancel(&mut store, root); |
|
| 179 | + | timers::cancel(&mut store, abi::Object { kind: abi::Kind::Events, index: child.index, epoch: child.epoch }); |
|
| 180 | + | timers::cancel(&mut store, abi::Object { kind: child.kind, index: child.index, epoch: child.epoch + 1 }); |
|
| 181 | + | timers::deliver(&mut store, 5, &mut DOMAINS[..]); |
|
| 182 | + | assert empty(&DOMAINS[root.index].events); |
|
| 183 | + | timeout(&mut DOMAINS[child.index].events, 2); |
|
| 184 | + | try! timers::arm(&mut store, child, 5, 1, 3); |
|
| 185 | + | try! timers::arm(&mut store, child, 5, 2, 4); |
|
| 186 | + | timers::cancel(&mut store, child); |
|
| 187 | + | timers::deliver(&mut store, 7, &mut DOMAINS[..]); |
|
| 188 | + | assert empty(&DOMAINS[child.index].events); |
|
| 189 | + | assert timers::next(&store, 7) == 0xffffffffffffffff; |
|
| 190 | + | } |
|
| 191 | + | ||
| 192 | + | /// Reallocated domain slots cannot receive old timeouts or old cancellation. |
|
| 193 | + | fn incarnations() { |
|
| 194 | + | let mut store: timers::Store = undefined; |
|
| 195 | + | let root = setup(&mut store); |
|
| 196 | + | let old = try! domains::create(&mut DOMAINS[..], root, 1); |
|
| 197 | + | try! timers::arm(&mut store, old, 0, 5, 1); |
|
| 198 | + | set DOMAINS[old.index].state = domains::Lifecycle::Dead; |
|
| 199 | + | handles::init(&mut DOMAINS[old.index].handles); |
|
| 200 | + | let current = try! domains::create(&mut DOMAINS[..], root, 2); |
|
| 201 | + | assert current.index == old.index and current.epoch <> old.epoch; |
|
| 202 | + | try! timers::arm(&mut store, current, 0, 5, 2); |
|
| 203 | + | timers::deliver(&mut store, 5, &mut DOMAINS[..]); |
|
| 204 | + | timeout(&mut DOMAINS[current.index].events, 2); |
|
| 205 | + | assert empty(&DOMAINS[current.index].events); |
|
| 206 | + | try! timers::arm(&mut store, current, 5, 5, 3); |
|
| 207 | + | timers::cancel(&mut store, old); |
|
| 208 | + | timers::deliver(&mut store, 10, &mut DOMAINS[..]); |
|
| 209 | + | timeout(&mut DOMAINS[current.index].events, 3); |
|
| 210 | + | try! timers::arm(&mut store, current, 10, 10, 4); |
|
| 211 | + | set DOMAINS[current.index].state = domains::Lifecycle::Dying; |
|
| 212 | + | timers::deliver(&mut store, 11, &mut DOMAINS[..]); |
|
| 213 | + | assert timers::next(&store, 11) == 0xffffffffffffffff; |
|
| 214 | + | set DOMAINS[current.index].state = domains::Lifecycle::Active; |
|
| 215 | + | timers::deliver(&mut store, 20, &mut DOMAINS[..]); |
|
| 216 | + | assert empty(&DOMAINS[current.index].events); |
|
| 217 | + | try! timers::arm(&mut store, abi::Object { kind: abi::Kind::Domain, index: abi::MAX_DOMAINS - 1, epoch: 1 }, 20, 1, 5); |
|
| 218 | + | timers::deliver(&mut store, 21, &mut DOMAINS[..]); |
|
| 219 | + | assert timers::next(&store, 21) == 0xffffffffffffffff; |
|
| 220 | + | rejected(&mut store, domains::none(), 21, 1, abi::Error::BadHandle); |
|
| 221 | + | } |
|
| 222 | + | ||
| 223 | + | /// Exercise timeout acceptance and publication using real domain event stores. |
|
| 224 | + | export fn run() { |
|
| 225 | + | timing(); |
|
| 226 | + | capacity(); |
|
| 227 | + | backpressure(); |
|
| 228 | + | rotation(); |
|
| 229 | + | deadlines(); |
|
| 230 | + | cancellation(); |
|
| 231 | + | incarnations(); |
|
| 232 | + | } |
kernel/context.rad
+3 -0
| 5 | 5 | use core::cpu; |
|
| 6 | 6 | use core::budgets; |
|
| 7 | 7 | use core::clock; |
|
| 8 | 8 | use core::contexts; |
|
| 9 | 9 | ||
| 10 | + | mod wait; |
|
| 11 | + | ||
| 10 | 12 | /// Address of the fixed user register and fault probe. |
|
| 11 | 13 | fn entry() -> u64; |
|
| 12 | 14 | /// Instruction address of the first user ecall. |
|
| 13 | 15 | fn checkpoint() -> u64; |
|
| 14 | 16 | /// Arm hart zero's machine timer for the idle-path check. |
| 115 | 117 | clock::arm(0x02000000, 0, 0xffffffffffffffff); |
|
| 116 | 118 | assert contexts::finish(&mut context, &mut budget, running, stopped) == 0; |
|
| 117 | 119 | assert context.frame.cause == 0x8000000000000007; |
|
| 118 | 120 | assert context.frame.registers[10] > 0 and context.frame.registers[10] <= 500; |
|
| 119 | 121 | exhausted(&mut context, &mut budget, stopped); |
|
| 122 | + | wait::run(); |
|
| 120 | 123 | return 0; |
|
| 121 | 124 | } |
kernel/context/wait.rad
added
+112 -0
| 1 | + | //! Real Timeout/Wait transitions with a retained finite budget and an idle hart. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::atomic; |
|
| 5 | + | use core::budget_caps; |
|
| 6 | + | use core::budgets; |
|
| 7 | + | use core::clock; |
|
| 8 | + | use core::contexts; |
|
| 9 | + | use core::cpu; |
|
| 10 | + | use core::domains; |
|
| 11 | + | use core::frames; |
|
| 12 | + | use core::handles; |
|
| 13 | + | use core::memory; |
|
| 14 | + | use core::notifications; |
|
| 15 | + | use core::platform; |
|
| 16 | + | use core::resources; |
|
| 17 | + | use core::state; |
|
| 18 | + | use core::timers; |
|
| 19 | + | ||
| 20 | + | /// Trusted test program that requests a timeout and consumes its event after Wait. |
|
| 21 | + | fn entry() -> u64; |
|
| 22 | + | /// One complete domain and event queue. |
|
| 23 | + | static DOMAINS: [domains::Domain; 1] = undefined; |
|
| 24 | + | /// One retained execution budget. |
|
| 25 | + | static OBJECTS: [resources::Slot; 1] = undefined; |
|
| 26 | + | /// The user context suspended across Wait. |
|
| 27 | + | static CONTEXTS: [contexts::Context; 1] = undefined; |
|
| 28 | + | /// Valid empty RAM accounting for this machine-only control-plane check. |
|
| 29 | + | static POOL: frames::Pool = undefined; |
|
| 30 | + | /// Empty-pool pins. |
|
| 31 | + | static PINS: [u16; 1] = undefined; |
|
| 32 | + | /// Empty-pool claims. |
|
| 33 | + | static ASSIGNED: [bool; 1] = undefined; |
|
| 34 | + | /// Empty-pool grants. |
|
| 35 | + | static GRANTS: [u64; 1] = undefined; |
|
| 36 | + | /// Private one-shot timer storage. |
|
| 37 | + | static TIMERS: timers::Store = undefined; |
|
| 38 | + | /// Shared kernel mechanisms. |
|
| 39 | + | static KERNEL: state::State = undefined; |
|
| 40 | + | ||
| 41 | + | /// Resume one authorized user interval until a real trap and charge it once. |
|
| 42 | + | unsafe fn step() -> u64 { |
|
| 43 | + | let context = &mut CONTEXTS[0]; |
|
| 44 | + | let budget = budget_caps::value(&mut KERNEL, context.budget); |
|
| 45 | + | let start = clock::read(0x02000000); |
|
| 46 | + | let running = try! contexts::begin(context, budget, start, 0); |
|
| 47 | + | let timeout = timers::next(&TIMERS, start); |
|
| 48 | + | let mut deadline = context.deadline; |
|
| 49 | + | if timeout < deadline { set deadline = timeout; } |
|
| 50 | + | clock::arm(0x02000000, 0, deadline); |
|
| 51 | + | clock::interrupts(128); |
|
| 52 | + | cpu::enter(&mut context.frame); |
|
| 53 | + | let now = clock::read(0x02000000); |
|
| 54 | + | clock::interrupts(0); |
|
| 55 | + | assert contexts::finish(context, budget, running, now) > 0; |
|
| 56 | + | return now; |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// An idle interval neither consumes nor replenishes the waiting context's budget. |
|
| 60 | + | export unsafe fn run() { |
|
| 61 | + | domains::init(&mut DOMAINS[..]); |
|
| 62 | + | contexts::init(&mut CONTEXTS[0], 0); |
|
| 63 | + | resources::init(&mut OBJECTS[..]); |
|
| 64 | + | timers::init(&mut TIMERS); |
|
| 65 | + | let mut machine: platform::Platform = undefined; |
|
| 66 | + | set machine.memoryCount = 0; |
|
| 67 | + | set machine.reservedCount = 0; |
|
| 68 | + | try! frames::init(&mut POOL, &machine); |
|
| 69 | + | let mut ram: memory::Memory = undefined; |
|
| 70 | + | memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 1); |
|
| 71 | + | set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram }; |
|
| 72 | + | let handle = domains::root(&mut DOMAINS[..], 0); |
|
| 73 | + | let root = try! domains::resolve(&DOMAINS[..], 0, handle, abi::EXECUTE); |
|
| 74 | + | set DOMAINS[0].state = domains::Lifecycle::Active; |
|
| 75 | + | set DOMAINS[0].env.eventsPointer = &DOMAINS[0].events.ring as u64; |
|
| 76 | + | try! contexts::prepare(&mut CONTEXTS[0], root, entry(), 0, DOMAINS[0].env); |
|
| 77 | + | let object = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 100000))); |
|
| 78 | + | resources::retain(&mut OBJECTS[..], object); |
|
| 79 | + | let authority = handles::install(&mut DOMAINS[0].handles, 2, object, abi::EXECUTE); |
|
| 80 | + | try! budget_caps::bind(&mut KERNEL, 0, authority, handle, contexts::identity(&CONTEXTS[0])); |
|
| 81 | + | ||
| 82 | + | let now = step(); |
|
| 83 | + | let frame = &mut CONTEXTS[0].frame; |
|
| 84 | + | assert frame.cause == 8 and frame.registers[17] == 41; |
|
| 85 | + | try! timers::arm(&mut TIMERS, root, now, frame.registers[10], frame.registers[11] as u32); |
|
| 86 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 87 | + | let blocked = step(); |
|
| 88 | + | assert frame.cause == 8 and frame.registers[17] == 42; |
|
| 89 | + | cpu::reply(frame, abi::Error::Ok, 0, 0, 0, 0); |
|
| 90 | + | try! notifications::wait(&mut KERNEL, 0); |
|
| 91 | + | assert CONTEXTS[0].status == contexts::Status::Waiting; |
|
| 92 | + | let remaining = budget_caps::value(&mut KERNEL, object).remaining; |
|
| 93 | + | let deadline = timers::next(&TIMERS, blocked); |
|
| 94 | + | clock::arm(0x02000000, 0, deadline); |
|
| 95 | + | clock::interrupts(128); |
|
| 96 | + | let mut idle: cpu::Frame = undefined; |
|
| 97 | + | cpu::init(&mut idle, 0, 0, 0, 0); |
|
| 98 | + | cpu::idle(&mut idle); |
|
| 99 | + | let expired = clock::read(0x02000000); |
|
| 100 | + | clock::interrupts(0); |
|
| 101 | + | clock::arm(0x02000000, 0, 0xffffffffffffffff); |
|
| 102 | + | assert idle.cause == 0x8000000000000007 and expired >= deadline; |
|
| 103 | + | assert budget_caps::value(&mut KERNEL, object).remaining == remaining; |
|
| 104 | + | timers::deliver(&mut TIMERS, expired, &mut DOMAINS[..]); |
|
| 105 | + | assert notifications::poll(&mut KERNEL) == nil; |
|
| 106 | + | assert CONTEXTS[0].status == contexts::Status::Ready; |
|
| 107 | + | let _finished = step(); |
|
| 108 | + | clock::arm(0x02000000, 0, 0xffffffffffffffff); |
|
| 109 | + | assert frame.cause == 8 and frame.registers[17] == 49 and frame.registers[10] == 42; |
|
| 110 | + | timers::deliver(&mut TIMERS, expired + 1, &mut DOMAINS[..]); |
|
| 111 | + | assert atomic::load(&DOMAINS[0].events.ring.head) == atomic::load(&DOMAINS[0].events.ring.tail); |
|
| 112 | + | } |
kernel/core.rad
+2 -0
| 16 | 16 | export mod cpu; |
|
| 17 | 17 | export mod budgets; |
|
| 18 | 18 | export mod contexts; |
|
| 19 | 19 | export mod clock; |
|
| 20 | 20 | export mod budget_caps; |
|
| 21 | + | export mod timers; |
|
| 22 | + | export mod notifications; |
kernel/core/notifications.rad
added
+41 -0
| 1 | + | //! Event-driven context waits; notification never supplies execution authority. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::contexts; |
|
| 6 | + | use core::domains; |
|
| 7 | + | use core::events; |
|
| 8 | + | use core::state; |
|
| 9 | + | ||
| 10 | + | /// Block a quiescent current context only after validating the unread-event predicate. |
|
| 11 | + | export fn wait(kernel: *mut state::State, index: u32) throws (abi::Error) { |
|
| 12 | + | let context = &mut kernel.contexts[index]; |
|
| 13 | + | if context.status <> contexts::Status::Ready { throw abi::Error::Busy; } |
|
| 14 | + | if not domains::live(kernel.domains, context.owner) { throw abi::Error::BadHandle; } |
|
| 15 | + | let queue = &mut kernel.domains[context.owner.index].events; |
|
| 16 | + | try events::refresh(queue); |
|
| 17 | + | if not events::available(queue) { set context.status = contexts::Status::Waiting; } |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Make waiting contexts ready only while both an unread event and retained budget exist. |
|
| 21 | + | /// Return a malformed queue owner so the control path can apply fault termination. |
|
| 22 | + | export fn poll(kernel: *mut state::State) -> ?abi::Object { |
|
| 23 | + | for i in 0..kernel.contexts.len { |
|
| 24 | + | let context = &mut kernel.contexts[i]; |
|
| 25 | + | if context.status <> contexts::Status::Waiting { continue; } |
|
| 26 | + | if not domains::live(kernel.domains, context.owner) { continue; } |
|
| 27 | + | let queue = &mut kernel.domains[context.owner.index].events; |
|
| 28 | + | try events::refresh(queue) catch { return context.owner; }; |
|
| 29 | + | if context.budget.kind <> abi::Kind::Budget or not events::available(queue) { continue; } |
|
| 30 | + | let budget = budget_caps::value(kernel, context.budget); |
|
| 31 | + | if budget.remaining > 0 { set context.status = contexts::Status::Ready; } |
|
| 32 | + | } |
|
| 33 | + | return nil; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Publish a checked Wakeup notification without donating or replenishing budget. |
|
| 37 | + | export fn wakeup(kernel: *mut state::State, caller: u32, target: abi::Handle, token: u32) throws (abi::Error) { |
|
| 38 | + | let domain = try domains::resolve(kernel.domains, caller, target, abi::WAKE); |
|
| 39 | + | try events::push(&mut kernel.domains[domain.index].events, |
|
| 40 | + | events::Event { kind: 5, reserved: 0, code: token, value: caller as u64 }, events::Class::Ordinary); |
|
| 41 | + | } |
kernel/core/timers.rad
added
+100 -0
| 1 | + | //! Bounded one-shot timeouts owned and serialized by one hardware hart. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::domains; |
|
| 5 | + | use core::events; |
|
| 6 | + | ||
| 7 | + | /// Maximum accepted timeouts, including expired notifications under backpressure. |
|
| 8 | + | export constant CAPACITY: u32 = 64; |
|
| 9 | + | ||
| 10 | + | /// One accepted notification; a zero owner epoch marks an unused slot. |
|
| 11 | + | export record Timer: Copy { |
|
| 12 | + | /// Complete destination domain incarnation, never a capability-table slot. |
|
| 13 | + | owner: abi::Object, |
|
| 14 | + | /// Absolute monotonic CLINT deadline, private to the kernel. |
|
| 15 | + | deadline: u64, |
|
| 16 | + | /// Caller-supplied Timeout notification code. |
|
| 17 | + | token: u32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Fixed private timer storage; all accesses must be serialized by its owning hart. |
|
| 21 | + | export record Store: Copy { |
|
| 22 | + | /// Armed and expired-but-unpublished one-shot notifications. |
|
| 23 | + | entries: [Timer; CAPACITY], |
|
| 24 | + | /// First slot examined by the next bounded delivery pass. |
|
| 25 | + | cursor: u32, |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Initialize unused per-hart storage before accepting any timeouts. |
|
| 29 | + | export fn init(store: *mut Store) { |
|
| 30 | + | for i in 0..CAPACITY { |
|
| 31 | + | set store.entries[i] = Timer { owner: domains::none(), deadline: 0, token: 0 }; |
|
| 32 | + | } |
|
| 33 | + | set store.cursor = 0; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Accept one positive, non-overflowing delay in monotonic CLINT ticks. |
|
| 37 | + | /// The caller authorizes the live owner; malformed identities return BadHandle. |
|
| 38 | + | /// InvalidArg and Busy leave all accepted timeouts and delivery order unchanged. |
|
| 39 | + | export fn arm(store: *mut Store, owner: abi::Object, now: u64, delay: u64, token: u32) throws (abi::Error) { |
|
| 40 | + | if delay == 0 or now > 0xffffffffffffffff - delay { throw abi::Error::InvalidArg; } |
|
| 41 | + | if owner.kind <> abi::Kind::Domain or owner.index >= abi::MAX_DOMAINS or owner.epoch == 0 { |
|
| 42 | + | throw abi::Error::BadHandle; |
|
| 43 | + | } |
|
| 44 | + | for i in 0..CAPACITY { |
|
| 45 | + | if store.entries[i].owner.epoch == 0 { |
|
| 46 | + | set store.entries[i] = Timer { owner, deadline: now + delay, token }; |
|
| 47 | + | return; |
|
| 48 | + | } |
|
| 49 | + | } |
|
| 50 | + | throw abi::Error::Busy; |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Attempt each accepted timeout at most once, retaining failed publications. |
|
| 54 | + | /// Timeout entries carry kind=2, code=token, value=0, and reserved=0. |
|
| 55 | + | /// Dead, dying, and stale owners are discarded before any destination access. |
|
| 56 | + | /// Busy or invalid consumer progress retains the entry until a later kernel entry. |
|
| 57 | + | export fn deliver(store: *mut Store, now: u64, table: *mut [domains::Domain]) { |
|
| 58 | + | let start = store.cursor; |
|
| 59 | + | // Advance the starting point even when every publication is backpressured. |
|
| 60 | + | // Repeated reuse of an early free slot cannot monopolize newly freed capacity. |
|
| 61 | + | set store.cursor = (start + 1) % CAPACITY; |
|
| 62 | + | for offset in 0..CAPACITY { |
|
| 63 | + | let timer = &mut store.entries[(start + offset) % CAPACITY]; |
|
| 64 | + | if timer.owner.epoch == 0 { continue; } |
|
| 65 | + | if not domains::live(table, timer.owner) { |
|
| 66 | + | set timer.owner = domains::none(); |
|
| 67 | + | continue; |
|
| 68 | + | } |
|
| 69 | + | if timer.deadline > now { continue; } |
|
| 70 | + | let event = events::Event { kind: 2, reserved: 0, code: timer.token, value: 0 }; |
|
| 71 | + | try events::push(&mut table[timer.owner.index].events, event, events::Class::Ordinary) catch { |
|
| 72 | + | continue; |
|
| 73 | + | }; |
|
| 74 | + | set timer.owner = domains::none(); |
|
| 75 | + | } |
|
| 76 | + | } |
|
| 77 | + | ||
| 78 | + | /// Return the nearest strictly future deadline, or u64::MAX if none exists. |
|
| 79 | + | /// Ignore due retained notifications so backpressure cannot cause interrupt spin. |
|
| 80 | + | export fn next(store: *Store, now: u64) -> u64 { |
|
| 81 | + | let mut deadline: u64 = 0xffffffffffffffff; |
|
| 82 | + | for i in 0..CAPACITY { |
|
| 83 | + | let timer = &store.entries[i]; |
|
| 84 | + | if timer.owner.epoch <> 0 and timer.deadline > now and timer.deadline < deadline { |
|
| 85 | + | set deadline = timer.deadline; |
|
| 86 | + | } |
|
| 87 | + | } |
|
| 88 | + | return deadline; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | /// Cancel every unpublished timeout belonging to the exact domain incarnation. |
|
| 92 | + | /// Already published events remain in the destination's notification ring. |
|
| 93 | + | export fn cancel(store: *mut Store, owner: abi::Object) { |
|
| 94 | + | for i in 0..CAPACITY { |
|
| 95 | + | let timer = &mut store.entries[i]; |
|
| 96 | + | if timer.owner.kind == owner.kind and timer.owner.index == owner.index and timer.owner.epoch == owner.epoch { |
|
| 97 | + | set timer.owner = domains::none(); |
|
| 98 | + | } |
|
| 99 | + | } |
|
| 100 | + | } |