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