kernel: Dispatch contexts and enforce timers

754947a8b1529e3261c612399593274b2fcde81c35f8870de91617277fa2f237
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 5c2b051d
kernel/kernel.rad +1 -0
19 19
export mod pages;
20 20
export mod loader;
21 21
export mod instances;
22 22
export mod domains;
23 23
export mod budgets;
24 +
export mod dispatch;
24 25
export mod boot;
25 26
@test export mod tests;
kernel/kernel/boot.rad +12 -1
8 8
use super::trap;
9 9
use super::pages;
10 10
use super::registry;
11 11
use super::domains;
12 12
use super::budgets;
13 +
use super::dispatch;
13 14
use std::arch::rv64::shared::catalog;
14 15
15 16
/// Platform data published by hart zero before secondary initialization.
16 17
export unsafe static PLATFORM: platform::Platform = undefined;
17 18
/// Release/acquire publication flag for PLATFORM.
19 20
/// Number of harts that validated their machine stack.
20 21
static ARRIVED: u64 = 0;
21 22
/// Trap anchors owned by their physical harts.
22 23
unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined;
23 24
24 -
/// Stop on a trap before a domain or interrupt dispatcher is installed.
25 +
/// Handle timer boundaries and stop on unsupported traps.
25 26
unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) {
27 +
    if trap::classify(frame.cause) == trap::Cause::Timer {
28 +
        dispatch::interrupt(frame, hart);
29 +
        return;
30 +
    }
26 31
    print("kernel: unexpected trap\n");
27 32
    trap::halt();
28 33
}
29 34
30 35
/// Read a byte from a device register with I/O ordering.
96 101
    set HARTS[hart as u32] = trap::Hart {
97 102
        stackTop: stack.end, stackBottom: stack.start, kernelGp: stateTable as u64, handler: unexpected,
98 103
        savedT0: 0, savedT1: 0, savedSp: 0,
99 104
    };
100 105
    trap::install(&mut HARTS[hart as u32]);
106 +
    try! dispatch::initialize(&PLATFORM, hart as u32, stateTable as u64);
101 107
    let mut count: u64 = 0;
102 108
    for id in 0..limits::HARTS {
103 109
        if (PLATFORM.harts & (1 << id)) <> 0 {
104 110
            set count += 1;
105 111
        }
106 112
    }
107 113
    return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count;
108 114
}
115 +
116 +
/// Enter dispatch after this hart completes platform initialization.
117 +
export unsafe fn run() {
118 +
    dispatch::start(&mut HARTS[dispatch::hart()]);
119 +
}
kernel/kernel/boot.ras +2 -3
5 5
.export @kernel::boot::read8;
6 6
.export @kernel::boot::write8;
7 7
8 8
@kernel::boot::start
9 9
    call @kernel::boot::initialize;
10 -
@idle
11 -
    wfi;
12 -
    j @idle;
10 +
    call @kernel::boot::run;
11 +
    ebreak;
13 12
14 13
// Initialize machine CSRs and preserve the firmware stack across Radiance entry.
15 14
@kernel::boot::initialize
16 15
    csrw mie %zero;
17 16
    csrw mstatus %zero;
kernel/kernel/dispatch.rad added +209 -0
1 +
//! Hart-local selection of runnable CPU authority and timer boundaries.
2 +
3 +
use super::abi;
4 +
use super::limits;
5 +
use super::slots;
6 +
use super::budgets;
7 +
use super::domains;
8 +
use super::platform;
9 +
use super::trap;
10 +
use super::range;
11 +
12 +
/// Dispatch ownership and idle state retained by one physical hart.
13 +
export record State: Copy {
14 +
    /// Physical hart whose windows this state selects.
15 +
    hart: u32,
16 +
    /// Context currently executing or interrupted on this hart.
17 +
    current: ?abi::Ref,
18 +
    /// Budget selected for the current context.
19 +
    budget: ?abi::Ref,
20 +
    /// M-mode frame that enters the hart's idle loop.
21 +
    idle: trap::Frame,
22 +
    /// Firmware stack reserved for idle trap handling.
23 +
    stack: range::Range,
24 +
}
25 +
26 +
/// Validated RV64 CLINT registers owned by one hart.
27 +
export record Timer: Copy {
28 +
    /// Shared monotonic mtime register.
29 +
    clock: u64,
30 +
    /// This hart's mtimecmp register.
31 +
    compare: u64,
32 +
}
33 +
34 +
/// Timer and dispatch state owned by one physical hart.
35 +
record Runtime: Copy {
36 +
    /// Current context and retained idle frame.
37 +
    state: State,
38 +
    /// Validated timer registers for this hart.
39 +
    timer: Timer,
40 +
}
41 +
42 +
/// Runtime slots initialized before each hart enables timer interrupts.
43 +
unsafe static HARTS: [Runtime; limits::HARTS] = undefined;
44 +
45 +
/// Read the executing physical hart identifier.
46 +
export fn hart() -> u32;
47 +
/// Address of the M-mode idle loop.
48 +
fn idleAddress() -> u64;
49 +
/// Enable machine timer delivery while global interrupts remain disabled.
50 +
fn enable();
51 +
52 +
/// Initialize this hart's idle frame and validated timer before installing dispatch.
53 +
export unsafe fn initialize(machine: &platform::Platform, id: u32, kernelGp: u64) throws (abi::Error) {
54 +
    let device = try timer(machine, id);
55 +
    let stack = machine.stacks[id];
56 +
    let mut idle = trap::Frame { registers: [0; 32], pc: idleAddress(), status: 0x1880, cause: 0, value: 0 };
57 +
    set idle.registers[2] = stack.end;
58 +
    set idle.registers[3] = kernelGp;
59 +
    set HARTS[id] = Runtime {
60 +
        state: State { hart: id, current: nil, budget: nil, idle, stack }, timer: device,
61 +
    };
62 +
    arm(device, 0xffffffffffffffff);
63 +
}
64 +
65 +
/// Reevaluate authority after a timer interrupt with shared metadata serialized.
66 +
export unsafe fn interrupt(frame: &mut trap::Frame, anchor: &mut trap::Hart) {
67 +
    let id = hart();
68 +
    assert id < limits::HARTS;
69 +
    let clock = now(HARTS[id].timer);
70 +
    let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock);
