kernel: Deliver device interrupts

92262c08112aa9a0a441cca4c93933d9ba51745bd7f905fe4d108ad072d04e41
Alexis Sellier committed ago 1 parent e654149f
Makefile +2 -2
66 66
		lib/std.test.rv64.s \
67 67
		lib/std.test.rv64.o
68 68
69 69
# Kernel modules and tests use a separate package and test entry point.
70 70
KERNEL_SRC := kernel/kernel.rad $(shell find kernel/kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null)
71 -
KERNEL_ASM := 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 kernel/kernel/calls.ras
71 +
KERNEL_ASM := 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 kernel/kernel/calls.ras kernel/kernel/plic.ras
72 72
KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null)
73 73
KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC)))
74 74
KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64
75 75
76 76
kernel-test: emulator $(KERNEL_TEST)
210 210
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run
211 211
212 212
# Concurrent user contexts and metadata transactions on every online hart.
213 213
.PHONY: smp-test
214 214
smp-test: $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
215 -
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run smp "1 2 8"
215 +
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run smp "1 2 8" 7
kernel/kernel.rad +2 -0
11 11
export mod platform;
12 12
export mod trap;
13 13
export mod capability;
14 14
export mod transactions;
15 15
export mod events;
16 +
export mod interrupts;
17 +
export mod plic;
16 18
export mod registry;
17 19
export mod frames;
18 20
export mod backing;
19 21
export mod pages;
20 22
export mod loader;
kernel/kernel/abi.rad +6 -0
23 23
    base: u64,
24 24
    /// Number of contiguous physical frames.
25 25
    count: u64,
26 26
}
27 27
28 +
/// Physical source returned by QueryInterrupt.
29 +
export record InterruptInfo: Copy {
30 +
    /// PLIC source number; zero does not name an interrupt.
31 +
    number: u32,
32 +
}
33 +
28 34
/// Domain identity and lifetime returned by QueryDomain.
29 35
export record DomainInfo: Copy {
30 36
    /// Generation-bearing domain identity.
31 37
    id: u64,
32 38
    /// Historical creator identity.
kernel/kernel/boot.rad +5 -1
11 11
use super::domains;
12 12
use super::budgets;
13 13
use super::dispatch;
14 14
use super::timers;
15 15
use super::remote;
16 +
use super::interrupts;
17 +
use super::plic;
16 18
use std::arch::rv64::shared::catalog;
17 19
18 20
/// Platform data published by hart zero before secondary initialization.
19 21
export unsafe static PLATFORM: platform::Platform = undefined;
20 22
/// Release/acquire publication flag for PLATFORM.
25 27
unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined;
26 28
27 29
/// Handle timer boundaries and user calls, and stop on unsupported traps.
28 30
unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) {
29 31
    let cause = trap::classify(frame.cause);
30 -
    if cause == trap::Cause::Timer or cause == trap::Cause::Software {
32 +
    if cause == trap::Cause::Timer or cause == trap::Cause::Software or cause == trap::Cause::External {
31 33
        dispatch::interrupt(frame, hart);
32 34
        return;
33 35
    }
34 36
    if cause == trap::Cause::UserCall and trap::fromUser(frame) {
35 37
        dispatch::call(frame, hart);
80 82
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
81 83
        domains::initialize(&mut domains::STORE);
82 84
        budgets::initialize(&mut budgets::STORE);
83 85
        timers::initialize(&mut timers::STORE);
84 86
        remote::initialize(&mut remote::STORE, PLATFORM.harts);
87 +
        interrupts::initialize(&mut interrupts::STORE, PLATFORM.irqSources);
88 +
        try! plic::initialize(&PLATFORM);
85 89
        assert entryCount == 2;
86 90
        registry::initialize(&mut registry::STORE);
87 91
        let bootCatalog = @sliceOf(entries, entryCount);
88 92
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
89 93
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
kernel/kernel/calls.rad +27 -5
14 14
use super::loader;
15 15
use super::dispatch;
16 16
use super::timers;
17 17
use super::sync;
18 18
use super::remote;
19 +
use super::interrupts;
20 +
use super::plic;
19 21
20 22
/// Resolve the owning hart for an operation that can change CPU authority.
21 23
unsafe fn budgetHart(table: &capability::Table, operation: u64, arguments: &[u64]) -> ?u32 throws (abi::Error) {
22 24
    match operation {
23 25
        case 11, 12, 70, 71, 73 => {},
72 74
    }
73 75
    if entry.kind == abi::Kind::Budget {
74 76
        if not moving { throw abi::Error::Denied; }
75 77
        return try budgets::transfer(&mut budgets::STORE, &mut domain.memory.table, &mut destination.memory.table, handle, rights, now);
76 78
    }
79 +
    if entry.kind == abi::Kind::Interrupt {
80 +
        if not moving { throw abi::Error::Denied; }
81 +
        return try interrupts::transfer(&mut interrupts::STORE, &mut domain.memory.table,
82 +
            &mut destination.memory.table, handle, rights);
83 +
    }
77 84
    try resident(entry);
78 85
    let pending = try transactions::reserve(&mut destination.memory.table, owner, handle, entry, rights, moving);
79 86
    if moving { return transactions::transfer(&mut domain.memory.table, &mut destination.memory.table, pending); }
80 87
    return transactions::grant(&mut destination.memory.table, pending);
81 88
}
85 92
    let entry = try transactions::dropping(&domain.memory.table, handle, domain.events);
86 93
    let table: &mut capability::Table = &mut domain.memory.table;
87 94
    match entry.kind {
88 95
        case abi::Kind::Page => try pages::drop(&mut pages::STORE, table, handle),
89 96
        case abi::Kind::Budget => try budgets::drop(&mut budgets::STORE, table, handle),
97 +
        case abi::Kind::Interrupt => {
98 +
            let number = try interrupts::query(&interrupts::STORE, table, handle);
99 +
            plic::mask(number);
100 +
            try! interrupts::drop(&mut interrupts::STORE, table, handle);
101 +
        },
90 102
        else => {
91 103
            try resident(entry);
92 104
            let removed = try! capability::invalidate(table, handle);
93 105
        },
94 106
    }
95 107
}
96 108
97 109
/// Map a checked, aligned caller record address to words.
98 110
fn memory(address: u64) -> *mut u64;
111 +
/// Map a checked four-byte-aligned result buffer to its source-number word.
112 +
fn memory32(address: u64) -> *mut u32;
99 113
100 114
/// Test whether one nonwrapping extent contains the complete result range.
101 115
fn contains(base: u64, size: u64, address: u64, length: u64) -> bool {
102 116
    return address >= base and address - base <= size and length <= size - (address - base);
103 117
}
104 118
105 119
/// Validate aligned Page or private package-data storage with the given rights.
106 120
/// Package-state tables and kernel metadata cannot hold caller-supplied records.
107 -
unsafe fn buffer(domain: &domains::Domain, address: u64, size: u64, rights: abi::Rights) -> bool {
108 -
    if (address & 7) <> 0 or size > 0xffffffffffffffff - address { return false; }
121 +
unsafe fn buffer(domain: &domains::Domain, address: u64, size: u64, rights: abi::Rights, alignment: u64) -> bool {
122 +
    assert alignment == 4 or alignment == 8;
123 +
    if (address & (alignment - 1)) <> 0 or size > 0xffffffffffffffff - address { return false; }
109 124
    if pages::accessible(&pages::STORE, &domain.memory.table, address, size, rights) { return true; }
110 125
    for i in 0..limits::PACKAGES {
111 126
        if registry::STORE.slots[i].state <> slots::State::Live { continue; }
112 127
        let base = domain.graph.table[i];
113 128
        if base == 0 { continue; }
118 133
}
119 134
120 135
/// Copy a fixed query record only after validating its exact buffer size.
121 136
unsafe fn reply(domain: &domains::Domain, arguments: &[u64], words: &[u64]) throws (abi::Error) {
122 137
    let size = words.len as u64 * 8;
123 -
    if arguments[2] <> size or arguments[3] <> 0 or not buffer(domain, arguments[1], size, abi::Rights(abi::WRITE)) {
138 +
    if arguments[2] <> size or arguments[3] <> 0 or not buffer(domain, arguments[1], size, abi::Rights(abi::WRITE), 8) {
124 139
        throw abi::Error::InvalidArg;
125 140
    }
126 141
    let output = @sliceOf(memory(arguments[1]), words.len);
127 142
    for i in 0..words.len { set output[i] = words[i]; }
128 143
}
165 180
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Page, abi::Rights(0));
166 181
            let page = try pages::get(&pages::STORE, entry.object);
167 182
            try reply(&domain, arguments, &[page.base, page.count as u64]);
168 183
            return 0;
169 184
        },
185 +
        case 46 => {
186 +
            let number = try interrupts::query(&interrupts::STORE, &domain.memory.table, handle);
187 +
            if arguments[2] <> @sizeOf(abi::InterruptInfo) as u64 or arguments[3] <> 0
188 +
                or not buffer(&domain, arguments[1], 4, abi::Rights(abi::WRITE), 4) { throw abi::Error::InvalidArg; }
189 +
            set *memory32(arguments[1]) = number;
190 +
            return 0;
191 +
        },
170 192
        case 47 => {
171 193
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Domain, abi::Rights(0));
172 194
            let target = try domains::get(&domains::STORE, entry.object);
173 195
            let mut parent: u64 = 0;
174 196
            if let object = target.parent { set parent = abi::id(object); }
196 218
            try reply(&domain, arguments, &[abi::id(object), entry, package.codeAddress, package.code.len as u64 * 4, package.memory as u64]);
197 219
            return 0;
198 220
        },
199 221
        case 60 => {
200 222
            if arguments[2] <> @sizeOf(abi::ContextStart) as u64 or arguments[3] <> 0
201 -
                or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ)) {
223 +
                or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ), 8) {
202 224
                throw abi::Error::InvalidArg;
203 225
            }
204 226
            let words = @sliceOf(memory(arguments[1]), 4);
205 227
            let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] };
