kernel: Boot from platform data
3d23707f2440e584d6ec9cebd6f5a239b79f2f5229f4ab496e3ad3e5feb56313
Assisted-by: Codex:gpt-6
1 parent
1873ef7c
kernel/kernel.rad
+4 -1
| 1 | 1 | //! Kernel resource management and machine execution. |
|
| 2 | 2 | ||
| 3 | - | use std::testing; |
|
| 3 | + | @test use std::testing; |
|
| 4 | 4 | ||
| 5 | + | export mod fdt; |
|
| 5 | 6 | export mod abi; |
|
| 6 | 7 | export mod limits; |
|
| 7 | 8 | export mod slots; |
|
| 8 | 9 | export mod range; |
|
| 9 | 10 | export mod sync; |
|
| 11 | + | export mod platform; |
|
| 10 | 12 | @test export mod tests; |
|
| 13 | + | export mod boot; |
kernel/kernel/boot.rad
added
+75 -0
| 1 | + | //! Machine entry boundary and published platform state. |
|
| 2 | + | ||
| 3 | + | use super::platform; |
|
| 4 | + | use super::fdt; |
|
| 5 | + | use super::range; |
|
| 6 | + | use super::limits; |
|
| 7 | + | use super::sync; |
|
| 8 | + | ||
| 9 | + | /// Platform data published by hart zero before secondary initialization. |
|
| 10 | + | export unsafe static PLATFORM: platform::Platform = undefined; |
|
| 11 | + | /// Release/acquire publication flag for PLATFORM. |
|
| 12 | + | static READY: u64 = 0; |
|
| 13 | + | /// Number of harts that validated their machine stack. |
|
| 14 | + | static ARRIVED: u64 = 0; |
|
| 15 | + | ||
| 16 | + | /// Read a byte from a device register with I/O ordering. |
|
| 17 | + | fn read8(address: u64) -> u8; |
|
| 18 | + | /// Write a byte to a device register with I/O ordering. |
|
| 19 | + | fn write8(address: u64, value: u8); |
|
| 20 | + | ||
| 21 | + | /// Send a bounded diagnostic through the discovered byte-wide UART. |
|
| 22 | + | unsafe fn print(message: *[u8]) { |
|
| 23 | + | for i in 0..PLATFORM.deviceCount { |
|
| 24 | + | let device = PLATFORM.devices[i]; |
|
| 25 | + | if device.kind <> platform::Kind::Uart { |
|
| 26 | + | continue; |
|
| 27 | + | } |
|
| 28 | + | if device.memory.end - device.memory.start < 6 { |
|
| 29 | + | return; |
|
| 30 | + | } |
|
| 31 | + | for byte in message { |
|
| 32 | + | let mut polls: u32 = 0; |
|
| 33 | + | while (read8(device.memory.start + 5) & 32) == 0 { |
|
| 34 | + | set polls += 1; |
|
| 35 | + | if polls == 1000000 { |
|
| 36 | + | return; |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | write8(device.memory.start, byte); |
|
| 40 | + | } |
|
| 41 | + | return; |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Initialize one hart from firmware arguments. Return true for the last hart. |
|
| 46 | + | /// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart. |
|
| 47 | + | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool { |
|
| 48 | + | let treeAddress = tree as u64; |
|
| 49 | + | assert hart < limits::HARTS as u64; |
|
| 50 | + | if hart == 0 { |
|
| 51 | + | assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536; |
|
| 52 | + | let prefix = @sliceOf(tree, 40); |
|
| 53 | + | let size = try! fdt::integer(&prefix[..], 4, 4) as u32; |
|
| 54 | + | assert size >= 40 and size <= 65536; |
|
| 55 | + | let blob = @sliceOf(tree, size); |
|
| 56 | + | try! platform::decode(&blob[..], &mut PLATFORM); |
|
| 57 | + | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 58 | + | assert platform::inRam(&PLATFORM, treeRange); |
|
| 59 | + | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 60 | + | print("kernel: platform ready\n"); |
|
| 61 | + | sync::storeRelease(&mut READY, 1); |
|
| 62 | + | } else { |
|
| 63 | + | while sync::loadAcquire(&READY) == 0 { |
|
| 64 | + | } |
|
| 65 | + | } |
|
| 66 | + | assert (PLATFORM.harts & (1 << hart as u32)) <> 0; |
|
| 67 | + | assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0; |
|
| 68 | + | let mut count: u64 = 0; |
|
| 69 | + | for id in 0..limits::HARTS { |
|
| 70 | + | if (PLATFORM.harts & (1 << id)) <> 0 { |
|
| 71 | + | set count += 1; |
|
| 72 | + | } |
|
| 73 | + | } |
|
| 74 | + | return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count; |
|
| 75 | + | } |
kernel/kernel/boot.ras
added
+37 -0
| 1 | + | //! RV64 machine startup. Firmware supplies a0=hart, a1=FDT, and aligned sp. |
|
| 2 | + | .text; |
|
| 3 | + | .export @kernel::boot::start; |
|
| 4 | + | .export @kernel::boot::initialize; |
|
| 5 | + | .export @kernel::boot::read8; |
|
| 6 | + | .export @kernel::boot::write8; |
|
| 7 | + | ||
| 8 | + | @kernel::boot::start |
|
| 9 | + | call @kernel::boot::initialize; |
|
| 10 | + | @idle |
|
| 11 | + | wfi; |
|
| 12 | + | j @idle; |
|
| 13 | + | ||
| 14 | + | // Initialize machine CSRs and preserve the firmware stack across Radiance entry. |
|
| 15 | + | @kernel::boot::initialize |
|
| 16 | + | csrw mie %zero; |
|
| 17 | + | csrw mstatus %zero; |
|
| 18 | + | csrw mscratch %sp; |
|
| 19 | + | mv %a2 %sp; |
|
| 20 | + | addi %sp %sp -16; |
|
| 21 | + | sd %ra 0(%sp); |
|
| 22 | + | call @kernel::boot::enter; |
|
| 23 | + | ld %ra 0(%sp); |
|
| 24 | + | addi %sp %sp 16; |
|
| 25 | + | ret; |
|
| 26 | + | ||
| 27 | + | @kernel::boot::read8 |
|
| 28 | + | fence iorw iorw; |
|
| 29 | + | lbu %a0 0(%a0); |
|
| 30 | + | fence iorw iorw; |
|
| 31 | + | ret; |
|
| 32 | + | ||
| 33 | + | @kernel::boot::write8 |
|
| 34 | + | fence iorw iorw; |
|
| 35 | + | sb %a1 0(%a0); |
|
| 36 | + | fence iorw iorw; |
|
| 37 | + | ret; |
kernel/kernel/fdt.rad
added
+244 -0
| 1 | + | //! Bounded flattened device-tree decoding. All offsets refer to the input blob. |
|
| 2 | + | ||
| 3 | + | use super::range; |
|
| 4 | + | ||
| 5 | + | /// Invalid or unsupported platform description. |
|
| 6 | + | export union Error: Copy { |
|
| 7 | + | /// A field or byte sequence is outside the blob. |
|
| 8 | + | Truncated, |
|
| 9 | + | /// Header, structure, or reservation data is invalid. |
|
| 10 | + | Invalid, |
|
| 11 | + | /// The tree exceeds the supported nesting depth. |
|
| 12 | + | Depth, |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Byte interval within the validated blob. |
|
| 16 | + | export record Span: Copy { |
|
| 17 | + | /// First byte offset. |
|
| 18 | + | start: u32, |
|
| 19 | + | /// Exclusive end offset. |
|
| 20 | + | end: u32, |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | /// Validated header and block extents. |
|
| 24 | + | export record Header: Copy { |
|
| 25 | + | /// Declared blob length. |
|
| 26 | + | size: u32, |
|
| 27 | + | /// Structure block. |
|
| 28 | + | structure: Span, |
|
| 29 | + | /// Property-name strings. |
|
| 30 | + | strings: Span, |
|
| 31 | + | /// Reservation entries, excluding the zero terminator. |
|
| 32 | + | reservations: Span, |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | /// Structure traversal state. Use only with the blob passed to `header`. |
|
| 36 | + | export record Cursor: Copy { |
|
| 37 | + | /// Next structure byte. |
|
| 38 | + | offset: u32, |
|
| 39 | + | /// Number of open nodes. |
|
| 40 | + | depth: u32, |
|
| 41 | + | /// A root node has started. |
|
| 42 | + | started: bool, |
|
| 43 | + | /// The end token has been consumed. |
|
| 44 | + | finished: bool, |
|
| 45 | + | /// Depth bits for nodes whose children have started. |
|
| 46 | + | children: u32, |
|
| 47 | + | } |
|
| 48 | + | ||
| 49 | + | /// One tree structure item. Names exclude their zero terminator. |
|
| 50 | + | export union Event: Copy { |
|
| 51 | + | /// Node name. |
|
| 52 | + | Begin(Span), |
|
| 53 | + | /// Property name and value. |
|
| 54 | + | Property { name: Span, value: Span }, |
|
| 55 | + | /// End of the current node. |
|
| 56 | + | EndNode, |
|
| 57 | + | /// End of the complete tree. |
|
| 58 | + | End, |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Read a bounded big-endian integer of four or eight bytes. |
|
| 62 | + | export fn integer(bytes: &[u8], offset: u32, width: u32) -> u64 throws (Error) { |
|
| 63 | + | if width <> 4 and width <> 8 { |
|
| 64 | + | throw Error::Invalid; |
|
| 65 | + | } |
|
| 66 | + | if offset > bytes.len or width > bytes.len - offset { |
|
| 67 | + | throw Error::Truncated; |
|
| 68 | + | } |
|
| 69 | + | let mut value: u64 = 0; |
|
| 70 | + | for i in 0..width { |
|
| 71 | + | set value = (value << 8) | bytes[offset + i] as u64; |
|
| 72 | + | } |
|
| 73 | + | return value; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Check a block's extent without addition overflow. |
|
| 77 | + | fn block(total: u32, start: u32, size: u32) -> Span throws (Error) { |
|
| 78 | + | if start < 40 or start > total or size > total - start { |
|
| 79 | + | throw Error::Truncated; |
|
| 80 | + | } |
|
| 81 | + | return Span { start, end: start + size }; |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// Check whether two nonempty spans overlap. |
|
| 85 | + | fn overlaps(a: Span, b: Span) -> bool { |
|
| 86 | + | return a.start < a.end and b.start < b.end and a.start < b.end and b.start < a.end; |
|
| 87 | + | } |
|
| 88 | + | ||
| 89 | + | /// Validate a version-17 header, disjoint blocks, and all memory reservations. |
|
| 90 | + | export fn header(bytes: &[u8]) -> Header throws (Error) { |
|
| 91 | + | if bytes.len < 40 { |
|
| 92 | + | throw Error::Truncated; |
|
| 93 | + | } |
|
| 94 | + | if try integer(bytes, 0, 4) <> 0xd00dfeed { |
|
| 95 | + | throw Error::Invalid; |
|
| 96 | + | } |
|
| 97 | + | let size = try integer(bytes, 4, 4) as u32; |
|
| 98 | + | if size > bytes.len or size < 40 { |
|
| 99 | + | throw Error::Truncated; |
|
| 100 | + | } |
|
| 101 | + | if try integer(bytes, 20, 4) <> 17 or try integer(bytes, 24, 4) > 17 { |
|
| 102 | + | throw Error::Invalid; |
|
| 103 | + | } |
|
| 104 | + | let structure = try block(size, try integer(bytes, 8, 4) as u32, try integer(bytes, 36, 4) as u32); |
|
| 105 | + | let strings = try block(size, try integer(bytes, 12, 4) as u32, try integer(bytes, 32, 4) as u32); |
|
| 106 | + | let reserved = try integer(bytes, 16, 4) as u32; |
|
| 107 | + | if (structure.start & 3) <> 0 or (structure.end & 3) <> 0 or (reserved & 7) <> 0 or reserved < 40 { |
|
| 108 | + | throw Error::Invalid; |
|
| 109 | + | } |
|
| 110 | + | let mut end = reserved; |
|
| 111 | + | loop { |
|
| 112 | + | if end > size or size - end < 16 { |
|
| 113 | + | throw Error::Truncated; |
|
| 114 | + | } |
|
| 115 | + | let address = try integer(bytes, end, 8); |
|
| 116 | + | let length = try integer(bytes, end + 8, 8); |
|
| 117 | + | if address == 0 and length == 0 { |
|
| 118 | + | break; |
|
| 119 | + | } |
|
| 120 | + | if range::new(address, length) == nil { |
|
| 121 | + | throw Error::Invalid; |
|
| 122 | + | } |
|
| 123 | + | set end += 16; |
|
| 124 | + | } |
|
| 125 | + | let reservations = Span { start: reserved, end: end + 16 }; |
|
| 126 | + | if overlaps(structure, strings) or overlaps(structure, reservations) or overlaps(strings, reservations) { |
|
| 127 | + | throw Error::Invalid; |
|
| 128 | + | } |
|
| 129 | + | return Header { size, structure, strings, reservations: Span { start: reserved, end } }; |
|
| 130 | + | } |
|
| 131 | + | ||
| 132 | + | /// Start traversal at the first structure token. |
|
| 133 | + | export fn cursor(tree: Header) -> Cursor { |
|
| 134 | + | return Cursor { offset: tree.structure.start, depth: 0, started: false, finished: false, children: 0 }; |
|
| 135 | + | } |
|
| 136 | + | ||
| 137 | + | /// Find a terminated string inside one validated block. |
|
| 138 | + | fn name(bytes: &[u8], start: u32, end: u32) -> Span throws (Error) { |
|
| 139 | + | if end > bytes.len or start >= end { |
|
| 140 | + | throw Error::Truncated; |
|
| 141 | + | } |
|
| 142 | + | let mut at = start; |
|
| 143 | + | while at < end { |
|
| 144 | + | if bytes[at] == 0 { |
|
| 145 | + | return Span { start, end: at }; |
|
| 146 | + | } |
|
| 147 | + | set at += 1; |
|
| 148 | + | } |
|
| 149 | + | throw Error::Truncated; |
|
| 150 | + | } |
|
| 151 | + | ||
| 152 | + | /// Round an offset to the next token boundary within the structure block. |
|
| 153 | + | fn padded(bytes: &[u8], end: u32, limit: u32) -> u32 throws (Error) { |
|
| 154 | + | let padding = (4 - (end & 3)) & 3; |
|
| 155 | + | if end > limit or padding > limit - end { |
|
| 156 | + | throw Error::Truncated; |
|
| 157 | + | } |
|
| 158 | + | for i in end..end + padding { |
|
| 159 | + | if bytes[i] <> 0 { |
|
| 160 | + | throw Error::Invalid; |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | return end + padding; |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | /// Read one structure event, skipping NOP tokens. Nesting is bounded to 32 nodes. |
|
| 167 | + | export fn next(bytes: &[u8], tree: Header, state: &mut Cursor) -> Event throws (Error) { |
|
| 168 | + | if state.finished { |
|
| 169 | + | return Event::End; |
|
| 170 | + | } |
|
| 171 | + | loop { |
|
| 172 | + | if state.offset > tree.structure.end or tree.structure.end - state.offset < 4 { |
|
| 173 | + | throw Error::Truncated; |
|
| 174 | + | } |
|
| 175 | + | let token = try integer(bytes, state.offset, 4); |
|
| 176 | + | set state.offset += 4; |
|
| 177 | + | match token { |
|
| 178 | + | case 1 => { |
|
| 179 | + | if state.depth == 32 { |
|
| 180 | + | throw Error::Depth; |
|
| 181 | + | } |
|
| 182 | + | if state.depth == 0 and state.started { |
|
| 183 | + | throw Error::Invalid; |
|
| 184 | + | } |
|
| 185 | + | let value = try name(bytes, state.offset, tree.structure.end); |
|
| 186 | + | if state.depth == 0 and value.start <> value.end { |
|
| 187 | + | throw Error::Invalid; |
|
| 188 | + | } |
|
| 189 | + | if state.depth > 0 and value.start == value.end { |
|
| 190 | + | throw Error::Invalid; |
|
| 191 | + | } |
|
| 192 | + | if state.depth > 0 { |
|
| 193 | + | set state.children |= 1 << (state.depth - 1); |
|
| 194 | + | } |
|
| 195 | + | set state.children &= ~(1 << state.depth); |
|
| 196 | + | set state.depth += 1; |
|
| 197 | + | set state.started = true; |
|
| 198 | + | set state.offset = try padded(bytes, value.end + 1, tree.structure.end); |
|
| 199 | + | return Event::Begin(value); |
|
| 200 | + | }, |
|
| 201 | + | case 2 => { |
|
| 202 | + | if state.depth == 0 { |
|
| 203 | + | throw Error::Invalid; |
|
| 204 | + | } |
|
| 205 | + | set state.depth -= 1; |
|
| 206 | + | return Event::EndNode; |
|
| 207 | + | }, |
|
| 208 | + | case 3 => { |
|
| 209 | + | if state.depth == 0 or (state.children & (1 << (state.depth - 1))) <> 0 { |
|
| 210 | + | throw Error::Invalid; |
|
| 211 | + | } |
|
| 212 | + | if tree.structure.end - state.offset < 8 { |
|
| 213 | + | throw Error::Truncated; |
|
| 214 | + | } |
|
| 215 | + | let size = try integer(bytes, state.offset, 4) as u32; |
|
| 216 | + | let offset = try integer(bytes, state.offset + 4, 4) as u32; |
|
| 217 | + | set state.offset += 8; |
|
| 218 | + | if size > tree.structure.end - state.offset { |
|
| 219 | + | throw Error::Truncated; |
|
| 220 | + | } |
|
| 221 | + | if offset >= tree.strings.end - tree.strings.start { |
|
| 222 | + | throw Error::Invalid; |
|
| 223 | + | } |
|
| 224 | + | let key = try name(bytes, tree.strings.start + offset, tree.strings.end); |
|
| 225 | + | if key.start == key.end { |
|
| 226 | + | throw Error::Invalid; |
|
| 227 | + | } |
|
| 228 | + | let value = Span { start: state.offset, end: state.offset + size }; |
|
| 229 | + | set state.offset = try padded(bytes, value.end, tree.structure.end); |
|
| 230 | + | return Event::Property { name: key, value }; |
|
| 231 | + | }, |
|
| 232 | + | case 4 => { |
|
| 233 | + | }, |
|
| 234 | + | case 9 => { |
|
| 235 | + | if state.depth <> 0 or not state.started or state.offset <> tree.structure.end { |
|
| 236 | + | throw Error::Invalid; |
|
| 237 | + | } |
|
| 238 | + | set state.finished = true; |
|
| 239 | + | return Event::End; |
|
| 240 | + | }, |
|
| 241 | + | else => throw Error::Invalid, |
|
| 242 | + | } |
|
| 243 | + | } |
|
| 244 | + | } |
kernel/kernel/platform.rad
added
+366 -0
| 1 | + | //! Fixed platform resources and reserved-memory exclusion. |
|
| 2 | + | ||
| 3 | + | use super::fdt; |
|
| 4 | + | use super::range; |
|
| 5 | + | use super::limits; |
|
| 6 | + | ||
| 7 | + | /// Maximum RAM banks in the platform description. |
|
| 8 | + | export constant RAM_BANKS: u32 = 16; |
|
| 9 | + | /// Maximum firmware, image, stack, and device reservations. |
|
| 10 | + | export constant RESERVATIONS: u32 = 64; |
|
| 11 | + | ||
| 12 | + | /// Platform extraction failure. |
|
| 13 | + | export union Error: Copy { |
|
| 14 | + | /// Malformed flattened tree. |
|
| 15 | + | Tree(fdt::Error), |
|
| 16 | + | /// Unsupported or inconsistent platform resource. |
|
| 17 | + | Invalid, |
|
| 18 | + | /// The platform exceeds fixed storage. |
|
| 19 | + | Capacity, |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Device mechanism selected from its compatible property. |
|
| 23 | + | export union Kind: Copy { |
|
| 24 | + | /// Byte-wide 16550 UART. |
|
| 25 | + | Uart, |
|
| 26 | + | /// Per-hart machine timers and software interrupts. |
|
| 27 | + | Clint, |
|
| 28 | + | /// Platform interrupt controller. |
|
| 29 | + | Plic, |
|
| 30 | + | /// Emulator termination register. |
|
| 31 | + | Finish, |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// A supported memory-mapped device. |
|
| 35 | + | export record Device: Copy { |
|
| 36 | + | /// Hardware mechanism. |
|
| 37 | + | kind: Kind, |
|
| 38 | + | /// Physical register extent. |
|
| 39 | + | memory: range::Range, |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Validated boot resources. Only array elements below their counts are live. |
|
| 43 | + | export record Platform: Copy { |
|
| 44 | + | /// RAM bank ranges. |
|
| 45 | + | ram: [range::Range; RAM_BANKS], |
|
| 46 | + | /// Number of RAM banks. |
|
| 47 | + | ramCount: u32, |
|
| 48 | + | /// Memory excluded from general allocation. |
|
| 49 | + | reserved: [range::Range; RESERVATIONS], |
|
| 50 | + | /// Number of reserved ranges. |
|
| 51 | + | reservedCount: u32, |
|
| 52 | + | /// Supported MMIO devices in tree order. |
|
| 53 | + | devices: [Device; limits::DEVICES], |
|
| 54 | + | /// Number of MMIO devices. |
|
| 55 | + | deviceCount: u32, |
|
| 56 | + | /// Bit mask of online physical hart IDs. |
|
| 57 | + | harts: u32, |
|
| 58 | + | /// Timer ticks per second. |
|
| 59 | + | timebase: u32, |
|
| 60 | + | /// Kernel stack ranges, indexed by physical hart ID. |
|
| 61 | + | stacks: [range::Range; limits::HARTS], |
|
| 62 | + | /// Number of stack ranges in the boot contract. |
|
| 63 | + | stackCount: u32, |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Properties retained until a node closes. |
|
| 67 | + | record Node: Copy { |
|
| 68 | + | /// Node name in the input blob. |
|
| 69 | + | name: fdt::Span, |
|
| 70 | + | /// Parent bus address-cell count. |
|
| 71 | + | addressCells: u32, |
|
| 72 | + | /// Parent bus size-cell count. |
|
| 73 | + | sizeCells: u32, |
|
| 74 | + | /// Address-cell count for child nodes. |
|
| 75 | + | childAddress: u32, |
|
| 76 | + | /// Size-cell count for child nodes. |
|
| 77 | + | childSize: u32, |
|
| 78 | + | /// Register tuples, if present. |
|
| 79 | + | registers: ?fdt::Span, |
|
| 80 | + | /// Device compatibility strings, if present. |
|
| 81 | + | compatible: ?fdt::Span, |
|
| 82 | + | /// Device type string, if present. |
|
| 83 | + | deviceType: ?fdt::Span, |
|
| 84 | + | /// Node status permits use. |
|
| 85 | + | enabled: bool, |
|
| 86 | + | /// This node or its parent is the reserved-memory container. |
|
| 87 | + | reserved: bool, |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Compare a complete byte span with a name. |
|
| 91 | + | fn equal(bytes: &[u8], span: fdt::Span, value: &[u8]) -> bool { |
|
| 92 | + | if span.end - span.start <> value.len { |
|
| 93 | + | return false; |
|
| 94 | + | } |
|
| 95 | + | for i in 0..value.len { |
|
| 96 | + | if bytes[span.start + i] <> value[i] { |
|
| 97 | + | return false; |
|
| 98 | + | } |
|
| 99 | + | } |
|
| 100 | + | return true; |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Find an exact string in a zero-terminated property string list. |
|
| 104 | + | fn string(bytes: &[u8], span: fdt::Span, value: &[u8]) -> bool { |
|
| 105 | + | let mut start = span.start; |
|
| 106 | + | for i in span.start..span.end { |
|
| 107 | + | if bytes[i] == 0 { |
|
| 108 | + | if equal(bytes, fdt::Span { start, end: i }, value) { |
|
| 109 | + | return true; |
|
| 110 | + | } |
|
| 111 | + | set start = i + 1; |
|
| 112 | + | } |
|
| 113 | + | } |
|
| 114 | + | return false; |
|
| 115 | + | } |
|
| 116 | + | ||
| 117 | + | /// Read a cell count supported by the native 64-bit platform. |
|
| 118 | + | fn scalar(bytes: &[u8], value: fdt::Span) -> u32 throws (Error) { |
|
| 119 | + | if value.end - value.start <> 4 { |
|
| 120 | + | throw Error::Invalid; |
|
| 121 | + | } |
|
| 122 | + | let result = try fdt::integer(bytes, value.start, 4) catch err { |
|
| 123 | + | throw Error::Tree(err); |
|
| 124 | + | }; |
|
| 125 | + | return result as u32; |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// Read a nonempty physical range from one register tuple. |
|
| 129 | + | fn extent(bytes: &[u8], at: u32, addressCells: u32, sizeCells: u32) -> range::Range throws (Error) { |
|
| 130 | + | if addressCells < 1 or addressCells > 2 or sizeCells < 1 or sizeCells > 2 { |
|
| 131 | + | throw Error::Invalid; |
|
| 132 | + | } |
|
| 133 | + | let address = try fdt::integer(bytes, at, addressCells * 4) catch err { |
|
| 134 | + | throw Error::Tree(err); |
|
| 135 | + | }; |
|
| 136 | + | let size = try fdt::integer(bytes, at + addressCells * 4, sizeCells * 4) catch err { |
|
| 137 | + | throw Error::Tree(err); |
|
| 138 | + | }; |
|
| 139 | + | let result = range::new(address, size) else { |
|
| 140 | + | throw Error::Invalid; |
|
| 141 | + | }; |
|
| 142 | + | return result; |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Append a reservation. Identical entries from both FDT representations share one slot. |
|
| 146 | + | export fn protect(platform: &mut Platform, value: range::Range) throws (Error) { |
|
| 147 | + | for i in 0..platform.reservedCount { |
|
| 148 | + | if platform.reserved[i] == value { |
|
| 149 | + | return; |
|
| 150 | + | } |
|
| 151 | + | } |
|
| 152 | + | if platform.reservedCount == RESERVATIONS { |
|
| 153 | + | throw Error::Capacity; |
|
| 154 | + | } |
|
| 155 | + | set platform.reserved[platform.reservedCount] = value; |
|
| 156 | + | set platform.reservedCount += 1; |
|
| 157 | + | } |
|
| 158 | + | ||
| 159 | + | /// Test a physical extent for overlap. |
|
| 160 | + | fn overlaps(left: range::Range, right: range::Range) -> bool { |
|
| 161 | + | return left.start < right.end and right.start < left.end; |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | /// Test whether one complete extent belongs to a RAM bank. |
|
| 165 | + | export fn inRam(platform: &Platform, value: range::Range) -> bool { |
|
| 166 | + | for i in 0..platform.ramCount { |
|
| 167 | + | if range::contains(platform.ram[i], value) { |
|
| 168 | + | return true; |
|
| 169 | + | } |
|
| 170 | + | } |
|
| 171 | + | return false; |
|
| 172 | + | } |
|
| 173 | + | ||
| 174 | + | /// Check whether a nonempty physical extent is RAM with no reserved byte. |
|
| 175 | + | export fn available(platform: &Platform, value: range::Range) -> bool { |
|
| 176 | + | if not inRam(platform, value) { |
|
| 177 | + | return false; |
|
| 178 | + | } |
|
| 179 | + | for i in 0..platform.reservedCount { |
|
| 180 | + | if overlaps(platform.reserved[i], value) { |
|
| 181 | + | return false; |
|
| 182 | + | } |
|
| 183 | + | } |
|
| 184 | + | return true; |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | /// Record resources only after all properties of their node are known. |
|
| 188 | + | fn finish(bytes: &[u8], node: Node, platform: &mut Platform) throws (Error) { |
|
| 189 | + | if not node.enabled { |
|
| 190 | + | return; |
|
| 191 | + | } |
|
| 192 | + | let registers = node.registers else { |
|
| 193 | + | return; |
|
| 194 | + | }; |
|
| 195 | + | if let typeName = node.deviceType { |
|
| 196 | + | if string(bytes, typeName, &"cpu"[..]) { |
|
| 197 | + | if node.sizeCells <> 0 or node.addressCells < 1 or node.addressCells > 2 |
|
| 198 | + | or registers.end - registers.start <> node.addressCells * 4 { |
|
| 199 | + | throw Error::Invalid; |
|
| 200 | + | } |
|
| 201 | + | let hart = try fdt::integer(bytes, registers.start, node.addressCells * 4) catch err { |
|
| 202 | + | throw Error::Tree(err); |
|
| 203 | + | }; |
|
| 204 | + | if hart >= limits::HARTS as u64 or (platform.harts & (1 << hart as u32)) <> 0 { |
|
| 205 | + | throw Error::Invalid; |
|
| 206 | + | } |
|
| 207 | + | set platform.harts |= 1 << hart as u32; |
|
| 208 | + | return; |
|
| 209 | + | } |
|
| 210 | + | } |
|
| 211 | + | let width = (node.addressCells + node.sizeCells) * 4; |
|
| 212 | + | if width == 0 or (registers.end - registers.start) % width <> 0 { |
|
| 213 | + | throw Error::Invalid; |
|
| 214 | + | } |
|
| 215 | + | let mut at = registers.start; |
|
| 216 | + | while at < registers.end { |
|
| 217 | + | let value = try extent(bytes, at, node.addressCells, node.sizeCells); |
|
| 218 | + | if node.reserved { |
|
| 219 | + | try protect(platform, value); |
|
| 220 | + | } |
|
| 221 | + | else { |
|
| 222 | + | let mut memory = false; |
|
| 223 | + | if let typeName = node.deviceType { |
|
| 224 | + | set memory = string(bytes, typeName, &"memory"[..]); |
|
| 225 | + | } |
|
| 226 | + | if memory { |
|
| 227 | + | if platform.ramCount == RAM_BANKS { |
|
| 228 | + | throw Error::Capacity; |
|
| 229 | + | } |
|
| 230 | + | for i in 0..platform.ramCount { |
|
| 231 | + | if overlaps(platform.ram[i], value) { |
|
| 232 | + | throw Error::Invalid; |
|
| 233 | + | } |
|
| 234 | + | } |
|
| 235 | + | set platform.ram[platform.ramCount] = value; |
|
| 236 | + | set platform.ramCount += 1; |
|
| 237 | + | } else { |
|
| 238 | + | let mut kind: ?Kind = nil; |
|
| 239 | + | if let names = node.compatible { |
|
| 240 | + | if string(bytes, names, &"ns16550a"[..]) { |
|
| 241 | + | set kind = Kind::Uart; |
|
| 242 | + | } |
|
| 243 | + | else if string(bytes, names, &"riscv,clint0"[..]) { |
|
| 244 | + | set kind = Kind::Clint; |
|
| 245 | + | } |
|
| 246 | + | else if string(bytes, names, &"riscv,plic0"[..]) { |
|
| 247 | + | set kind = Kind::Plic; |
|
| 248 | + | } |
|
| 249 | + | else if string(bytes, names, &"radiant,finish"[..]) { |
|
| 250 | + | set kind = Kind::Finish; |
|
| 251 | + | } |
|
| 252 | + | } |
|
| 253 | + | if let deviceKind = kind { |
|
| 254 | + | if platform.deviceCount == limits::DEVICES { |
|
| 255 | + | throw Error::Capacity; |
|
| 256 | + | } |
|
| 257 | + | set platform.devices[platform.deviceCount] = Device { kind: deviceKind, memory: value }; |
|
| 258 | + | set platform.deviceCount += 1; |
|
| 259 | + | } |
|
| 260 | + | try protect(platform, value); |
|
| 261 | + | } |
|
| 262 | + | } |
|
| 263 | + | set at += width; |
|
| 264 | + | } |
|
| 265 | + | } |
|
| 266 | + | ||
| 267 | + | /// Extract native platform resources. Discard the output if decoding fails. |
|
| 268 | + | export unsafe fn decode(bytes: &[u8], platform: &mut Platform) throws (Error) { |
|
| 269 | + | set platform.ramCount = 0; set platform.reservedCount = 0; set platform.deviceCount = 0; |
|
| 270 | + | set platform.harts = 0; set platform.timebase = 0; set platform.stackCount = 0; |
|
| 271 | + | let tree = try fdt::header(bytes) catch err { |
|
| 272 | + | throw Error::Tree(err); |
|
| 273 | + | }; |
|
| 274 | + | let mut at = tree.reservations.start; |
|
| 275 | + | while at < tree.reservations.end { |
|
| 276 | + | try protect(platform, try extent(bytes, at, 2, 2)); set at += 16; |
|
| 277 | + | } |
|
| 278 | + | let mut nodes: [Node; 32] = undefined; |
|
| 279 | + | let mut state = fdt::cursor(tree); |
|
| 280 | + | loop { |
|
| 281 | + | let event = try fdt::next(bytes, tree, &mut state) catch err { |
|
| 282 | + | throw Error::Tree(err); |
|
| 283 | + | }; |
|
| 284 | + | match event { |
|
| 285 | + | case fdt::Event::Begin(name) => { |
|
| 286 | + | let mut addressCells: u32 = 2; |
|
| 287 | + | let mut sizeCells: u32 = 1; |
|
| 288 | + | let mut reserved = false; |
|
| 289 | + | let mut enabled = true; |
|
| 290 | + | if state.depth > 1 { |
|
| 291 | + | let parent = nodes[state.depth - 2]; |
|
| 292 | + | set addressCells = parent.childAddress; set sizeCells = parent.childSize; |
|
| 293 | + | set reserved = parent.reserved; |
|
| 294 | + | set enabled = parent.enabled; |
|
| 295 | + | } |
|
| 296 | + | if state.depth == 2 and equal(bytes, name, &"reserved-memory"[..]) { |
|
| 297 | + | set reserved = true; |
|
| 298 | + | } |
|
| 299 | + | set nodes[state.depth - 1] = Node { name, addressCells, sizeCells, childAddress: 2, childSize: 1, |
|
| 300 | + | registers: nil, compatible: nil, deviceType: nil, enabled, reserved }; |
|
| 301 | + | }, |
|
| 302 | + | case fdt::Event::Property { name, value } => { |
|
| 303 | + | if equal(bytes, name, &"#address-cells"[..]) { |
|
| 304 | + | let count = try scalar(bytes, value); |
|
| 305 | + | if count > 2 { |
|
| 306 | + | throw Error::Invalid; |
|
| 307 | + | } |
|
| 308 | + | set nodes[state.depth - 1].childAddress = count; |
|
| 309 | + | } else if equal(bytes, name, &"#size-cells"[..]) { |
|
| 310 | + | let count = try scalar(bytes, value); |
|
| 311 | + | if count > 2 { |
|
| 312 | + | throw Error::Invalid; |
|
| 313 | + | } |
|
| 314 | + | set nodes[state.depth - 1].childSize = count; |
|
| 315 | + | } else if equal(bytes, name, &"reg"[..]) { |
|
| 316 | + | set nodes[state.depth - 1].registers = value; |
|
| 317 | + | } |
|
| 318 | + | else if equal(bytes, name, &"compatible"[..]) { |
|
| 319 | + | set nodes[state.depth - 1].compatible = value; |
|
| 320 | + | } |
|
| 321 | + | else if equal(bytes, name, &"device_type"[..]) { |
|
| 322 | + | set nodes[state.depth - 1].deviceType = value; |
|
| 323 | + | } |
|
| 324 | + | else if equal(bytes, name, &"status"[..]) { |
|
| 325 | + | set nodes[state.depth - 1].enabled = nodes[state.depth - 1].enabled and (string(bytes, value, &"okay"[..]) or string(bytes, value, &"ok"[..])); |
|
| 326 | + | } else if equal(bytes, name, &"ranges"[..]) and value.start <> value.end { |
|
| 327 | + | throw Error::Invalid; |
|
| 328 | + | } |
|
| 329 | + | else if state.depth == 2 and equal(bytes, nodes[state.depth - 1].name, &"cpus"[..]) and equal(bytes, name, &"timebase-frequency"[..]) { |
|
| 330 | + | set platform.timebase = try scalar(bytes, value); |
|
| 331 | + | } else if state.depth == 2 and equal(bytes, nodes[state.depth - 1].name, &"chosen"[..]) and equal(bytes, name, &"radiance,kernel-stacks"[..]) { |
|
| 332 | + | let size = value.end - value.start; |
|
| 333 | + | if size % 16 <> 0 or size / 16 > limits::HARTS { |
|
| 334 | + | throw Error::Invalid; |
|
| 335 | + | } |
|
| 336 | + | set platform.stackCount = size / 16; |
|
| 337 | + | for i in 0..platform.stackCount { |
|
| 338 | + | let stack = try extent(bytes, value.start + i * 16, 2, 2); |
|
| 339 | + | if (stack.start & 15) <> 0 or (stack.end & 15) <> 0 { |
|
| 340 | + | throw Error::Invalid; |
|
| 341 | + | } |
|
| 342 | + | set platform.stacks[i] = stack; try protect(platform, stack); |
|
| 343 | + | } |
|
| 344 | + | } |
|
| 345 | + | }, |
|
| 346 | + | case fdt::Event::EndNode => try finish(bytes, nodes[state.depth], platform), |
|
| 347 | + | case fdt::Event::End => break, |
|
| 348 | + | } |
|
| 349 | + | } |
|
| 350 | + | if platform.ramCount == 0 or platform.harts == 0 or platform.timebase == 0 { |
|
| 351 | + | throw Error::Invalid; |
|
| 352 | + | } |
|
| 353 | + | for hart in 0..limits::HARTS { |
|
| 354 | + | if (platform.harts & (1 << hart)) == 0 { |
|
| 355 | + | continue; |
|
| 356 | + | } |
|
| 357 | + | if hart >= platform.stackCount or not inRam(platform, platform.stacks[hart]) { |
|
| 358 | + | throw Error::Invalid; |
|
| 359 | + | } |
|
| 360 | + | for other in 0..hart { |
|
| 361 | + | if (platform.harts & (1 << other)) <> 0 and overlaps(platform.stacks[hart], platform.stacks[other]) { |
|
| 362 | + | throw Error::Invalid; |
|
| 363 | + | } |
|
| 364 | + | } |
|
| 365 | + | } |
|
| 366 | + | } |
kernel/kernel/tests.rad
+2 -0
| 1 | 1 | //! Kernel unit tests. Machine execution tests have separate entry points. |
|
| 2 | 2 | ||
| 3 | 3 | export mod range; |
|
| 4 | 4 | export mod abi; |
|
| 5 | 5 | export mod slots; |
|
| 6 | + | export mod fdt; |
|
| 7 | + | export mod platform; |
kernel/kernel/tests/fdt.rad
added
+143 -0
| 1 | + | //! Device-tree byte fixtures and structural rejection checks. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::fdt; |
|
| 5 | + | ||
| 6 | + | /// Store a big-endian field in a test fixture. |
|
| 7 | + | fn put(bytes: &mut [u8], offset: u32, width: u32, value: u64) { |
|
| 8 | + | for i in 0..width { |
|
| 9 | + | set bytes[offset + i] = (value >> ((width - i - 1) * 8) as u64) as u8; |
|
| 10 | + | } |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Build one root property and one high-address memory reservation. |
|
| 14 | + | fn fixture(bytes: &mut [u8]) { |
|
| 15 | + | for i in 0..bytes.len { |
|
| 16 | + | set bytes[i] = 0; |
|
| 17 | + | } |
|
| 18 | + | put(bytes, 0, 4, 0xd00dfeed); put(bytes, 4, 4, 108); |
|
| 19 | + | put(bytes, 8, 4, 72); put(bytes, 12, 4, 104); put(bytes, 16, 4, 40); |
|
| 20 | + | put(bytes, 20, 4, 17); put(bytes, 24, 4, 16); |
|
| 21 | + | put(bytes, 32, 4, 4); put(bytes, 36, 4, 32); |
|
| 22 | + | put(bytes, 40, 8, 0x80000000); put(bytes, 48, 8, 4096); |
|
| 23 | + | put(bytes, 72, 4, 1); |
|
| 24 | + | put(bytes, 80, 4, 3); put(bytes, 84, 4, 4); put(bytes, 88, 4, 0); |
|
| 25 | + | put(bytes, 92, 4, 42); put(bytes, 96, 4, 2); put(bytes, 100, 4, 9); |
|
| 26 | + | set bytes[104] = 'r'; set bytes[105] = 'e'; set bytes[106] = 'g'; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Validate the complete tree, including the final structure token. |
|
| 30 | + | fn validate(bytes: &[u8]) throws (fdt::Error) { |
|
| 31 | + | let tree = try fdt::header(bytes); |
|
| 32 | + | let mut state = fdt::cursor(tree); |
|
| 33 | + | loop { |
|
| 34 | + | match try fdt::next(bytes, tree, &mut state) { |
|
| 35 | + | case fdt::Event::End => return, |
|
| 36 | + | else => { |
|
| 37 | + | }, |
|
| 38 | + | } |
|
| 39 | + | } |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Require malformed input to fail through a bounded decoder error. |
|
| 43 | + | fn rejects(bytes: &[u8]) throws (testing::TestError) { |
|
| 44 | + | let mut failed = false; |
|
| 45 | + | try validate(bytes) catch { |
|
| 46 | + | set failed = true; |
|
| 47 | + | }; |
|
| 48 | + | try testing::expect(failed); |
|
| 49 | + | } |
|
| 50 | + | ||
| 51 | + | /// Decode big-endian ranges and the ordered structure events. |
|
| 52 | + | @test unsafe fn traversal() throws (testing::TestError) { |
|
| 53 | + | let mut bytes: [u8; 108] = undefined; |
|
| 54 | + | fixture(&mut bytes[..]); |
|
| 55 | + | let tree = try! fdt::header(&bytes[..]); |
|
| 56 | + | try testing::expect(tree.reservations.start == 40 and tree.reservations.end == 56); |
|
| 57 | + | try testing::expect(try! fdt::integer(&bytes[..], 40, 8) == 0x80000000); |
|
| 58 | + | let mut state = fdt::cursor(tree); |
|
| 59 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 60 | + | case fdt::Event::Begin(name) => try testing::expect(name.start == name.end), |
|
| 61 | + | else => try testing::expect(false), |
|
| 62 | + | } |
|
| 63 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 64 | + | case fdt::Event::Property { name, value } => { |
|
| 65 | + | try testing::expect(name.end - name.start == 3 and bytes[name.start] == 'r'); |
|
| 66 | + | try testing::expect(value.end - value.start == 4); |
|
| 67 | + | try testing::expect(try! fdt::integer(&bytes[..], value.start, 4) == 42); |
|
| 68 | + | }, |
|
| 69 | + | else => try testing::expect(false), |
|
| 70 | + | } |
|
| 71 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 72 | + | case fdt::Event::EndNode => { |
|
| 73 | + | }, else => try testing::expect(false), |
|
| 74 | + | } |
|
| 75 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 76 | + | case fdt::Event::End => { |
|
| 77 | + | }, else => try testing::expect(false), |
|
| 78 | + | } |
|
| 79 | + | try testing::expect(state.finished and state.depth == 0); |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Reject every truncated prefix and invalid block or reservation extents. |
|
| 83 | + | @test unsafe fn malformedHeader() throws (testing::TestError) { |
|
| 84 | + | let mut bytes: [u8; 108] = undefined; |
|
| 85 | + | fixture(&mut bytes[..]); |
|
| 86 | + | for size in 0..bytes.len { |
|
| 87 | + | try rejects(&bytes[..size]); |
|
| 88 | + | } |
|
| 89 | + | for offset in &[0 as u32, 4, 8, 12, 16, 20, 24, 32, 36] { |
|
| 90 | + | fixture(&mut bytes[..]); put(&mut bytes[..], offset, 4, 0xffffffff); |
|
| 91 | + | try rejects(&bytes[..]); |
|
| 92 | + | } |
|
| 93 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 12, 4, 80); |
|
| 94 | + | try rejects(&bytes[..]); |
|
| 95 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 40, 8, 0xfffffffffffff800); |
|
| 96 | + | try rejects(&bytes[..]); |
|
| 97 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 48, 8, 0); |
|
| 98 | + | try rejects(&bytes[..]); |
|
| 99 | + | } |
|
| 100 | + | ||
| 101 | + | /// Reject missing names, oversized properties, bad tokens, and unbalanced nodes. |
|
| 102 | + | @test unsafe fn malformedStructure() throws (testing::TestError) { |
|
| 103 | + | let mut bytes: [u8; 108] = undefined; |
|
| 104 | + | for offset in &[72 as u32, 80, 84, 88, 96, 100] { |
|
| 105 | + | fixture(&mut bytes[..]); put(&mut bytes[..], offset, 4, 0xffffffff); |
|
| 106 | + | try rejects(&bytes[..]); |
|
| 107 | + | } |
|
| 108 | + | fixture(&mut bytes[..]); set bytes[107] = 1; |
|
| 109 | + | try rejects(&bytes[..]); |
|
| 110 | + | fixture(&mut bytes[..]); set bytes[77] = 1; |
|
| 111 | + | try rejects(&bytes[..]); |
|
| 112 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 96, 4, 4); |
|
| 113 | + | try rejects(&bytes[..]); |
|
| 114 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 80, 4, 2); |
|
| 115 | + | try rejects(&bytes[..]); |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Excessive nesting fails before any shift or stack index exceeds its bound. |
|
| 119 | + | @test unsafe fn depthLimit() throws (testing::TestError) { |
|
| 120 | + | let mut bytes: [u8; 512] = undefined; |
|
| 121 | + | fixture(&mut bytes[..]); |
|
| 122 | + | put(&mut bytes[..], 4, 4, 512); put(&mut bytes[..], 12, 4, 512); |
|
| 123 | + | put(&mut bytes[..], 32, 4, 0); put(&mut bytes[..], 36, 4, 440); |
|
| 124 | + | for i in 72..512 { |
|
| 125 | + | set bytes[i] = 0; |
|
| 126 | + | } |
|
| 127 | + | for i in 0..33 { |
|
| 128 | + | put(&mut bytes[..], 72 + i * 8, 4, 1); |
|
| 129 | + | if i > 0 { |
|
| 130 | + | set bytes[76 + i * 8] = 'x'; |
|
| 131 | + | } |
|
| 132 | + | } |
|
| 133 | + | let tree = try! fdt::header(&bytes[..]); |
|
| 134 | + | let mut state = fdt::cursor(tree); |
|
| 135 | + | for i in 0..32 { |
|
| 136 | + | let event = try! fdt::next(&bytes[..], tree, &mut state); |
|
| 137 | + | } |
|
| 138 | + | let mut failed = false; |
|
| 139 | + | try fdt::next(&bytes[..], tree, &mut state) catch err { |
|
| 140 | + | try testing::expect(err == fdt::Error::Depth); set failed = true; |
|
| 141 | + | }; |
|
| 142 | + | try testing::expect(failed); |
|
| 143 | + | } |
kernel/kernel/tests/platform.rad
added
+142 -0
| 1 | + | //! Platform resource discovery and reserved-memory exclusion fixtures. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use kernel::platform; |
|
| 5 | + | use kernel::range; |
|
| 6 | + | ||
| 7 | + | /// Fixed FDT construction workspace with separate structure and string regions. |
|
| 8 | + | record Fixture: Copy { |
|
| 9 | + | /// Entire blob. |
|
| 10 | + | bytes: [u8; 4096], |
|
| 11 | + | /// Next structure byte. |
|
| 12 | + | offset: u32, |
|
| 13 | + | /// Next property-name byte. |
|
| 14 | + | strings: u32, |
|
| 15 | + | /// Stack range value offset for malformed-input checks. |
|
| 16 | + | stack: u32, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Write a big-endian fixture field. |
|
| 20 | + | fn put(f: &mut Fixture, offset: u32, width: u32, value: u64) { |
|
| 21 | + | for i in 0..width { |
|
| 22 | + | set f.bytes[offset + i] = (value >> ((width - i - 1) * 8) as u64) as u8; |
|
| 23 | + | } |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Append one structure token. |
|
| 27 | + | fn word(f: &mut Fixture, value: u32) { |
|
| 28 | + | let offset = f.offset; put(f, offset, 4, value as u64); set f.offset += 4; |
|
| 29 | + | } |
|
| 30 | + | ||
| 31 | + | /// Append a node name and its terminator. |
|
| 32 | + | fn begin(f: &mut Fixture, name: &[u8]) { |
|
| 33 | + | word(f, 1); |
|
| 34 | + | for i in 0..name.len { |
|
| 35 | + | set f.bytes[f.offset + i] = name[i]; |
|
| 36 | + | } |
|
| 37 | + | set f.offset = (f.offset + name.len + 4) & ~3; |
|
| 38 | + | } |
|
| 39 | + | ||
| 40 | + | /// Append a property header and allocate its zeroed payload. |
|
| 41 | + | fn property(f: &mut Fixture, name: &[u8], size: u32) -> u32 { |
|
| 42 | + | let nameOffset = f.strings - 3072; |
|
| 43 | + | word(f, 3); word(f, size); word(f, nameOffset); |
|
| 44 | + | for i in 0..name.len { |
|
| 45 | + | set f.bytes[f.strings + i] = name[i]; |
|
| 46 | + | } |
|
| 47 | + | set f.strings += name.len + 1; |
|
| 48 | + | let offset = f.offset; |
|
| 49 | + | set f.offset = (f.offset + size + 3) & ~3; |
|
| 50 | + | return offset; |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Append a one-cell property. |
|
| 54 | + | fn scalar(f: &mut Fixture, name: &[u8], value: u32) { |
|
| 55 | + | let offset = property(f, name, 4); put(f, offset, 4, value as u64); |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Append a zero-terminated string property. |
|
| 59 | + | fn string(f: &mut Fixture, name: &[u8], value: &[u8]) { |
|
| 60 | + | let offset = property(f, name, value.len + 1); |
|
| 61 | + | for i in 0..value.len { |
|
| 62 | + | set f.bytes[offset + i] = value[i]; |
|
| 63 | + | } |
|
| 64 | + | } |
|
| 65 | + | ||
| 66 | + | /// Append a two-address-cell, two-size-cell range. |
|
| 67 | + | fn region(f: &mut Fixture, name: &[u8], start: u64, size: u64) -> u32 { |
|
| 68 | + | let offset = property(f, name, 16); put(f, offset, 8, start); put(f, offset + 8, 8, size); |
|
| 69 | + | return offset; |
|
| 70 | + | } |
|
| 71 | + | ||
| 72 | + | /// Construct the supported one-hart boot contract. |
|
| 73 | + | fn build(f: &mut Fixture) { |
|
| 74 | + | for i in 0..f.bytes.len { |
|
| 75 | + | set f.bytes[i] = 0; |
|
| 76 | + | } |
|
| 77 | + | set f.offset = 56; set f.strings = 3072; |
|
| 78 | + | begin(f, &""[..]); |
|
| 79 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 80 | + | begin(f, &"chosen"[..]); |
|
| 81 | + | set f.stack = region(f, &"radiance,kernel-stacks"[..], 0x80008000, 0x2000); |
|
| 82 | + | word(f, 2); |
|
| 83 | + | begin(f, &"memory@80000000"[..]); |
|
| 84 | + | string(f, &"device_type"[..], &"memory"[..]); |
|
| 85 | + | let ram = region(f, &"reg"[..], 0x80000000, 0x10000); word(f, 2); |
|
| 86 | + | begin(f, &"cpus"[..]); |
|
| 87 | + | scalar(f, &"#address-cells"[..], 1); scalar(f, &"#size-cells"[..], 0); |
|
| 88 | + | scalar(f, &"timebase-frequency"[..], 10000000); |
|
| 89 | + | begin(f, &"cpu@0"[..]); string(f, &"device_type"[..], &"cpu"[..]); |
|
| 90 | + | scalar(f, &"reg"[..], 0); word(f, 2); word(f, 2); |
|
| 91 | + | begin(f, &"reserved-memory"[..]); |
|
| 92 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 93 | + | let ranges = property(f, &"ranges"[..], 0); |
|
| 94 | + | begin(f, &"image@80002000"[..]); |
|
| 95 | + | let image = region(f, &"reg"[..], 0x80002000, 0x3000); word(f, 2); word(f, 2); |
|
| 96 | + | begin(f, &"soc"[..]); |
|
| 97 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 98 | + | let bus = property(f, &"ranges"[..], 0); |
|
| 99 | + | begin(f, &"uart@10000000"[..]); |
|
| 100 | + | string(f, &"compatible"[..], &"ns16550a"[..]); |
|
| 101 | + | let uart = region(f, &"reg"[..], 0x10000000, 0x100); word(f, 2); word(f, 2); |
|
| 102 | + | word(f, 2); word(f, 9); |
|
| 103 | + | let total = f.strings; let size = f.offset - 56; |
|
| 104 | + | put(f, 0, 4, 0xd00dfeed); put(f, 4, 4, total as u64); |
|
| 105 | + | put(f, 8, 4, 56); put(f, 12, 4, 3072); put(f, 16, 4, 40); |
|
| 106 | + | put(f, 20, 4, 17); put(f, 24, 4, 16); |
|
| 107 | + | put(f, 32, 4, (total - 3072) as u64); put(f, 36, 4, size as u64); |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Resources and their reservations come from the tree rather than fixed addresses. |
|
| 111 | + | @test unsafe fn discovery() throws (testing::TestError) { |
|
| 112 | + | let mut f: Fixture = undefined; build(&mut f); |
|
| 113 | + | let mut p: platform::Platform = undefined; |
|
| 114 | + | try! platform::decode(&f.bytes[..f.strings], &mut p); |
|
| 115 | + | try testing::expect(p.ramCount == 1 and p.harts == 1 and p.timebase == 10000000); |
|
| 116 | + | try testing::expect(p.deviceCount == 1 and p.devices[0].kind == platform::Kind::Uart); |
|
| 117 | + | try testing::expect(p.devices[0].memory.start == 0x10000000); |
|
| 118 | + | try testing::expect(p.stackCount == 1 and p.stacks[0].end == 0x8000a000); |
|
| 119 | + | try testing::expect(p.reservedCount == 3); |
|
| 120 | + | for i in 0..16 { |
|
| 121 | + | let page = range::new(0x80000000 + i as u64 * 4096, 4096) else panic "valid page"; |
|
| 122 | + | let free = i < 2 or (i >= 5 and i < 8) or i >= 10; |
|
| 123 | + | try testing::expect(platform::available(&p, page) == free); |
|
| 124 | + | } |
|
| 125 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x7ffff000, end: 0x80001000 })); |
|
| 126 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x80001000, end: 0x80003000 })); |
|
| 127 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x80010000, end: 0x80011000 })); |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | /// Misaligned, overflowing, and non-RAM kernel stacks cannot become boot state. |
|
| 131 | + | @test unsafe fn invalidStacks() throws (testing::TestError) { |
|
| 132 | + | let mut f: Fixture = undefined; |
|
| 133 | + | let mut p: platform::Platform = undefined; |
|
| 134 | + | for start in &[0x80008001 as u64, 0x70000000, 0xfffffffffffff000] { |
|
| 135 | + | build(&mut f); let stack = f.stack; put(&mut f, stack, 8, start); |
|
| 136 | + | let mut failed = false; |
|
| 137 | + | try platform::decode(&f.bytes[..f.strings], &mut p) catch { |
|
| 138 | + | set failed = true; |
|
| 139 | + | }; |
|
| 140 | + | try testing::expect(failed); |
|
| 141 | + | } |
|
| 142 | + | } |
kernel/tools/build.rad
added
+82 -0
| 1 | + | //! Build a physical kernel image from trusted binary RIL and startup assembly. |
|
| 2 | + | ||
| 3 | + | use std::sys; |
|
| 4 | + | use std::io; |
|
| 5 | + | use std::sys::unix; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::strings; |
|
| 8 | + | use std::lang::il::binary; |
|
| 9 | + | use std::lang::il::binary::program; |
|
| 10 | + | use std::lang::gen::data; |
|
| 11 | + | use std::collections::dict; |
|
| 12 | + | use std::arch::rv64; |
|
| 13 | + | use std::arch::rv64::asm; |
|
| 14 | + | use std::arch::rv64::image; |
|
| 15 | + | ||
| 16 | + | /// Persistent native code-generation workspace. |
|
| 17 | + | static CODE: [u8; 16777216] = [0; 16777216]; |
|
| 18 | + | /// Reusable function workspace. |
|
| 19 | + | static SCRATCH: [u8; 16777216] = [0; 16777216]; |
|
| 20 | + | /// Decoded package storage. |
|
| 21 | + | static DECODE: [u8; 16777216] = [0; 16777216]; |
|
| 22 | + | /// Binary package input. |
|
| 23 | + | static INPUT: [u8; 1048576] = [0; 1048576]; |
|
| 24 | + | /// Combined startup and boundary assembly. |
|
| 25 | + | static SOURCE: [u8; 65536] = [0; 65536]; |
|
| 26 | + | /// Assembler workspace. |
|
| 27 | + | static ASSEMBLY: [u8; 4194304] = [0; 4194304]; |
|
| 28 | + | /// Assembled startup words. |
|
| 29 | + | static TEXT: [u32; 16384] = [0; 16384]; |
|
| 30 | + | /// Assembly identifiers. |
|
| 31 | + | unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 32 | + | /// Data symbol placement workspace. |
|
| 33 | + | unsafe static SYMBOLS: [data::DataSym; 1024] = undefined; |
|
| 34 | + | /// Data name lookup workspace. |
|
| 35 | + | unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined; |
|
| 36 | + | /// Initialized read-only bytes. |
|
| 37 | + | static RO: [u8; 1048576] = [0; 1048576]; |
|
| 38 | + | /// Initialized writable bytes. |
|
| 39 | + | static RW: [u8; 1048576] = [0; 1048576]; |
|
| 40 | + | ||
| 41 | + | /// Assemble entry code and lower the kernel at explicit physical addresses. |
|
| 42 | + | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 43 | + | assert env.args.len == 4; |
|
| 44 | + | let inputLength = unix::readFile(env.args[1], &mut INPUT[..]) else panic "kernel RIL"; |
|
| 45 | + | let mut decoder = alloc::new(&mut DECODE[..]); |
|
| 46 | + | let package = try! program::decode(&INPUT[..inputLength], &mut decoder, binary::Limits { registers: 8192, blocks: 4096 }); |
|
| 47 | + | assert package.dependencies.len == 0; |
|
| 48 | + | let sourceLength = unix::readFile(env.args[2], &mut SOURCE[..]) else panic "kernel assembly"; |
|
| 49 | + | let mut assembly = alloc::new(&mut ASSEMBLY[..]); |
|
| 50 | + | let empty: *mut [u8] = &mut []; |
|
| 51 | + | let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 52 | + | &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0); |
|
| 53 | + | let mut arena = alloc::new(&mut CODE[..]); |
|
| 54 | + | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 55 | + | let mut generator = try! rv64::beginProgram(rv64::ProgramOptions { |
|
| 56 | + | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 57 | + | placement: image::Placement::Physical { |
|
| 58 | + | code: 0x80010000, roData: 0x80200000, rwData: 0x80400000, entry: 0x80010000, |
|
| 59 | + | }, |
|
| 60 | + | }, &mut arena); |
|
| 61 | + | rv64::addAssembly(&mut generator, startup); |
|
| 62 | + | for func in package.program.fns { |
|
| 63 | + | rv64::generateFunction(&mut generator, func, &mut scratch); |
|
| 64 | + | alloc::reset(&mut scratch); |
|
| 65 | + | } |
|
| 66 | + | for call in &generator.e.pendingCalls[..] { |
|
| 67 | + | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 68 | + | io::print("undefined kernel function: "); io::printLn(call.target); return 1; |
|
| 69 | + | } |
|
| 70 | + | } |
|
| 71 | + | let output = try! rv64::finishProgram(&mut generator, package.program.data, |
|
| 72 | + | rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 73 | + | &[], &mut RO[..], &mut RW[..]); |
|
| 74 | + | let header = try! image::header(output.layout); |
|
| 75 | + | let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 76 | + | assert fd >= 0; |
|
| 77 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4)) |
|
| 78 | + | and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]); |
|
| 79 | + | let closed = unix::close(fd) == 0; |
|
| 80 | + | assert written and closed; |
|
| 81 | + | return 0; |
|
| 82 | + | } |
test/boot/machine.ras
added
+10 -0
| 1 | + | //! Complete only after every hart passes the production boot initialization. |
|
| 2 | + | .text; |
|
| 3 | + | call @kernel::boot::initialize; |
|
| 4 | + | beqz %a0 @wait; |
|
| 5 | + | li %t0 0x10001000; |
|
| 6 | + | li %t1 0x5555; |
|
| 7 | + | sw %t1 0(%t0); |
|
| 8 | + | @wait |
|
| 9 | + | wfi; |
|
| 10 | + | j @wait; |
test/boot/run
added
+26 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Boot the kernel through its production initialization on each supported hart count. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | work=$(mktemp -d) |
|
| 6 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | + | cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras > "$work/boot.ras" |
|
| 8 | + | "$emulator" -run bin/kernel.build.rv64 -- bin/kernel.ril "$work/boot.ras" "$work/boot.rv64" |
|
| 9 | + | for harts in 1 2 8; do |
|
| 10 | + | if ! "$emulator" -machine -max-steps=10000000 -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then |
|
| 11 | + | cat "$work/log" >&2 |
|
| 12 | + | exit 1 |
|
| 13 | + | fi |
|
| 14 | + | if ! grep -q '^kernel: platform ready$' "$work/log"; then |
|
| 15 | + | cat "$work/log" >&2 |
|
| 16 | + | exit 1 |
|
| 17 | + | fi |
|
| 18 | + | printf 'kernel boot: %s harts passed\n' "$harts" |
|
| 19 | + | done |
|
| 20 | + | status=0 |
|
| 21 | + | "$emulator" -machine -harts=8 -max-steps=10000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$? |
|
| 22 | + | if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then |
|
| 23 | + | cat "$work/log" >&2 |
|
| 24 | + | exit 1 |
|
| 25 | + | fi |
|
| 26 | + | printf 'kernel startup: all eight harts reached machine idle without traps\n' |