71 +
    arm(HARTS[id].timer, choice.deadline);
72 +
}
73 +
74 +
/// Enter the first authorized context or the hart's retained idle frame.
75 +
export unsafe fn start(anchor: &mut trap::Hart) -> ! {
76 +
    let id = hart();
77 +
    assert id < limits::HARTS;
78 +
    let mut frame = HARTS[id].state.idle;
79 +
    interrupt(&mut frame, anchor);
80 +
    enable();
81 +
    trap::resume(&frame);
82 +
}
83 +
84 +
/// Read one validated, naturally aligned 64-bit device register.
85 +
fn read(address: u64) -> u64;
86 +
/// Write one validated, naturally aligned 64-bit device register.
87 +
fn write(address: u64, value: u64);
88 +
89 +
/// Select one CLINT extent and derive the selected hart's timer registers.
90 +
export fn timer(machine: &platform::Platform, hart: u32) -> Timer throws (abi::Error) {
91 +
    if hart >= limits::HARTS or (machine.harts & (1 << hart)) == 0 {
92 +
        throw abi::Error::InvalidArg;
93 +
    }
94 +
    let mut result: ?Timer = nil;
95 +
    for i in 0..machine.deviceCount {
96 +
        let device = machine.devices[i];
97 +
        if device.kind <> platform::Kind::Clint {
98 +
            continue;
99 +
        }
100 +
        if result <> nil or device.memory.end < device.memory.start
101 +
            or device.memory.end - device.memory.start < 0xc000 or (device.memory.start & 7) <> 0 {
102 +
            throw abi::Error::InvalidArg;
103 +
        }
104 +
        set result = Timer { clock: device.memory.start + 0xbff8, compare: device.memory.start + 0x4000 + hart as u64 * 8 };
105 +
    }
106 +
    let selected = result else {
107 +
        throw abi::Error::InvalidArg;
108 +
    };
109 +
    return selected;
110 +
}
111 +
112 +
/// Read the platform's monotonic clock in mtime ticks.
113 +
export fn now(timer: Timer) -> u64 {
114 +
    return read(timer.clock);
115 +
}
116 +
117 +
/// Program the next boundary while this hart's timer handling is serialized.
118 +
export fn arm(timer: Timer, deadline: u64) {
119 +
    write(timer.compare, deadline);
120 +
}
121 +
122 +
/// Runnable context and the next boundary that requires a dispatch decision.
123 +
export record Choice: Copy {
124 +
    /// Current exclusive interval, or nil while idle.
125 +
    budget: ?abi::Ref,
126 +
    /// Context authorized to execute now, or nil while idle.
127 +
    context: ?abi::Ref,
128 +
    /// Platform mtime tick for the next timer interrupt.
129 +
    deadline: u64,
130 +
}
131 +
132 +
/// Test whether a live context belongs to the active domain owning its budget.
133 +
fn runnable(store: &domains::Store, owner: abi::Ref, context: abi::Ref) -> bool {
134 +
    if not slots::matches(&store.slots[..], owner, slots::State::Live) {
135 +
        return false;
136 +
    }
137 +
    if store.records[owner.index].state <> domains::Lifecycle::Active {
138 +
        return false;
139 +
    }
140 +
    if not slots::matches(&store.contextSlots[..], context, slots::State::Live) {
141 +
        return false;
142 +
    }
143 +
    return store.contexts[context.index].owner == owner;
144 +
}
145 +
146 +
/// Select the unique runnable window and the earliest relevant timer boundary.
147 +
/// The caller serializes budget and domain metadata while taking this snapshot.
148 +
export fn select(windows: &budgets::Store, contexts: &domains::Store, hart: u32, now: u64)
149 +
    -> Choice throws (abi::Error)