206 228
            return abi::id(try domains::contextCreate(&mut domains::STORE, &mut pages::STORE, &registry::STORE,
360 382
        case 51 => return try runtimeLoad(&mut domain, loader::Request {
361 383
            authority: handle, source: abi::Handle(arguments[1]), offset: arguments[2], length: arguments[3],
362 384
        }, guard),
363 385
        case 60 => {
364 386
            if arguments[2] <> @sizeOf(abi::ContextStart) as u64 or arguments[3] <> 0
365 -
                or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ)) {
387 +
                or not buffer(&domain, arguments[1], arguments[2], abi::Rights(abi::READ), 8) {
366 388
                sync::leave(guard); throw abi::Error::InvalidArg;
367 389
            }
368 390
            let words = @sliceOf(memory(arguments[1]), 4);
369 391
            let start = abi::ContextStart { entry: words[0], stack: words[1], args: words[2], size: words[3] };
370 392
            let pending = try domains::contextReserve(&mut domains::STORE, &mut pages::STORE, &registry::STORE,
kernel/kernel/calls.ras +2 -0
1 1
//! Typed access to a checked user record buffer.
2 2
.text;
3 3
.export @kernel::calls::memory;
4 +
.export @kernel::calls::memory32;
4 5
5 6
// The caller checked alignment, extent, and the required domain access rights.
6 7
@kernel::calls::memory
8 +
@kernel::calls::memory32
7 9
    ret;
kernel/kernel/dispatch.rad +5 -2
10 10
use super::range;
11 11
use super::calls;
12 12
use super::timers;
13 13
use super::sync;
14 14
use super::remote;
15 +
use super::plic;
15 16
16 17
/// Dispatch ownership and idle state retained by one physical hart.
17 18
export record State: Copy {
18 19
    /// Physical hart whose windows this state selects.
19 20
    hart: u32,
54 55
fn stackPointer() -> u64;
55 56
/// Trap anchor of the hart currently executing this continuation.
56 57
fn localAnchor() -> *mut trap::Hart;
57 58
/// Address of the M-mode idle loop.
58 59
fn idleAddress() -> u64;
59 -
/// Enable timer and software interrupt delivery with global interrupts disabled.
60 +
/// Enable machine interrupt sources with global interrupts disabled.
60 61
fn enable();
61 62
62 63
/// Initialize this hart's idle frame and validated timer before installing dispatch.
63 64
export unsafe fn initialize(machine: &platform::Platform, id: u32, kernelGp: u64) throws (abi::Error) {
64 65
    let device = try timer(machine, id);
84 85
    assert pointer >= stack.start and pointer < stack.end;
85 86
    let guard = sync::enter();
86 87
    writeSoftware(HARTS[id].timer.software, 0);
87 88
    let previous = HARTS[id].state.current;
88 89
    save(&mut HARTS[id].state, &mut domains::STORE, frame);
90 +
    if trap::classify(frame.cause) == trap::Cause::External { plic::claim(id); }
89 91
    let mut deliveries: [remote::Delivery; limits::REMOTE_PER_HART] = undefined;
90 92
    let mut count: u32 = 0;
91 93
    for i in 0..limits::REMOTE_PER_HART {
92 94
        let delivery = try! remote::take(&mut remote::STORE, id) else { break; };
93 95
        match delivery.request.action {
101 103
        set deliveries[count] = delivery;
102 104
        set count += 1;
103 105
    }
104 106
    let clock = now(HARTS[id].timer);
105 107
    let timeout = try! timers::service(&mut timers::STORE, &mut domains::STORE, &budgets::STORE, id, clock);
108 +
    let notified = timeout.harts | plic::service(clock);
106 109
    let choice = try! exchange(&mut HARTS[id].state, &budgets::STORE, &mut domains::STORE, frame, anchor, clock);
107 110
    let mut deadline = choice.deadline;
108 111
    if timeout.deadline < deadline { set deadline = timeout.deadline; }
109 112
    for target in 0..limits::HARTS {
110 -
        if target <> id and (timeout.harts & (1 << target)) <> 0 { signal(target); }
113 +
        if target <> id and (notified & (1 << target)) <> 0 { signal(target); }
111 114
    }
112 115
    arm(HARTS[id].timer, deadline);
113 116
    if previous <> choice.context {
114 117
        if let context = previous { signalContext(context, clock); }
115 118
    }
kernel/kernel/dispatch.ras +2 -1
60 60
    j @dispatchIdle;
61 61
62 62
// Global interrupt enable is restored from the selected frame by mret.
63 63
@kernel::dispatch::enable
64 64
    csrr %t0 mie;
65 -
    ori %t0 %t0 136;
65 +
    li %t1 2184;
66 +
    or %t0 %t0 %t1;
66 67
    csrw mie %t0;
67 68
    ret;
68 69
69 70
// The validated CLINT mapping contains a naturally aligned mtime register.
70 71
@kernel::dispatch::read
kernel/kernel/events.rad +18 -0
51 51
    head: u32,
52 52
    /// Authoritative producer counter.
53 53
    tail: u32,
54 54
    /// Terminal domain per occupied slot; generation zero denotes an ordinary event.
55 55
    subjects: [abi::Ref; CAPACITY],
56 +
    /// Interrupt source per occupied slot, or zero for another event kind.
57 +
    sources: [u32; CAPACITY],
56 58
}
57 59
58 60
/// Lifetime state of a terminal-event record.
59 61
export union State: Copy {
60 62
    /// Available for a new domain's terminal reservation.
153 155
/// Publish one event after capacity and receiver validation.
154 156
fn publish(queue: &mut Queue, ring: &mut Ring, event: Event, subject: abi::Ref) {
155 157
    let index = queue.tail & (CAPACITY - 1);
156 158
    set ring.data[index] = event;
157 159
    set queue.subjects[index] = subject;
160 +
    let mut source: u32 = 0;
161 +
    if event.kind == INTERRUPT { set source = event.code; }
162 +
    set queue.sources[index] = source;
158 163
    set queue.tail = next(queue.tail);
159 164
    sync::storeRelease32(&mut ring.tail, queue.tail);
160 165
}
161 166
162 167
/// Send an ordinary event, returning false when its reserved occupancy limit is reached.
163 168
export fn send(store: &mut Store, receiver: abi::Ref, ring: &mut Ring, event: Event) -> bool throws (abi::Error) {
164 169
    if event.reserved <> 0 or (event.kind <> INTERRUPT and event.kind <> TIMEOUT and event.kind <> WAKEUP) {
165 170
        throw abi::Error::InvalidArg;
166 171
    }
172 +
    if event.kind == INTERRUPT and event.code == 0 { throw abi::Error::InvalidArg; }
167 173
    try refresh(store, receiver, ring);
174 +
    if event.kind == INTERRUPT and interruptPending(store, receiver, event.code) { return true; }
168 175
    let queue = &mut store.queues[receiver.index];
169 176
    if distance(queue.tail, queue.head) >= ORDINARY { return false; }
170 177
    publish(queue, ring, event, abi::Ref { index: 0, generation: 0 });
171 178
    return true;
172 179
}
173 180
181 +
/// Test trusted IRQ metadata after the receiver's consumer progress is refreshed.
182 +
export fn interruptPending(store: &Store, receiver: abi::Ref, source: u32) -> bool {
183 +
    if source == 0 or not live(store, receiver) { return false; }
184 +
    let queue = &store.queues[receiver.index];
185 +
    for i in 0..distance(queue.tail, queue.head) {
186 +
        let index = ((queue.head & (CAPACITY - 1)) + i) & (CAPACITY - 1);
187 +
        if queue.sources[index] == source { return true; }
188 +
    }
189 +
    return false;
190 +
}
191 +
174 192
/// Read one published event and release its slot. Callers serialize consumers.
175 193
export fn pop(ring: &mut Ring) -> ?Event throws (abi::Error) {
176 194
    let head = sync::loadAcquire32(&ring.head);
177 195
    let tail = sync::loadAcquire32(&ring.tail);
178 196
    let count = distance(tail, head);
kernel/kernel/interrupts.rad added +137 -0
1 +
//! Exclusive IRQ ownership and retained notification state.
2 +
use super::abi;
3 +
use super::limits;
4 +
use super::slots;
5 +
use super::capability;
6 +
use super::transactions;
7 +
use super::domains;
8 +
use super::events;
9 +
10 +
/// Notification state while hardware delivery is serialized.
11 +
export union State: Copy {
12 +
    /// No retained notification prevents the source from being enabled.
13 +
    Idle,
14 +
    /// The claimed source needs an event in its current receiver.
15 +
    Pending,
16 +
    /// A queued event must be consumed before the source is enabled.
17 +
    Queued,
18 +
}
19 +
20 +
/// One physical source; its table index is the PLIC number minus one.
21 +
export record Interrupt: Copy {
22 +
    /// Domain holding the sole interrupt capability.
23 +
    owner: abi::Ref,
24 +
    /// Sole live capability in the owner's table.
25 +
    handle: abi::Handle,
26 +
    /// Retained notification and rearming state.
27 +
    state: State,
28 +
}
29 +
30 +
/// Fixed source identities and their exclusive receivers.
31 +
export record Store: Copy {
32 +
    /// Number of physical sources reported by the platform.
33 +
    count: u32,
34 +
    /// Source object generations, indexed by PLIC number minus one.
35 +
    slots: [slots::Slot; limits::INTERRUPTS],
36 +
    /// Published ownership and notification state.
37 +
    records: [Interrupt; limits::INTERRUPTS],
38 +
}
39 +
40 +
/// Global interrupt ownership serialized by the metadata lock.
41 +
export unsafe static STORE: Store = undefined;
42 +
43 +
/// Initialize the bounded physical-source profile before issuing capabilities.
44 +
export fn initialize(store: &mut Store, count: u32) {
45 +
    assert count <= limits::INTERRUPTS;
46 +
    set store.count = count;
47 +
    slots::initialize(&mut store.slots[..]);
48 +
}
49 +
50 +
/// Resolve a source object after checking its generation.
51 +
export fn get(store: &Store, object: abi::Ref) -> Interrupt throws (abi::Error) {
52 +
    if object.index >= store.count or not slots::matches(&store.slots[..], object, slots::State::Live) {
53 +
        throw abi::Error::BadHandle;
54 +
    }
55 +
    return store.records[object.index];
56 +
}
57 +
58 +
/// Resolve the sole capability owner with the required rights.
59 +
fn owned(store: &Store, table: &capability::Table, handle: abi::Handle, rights: u16) -> abi::Ref throws (abi::Error) {
60 +
    let entry = try capability::lookup(table, handle, abi::Kind::Interrupt, abi::Rights(rights));
61 +
    let source = try get(store, entry.object);
62 +
    if source.owner <> table.owner or source.handle <> handle { throw abi::Error::BadHandle; }
63 +
    return entry.object;
64 +
}
65 +
66 +
/// Issue one source during trusted resource initialization.
67 +
export fn seed(store: &mut Store, table: &mut capability::Table, number: u32) -> abi::Handle throws (abi::Error) {
68 +
    if number == 0 or number > store.count { throw abi::Error::InvalidArg; }
69 +
    let index = number - 1;
70 +
    if store.slots[index].state <> slots::State::Free { throw abi::Error::Busy; }
71 +
    let pending = try slots::reserve(&mut table.slots[..]);
72 +
    let object = abi::Ref { index, generation: store.slots[index].generation };
73 +
    let handle = try! abi::handle(abi::Kind::Interrupt, slots::reference(&pending));
74 +
    set store.slots[index].state = slots::State::Reserved;
75 +
    set store.records[index] = Interrupt { owner: table.owner, handle, state: State::Idle };
76 +
    let live = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(object));
77 +
    return capability::publish(table, pending, capability::Entry {
78 +
        kind: abi::Kind::Interrupt, object: live, rights: abi::Rights(abi::READ | abi::TRANSFER),
79 +
    });
80 +
}
81 +
82 +
/// Read the source number through its sole capability.
83 +
export fn query(store: &Store, table: &capability::Table, handle: abi::Handle) -> u32 throws (abi::Error) {
84 +
    return (try owned(store, table, handle, 0)).index + 1;
85 +
}
86 +
87 +
/// Move exclusive ownership after reserving the receiver's capability slot.
88 +
export fn transfer(store: &mut Store, source: &mut capability::Table, target: &mut capability::Table,
89 +
    handle: abi::Handle, rights: u64) -> abi::Handle throws (abi::Error)
90 +
{
91 +
    let object = try owned(store, source, handle, abi::TRANSFER);
92 +
    let entry = try! capability::get(source, handle);
93 +
    let pending = try transactions::reserve(target, source.owner, handle, entry, rights, true);
94 +
    let result = transactions::transfer(source, target, pending);
95 +
    set store.records[object.index].owner = target.owner;
96 +
    set store.records[object.index].handle = result;
97 +
    if store.records[object.index].state == State::Queued { set store.records[object.index].state = State::Pending; }
98 +
    return result;
99 +
}
100 +
101 +
/// Invalidate a source capability after its hardware route has been masked.
102 +
export fn drop(store: &mut Store, table: &mut capability::Table, handle: abi::Handle) throws (abi::Error) {
103 +
    let object = try owned(store, table, handle, 0);
104 +
    let removed = try! capability::invalidate(table, handle);
105 +
    try! slots::release(&mut store.slots[..], object);
106 +
}
107 +
108 +
/// Retain a claimed source without scanning domain capability tables.
109 +
export fn claim(store: &mut Store, number: u32) -> bool {
110 +
    if number == 0 or number > store.count { return false; }
111 +
    let index = number - 1;
112 +
    if store.slots[index].state <> slots::State::Live { return false; }
113 +
    if store.records[index].state == State::Idle { set store.records[index].state = State::Pending; }
114 +
    return true;
115 +
}
116 +
117 +
/// Deliver retained work or acknowledge consumption; true permits hardware rearming.
118 +
export unsafe fn service(store: &mut Store, receivers: &mut domains::Store, number: u32) -> bool throws (abi::Error) {
119 +
    if number == 0 or number > store.count { throw abi::Error::InvalidArg; }
120 +
    let index = number - 1;
121 +
    if store.slots[index].state <> slots::State::Live { return false; }
122 +
    let source = store.records[index];
123 +
    let mut domain = try domains::get(receivers, source.owner) catch { return false; };
124 +
    if domain.state == domains::Lifecycle::Dead { return false; }
125 +
    try events::refresh(&mut receivers.events, source.owner, &domain.memory.ring);
126 +
    if source.state == State::Queued {
127 +
        if events::interruptPending(&receivers.events, source.owner, number) { return false; }
128 +
        set store.records[index].state = State::Idle;
129 +
    } else if source.state == State::Pending {
130 +
        if try domains::notify(receivers, source.owner,
131 +
            events::Event { kind: events::INTERRUPT, reserved: 0, code: number, value: 0 }) {
132 +
            set store.records[index].state = State::Queued;
133 +
        }
134 +
        return false;
135 +
    }
136 +
    return true;
137 +
}
kernel/kernel/platform.rad +90 -7
53 53
    devices: [Device; limits::DEVICES],
54 54
    /// Number of MMIO devices.
55 55
    deviceCount: u32,
56 56
    /// Bit mask of online physical hart IDs.
57 57
    harts: u32,
58 +
    /// PLIC source count, or zero when no supported controller is present.
59 +
    irqSources: u32,
60 +
    /// Machine-mode PLIC context index for each online hart.
61 +
    irqContexts: [?u32; limits::HARTS],
58 62
    /// Timer ticks per second.
59 63
    timebase: u32,
60 64
    /// Kernel stack ranges, indexed by physical hart ID.
61 65
    stacks: [range::Range; limits::HARTS],
62 66
    /// Number of stack ranges in the boot contract.
79 83
    registers: ?fdt::Span,
80 84
    /// Device compatibility strings, if present.
81 85
    compatible: ?fdt::Span,
82 86
    /// Device type string, if present.
83 87
    deviceType: ?fdt::Span,
88 +
    /// Interrupt-controller reference identity, or zero when absent.
89 +
    phandle: u32,
90 +
    /// Cells in one interrupt specifier.
91 +
    interruptCells: u32,
92 +
    /// PLIC source count, or zero when absent.
93 +
    sources: u32,
94 +
    /// PLIC context references and local interrupt numbers.
95 +
    interrupts: ?fdt::Span,
84 96
    /// Node status permits use.
85 97
    enabled: bool,
86 98
    /// This node or its parent is the reserved-memory container.
87 99
    reserved: bool,
88 100
}
89 101
102 +
/// Read a CPU node's bounded physical hart identifier.
103 +
fn cpu(bytes: &[u8], node: Node) -> u32 throws (Error) {
104 +
    let registers = node.registers else { throw Error::Invalid; };
105 +
    if node.sizeCells <> 0 or node.addressCells < 1 or node.addressCells > 2
106 +
        or registers.end - registers.start <> node.addressCells * 4 { throw Error::Invalid; }
107 +
    let hart = try fdt::integer(bytes, registers.start, node.addressCells * 4) catch err { throw Error::Tree(err); };
108 +
    if hart >= limits::HARTS as u64 { throw Error::Invalid; }
109 +
    return hart as u32;
110 +
}
111 +
112 +
/// Associate a CPU-local interrupt controller with its parent hart.
113 +
fn controller(bytes: &[u8], node: Node, parent: Node, references: &mut [u32]) throws (Error) {
114 +
    let kind = parent.deviceType else { throw Error::Invalid; };
115 +
    if not string(bytes, kind, &"cpu"[..]) or node.phandle == 0 or node.interruptCells <> 1 { throw Error::Invalid; }
116 +
    let hart = try cpu(bytes, parent);
117 +
    if references[hart] <> 0 { throw Error::Invalid; }
118 +
    for reference in &references[..] { if reference == node.phandle { throw Error::Invalid; } }
119 +
    set references[hart] = node.phandle;
120 +
}
121 +
122 +
/// Resolve PLIC context ordinals after all CPU controller references are known.
123 +
fn plic(bytes: &[u8], node: Node, references: &[u32], platform: &mut Platform) throws (Error) {
124 +
    if node.sources == 0 or node.sources > limits::INTERRUPTS or node.interruptCells <> 1 { throw Error::Invalid; }
125 +
    let contexts = node.interrupts else { throw Error::Invalid; };
126 +
    let length = contexts.end - contexts.start;
127 +
    if length == 0 or length % 8 <> 0 or length / 8 > limits::HARTS * 2 { throw Error::Invalid; }
128 +
    let registers = node.registers else { throw Error::Invalid; };
129 +
    if registers.end - registers.start <> (node.addressCells + node.sizeCells) * 4 { throw Error::Invalid; }
130 +
    let memory = try extent(bytes, registers.start, node.addressCells, node.sizeCells);
131 +
    let required = 0x200008 + (length as u64 / 8 - 1) * 0x1000;
132 +
    if (memory.start & 3) <> 0 or memory.end - memory.start < required { throw Error::Invalid; }
133 +
    for index in 0..length / 8 {
134 +
        let offset = contexts.start + index * 8;
135 +
        let reference = try fdt::integer(bytes, offset, 4) catch err { throw Error::Tree(err); };
136 +
        let cause = try fdt::integer(bytes, offset + 4, 4) catch err { throw Error::Tree(err); };
137 +
        let mut hart: ?u32 = nil;
138 +
        for id in 0..limits::HARTS { if references[id] <> 0 and references[id] as u64 == reference { set hart = id; } }
139 +
        let id = hart else { throw Error::Invalid; };
140 +
        if cause == 9 or cause == 0xffffffff { continue; }
141 +
        if cause <> 11 { throw Error::Invalid; }
142 +
        if (platform.harts & (1 << id)) == 0 { continue; }
143 +
        if platform.irqContexts[id] <> nil { throw Error::Invalid; }
144 +
        set platform.irqContexts[id] = index;
145 +
    }
146 +
    for hart in 0..limits::HARTS {
147 +
        if (platform.harts & (1 << hart)) <> 0 and platform.irqContexts[hart] == nil { throw Error::Invalid; }
148 +
    }
149 +
    set platform.irqSources = node.sources;
150 +
}
151 +
90 152
/// Compare a complete byte span with a name.
91 153
fn equal(bytes: &[u8], span: fdt::Span, value: &[u8]) -> bool {
92 154
    if span.end - span.start <> value.len { return false; }
93 155
    for i in 0..value.len { if bytes[span.start + i] <> value[i] { return false; } }
94 156
    return true;
152 214
fn finish(bytes: &[u8], node: Node, platform: &mut Platform) throws (Error) {
153 215
    if not node.enabled { return; }
154 216
    let registers = node.registers else { return; };
155 217
    if let typeName = node.deviceType {
156 218
        if string(bytes, typeName, &"cpu"[..]) {
157 -
            if node.sizeCells <> 0 or node.addressCells < 1 or node.addressCells > 2
158 -
                or registers.end - registers.start <> node.addressCells * 4 { throw Error::Invalid; }
159 -
            let hart = try fdt::integer(bytes, registers.start, node.addressCells * 4) catch err { throw Error::Tree(err); };
160 -
            if hart >= limits::HARTS as u64 or (platform.harts & (1 << hart as u32)) <> 0 { throw Error::Invalid; }
161 -
            set platform.harts |= 1 << hart as u32;
219 +
            let hart = try cpu(bytes, node);
220 +
            if (platform.harts & (1 << hart)) <> 0 { throw Error::Invalid; }
221 +
            set platform.harts |= 1 << hart;
162 222
            return;
163 223
        }
164 224
    }
165 225
    let width = (node.addressCells + node.sizeCells) * 4;
166 226
    if width == 0 or (registers.end - registers.start) % width <> 0 { throw Error::Invalid; }
200 260
201 261
/// Extract native platform resources. Discard the output if decoding fails.
202 262
export unsafe fn decode(bytes: &[u8], platform: &mut Platform) throws (Error) {
203 263
    set platform.ramCount = 0; set platform.reservedCount = 0; set platform.deviceCount = 0;
204 264
    set platform.harts = 0; set platform.timebase = 0; set platform.stackCount = 0;
265 +
    set platform.irqSources = 0;
266 +
    for hart in 0..limits::HARTS { set platform.irqContexts[hart] = nil; }
267 +
    let mut references: [u32; limits::HARTS] = [0; limits::HARTS];
268 +
    let mut interruptController: ?Node = nil;
205 269
    let tree = try fdt::header(bytes) catch err { throw Error::Tree(err); };
206 270
    let mut at = tree.reservations.start;
207 271
    while at < tree.reservations.end {
208 272
        try protect(platform, try extent(bytes, at, 2, 2)); set at += 16;
209 273
    }
223 287
                    set reserved = parent.reserved;
224 288
                    set enabled = parent.enabled;
225 289
                }
226 290
                if state.depth == 2 and equal(bytes, name, &"reserved-memory"[..]) { set reserved = true; }
227 291
                set nodes[state.depth - 1] = Node { name, addressCells, sizeCells, childAddress: 2, childSize: 1,
228 -
                    registers: nil, compatible: nil, deviceType: nil, enabled, reserved };
292 +
                    registers: nil, compatible: nil, deviceType: nil, phandle: 0, interruptCells: 0,
293 +
                    sources: 0, interrupts: nil, enabled, reserved };
229 294
            },
230 295
            case fdt::Event::Property { name, value } => {
231 296
                if equal(bytes, name, &"#address-cells"[..]) {
232 297
                    let count = try scalar(bytes, value);
233 298
                    if count > 2 { throw Error::Invalid; } set nodes[state.depth - 1].childAddress = count;
235 300
                    let count = try scalar(bytes, value);
236 301
                    if count > 2 { throw Error::Invalid; } set nodes[state.depth - 1].childSize = count;
237 302
                } else if equal(bytes, name, &"reg"[..]) { set nodes[state.depth - 1].registers = value; }
238 303
                else if equal(bytes, name, &"compatible"[..]) { set nodes[state.depth - 1].compatible = value; }
239 304
                else if equal(bytes, name, &"device_type"[..]) { set nodes[state.depth - 1].deviceType = value; }
305 +
                else if equal(bytes, name, &"phandle"[..]) { set nodes[state.depth - 1].phandle = try scalar(bytes, value); }
306 +
                else if equal(bytes, name, &"#interrupt-cells"[..]) { set nodes[state.depth - 1].interruptCells = try scalar(bytes, value); }
307 +
                else if equal(bytes, name, &"riscv,ndev"[..]) { set nodes[state.depth - 1].sources = try scalar(bytes, value); }
308 +
                else if equal(bytes, name, &"interrupts-extended"[..]) { set nodes[state.depth - 1].interrupts = value; }
240 309
                else if equal(bytes, name, &"status"[..]) {
241 310
                    set nodes[state.depth - 1].enabled = nodes[state.depth - 1].enabled and (string(bytes, value, &"okay"[..]) or string(bytes, value, &"ok"[..]));
242 311
                } else if equal(bytes, name, &"ranges"[..]) and value.start <> value.end { throw Error::Invalid; }
243 312
                else if state.depth == 2 and equal(bytes, nodes[state.depth - 1].name, &"cpus"[..]) and equal(bytes, name, &"timebase-frequency"[..]) {
244 313
                    set platform.timebase = try scalar(bytes, value);
251 320
                        if (stack.start & 15) <> 0 or (stack.end & 15) <> 0 { throw Error::Invalid; }
252 321
                        set platform.stacks[i] = stack; try protect(platform, stack);
253 322
                    }
254 323
                }
255 324
            },
256 -
            case fdt::Event::EndNode => try finish(bytes, nodes[state.depth], platform),
325 +
            case fdt::Event::EndNode => {
326 +
                let node = nodes[state.depth];
327 +
                if let compatible = node.compatible {
328 +
                    if string(bytes, compatible, &"riscv,cpu-intc"[..]) {
329 +
                        if state.depth == 0 { throw Error::Invalid; }
330 +
                        try controller(bytes, node, nodes[state.depth - 1], &mut references[..]);
331 +
                    }
332 +
                    if node.enabled and string(bytes, compatible, &"riscv,plic0"[..]) {
333 +
                        if interruptController <> nil { throw Error::Invalid; }
334 +
                        set interruptController = node;
335 +
                    }
336 +
                }
337 +
                try finish(bytes, node, platform);
338 +
            },
257 339
            case fdt::Event::End => break,
258 340
        }
259 341
    }
260 342
    if platform.ramCount == 0 or platform.harts == 0 or platform.timebase == 0 { throw Error::Invalid; }
343 +
    if let node = interruptController { try plic(bytes, node, &references[..], platform); }
261 344
    for hart in 0..limits::HARTS {
262 345
        if (platform.harts & (1 << hart)) == 0 { continue; }
263 346
        if hart >= platform.stackCount or not inRam(platform, platform.stacks[hart]) { throw Error::Invalid; }
264 347
        for other in 0..hart {
265 348
            if (platform.harts & (1 << other)) <> 0 and overlaps(platform.stacks[hart], platform.stacks[other]) {
kernel/kernel/plic.rad added +107 -0
1 +
//! PLIC register access and bounded external-interrupt delivery.
2 +
use super::abi;
3 +
use super::limits;
4 +
use super::platform;
5 +
use super::sync;
6 +
use super::slots;
7 +
use super::interrupts;
8 +
use super::domains;
9 +
use super::budgets;
10 +
11 +
/// Validated registers for the kernel's machine interrupt receiver.
12 +
export record Mapping: Copy {
13 +
    /// Physical controller base.
14 +
    base: u64,
15 +
    /// Supported nonzero source count.
16 +
    count: u32,
17 +
    /// PLIC machine context assigned to physical hart zero.
18 +
    context: u32,
19 +
}
20 +
21 +
/// Controller state published before secondary harts start.
22 +
static CONTROLLER: ?Mapping = nil;
23 +
24 +
/// Map a validated aligned MMIO address to one device word.
25 +
fn pointer(address: u64) -> *unsafe mut u32;
26 +
27 +
/// Select the controller and validate its routed context's register extent.
28 +
export fn mapping(machine: &platform::Platform) -> ?Mapping throws (abi::Error) {
29 +
    if machine.irqSources == 0 { return nil; }
30 +
    if machine.irqSources > limits::INTERRUPTS or (machine.harts & 1) == 0 { throw abi::Error::InvalidArg; }
31 +
    let context = machine.irqContexts[0] else { throw abi::Error::InvalidArg; };
32 +
    if context >= limits::HARTS * 2 { throw abi::Error::InvalidArg; }
33 +
    let mut result: ?Mapping = nil;
34 +
    for i in 0..machine.deviceCount {
35 +
        let device = machine.devices[i];
36 +
        if device.kind <> platform::Kind::Plic { continue; }
37 +
        let required = 0x200008 + context as u64 * 0x1000;
38 +
        if result <> nil or (device.memory.start & 3) <> 0 or device.memory.end < device.memory.start
39 +
            or device.memory.end - device.memory.start < required { throw abi::Error::InvalidArg; }
40 +
        set result = Mapping { base: device.memory.start, count: machine.irqSources, context };
41 +
    }
42 +
    if result == nil { throw abi::Error::InvalidArg; }
43 +
    return result;
44 +
}
45 +
46 +
/// Mask all machine contexts and initialize one priority class before dispatch.
47 +
export unsafe fn initialize(machine: &platform::Platform) throws (abi::Error) {
48 +
    set CONTROLLER = try mapping(machine);
49 +
    let device = CONTROLLER else { return; };
50 +
    for hart in 0..limits::HARTS {
51 +
        let context = machine.irqContexts[hart] else { continue; };
52 +
        for word in 0..(device.count + 32) / 32 {
53 +
            sync::write32(pointer(device.base + 0x2000 + context as u64 * 0x80 + word as u64 * 4), 0);
54 +
        }
55 +
        sync::write32(pointer(device.base + 0x200000 + context as u64 * 0x1000), 0);
56 +
    }
57 +
    for number in 1..device.count + 1 { sync::write32(pointer(device.base + number as u64 * 4), 1); }
58 +
}
59 +
60 +
/// Change one source enable bit while the metadata lock serializes the word.
61 +
unsafe fn enable(device: Mapping, number: u32, enabled: bool) {
62 +
    assert number > 0 and number <= device.count;
63 +
    let address = pointer(device.base + 0x2000 + device.context as u64 * 0x80 + (number / 32) as u64 * 4);
64 +
    let before = sync::read32(address as *unsafe u32);
65 +
    let bit = 1 << (number % 32);
66 +
    let mut after = before & ~bit;
67 +
    if enabled { set after |= bit; }
68 +
    if after <> before { sync::write32(address, after); }
69 +
}
70 +
71 +
/// Mask a source before its exclusive capability is invalidated.
72 +
export unsafe fn mask(number: u32) {
73 +
    let device = CONTROLLER else { return; };
74 +
    enable(device, number, false);
75 +
}
76 +
77 +
/// Claim one source, retain its work, complete it while enabled, then mask it.
78 +
export unsafe fn claim(hart: u32) {
79 +
    let device = CONTROLLER else { return; };
80 +
    assert hart == 0;
81 +
    let address = pointer(device.base + 0x200004 + device.context as u64 * 0x1000);
82 +
    let number = sync::read32(address as *unsafe u32);
83 +
    if number == 0 { return; }
84 +
    assert number <= device.count;
85 +
    let retained = interrupts::claim(&mut interrupts::STORE, number);
86 +
    sync::write32(address, number);
87 +
    enable(device, number, false);
88 +
}
89 +
90 +
/// Deliver retained notifications, rearm consumed sources, and report runnable harts.
91 +
export unsafe fn service(now: u64) -> u32 {
92 +
    let device = CONTROLLER else { return 0; };
93 +
    let mut harts: u32 = 0;
94 +
    for number in 1..device.count + 1 {
95 +
        let index = number - 1;
96 +
        let mut pending = false;
97 +
        if interrupts::STORE.slots[index].state == slots::State::Live {
98 +
            set pending = interrupts::STORE.records[index].state == interrupts::State::Pending;
99 +
        }
100 +
        let enabled = try interrupts::service(&mut interrupts::STORE, &mut domains::STORE, number) catch { false };
101 +
        enable(device, number, enabled);
102 +
        if pending and interrupts::STORE.records[index].state == interrupts::State::Queued {
103 +
            set harts |= budgets::wakeTargets(&budgets::STORE, &domains::STORE, interrupts::STORE.records[index].owner, now);
104 +
        }
105 +
    }
106 +
    return harts;
107 +
}
kernel/kernel/plic.ras added +7 -0
1 +
//! Typed access to validated, naturally aligned PLIC registers.
2 +
.text;
3 +
.export @kernel::plic::pointer;
4 +
5 +
// Register extents and alignment are checked before device access.
6 +
@kernel::plic::pointer
7 +
    ret;
kernel/kernel/sys.rad +10 -0
122 122
        try! result(ecall(abi::Operation::QueryPage as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::PageInfo) as i64, 0));
123 123
        return info;
124 124
    }
125 125
}
126 126
127 +
/// Read a physical interrupt source. An invalid handle is a program error.
128 +
export fn queryInterrupt(handle: abi::Handle) -> abi::InterruptInfo {
129 +
    unsafe {
130 +
        let mut info: abi::InterruptInfo = undefined;
131 +
        try! result(ecall(abi::Operation::QueryInterrupt as u32, *handle as i64,
132 +
            (&mut info) as i64, @sizeOf(abi::InterruptInfo) as i64, 0));
133 +
        return info;
134 +
    }
135 +
}
136 +
127 137
/// Read domain identity and lifetime. An invalid handle is a program error.
128 138
export fn queryDomain(handle: abi::Handle) -> abi::DomainInfo {
129 139
    unsafe {
130 140
        let mut info: abi::DomainInfo = undefined;
131 141
        try! result(ecall(abi::Operation::QueryDomain as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::DomainInfo) as i64, 0));
kernel/kernel/tests.rad +1 -0
10 10
export mod frames;
11 11
export mod backing;
12 12
export mod pages;
13 13
export mod transactions;
14 14
export mod events;
15 +
export mod interrupts;
15 16
export mod registry;
16 17
export mod loader;
17 18
export mod instances;
18 19
export mod domains;
19 20
export mod budgets;
kernel/kernel/tests/calls.rad +33 -0
11 11
use kernel::registry;
12 12
use kernel::limits;
13 13
use kernel::trap;
14 14
use kernel::budgets;
15 15
use kernel::remote;
16 +
use kernel::interrupts;
17 +
18 +
/// Interrupt queries write exactly four bytes at four-byte-aligned addresses.
19 +
@test unsafe fn interruptRecord() throws (testing::TestError) {
20 +
    let owner = initialize();
21 +
    interrupts::initialize(&mut interrupts::STORE, 8);
22 +
    let handle = try! interrupts::seed(&mut interrupts::STORE, &mut MEMORY.table, 7);
23 +
    let page = try! calls::invoke(owner, 30, &[0, 1, 0, 0], 0);
24 +
    let base = (&RAM[OFFSET]) as u64;
25 +
    set RAM[OFFSET] = 0xaaaaaaaa55555555;
26 +
    set RAM[OFFSET + 1] = 0x1122334455667788;
27 +
    let result = try! calls::invoke(owner, 46, &[*handle, base + 4, 4, 0], 0);
28 +
    assert result == 0 and @sizeOf(abi::InterruptInfo) == 4;
29 +
    assert RAM[OFFSET] == 0x0000000755555555 and RAM[OFFSET + 1] == 0x1122334455667788;
30 +
    let mut rejected: u32 = 0;
31 +
    for args in &[
32 +
        [*handle, base + 2, 4, 0], [*handle, base + 4, 8, 0],
33 +
        [*handle, base + 4096, 4, 0], [*handle, base + 4, 4, 1],
34 +
    ] {
35 +
        try calls::invoke(owner, 46, &args[..], 0) catch error {
36 +
            assert error == abi::Error::InvalidArg; set rejected += 1;
37 +
        };
38 +
    }
39 +
    assert rejected == 4 and RAM[OFFSET] == 0x0000000755555555;
40 +
    let boundary = try! calls::invoke(owner, 46, &[*handle, base + 4092, 4, 0], 0);
41 +
    assert boundary == 0 and RAM[OFFSET + 511] == 0x0000000700000000;
42 +
    let decoded = try! abi::decode(abi::Handle(page));
43 +
    set MEMORY.table.entries[decoded.object.index].rights = abi::Rights(abi::READ);
44 +
    try calls::invoke(owner, 46, &[*handle, base + 4, 4, 0], 0) catch error {
45 +
        assert error == abi::Error::InvalidArg; set rejected += 1;
46 +
    };
47 +
    assert rejected == 5;
48 +
}
16 49
17 50
/// Deferred budget operations recheck authority and expiration at execution time.
18 51
@test unsafe fn deferredBudget() throws (testing::TestError) {
19 52
    let owner = initialize();
20 53
    let handle = try! budgets::seed(&mut budgets::STORE, &mut MEMORY.table, 1, 10, 100);
kernel/kernel/tests/events.rad +49 -0
7 7
8 8
/// Kernel event metadata workspace.
9 9
unsafe static STORE: events::Store = undefined;
10 10
/// Consumer-visible ring for the receiver.
11 11
unsafe static RING: events::Ring = undefined;
12 +
/// Separate receiver used to check per-domain IRQ coalescing.
13 +
unsafe static OTHER: events::Ring = undefined;
12 14
13 15
/// Return the receiver's current domain reference.
14 16
fn receiver() -> abi::Ref { return abi::Ref { index: 0, generation: 1 }; }
15 17
16 18
/// Reset metadata and open one queue.
22 24
/// Build an ordinary wakeup with a recognizable payload.
23 25
fn wake(value: u64) -> events::Event {
24 26
    return events::Event { kind: events::WAKEUP, reserved: 0, code: 17, value };
25 27
}
26 28
29 +
/// Duplicate IRQ notifications retain one queue entry even at ordinary capacity.
30 +
@test unsafe fn interruptCoalescing() throws (testing::TestError) {
31 +
    initialize();
32 +
    let irq = events::Event { kind: events::INTERRUPT, reserved: 0, code: 5, value: 0 };
33 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
34 +
    set RING.data[0].code = 99;
35 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
36 +
    assert STORE.queues[0].tail == 1;
37 +
    for i in 1..events::ORDINARY { assert try! events::send(&mut STORE, receiver(), &mut RING, wake(i as u64)); }
38 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
39 +
    assert STORE.queues[0].tail == events::ORDINARY;
40 +
    assert not try! events::send(&mut STORE, receiver(), &mut RING,
41 +
        events::Event { kind: events::INTERRUPT, reserved: 0, code: 6, value: 0 });
42 +
    let consumed = try! events::pop(&mut RING) else panic "missing interrupt";
43 +
    assert consumed.code == 99;
44 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
45 +
    assert STORE.queues[0].tail == events::ORDINARY + 1;
46 +
}
47 +
48 +
/// Pending IRQ identity is scoped to the receiver and survives counter wrap.
49 +
@test unsafe fn interruptReceivers() throws (testing::TestError) {
50 +
    initialize();
51 +
    let other = abi::Ref { index: 1, generation: 1 };
52 +
    try! events::open(&mut STORE, other, &mut OTHER);
53 +
    set STORE.queues[0].head = 0xfffffffe;
54 +
    set STORE.queues[0].tail = 0xfffffffe;
55 +
    set RING.head = 0xfffffffe;
56 +
    set RING.tail = 0xfffffffe;
57 +
    let irq = events::Event { kind: events::INTERRUPT, reserved: 0, code: 5, value: 0 };
58 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
59 +
    assert try! events::send(&mut STORE, receiver(), &mut RING,
60 +
        events::Event { kind: events::INTERRUPT, reserved: 0, code: 6, value: 0 });
61 +
    assert try! events::send(&mut STORE, other, &mut OTHER, irq);
62 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
63 +
    assert STORE.queues[0].tail == 0 and STORE.queues[1].tail == 1;
64 +
    assert events::interruptPending(&STORE, receiver(), 5);
65 +
    assert events::interruptPending(&STORE, other, 5);
66 +
    let first = try! events::pop(&mut RING) else panic "missing wrapped IRQ";
67 +
    assert first.code == 5;
68 +
    try! events::refresh(&mut STORE, receiver(), &RING);
69 +
    assert not events::interruptPending(&STORE, receiver(), 5);
70 +
    assert events::interruptPending(&STORE, receiver(), 6);
71 +
    assert events::interruptPending(&STORE, other, 5);
72 +
    assert try! events::send(&mut STORE, receiver(), &mut RING, irq);
73 +
    assert STORE.queues[0].tail == 1;
74 +
}
75 +
27 76
/// Fill ordinary capacity while preserving the reserved lifecycle capacity.
28 77
@test unsafe fn saturation() throws (testing::TestError) {
29 78
    initialize();
30 79
    for i in 0..events::ORDINARY {
31 80
        try testing::expect(try! events::send(&mut STORE, receiver(), &mut RING, wake(i as u64)));
kernel/kernel/tests/interrupts.rad added +120 -0
1 +
//! Exclusive source ownership, deferred notification, and rearming.
2 +
use std::testing;
3 +
use kernel::abi;
4 +
use kernel::slots;
5 +
use kernel::capability;
6 +
use kernel::domains;
7 +
use kernel::events;
8 +
use kernel::interrupts;
9 +
use kernel::limits;
10 +
11 +
/// Physical source metadata under test.
12 +
unsafe static STORE: interrupts::Store = undefined;
13 +
/// Receiver lifetimes and trusted event metadata.
14 +
unsafe static DOMAINS: domains::Store = undefined;
15 +
/// Source domain capability table and ring.
16 +
unsafe static SOURCE: domains::Memory = undefined;
17 +
/// Target domain capability table and ring.
18 +
unsafe static TARGET: domains::Memory = undefined;
19 +
20 +
/// Initialize two live receivers and eight physical sources.
21 +
unsafe fn initialize() {
22 +
    interrupts::initialize(&mut STORE, 8);
23 +
    domains::initialize(&mut DOMAINS);
24 +
    open(&mut SOURCE);
25 +
    open(&mut TARGET);
26 +
}
27 +
28 +
/// Publish one receiver backed by statically retained test memory.
29 +
unsafe fn open(memory: &mut domains::Memory) {
30 +
    let pending = try! slots::reserve(&mut DOMAINS.slots[..]);
31 +
    let owner = try! slots::commit(&mut DOMAINS.slots[..], pending);
32 +
    capability::initialize(&mut memory.table, owner);
33 +
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Active;
34 +
    set DOMAINS.records[owner.index].memory = (&mut *memory) as *unsafe mut domains::Memory;
35 +
    try! events::open(&mut DOMAINS.events, owner, &mut memory.ring);
36 +
}
37 +
38 +
/// Transfer preserves queued events and moves pending service to the new owner.
39 +
@test unsafe fn ownership() throws (testing::TestError) {
40 +
    initialize();
41 +
    let first = try! interrupts::seed(&mut STORE, &mut SOURCE.table, 5);
42 +
    assert try! interrupts::query(&STORE, &SOURCE.table, first) == 5;
43 +
    assert interrupts::claim(&mut STORE, 5);
44 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 5);
45 +
    assert STORE.records[4].state == interrupts::State::Queued;
46 +
    let second = try! interrupts::transfer(&mut STORE, &mut SOURCE.table, &mut TARGET.table,
47 +
        first, (abi::READ | abi::TRANSFER) as u64);
48 +
    assert STORE.records[4].state == interrupts::State::Pending;
49 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 5);
50 +
    assert DOMAINS.events.queues[0].tail == 1 and DOMAINS.events.queues[1].tail == 1;
51 +
    let returned = try! interrupts::transfer(&mut STORE, &mut TARGET.table, &mut SOURCE.table,
52 +
        second, (abi::READ | abi::TRANSFER) as u64);
53 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 5);
54 +
    assert DOMAINS.events.queues[0].tail == 1;
55 +
    let consumed = try! events::pop(&mut SOURCE.ring) else panic "missing source event";
56 +
    assert consumed.kind == events::INTERRUPT and consumed.code == 5;
57 +
    assert try! interrupts::service(&mut STORE, &mut DOMAINS, 5);
58 +
    assert STORE.records[4].state == interrupts::State::Idle;
59 +
    let old = try! capability::get(&SOURCE.table, returned);
60 +
    try! interrupts::drop(&mut STORE, &mut SOURCE.table, returned);
61 +
    assert not interrupts::claim(&mut STORE, 5);
62 +
    let replacement = try! interrupts::seed(&mut STORE, &mut SOURCE.table, 5);
63 +
    let fresh = try! capability::get(&SOURCE.table, replacement);
64 +
    assert fresh.object.index == old.object.index and fresh.object.generation <> old.object.generation;
65 +
}
66 +
67 +
/// A full event queue retains the source until capacity and consumer progress permit rearming.
68 +
@test unsafe fn saturation() throws (testing::TestError) {
69 +
    initialize();
70 +
    let handle = try! interrupts::seed(&mut STORE, &mut SOURCE.table, 3);
71 +
    let owner = SOURCE.table.owner;
72 +
    for i in 0..events::ORDINARY {
73 +
        assert try! events::send(&mut DOMAINS.events, owner, &mut SOURCE.ring,
74 +
            events::Event { kind: events::WAKEUP, reserved: 0, code: i, value: 0 });
75 +
    }
76 +
    assert interrupts::claim(&mut STORE, 3);
77 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 3);
78 +
    assert STORE.records[2].state == interrupts::State::Pending;
79 +
    assert interrupts::claim(&mut STORE, 3);
80 +
    let freed = try! events::pop(&mut SOURCE.ring) else panic "full ring";
81 +
    assert freed.kind == events::WAKEUP;
82 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 3);
83 +
    assert STORE.records[2].state == interrupts::State::Queued;
84 +
    for i in 1..events::ORDINARY {
85 +
        let event = try! events::pop(&mut SOURCE.ring) else panic "retained wakeup";
86 +
        assert event.kind == events::WAKEUP;
87 +
    }
88 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 3);
89 +
    let event = try! events::pop(&mut SOURCE.ring) else panic "retained interrupt";
90 +
    assert event.code == 3 and event.kind == events::INTERRUPT;
91 +
    assert try! interrupts::service(&mut STORE, &mut DOMAINS, 3);
92 +
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Dead;
93 +
    assert interrupts::claim(&mut STORE, 3);
94 +
    assert not try! interrupts::service(&mut STORE, &mut DOMAINS, 3);
95 +
}
96 +
97 +
/// Target exhaustion leaves the source handle and retained notification unchanged.
98 +
@test unsafe fn atomicTransfer() throws (testing::TestError) {
99 +
    initialize();
100 +
    let handle = try! interrupts::seed(&mut STORE, &mut SOURCE.table, 7);
101 +
    assert interrupts::claim(&mut STORE, 7);
102 +
    for i in 0..limits::HANDLES {
103 +
        let filler = try! capability::install(&mut TARGET.table, capability::Entry {
104 +
            kind: abi::Kind::Domain, object: SOURCE.table.owner, rights: abi::Rights(0),
105 +
        });
106 +
    }
107 +
    let mut full = false;
108 +
    try interrupts::transfer(&mut STORE, &mut SOURCE.table, &mut TARGET.table, handle, abi::READ as u64) catch error {
109 +
        assert error == abi::Error::InvalidArg; set full = true;
110 +
    };
111 +
    assert full;
112 +
    assert STORE.records[6].owner == SOURCE.table.owner and STORE.records[6].handle == handle;
113 +
    assert STORE.records[6].state == interrupts::State::Pending;
114 +
    assert try! interrupts::query(&STORE, &SOURCE.table, handle) == 7;
115 +
    let mut invalid = false;
116 +
    try interrupts::seed(&mut STORE, &mut SOURCE.table, 0) catch error {
117 +
        assert error == abi::Error::InvalidArg; set invalid = true;
118 +
    };
119 +
    assert invalid and not interrupts::claim(&mut STORE, 0) and not interrupts::claim(&mut STORE, 9);
120 +
}
kernel/kernel/tests/platform.rad +90 -2
1 1
//! Platform resource discovery and reserved-memory exclusion fixtures.
2 2
3 3
use std::testing;
4 4
use kernel::platform;
5 5
use kernel::range;
6 +
use kernel::plic;
7 +
use kernel::abi;
6 8
7 9
/// Fixed FDT construction workspace with separate structure and string regions.
8 10
record Fixture: Copy {
9 11
    /// Entire blob.
10 12
    bytes: [u8; 4096],
12 14
    offset: u32,
13 15
    /// Next property-name byte.
14 16
    strings: u32,
15 17
    /// Stack range value offset for malformed-input checks.
16 18
    stack: u32,
19 +
    /// PLIC context-list payload for mapping rejection checks.
20 +
    contexts: u32,
21 +
    /// PLIC source-count payload for capacity checks.
22 +
    sources: u32,
23 +
    /// PLIC register extent payload for bounds checks.
24 +
    plic: u32,
17 25
}
18 26
19 27
/// Write a big-endian fixture field.
20 28
fn put(f: &mut Fixture, offset: u32, width: u32, value: u64) {
21 29
    for i in 0..width { set f.bytes[offset + i] = (value >> ((width - i - 1) * 8) as u64) as u8; }
61 69
    return offset;
62 70
}
63 71
64 72
/// Construct the supported one-hart boot contract.
65 73
fn build(f: &mut Fixture) {
74 +
    construct(f, false);
75 +
}
76 +
77 +
/// Construct a boot contract with optional supervisor and machine PLIC contexts.
78 +
fn construct(f: &mut Fixture, interrupts: bool) {
66 79
    for i in 0..f.bytes.len { set f.bytes[i] = 0; }
67 80
    set f.offset = 56; set f.strings = 3072;
68 81
    begin(f, &""[..]);
69 82
    scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2);
