kernel: Deliver device interrupts
92262c08112aa9a0a441cca4c93933d9ba51745bd7f905fe4d108ad072d04e41
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(®istry::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, ®istry::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, ®istry::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(®istry::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 | + | } |