150 +
{
151 +
    if hart >= limits::HARTS {
152 +
        throw abi::Error::InvalidArg;
153 +
    }
154 +
    let mut result = Choice { budget: nil, context: nil, deadline: 0xffffffffffffffff };
155 +
    for i in 0..limits::BUDGETS {
156 +
        if windows.slots[i].state <> slots::State::Live {
157 +
            continue;
158 +
        }
159 +
        let window = windows.windows[i];
160 +
        if window.hart <> hart or window.end <= now {
161 +
            continue;
162 +
        }
163 +
        let context = window.context else {
164 +
            continue;
165 +
        };
166 +
        if not runnable(contexts, window.owner, context) {
167 +
            continue;
168 +
        }
169 +
        if window.start > now {
170 +
            if window.start < result.deadline {
171 +
                set result.deadline = window.start;
172 +
            }
173 +
            continue;
174 +
        }
175 +
        assert result.budget == nil;
176 +
        set result.budget = abi::Ref { index: i, generation: windows.slots[i].generation };
177 +
        set result.context = context;
178 +
        if window.end < result.deadline {
179 +
            set result.deadline = window.end;
180 +
        }
181 +
    }
182 +
    return result;
183 +
}
184 +
185 +
/// Save the interrupted context and install the selected frame and kernel stack.
186 +
/// Interrupts remain disabled and shared metadata stays serialized through exchange.
187 +
export fn exchange(state: &mut State, windows: &budgets::Store, contexts: &mut domains::Store,
188 +
    frame: &mut trap::Frame, anchor: &mut trap::Hart, now: u64) -> Choice throws (abi::Error)
189 +
{
190 +
    let choice = try select(windows, contexts, state.hart, now);
191 +
    if let current = state.current {
192 +
        assert slots::matches(&contexts.contextSlots[..], current, slots::State::Live);
193 +
        set contexts.contexts[current.index].frame = *frame;
194 +
    }
195 +
    if let next = choice.context {
196 +
        let context = contexts.contexts[next.index];
197 +
        assert context.kernelStack.start < context.kernelStack.end;
198 +
        set *frame = context.frame;
199 +
        set anchor.stackBottom = context.kernelStack.start;
200 +
        set anchor.stackTop = context.kernelStack.end;
201 +
    } else {
202 +
        set *frame = state.idle;
203 +
        set anchor.stackBottom = state.stack.start;
204 +
        set anchor.stackTop = state.stack.end;
205 +
    }
206 +
    set state.current = choice.context;
207 +
    set state.budget = choice.budget;
208 +
    return choice;
209 +
}
kernel/kernel/dispatch.ras added +41 -0
1 +
//! Naturally aligned RV64 CLINT access with device ordering.
2 +
.text;
3 +
.export @kernel::dispatch::read;
4 +
.export @kernel::dispatch::write;
5 +
.export @kernel::dispatch::hart;
6 +
.export @kernel::dispatch::idleAddress;
7 +
.export @kernel::dispatch::enable;
8 +
9 +
// The executing hart selects its private dispatch runtime slot.
10 +
@kernel::dispatch::hart
11 +
    csrr %a0 mhartid;
12 +
    ret;
13 +
14 +
// Saved idle frames enter this loop with machine interrupts enabled by mret.
15 +
@kernel::dispatch::idleAddress
16 +
    la %a0 @dispatchIdle;
17 +
    ret;
18 +
@dispatchIdle
19 +
    wfi;
20 +
    j @dispatchIdle;
21 +
22 +
// Global interrupt enable is restored from the selected frame by mret.
23 +
@kernel::dispatch::enable
24 +
    csrr %t0 mie;
25 +
    ori %t0 %t0 128;
26 +
    csrw mie %t0;
27 +
    ret;
28 +
29 +
// The validated CLINT mapping contains a naturally aligned mtime register.
30 +
@kernel::dispatch::read
31 +
    fence iorw iorw;
32 +
    ld %a0 0(%a0);
33 +
    fence iorw iorw;
34 +
    ret;
35 +
36 +
// One RV64 store updates the owning hart's complete mtimecmp value.
37 +
@kernel::dispatch::write
38 +
    fence iorw iorw;
39 +
    sd %a1 0(%a0);
40 +
    fence iorw iorw;
41 +
    ret;
kernel/kernel/domains.rad +24 -2
8 8
use super::capability;
9 9
use super::registry;
10 10
use super::instances;
11 11
use super::events;
12 12
use super::trap;
13 +
use super::range;
14 +
15 +
/// Retained kernel stack capacity for one suspended execution context.
16 +
export constant KERNEL_STACK_PAGES: u32 = 64;
13 17
14 18
/// Management rights returned to a domain's creator.
15 19
export constant MANAGEMENT_RIGHTS: u16 = abi::DESTROY | abi::EXECUTE | abi::GRANT | abi::TRANSFER | abi::WAKE;
16 20
17 21
/// Protection-domain lifetime independent of execution budgets.
34 38
35 39
/// Saved integer state owned by one execution context.
36 40
export record Context: Copy {
37 41
    /// Domain that owns this context.
38 42
    owner: abi::Ref,
43 +
    /// Physical frames retained until this context stops.
44 +
    kernelFrames: frames::Run,
45 +
    /// Exclusive stack bounds used by trap entry and suspended M-mode calls.
46 +
    kernelStack: range::Range,
39 47
    /// Architectural state used when this context resumes.
40 48
    frame: trap::Frame,
41 49
}
42 50
43 51
/// Published domain resources and lifecycle metadata.
105 113
    allocation: frames::Run,
106 114
    /// Mapped metadata at the allocation's start.
107 115
    memory: *unsafe mut Memory,