70 83
    begin(f, &"chosen"[..]);
75 88
    let ram = region(f, &"reg"[..], 0x80000000, 0x10000); word(f, 2);
76 89
    begin(f, &"cpus"[..]);
77 90
    scalar(f, &"#address-cells"[..], 1); scalar(f, &"#size-cells"[..], 0);
78 91
    scalar(f, &"timebase-frequency"[..], 10000000);
79 92
    begin(f, &"cpu@0"[..]); string(f, &"device_type"[..], &"cpu"[..]);
80 -
    scalar(f, &"reg"[..], 0); word(f, 2); word(f, 2);
93 +
    scalar(f, &"reg"[..], 0);
94 +
    if interrupts {
95 +
        begin(f, &"interrupt-controller"[..]);
96 +
        string(f, &"compatible"[..], &"riscv,cpu-intc"[..]);
97 +
        scalar(f, &"phandle"[..], 17); scalar(f, &"#interrupt-cells"[..], 1);
98 +
        let marker = property(f, &"interrupt-controller"[..], 0);
99 +
        word(f, 2);
100 +
    }
101 +
    word(f, 2); word(f, 2);
81 102
    begin(f, &"reserved-memory"[..]);
82 103
    scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2);
83 104
    let ranges = property(f, &"ranges"[..], 0);