108 116
    /// Independent package-state graph.
109 117
    graph: instances::Instance,
118 +
    /// Retained stack allocation for the initial context.
119 +
    kernelFrames: frames::Run,
120 +
    /// Initial context's mapped stack bounds.
121 +
    kernelStack: range::Range,
110 122
}
111 123
112 124
/// Allocate private storage and return all earlier allocations on failure.
113 125
unsafe fn prepare(pool: &mut frames::Pool, packages: &registry::Store, image: abi::Ref) -> Prepared throws (abi::Error) {
114 126
    let pending = try frames::reserve(pool, (@sizeOf(Memory) + 4095) / 4096);
115 127
    let allocation = frames::commit(pending);
116 128
    let graph = try instances::create(packages, pool, image) catch err {
117 129
        try! frames::release(pool, allocation); throw err;
118 130
    };
131 +
    let pendingStack = try frames::reserve(pool, KERNEL_STACK_PAGES) catch err {
132 +
        try! frames::release(pool, graph.frames);
133 +
        try! frames::release(pool, allocation); throw err;
134 +
    };
135 +
    let kernelFrames = frames::commit(pendingStack);
136 +
    let kernelStack = try! frames::extent(pool, kernelFrames);
119 137
    let extent = try! frames::extent(pool, allocation);
120 138
    zero(extent.start, allocation.count);
121 -
    return Prepared { allocation, memory: memory(extent.start), graph };
139 +
    zero(kernelStack.start, kernelFrames.count);
140 +
    return Prepared { allocation, memory: memory(extent.start), graph, kernelFrames, kernelStack };
122 141
}
123 142
124 143
/// Return storage that has never been exposed to a published domain.
125 144
fn discard(pool: &mut frames::Pool, prepared: Prepared) {
145 +
    try! frames::release(pool, prepared.kernelFrames);
126 146
    try! frames::release(pool, prepared.graph.frames);
127 147
    try! frames::release(pool, prepared.allocation);
128 148
}
129 149
130 150
/// Create an unscheduled domain with Events and one initial integer context.
174 194
    });
175 195
    let initial = slots::reference(&contextSlot);
176 196
    // MPIE enables interrupts after the first return into U-mode.
177 197
    let mut frame = trap::Frame { registers: [0; 32], pc: entry, status: 0x80, cause: 0, value: 0 };
178 198
    set frame.registers[3] = prepared.graph.table.ptr as u64;
179 -
    set store.contexts[initial.index] = Context { owner: object, frame };
199 +
    set store.contexts[initial.index] = Context {
200 +
        owner: object, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack, frame,
201 +
    };
180 202
    set store.records[object.index] = Domain {
181 203
        state: Lifecycle::Pending, creator: table.owner, parent: table.owner, image: root, initial,
182 204
        allocation: prepared.allocation, memory: prepared.memory, graph: prepared.graph, events: queue,
183 205
    };
184 206
    let context = try! slots::commit(&mut store.contextSlots[..], contextSlot);
kernel/kernel/tests.rad +1 -0
15 15
export mod registry;
16 16
export mod loader;
17 17
export mod instances;
18 18
export mod domains;
19 19
export mod budgets;
20 +
export mod dispatch;
kernel/kernel/tests/budgets.rad +3 -1
7 7
use kernel::slots;
8 8
use kernel::domains;
9 9
use kernel::frames;
10 10
use kernel::instances;
11 11
use kernel::trap;
12 +
use kernel::range;
12 13
13 14
/// Exclusive CPU-window metadata.
14 15
unsafe static STORE: budgets::Store = undefined;
15 16
/// Capability owner for the test windows.
16 17
unsafe static TABLE: capability::Table = undefined;
131 132
    let child = slots::reference(&childSlot);
132 133
    capability::initialize(&mut MEMORY.table, child);
133 134
    let contextSlot = try! slots::reserve(&mut DOMAINS.contextSlots[..]);
134 135
    let context = slots::reference(&contextSlot);
135 136
    set DOMAINS.contexts[context.index] = domains::Context {
136 -
        owner: child, frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
137 +
        owner: child, kernelFrames: frames::Run { first: 0, count: 0 }, kernelStack: range::Range { start: 0, end: 0 },
138 +
        frame: trap::Frame { registers: [0; 32], pc: 4, status: 0x80, cause: 0, value: 0 },
137 139
    };
138 140
    set DOMAINS.records[child.index] = domains::Domain {
139 141
        state: domains::Lifecycle::Pending, creator: root, parent: root,
140 142
        image: abi::Ref { index: 0, generation: 1 }, initial: context,
141 143
        allocation: frames::Run { first: 0, count: 0 }, memory: &mut MEMORY,
kernel/kernel/tests/dispatch.rad added +136 -0
1 +
//! Dispatch selection at CPU-window boundaries and idle gaps.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::slots;
6 +
use kernel::domains;
7 +
use kernel::budgets;
8 +
use kernel::capability;
9 +
use kernel::dispatch;
10 +
use kernel::platform;
11 +
use kernel::range;
12 +
use kernel::trap;
13 +
14 +
/// Platform resources used to validate CLINT address selection.
15 +
unsafe static PLATFORM: platform::Platform = undefined;
16 +
17 +
/// Domain lifetimes and saved contexts used for selection.
18 +
unsafe static DOMAINS: domains::Store = undefined;
19 +
/// Exclusive intervals presented to dispatch.
20 +
unsafe static BUDGETS: budgets::Store = undefined;
21 +
/// Owner of the test CPU capabilities.
22 +
unsafe static TABLE: capability::Table = undefined;
23 +
24 +
/// Install one live context and two separated windows on hart zero.
25 +
unsafe fn initialize() {
26 +
    domains::initialize(&mut DOMAINS);
27 +
    budgets::initialize(&mut BUDGETS);
28 +
    let owner = abi::Ref { index: 0, generation: 1 };
29 +
    capability::initialize(&mut TABLE, owner);
30 +
    set DOMAINS.slots[0] = slots::Slot { generation: 1, state: slots::State::Live };
31 +
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
32 +
    set DOMAINS.contextSlots[0] = slots::Slot { generation: 1, state: slots::State::Live };
33 +
    set DOMAINS.contexts[0].owner = owner;
34 +
    let first = try! budgets::seed(&mut BUDGETS, &mut TABLE, 0, 10, 40);
35 +
    let gap = try! budgets::split(&mut BUDGETS, &mut TABLE, first, 20, 0);
36 +
    let last = try! budgets::split(&mut BUDGETS, &mut TABLE, gap, 30, 0);
37 +
    let a = try! capability::get(&TABLE, first);
38 +
    let b = try! capability::get(&TABLE, last);
39 +
    set BUDGETS.windows[a.object.index].context = owner;
40 +
    set BUDGETS.windows[b.object.index].context = owner;
41 +
}
42 +
43 +
/// Dispatch selects only the half-open interval and wakes at the next runnable start.
44 +
@test unsafe fn boundaries() throws (testing::TestError) {
45 +
    initialize();
46 +
    let before = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 9);
47 +
    try testing::expect(before.context == nil and before.deadline == 10);
48 +
    let first = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
49 +
    try testing::expect(first.context == TABLE.owner and first.deadline == 20);
50 +
    let gap = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 20);
51 +
    try testing::expect(gap.context == nil and gap.deadline == 30);
52 +
    let last = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 30);
53 +
    try testing::expect(last.context == TABLE.owner and last.deadline == 40);
54 +
    let ended = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 40);
55 +
    try testing::expect(ended.context == nil and ended.deadline == 0xffffffffffffffff);
56 +
    let other = try! dispatch::select(&BUDGETS, &DOMAINS, 1, 10);
57 +
    try testing::expect(other.context == nil and other.deadline == 0xffffffffffffffff);
58 +
}
59 +
60 +
/// Pending domains and stale context generations cannot become runnable through a budget.
61 +
@test unsafe fn lifetimes() throws (testing::TestError) {
62 +
    initialize();
63 +
    set DOMAINS.records[0].state = domains::Lifecycle::Pending;
64 +
    let pending = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
65 +
    try testing::expect(pending.context == nil);
66 +
    set DOMAINS.records[0].state = domains::Lifecycle::Active;
67 +
    set DOMAINS.contextSlots[0].generation = 2;
68 +
    let stale = try! dispatch::select(&BUDGETS, &DOMAINS, 0, 10);
69 +
    try testing::expect(stale.context == nil);
70 +
}
71 +
72 +
/// Each online hart has a separate comparison word and shares one clock register.
73 +
@test unsafe fn timerRegisters() throws (testing::TestError) {
74 +
    set PLATFORM.harts = 0x81;
75 +
    set PLATFORM.deviceCount = 1;
76 +
    set PLATFORM.devices[0] = platform::Device {
77 +
        kind: platform::Kind::Clint, memory: range::Range { start: 0x2000000, end: 0x2010000 },
78 +
    };
79 +
    let first = try! dispatch::timer(&PLATFORM, 0);
80 +
    let last = try! dispatch::timer(&PLATFORM, 7);
81 +
    try testing::expect(first.clock == 0x200bff8 and first.compare == 0x2004000);
82 +
    try testing::expect(last.clock == first.clock and last.compare == first.compare + 56);
83 +
    let mut failures: u32 = 0;
84 +
    try dispatch::timer(&PLATFORM, 1) catch err {
85 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
86 +
    };
87 +
    set PLATFORM.devices[0].memory.end = 0x200bff8;
88 +
    try dispatch::timer(&PLATFORM, 0) catch err {
89 +
        try testing::expect(err == abi::Error::InvalidArg); set failures += 1;
90 +
    };
91 +
    try testing::expect(failures == 2);
92 +
}
93 +
94 +
/// Trap callback supplied to the frame-exchange fixture.
95 +
fn handler(frame: &mut trap::Frame, hart: &mut trap::Hart) {}
96 +
97 +
/// Exchange preserves an M-mode continuation while another context uses its own stack.
98 +
@test unsafe fn continuations() throws (testing::TestError) {
99 +
    initialize();
100 +
    let mut frame = trap::Frame { registers: [0; 32], pc: 0x40, status: 0x80, cause: 0, value: 0 };
101 +
    set DOMAINS.contexts[0].frame = frame;
102 +
    set DOMAINS.contexts[0].kernelStack = range::Range { start: 0x8000, end: 0xc000 };
103 +
    set DOMAINS.contextSlots[1] = slots::Slot { generation: 1, state: slots::State::Live };
104 +
    set DOMAINS.contexts[1].owner = TABLE.owner;
105 +
    set DOMAINS.contexts[1].frame = frame;
106 +
    set DOMAINS.contexts[1].kernelStack = range::Range { start: 0xc000, end: 0x10000 };
107 +
    set BUDGETS.windows[1].context = abi::Ref { index: 1, generation: 1 };
108 +
    let mut idle = frame;
109 +
    set idle.pc = 0x100;
110 +
    set idle.status = 0x1880;
111 +
    set idle.registers[2] = 0x4000;
112 +
    let mut state = dispatch::State {
113 +
        hart: 0, current: nil, budget: nil, idle,
114 +
        stack: range::Range { start: 0x2000, end: 0x4000 },
115 +
    };
116 +
    let mut anchor = trap::Hart {
117 +
        stackTop: 0x4000, stackBottom: 0x2000, kernelGp: 0x20000, handler,
118 +
        savedT0: 0, savedT1: 0, savedSp: 0,
119 +
    };
120 +
    let first = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 10);