84 105
    begin(f, &"image@80002000"[..]);
85 106
    let image = region(f, &"reg"[..], 0x80002000, 0x3000); word(f, 2); word(f, 2);
86 107
    begin(f, &"soc"[..]);
87 108
    scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2);
88 109
    let bus = property(f, &"ranges"[..], 0);
89 110
    begin(f, &"uart@10000000"[..]);
90 111
    string(f, &"compatible"[..], &"ns16550a"[..]);
91 -
    let uart = region(f, &"reg"[..], 0x10000000, 0x100); word(f, 2); word(f, 2);
112 +
    let uart = region(f, &"reg"[..], 0x10000000, 0x100); word(f, 2);
113 +
    if interrupts {
114 +
        begin(f, &"plic@c000000"[..]);
115 +
        string(f, &"compatible"[..], &"riscv,plic0"[..]);
116 +
        set f.plic = region(f, &"reg"[..], 0xc000000, 0x4000000);
117 +
        scalar(f, &"#interrupt-cells"[..], 1);
118 +
        let sources = property(f, &"riscv,ndev"[..], 4);
119 +
        set f.sources = sources; put(f, sources, 4, 31);
120 +
        let contexts = property(f, &"interrupts-extended"[..], 16);
121 +
        set f.contexts = contexts;
122 +
        put(f, contexts, 4, 17); put(f, contexts + 4, 4, 9);
123 +
        put(f, contexts + 8, 4, 17); put(f, contexts + 12, 4, 11);
124 +
        word(f, 2);
125 +
    }