121 +
    try testing::expect(frame.pc == 0x40 and anchor.stackTop == 0xc000);
122 +
    set frame.pc = 0x88;
123 +
    set frame.status = 0x1880;
124 +
    set frame.registers[2] = 0xb000;
125 +
    set frame.registers[5] = 43;
126 +
    let second = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 20);
127 +
    try testing::expect(anchor.stackBottom == 0xc000 and anchor.stackTop == 0x10000);
128 +
    set frame.pc = 0x77;
129 +
    let resumed = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 30);
130 +
    try testing::expect(frame.pc == 0x88 and frame.status == 0x1880);
131 +
    try testing::expect(frame.registers[2] == 0xb000 and frame.registers[5] == 43);
132 +
    try testing::expect(DOMAINS.contexts[1].frame.pc == 0x77 and anchor.stackTop == 0xc000);
133 +
    let ended = try! dispatch::exchange(&mut state, &BUDGETS, &mut DOMAINS, &mut frame, &mut anchor, 40);
134 +
    try testing::expect(ended.context == nil and frame.pc == idle.pc and frame.registers[2] == 0x4000);
135 +
    try testing::expect(anchor.stackTop == 0x4000 and anchor.stackBottom == 0x2000);
136 +
}
kernel/kernel/tests/domains.rad +5 -0
69 69
    try testing::expect(child.memory.table.slots[1].state == slots::State::Free);
70 70
    let context = DOMAINS.contexts[child.initial.index];
71 71
    try testing::expect(context.owner == authority.object and context.frame.pc == 0x80000000);
72 72
    try testing::expect(context.frame.registers[3] == child.graph.table.ptr as u64);
73 73
    try testing::expect(context.frame.registers[2] == 0 and context.frame.status == 0x80);
74 +
    try testing::expect(context.kernelFrames.count == domains::KERNEL_STACK_PAGES);
75 +
    try testing::expect(context.kernelStack.end - context.kernelStack.start == domains::KERNEL_STACK_PAGES as u64 * 4096);
76 +
    try testing::expect((context.kernelStack.end & 15) == 0);
74 77
    try testing::expect(child.memory.ring.head == 0 and child.memory.ring.tail == 0);
75 78
    try testing::expect(child.memory.ring.mask == 255);
76 79
}
77 80
78 81
/// Check that a failed creation has returned all unpublished resources.
97 100
    let count = BACKINGS.pool.count;
98 101
    set BACKINGS.pool.count = 0;
99 102
    try failed(abi::Error::OutOfMemory);
100 103
    set BACKINGS.pool.count = (@sizeOf(domains::Memory) + 4095) / 4096;
101 104
    try failed(abi::Error::OutOfMemory);
105 +
    set BACKINGS.pool.count += 1;
106 +
    try failed(abi::Error::OutOfMemory);
102 107
    set BACKINGS.pool.count = count;
103 108
    set DOMAINS.events.queues[1].generation = 99;
104 109
    try failed(abi::Error::Busy);
105 110
    try testing::expect(DOMAINS.events.queues[1].generation == 99);
106 111
    set DOMAINS.events.queues[1].generation = 0;
kernel/kernel/trap.rad +1 -1
90 90
91 91
/// Install trap entry for the current hart with machine interrupts disabled.
92 92
export fn install(hart: *mut Hart);
93 93
/// Restore a complete integer context through mret. The current mscratch must
94 94
/// name an installed Hart, and the frame must have machine interrupts disabled.
95 -
export fn resume(frame: *Frame) -> !;
95 +
export fn resume(frame: &Frame) -> !;
96 96
97 97
/// Disable machine interrupts and stop the current hart.
98 98
export fn halt() -> !;
test/boot/run +2 -2
2 2
# Boot the kernel through its production initialization on each supported hart count.
3 3
set -eu
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
work=$(mktemp -d)
6 6
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 -
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/boot.ras"
7 +
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/boot.ras"
8 8
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/boot.ras" "$work/boot.rv64"
9 9
for harts in 1 2 8; do
10 10
    # The deterministic emulator shares its tick budget across all harts.
11 11
    steps=$((10000000 * harts))
12 12
    if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then
28 28
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
29 29
    cat "$work/log" >&2
30 30
    exit 1
31 31
fi
32 32
printf 'kernel startup: all eight harts reached machine idle without traps\n'
33 -
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/trap.ras"
33 +
cat test/boot/trap.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/trap.ras"
34 34
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
35 35
status=0
36 36
"$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$?
37 37
if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \
38 38
    || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then
test/dispatch/kernel/dispatchcheck.rad added +108 -0
1 +
//! Native timer preemption across user execution, idle, and machine continuations.
2 +
use std::mem;
3 +
use std::arch::rv64::shared;
4 +
use kernel::abi;
5 +
use kernel::slots;
6 +
use kernel::backing;
7 +
use kernel::pages;
8 +
use kernel::capability;
9 +
use kernel::registry;
10 +
use kernel::loader;
11 +
use kernel::domains;
12 +
use kernel::budgets;
13 +
use kernel::dispatch;
14 +
use kernel::boot;
15 +
use kernel::dispatchinput;
16 +
17 +
/// Bootstrap authority used to create fixture domains and CPU windows.
18 +
unsafe static TABLE: capability::Table = undefined;
19 +
/// Domain handles used for budget binding.
20 +
unsafe static HANDLES: [abi::Handle; 3] = undefined;
21 +
/// Initial contexts for the user, machine worker, and completion check.
22 +
unsafe static CONTEXTS: [abi::Ref; 3] = undefined;
23 +
/// View a validated allocated source page.
24 +
fn memory(address: u64) -> *mut u8;
25 +
/// Current kernel package-state base.
26 +
fn kernelGp() -> u64;
27 +
/// Native entry for retained M-mode work.
28 +
fn worker() -> u64;
29 +
/// Native entry for the final assertion callback.
30 +
fn completion() -> u64;
31 +
/// Complete the machine fixture with a passing status.
32 +
fn finish();
33 +
34 +
/// Prepare real private domains before enabling dispatch on hart zero.
35 +
export unsafe fn setup() {
36 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
37 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
38 +
    capability::initialize(&mut TABLE, owner);
39 +
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
40 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
41 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
42 +
    });
43 +
    let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
44 +
    let sourceEntry = try! capability::get(&TABLE, source);
45 +
    let page = try! pages::get(&pages::STORE, sourceEntry.object);
46 +
    let bytes = @sliceOf(memory(page.base), 4096);
47 +
    let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]);
48 +
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
49 +
        loader::Request { authority, source, offset: 0, length: length as u64 });
50 +
    for i in 0..3 {
51 +
        set HANDLES[i] = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
52 +
        let cap = try! capability::get(&TABLE, HANDLES[i]);
53 +
        let child = try! domains::get(&domains::STORE, cap.object);
54 +
        set CONTEXTS[i] = child.initial;
55 +
        set domains::STORE.records[cap.object.index].state = domains::Lifecycle::Active;
56 +
        if i > 0 {
57 +
            set domains::STORE.contexts[child.initial.index].frame.status = 0x1880;
58 +
            set domains::STORE.contexts[child.initial.index].frame.registers[2] = domains::STORE.contexts[child.initial.index].kernelStack.end;
59 +
            set domains::STORE.contexts[child.initial.index].frame.registers[3] = kernelGp();
60 +
        }
61 +
    }
62 +
    let cap = try! capability::get(&TABLE, HANDLES[0]);
63 +
    let mut user = try! domains::get(&domains::STORE, cap.object);
64 +
    let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
65 +
    let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut user.memory.table, stack, (abi::READ | abi::WRITE) as u64);
66 +
    let stackCap = try! capability::get(&TABLE, stack);
67 +
    let stackPage = try! pages::get(&pages::STORE, stackCap.object);
68 +
    set domains::STORE.contexts[CONTEXTS[0].index].frame.registers[2] = stackPage.base + 4096;
69 +
    let imageRef = try! registry::image(&registry::STORE, &TABLE, image, abi::Rights(abi::EXECUTE));
70 +
    let target = try! registry::exported(&registry::STORE, imageRef, &"spin::count"[..]);
71 +
    let case shared::Target::Data(data) = target else panic "counter";
72 +
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
73 +
    let clock = dispatch::now(timer);
74 +
    let start = clock + 1000000;
75 +
    let span: u64 = 1000000;
76 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.pc = worker();
77 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[10] = user.graph.table[data.slot] + data.offset as u64;
78 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[11] = timer.clock;
79 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[12] = start + 3 * span;
80 +
    set domains::STORE.contexts[CONTEXTS[1].index].frame.registers[13] = start + 4 * span;
81 +
    set domains::STORE.contexts[CONTEXTS[2].index].frame.pc = completion();
82 +
    let mut windows: [abi::Handle; 6] = undefined;
83 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 6 * span);
84 +
    for i in 0..6 {
85 +
        set windows[i] = remaining;
86 +
        if i < 5 {
87 +
            set remaining = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * span, clock);
88 +
        }
89 +
    }
90 +
    let owners = &[0 as u32, 0, 1, 0, 1, 2];