126 +
    word(f, 2);
92 127
    word(f, 2); word(f, 9);
93 128
    let total = f.strings; let size = f.offset - 56;
94 129
    put(f, 0, 4, 0xd00dfeed); put(f, 4, 4, total as u64);
95 130
    put(f, 8, 4, 56); put(f, 12, 4, 3072); put(f, 16, 4, 40);
96 131
    put(f, 20, 4, 17); put(f, 24, 4, 16);
97 132
    put(f, 32, 4, (total - 3072) as u64); put(f, 36, 4, size as u64);
98 133
}
99 134
135 +
/// PLIC machine contexts follow reference-list order rather than physical hart ID.
136 +
@test unsafe fn interruptContexts() throws (testing::TestError) {
137 +
    let mut f: Fixture = undefined; construct(&mut f, true);
138 +
    let mut p: platform::Platform = undefined;
139 +
    try! platform::decode(&f.bytes[..f.strings], &mut p);
140 +
    assert p.irqSources == 31 and p.irqContexts[0] == 1;
141 +
    assert p.deviceCount == 2 and p.devices[1].kind == platform::Kind::Plic;
142 +
    let initialContexts = f.contexts;
143 +
    put(&mut f, initialContexts + 4, 4, 0xffffffff);
144 +
    try! platform::decode(&f.bytes[..f.strings], &mut p);
145 +
    assert p.irqContexts[0] == 1;
146 +
    for change in 0..7 {
147 +
        construct(&mut f, true);
148 +
        let sources = f.sources;
149 +
        let contexts = f.contexts;
150 +
        let registers = f.plic;
151 +
        match change {
152 +
            case 0 => put(&mut f, sources, 4, 0),
153 +
            case 1 => put(&mut f, sources, 4, 129),
154 +
            case 2 => put(&mut f, contexts + 8, 4, 99),
155 +
            case 3 => put(&mut f, contexts + 4, 4, 11),
156 +
            case 4 => put(&mut f, contexts + 12, 4, 9),
157 +
            case 5 => put(&mut f, registers + 8, 8, 0x200000),
158 +
            else => put(&mut f, registers, 8, 0xc000002),
159 +
        }
160 +
        let mut rejected = false;
161 +
        try platform::decode(&f.bytes[..f.strings], &mut p) catch error {
162 +
            assert error == platform::Error::Invalid; set rejected = true;
163 +
        };
164 +
        assert rejected;
165 +
    }
166 +
}
167 +
168 +
/// PLIC register selection uses the decoded machine context and rejects ambiguous mappings.
169 +
@test unsafe fn plicMapping() throws (testing::TestError) {
170 +
    let mut f: Fixture = undefined; construct(&mut f, true);
171 +
    let mut p: platform::Platform = undefined;
172 +
    try! platform::decode(&f.bytes[..f.strings], &mut p);
173 +
    let selected = try! plic::mapping(&p) else panic "missing PLIC";
174 +
    assert selected.base == 0xc000000 and selected.count == 31 and selected.context == 1;
175 +
    set p.irqContexts[0] = 16;
176 +
    let mut rejected = false;
177 +
    try plic::mapping(&p) catch error { assert error == abi::Error::InvalidArg; set rejected = true; };
178 +
    assert rejected;
179 +
    set p.irqContexts[0] = 1;
180 +
    set p.devices[2] = p.devices[1]; set p.deviceCount = 3;
181 +
    let mut duplicate = false;
182 +
    try plic::mapping(&p) catch error { assert error == abi::Error::InvalidArg; set duplicate = true; };
183 +
    assert duplicate;
184 +
    set p.irqSources = 0;
185 +
    assert try! plic::mapping(&p) == nil;
186 +
}
187 +
100 188
/// Resources and their reservations come from the tree rather than fixed addresses.
101 189
@test unsafe fn discovery() throws (testing::TestError) {
102 190
    let mut f: Fixture = undefined; build(&mut f);
103 191
    let mut p: platform::Platform = undefined;
104 192
    try! platform::decode(&f.bytes[..f.strings], &mut p);
lib/std/lang/lower.rad +8 -1
1419 1419
    }