91 +
    for i in 0..6 {
92 +
        if i == 1 {
93 +
            continue;
94 +
        }
95 +
        let owner = owners[i];
96 +
        let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
97 +
            budgets::Binding { budget: windows[i], domain: HANDLES[owner], context: CONTEXTS[owner] }, clock);
98 +
    }
99 +
}
100 +
101 +
/// Check state captured by production timer handling after both resumptions.
102 +
export unsafe fn verify() {
103 +
    let user = domains::STORE.contexts[CONTEXTS[0].index].frame;
104 +
    let machine = domains::STORE.contexts[CONTEXTS[1].index].frame;
105 +
    assert (user.status & 0x1800) == 0 and (machine.status & 0x1800) == 0x1800;
106 +
    assert machine.registers[8] > 0 and machine.registers[9] == 1 and machine.registers[18] > 0;
107 +
    finish();
108 +
}
test/dispatch/machine.ras added +53 -0
1 +
//! Exercise real timer-driven switching on hart zero.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
@fail
7 +
    li %t0 0x10001000;
8 +
    li %t1 0x13333;
9 +
    sw %t1 0(%t0);
10 +
.export @kernel::dispatchcheck::memory;
11 +
.export @kernel::dispatchcheck::kernelGp;
12 +
.export @kernel::dispatchcheck::worker;
13 +
.export @kernel::dispatchcheck::completion;
14 +
.export @kernel::dispatchcheck::finish;
15 +
@kernel::dispatchcheck::memory
16 +
    ret;
17 +
@kernel::dispatchcheck::kernelGp
18 +
    mv %a0 %gp;
19 +
    ret;
20 +
@kernel::dispatchcheck::worker
21 +
    la %a0 @machineWork;
22 +
    ret;
23 +
@kernel::dispatchcheck::completion
24 +
    la %a0 @complete;
25 +
    ret;
26 +
@machineWork
27 +
    ld %t0 0(%a1);
28 +
    bgeu %t0 %a2 @fail;
29 +
    ld %s2 0(%a0);
30 +
    beqz %s2 @fail;
31 +
    addi %sp %sp -16;
32 +
    li %s3 1234567;
33 +
    sd %s3 0(%sp);
34 +
    li %s0 0;
35 +
    li %s1 0;
36 +
@workLoop
37 +
    addi %s0 %s0 1;
38 +
    ld %t0 0(%sp);
39 +
    bne %t0 %s3 @fail;
40 +
    ld %t0 0(%a1);
41 +
    bltu %t0 %a3 @workLoop;
42 +
    ld %t0 0(%a0);
43 +
    bgeu %s2 %t0 @fail;
44 +
    li %s1 1;
45 +
    j @workLoop;
46 +
@complete
47 +
    call @kernel::dispatchcheck::verify;
48 +
    j @fail;
49 +
@kernel::dispatchcheck::finish
50 +
    li %t0 0x10001000;
51 +
    li %t1 0x5555;
52 +
    sw %t1 0(%t0);
53 +
    j @fail;
test/dispatch/run added +23 -0
1 +
#!/bin/sh
2 +
# Preempt and resume U-mode and M-mode work through production dispatch.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
8 +
    -pkg spin -mod test/dispatch/spin.rad -entry spin -ril "$work"
9 +
cp kernel/kernel.rad "$work/kernel.rad"
10 +
printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
11 +
mkdir "$work/kernel"
12 +
cp kernel/kernel/*.rad "$work/kernel/"
13 +
cp test/dispatch/kernel/dispatchcheck.rad "$work/kernel/"
14 +
length=$(wc -c < "$work/spin.ril")
15 +
printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad"
16 +
od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
17 +
printf '];\n' >> "$work/kernel/dispatchinput.rad"
18 +
sh test/acceptance/compile "$emulator" "$work"
19 +
cat test/dispatch/machine.ras kernel/kernel/*.ras > "$work/dispatch.ras"
20 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
21 +
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
22 +
"$emulator" -machine -memory-size=262144 -max-steps=80000000 -run "$work/dispatch.rv64"
23 +
printf 'dispatch: user preemption, idle gap, and retained machine continuation passed\n'
test/dispatch/spin.rad added +9 -0
1 +
//! Non-yielding user code preempted by hardware timer boundaries.
2 +
/// Private progress counter observed by the machine fixture.
3 +
export static count: u64 = 0;
4 +
/// Consume each assigned window without making a kernel call.
5 +
@default fn main() {
6 +
    while true {
7 +
        set count += 1;
8 +
    }
9 +
}
test/loader/run +1 -1
20 20
printf '//! Binary package used by both execution paths.\n/// Complete trusted binary RIL input.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/loadinput.rad"
21 21
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/loadinput.rad"
22 22
printf '];\n' >> "$work/kernel/loadinput.rad"
23 23
sh test/acceptance/compile "$emulator" "$work"
24 24
cat test/loader/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras \
25 -
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras > "$work/loader.ras"
25 +
    kernel/kernel/trap.ras kernel/kernel/pages.ras kernel/kernel/loader.ras kernel/kernel/instances.ras kernel/kernel/domains.ras kernel/kernel/dispatch.ras > "$work/loader.ras"
26 26
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
27 27
    -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64"
28 28
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64"
29 29
printf 'runtime loader: hosted and loaded entries agree after workspace release\n'