1420 1420
    dataSliceHeader(b, dataName, arrInfo.length);
1421 1421
}
1422 1422
1423 1423
/// Lower a constant expression payload into a builder without slot padding.
1424 -
/// Compute the type layout only when undefined data needs a byte count.
1424 +
/// Compute the type layout when nil or undefined data needs a byte count.
1425 1425
unsafe fn lowerConstDataPayloadInto(
1426 1426
    self: &mut Lowerer,
1427 1427
    node: *ast::Node,
1428 1428
    ty: resolver::Type,
1429 1429
    dataPrefix: *[u8],
1449 1449
                return;
1450 1450
            }
1451 1451
        }
1452 1452
    }
1453 1453
    match node.value {
1454 +
        case ast::NodeValue::Nil => {
1455 +
            let case resolver::Type::Optional(_) = ty else throw LowerError::NilInNonOptional;
1456 +
            let layout = resolver::getTypeLayout(ty);
1457 +
            dataBuilderPush(b, il::DataValue {
1458 +
                item: il::DataItem::Val { typ: il::Type::W8, val: 0 }, count: layout.size,
1459 +
            });
1460 +
        },
1454 1461
        case ast::NodeValue::Undef => {
1455 1462
            let layout = resolver::getTypeLayout(ty);
1456 1463
            dataBuilderPush(b, il::DataValue {
1457 1464
                item: il::DataItem::Undef,
1458 1465
                count: layout.size
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 kernel/kernel/dispatch.ras kernel/kernel/calls.ras > "$work/boot.ras"
7 +
cat test/boot/machine.ras kernel/kernel/*.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 kernel/kernel/dispatch.ras kernel/kernel/calls.ras > "$work/trap.ras"
33 +
cat test/boot/trap.ras kernel/kernel/*.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/run +4 -1
2 2
# Preempt and resume U-mode and M-mode work through production dispatch.
3 3
set -eu
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
fixture=${1:-dispatch}
6 6
harts=${2:-1}
7 +
irq=${3:-0}
7 8
work=$(mktemp -d)
8 9
trap 'rm -rf "$work"' EXIT HUP INT TERM
9 10
mkdir "$work/spin"
10 11
cp "test/$fixture/spin.rad" "$work/spin.rad"
11 12
cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/"
22 23
printf '];\n' >> "$work/kernel/dispatchinput.rad"
23 24
sh test/acceptance/compile "$emulator" "$work"
24 25
cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras"
25 26
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
26 27
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
28 +
set --
29 +
if [ "$irq" -ne 0 ]; then set -- "-irq=$irq" "-irq-at=1"; fi
27 30
for count in $harts; do
28 -
    "$emulator" -machine -harts="$count" -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64"
31 +
    "$emulator" -machine -harts="$count" -memory-size=262144 -max-steps=2000000000 "$@" -run "$work/dispatch.rv64"
29 32
    printf '%s: %s-hart execution passed\n' "$fixture" "$count"
30 33
done
test/loader/run +1 -2
19 19
length=$(wc -c < "$work/loaded.ril")
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 -
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 kernel/kernel/dispatch.ras kernel/kernel/calls.ras > "$work/loader.ras"
24 +
cat test/loader/machine.ras kernel/kernel/*.ras > "$work/loader.ras"
26 25
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
27 26
    -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64"
28 27
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64"
29 28
printf 'runtime loader: hosted and loaded entries agree after workspace release\n'
test/smp/kernel/dispatchcheck.rad +125 -1
15 15
use kernel::sync;
16 16
use kernel::remote;
17 17
use kernel::calls;
18 18
use kernel::events;
19 19
use kernel::timers;
20 +
use kernel::interrupts;
21 +
use kernel::plic;
20 22
use kernel::dispatchinput;
21 23
22 24
/// Bootstrap resource authority.
23 25
unsafe static TABLE: capability::Table = undefined;
24 26
/// Release/acquire barrier after fixture publication.
53 55
fn kernelGp() -> u64;
54 56
/// M-mode entry of the completion checker.
55 57
fn completion() -> u64;
56 58
/// Write one diagnostic byte to the fixture UART.
57 59
fn put(byte: u8);
60 +
/// Set the fixture UART interrupt-enable register.
61 +
fn uartInterrupt(value: u8);
58 62
/// Finish the machine test successfully.
59 63
fn finish();
60 64
61 65
/// Publish fixture state on the last initialized hart and wait on every hart.
62 66
export unsafe fn start(last: bool) {
85 89
    set USER = cap.object;
86 90
    let mut user = try! domains::get(&domains::STORE, USER);
87 91
    let self = try! capability::install(&mut user.memory.table, capability::Entry {
88 92
        kind: abi::Kind::Domain, object: USER, rights: abi::Rights(abi::ALLOCATE | abi::WAKE),
89 93
    });
94 +
    assert boot::PLATFORM.irqSources == 31;
95 +
    let irq = try! interrupts::seed(&mut interrupts::STORE, &mut user.memory.table, 7);
96 +
    sync::storeRelease(memory(dataAddress(&user, &"spin::interrupt"[..])) as *mut u64, *irq);
90 97
    let target = try! registry::exported(&registry::STORE, user.image, &"spin::worker"[..]);
91 98
    let case shared::Target::Function(entry) = target else panic "worker entry";
92 99
    set COUNTERS = dataAddress(&user, &"spin::count"[..]);
93 100
    set MIGRATIONS = dataAddress(&user, &"spin::migrations"[..]);
94 101
    let notification = @sliceOf(memory(dataAddress(&user, &"spin::notification"[..])), 8);
193 200
        let destination = (hart + notifications - 1) % notifications;
194 201
        assert context.hart == nil or context.hart == destination;
195 202
        assert sync::loadAcquire(memory(MIGRATIONS + hart as u64 * 8) as *u64) == destination as u64 + 1;
196 203
        assert context.pending == nil;
197 204
    }
198 -
    assert domains::STORE.events.queues[USER.index].tail == notifications;
205 +
    assert domains::STORE.events.queues[USER.index].tail == notifications + 1;
206 +
    let user = try! domains::get(&domains::STORE, USER);
207 +
    assert user.memory.ring.data[0].kind == events::INTERRUPT and user.memory.ring.data[0].code == 7;
199 208
    sync::leave(guard);
200 209
    for byte in "smp completed transactions:" { put(byte); }
201 210
    for hart in 0..8 {
202 211
        if (boot::PLATFORM.harts & (1 << hart)) == 0 { continue; }
203 212
        put(32); number(counts[hart]);
225 234
            assert acknowledged;
226 235
        }
227 236
    }
228 237
    for byte in "smp request acknowledgements passed\n" { put(byte); }
229 238
    if (boot::PLATFORM.harts & 2) <> 0 { idleWakeup(); }
239 +
    interruptOwnership();
240 +
    interruptStorm();
230 241
    let maximum = sync::maximum();
231 242
    assert maximum > 0 and maximum < 1000000;
232 243
    for byte in "smp metadata instructions: 0x" { put(byte); }
233 244
    number(maximum);
234 245
    put(10);
235 246
    finish();
236 247
}
237 248
249 +
/// Transfer a queued IRQ, rearm after consumption, and mask it before capability drop.
250 +
unsafe fn interruptOwnership() {
251 +
    let guard = sync::enter();
252 +
    let mut user = try! domains::get(&domains::STORE, USER);
253 +
    let mut checker = try! domains::get(&domains::STORE, CHECKER);
254 +
    let device = try! plic::mapping(&boot::PLATFORM) else panic "missing PLIC";
255 +
    let enabled = memory(device.base + 0x2000 + device.context as u64 * 0x80) as *u32;
256 +
    assert (sync::read32(enabled) & (1 << 7)) == 0;
257 +
    assert interrupts::STORE.records[6].state == interrupts::State::Queued;
258 +
    let original = interrupts::STORE.records[6].handle;
259 +
    let target = try! capability::install(&mut user.memory.table, capability::Entry {
260 +
        kind: abi::Kind::Domain, object: CHECKER, rights: abi::Rights(0),
261 +
    });
262 +
    let back = try! capability::install(&mut checker.memory.table, capability::Entry {
263 +
        kind: abi::Kind::Domain, object: USER, rights: abi::Rights(0),
264 +
    });
265 +
    let moved = try! calls::invoke(USER, 11, &[*original, *target, (abi::READ | abi::TRANSFER) as u64, 0], 0);
266 +
    assert interrupts::STORE.records[6].owner == CHECKER;
267 +
    let first = plic::service(dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)));
268 +
    assert interrupts::STORE.records[6].state == interrupts::State::Queued;
269 +
    assert (sync::read32(enabled) & (1 << 7)) == 0;
270 +
    let event = try! events::pop(&mut checker.memory.ring) else panic "missing transferred IRQ";
271 +
    assert event.kind == events::INTERRUPT and event.code == 7;
272 +
    let before = domains::STORE.events.queues[USER.index].tail;
273 +
    let returned = try! calls::invoke(CHECKER, 11, &[moved, *back, (abi::READ | abi::TRANSFER) as u64, 0], 0);
274 +
    let second = plic::service(dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)));
275 +
    assert domains::STORE.events.queues[USER.index].tail == before;
276 +
    assert (sync::read32(enabled) & (1 << 7)) == 0;
277 +
    let consumed = try! events::pop(&mut user.memory.ring) else panic "missing original IRQ";
278 +
    assert consumed.kind == events::INTERRUPT and consumed.code == 7;
279 +
    let rearmed = plic::service(dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)));
280 +
    assert interrupts::STORE.records[6].state == interrupts::State::Idle;
281 +
    assert (sync::read32(enabled) & (1 << 7)) <> 0;
282 +
    let dropped = try! calls::invoke(USER, 12, &[returned, 0, 0, 0], 0);
283 +
    assert dropped == 0 and interrupts::STORE.slots[6].state == slots::State::Free;
284 +
    assert (sync::read32(enabled) & (1 << 7)) == 0;
285 +
    sync::leave(guard);
286 +
    for byte in "smp IRQ transfer, rearm, and drop passed\n" { put(byte); }
287 +
}
288 +
289 +
/// Keep a level source asserted across full queues and repeated driver progress.
290 +
unsafe fn interruptStorm() {
291 +
    let guard = sync::enter();
292 +
    let mut checker = try! domains::get(&domains::STORE, CHECKER);
293 +
    let handle = try! interrupts::seed(&mut interrupts::STORE, &mut checker.memory.table, 10);
294 +
    let device = try! plic::mapping(&boot::PLATFORM) else panic "missing PLIC";
295 +
    let enabled = memory(device.base + 0x2000 + device.context as u64 * 0x80) as *u32;
296 +
    sync::leave(guard);
297 +
    for i in 0..events::ORDINARY {
298 +
        let filling = sync::enter();
299 +
        assert try! domains::notify(&mut domains::STORE, CHECKER,
300 +
            events::Event { kind: events::WAKEUP, reserved: 0, code: i, value: 0 });
301 +
        sync::leave(filling);
302 +
    }
303 +
    let arming = sync::enter();
304 +
    let ready = plic::service(dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)));
305 +
    uartInterrupt(2);
306 +
    sync::leave(arming);
307 +
    let mut pending = false;
308 +
    for attempt in 0..10000 {
309 +
        let checking = sync::enter();
310 +
        set pending = interrupts::STORE.records[9].state == interrupts::State::Pending;
311 +
        if pending {
312 +
            assert (sync::read32(enabled) & (1 << 10)) == 0;
313 +
            let queue = domains::STORE.events.queues[CHECKER.index];
314 +
            assert queue.tail - queue.head == events::ORDINARY;
315 +
        }
316 +
        sync::leave(checking);
317 +
        if pending { break; }
318 +
    }
319 +
    assert pending;
320 +
    for i in 0..events::ORDINARY {
321 +
        let draining = sync::enter();
322 +
        let mut receiver = try! domains::get(&domains::STORE, CHECKER);
323 +
        let event = try! events::pop(&mut receiver.memory.ring) else panic "missing saturation event";
324 +
        assert event.kind == events::WAKEUP and event.code == i;
325 +
        sync::leave(draining);
326 +
    }
327 +
    let posting = sync::enter();
328 +
    let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0));
329 +
    try! timers::request(&mut timers::STORE, &domains::STORE, CHECKER, 0, clock, 2000000, 0x77);
330 +
    let request = try! dispatch::request(0, remote::Action::Reschedule);
331 +
    sync::leave(posting);
332 +
    let acknowledged = try! dispatch::awaitRequest(0, request);
333 +
    assert acknowledged == 0;
334 +
    let mut received: u32 = 0;
335 +
    let mut timed = false;
336 +
    for attempt in 0..1000 {
337 +
        let consuming = sync::enter();
338 +
        let mut receiver = try! domains::get(&domains::STORE, CHECKER);
339 +
        if let event = try! events::pop(&mut receiver.memory.ring) {
340 +
            if event.kind == events::INTERRUPT {
341 +
                assert event.code == 10;
342 +
                set received += 1;
343 +
            } else {
344 +
                assert event.kind == events::TIMEOUT and event.code == 0x77;
345 +
                assert dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)) >= clock + 2000000;
346 +
                set timed = true;
347 +
            }
348 +
        }
349 +
        let notified = plic::service(dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0)));
350 +
        sync::leave(consuming);
351 +
        if timed and received >= 16 { break; }
352 +
    }
353 +
    let stopping = sync::enter();
354 +
    uartInterrupt(0);
355 +
    let dropped = try! calls::invoke(CHECKER, 12, &[*handle, 0, 0, 0], 0);
356 +
    assert dropped == 0 and (sync::read32(enabled) & (1 << 10)) == 0;
357 +
    sync::leave(stopping);
358 +
    assert timed and received >= 16;
359 +
    for byte in "smp IRQ saturation and storm timeout passed\n" { put(byte); }
360 +
}
361 +
238 362
/// Resume an actual U-mode Wait call through an IPI to an idle remote hart.
239 363
unsafe fn idleWakeup() {
240 364
    let guard = sync::enter();
241 365
    let clock = dispatch::now(try! dispatch::timer(&boot::PLATFORM, 0));
242 366
    let handle = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
test/smp/machine.ras +7 -0
7 7
.export @kernel::dispatchcheck::memory;
8 8
.export @kernel::dispatchcheck::kernelGp;
9 9
.export @kernel::dispatchcheck::completion;
10 10
.export @kernel::dispatchcheck::finish;
11 11
.export @kernel::dispatchcheck::put;
12 +
.export @kernel::dispatchcheck::uartInterrupt;
12 13
@kernel::dispatchcheck::memory
13 14
    ret;
14 15
@kernel::dispatchcheck::kernelGp
15 16
    mv %a0 %gp;
16 17
    ret;
22 23
    ebreak;
23 24
@kernel::dispatchcheck::put
24 25
    li %t0 0x10000000;
25 26
    sb %a0 0(%t0);
26 27
    ret;
28 +
@kernel::dispatchcheck::uartInterrupt
29 +
    li %t0 0x10000000;
30 +
    fence iorw iorw;
31 +
    sb %a0 1(%t0);
32 +
    fence iorw iorw;
33 +
    ret;
27 34
@kernel::dispatchcheck::finish
28 35
    li %t0 0x10001000;
29 36
    li %t1 0x5555;
30 37
    sw %t1 0(%t0);
31 38
    ebreak;
test/smp/spin.rad +5 -0
9 9
export static windows: [u64; 8] = [0; 8];
10 10
11 11
/// Capability used to notify the shared domain from each online hart.
12 12
export static notification: u64 = 0;
13 13
14 +
/// Shared-domain capability for the injected physical interrupt source.
15 +
export static interrupt: u64 = 0;
16 +
14 17
/// Observed destination hart plus one after each context changes budget.
15 18
export static migrations: [u64; 8] = [0; 8];
16 19
17 20
/// Wait handshake: 2/3 bracket wakeup; 4 requests a second wait, bracketed by 5/6.
18 21
export static wakeState: u64 = 0;
20 23
/// Nonzero selects the independently activated idle-wakeup context.
21 24
export static idle: u64 = 0;
22 25
23 26
/// Check hart identity and shared-table allocation through repeated direct calls.
24 27
fn run(hart: u64) {
28 +
    let source = sys::queryInterrupt(abi::Handle(interrupt));
29 +
    assert source.number == 7;
25 30
    let original = sys::currentContext();
26 31
    assert original.hart == hart;
27 32
    let budget = abi::Handle(windows[hart as u32]);
28 33
    let before = try! sys::queryBudget(budget);
29 34
    let right = try! sys::budgetSplit(budget, before.start + 1000);
test/tests/static.optional.nil.rad added +24 -0
1 +
//! returns: 0
2 +
//! Static nil values occupy their complete scalar, pointer, and aggregate layouts.
3 +
4 +
/// Payload with alignment and padding requirements.
5 +
record Payload: Copy { base: u64, count: u32 }
6 +
/// Adjacent fields must remain intact around a nil aggregate.
7 +
record Container: Copy { first: u64, item: ?Payload, last: u64 }
8 +
9 +
static VALUE: ?Payload = nil;
10 +
static NUMBER: ?u64 = nil;
11 +
static POINTER: ?*u64 = nil;
12 +
static ITEMS: [?Payload; 2] = [nil; 2];
13 +
static BOX: Container = Container { first: 0x1122334455667788, item: nil, last: 0x8877665544332211 };
14 +
15 +
/// Check complete nil layouts and subsequent mutable initialization.
16 +
@default fn main() -> i32 {
17 +
    assert VALUE == nil and NUMBER == nil and POINTER == nil;
18 +
    assert ITEMS[0] == nil and ITEMS[1] == nil;
19 +
    assert BOX.item == nil and BOX.first == 0x1122334455667788 and BOX.last == 0x8877665544332211;
20 +
    set VALUE = Payload { base: 42, count: 7 };
21 +
    let value = VALUE else panic "missing initialized payload";
22 +
    assert value.base == 42 and value.count == 7;
23 +
    return 0;
24 +
}