kernel: Boot from platform data
3661ec1fdb56529e0a301a0b1380fbd9bf158921e5c6365db01eaebeb6c4205b
Assisted-by: Codex:gpt-6
1 parent
eb5c3d4d
Makefile
+19 -4
| 21 | 21 | ||
| 22 | 22 | # Verify the emulator binary exists. |
|
| 23 | 23 | EMU_PATH := $(shell command -v $(EMU) 2>/dev/null) |
|
| 24 | 24 | ||
| 25 | 25 | default: emulator $(RAD_BIN) |
|
| 26 | - | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 65 | 65 | lib/std.test.rv64.debug \ |
|
| 66 | 66 | lib/std.test.rv64.s \ |
|
| 67 | 67 | lib/std.test.rv64.o |
|
| 68 | 68 | ||
| 69 | 69 | # Kernel modules and tests use a separate package and test entry point. |
|
| 70 | - | KERNEL_SRC := $(shell find kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null) |
|
| 70 | + | KERNEL_SRC := kernel/kernel.rad $(shell find kernel/kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null) |
|
| 71 | + | KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras |
|
| 71 | 72 | KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null) |
|
| 72 | 73 | KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC))) |
|
| 73 | 74 | KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64 |
|
| 74 | 75 | ||
| 75 | 76 | kernel-test: emulator $(KERNEL_TEST) |
|
| 76 | 77 | @$(EMU) $(EMU_FLAGS) -run $(KERNEL_TEST) |
|
| 77 | 78 | @sh test/slots/run "$(EMU)" $(STD_TEST) $(KERNEL) $(patsubst %,-mod %,$(KERNEL_TEST_SRC)) |
|
| 78 | 79 | ||
| 79 | - | $(KERNEL_TEST): $(KERNEL_SRC) $(KERNEL_TEST_SRC) $(STD_LIB) $(RAD_BIN) |
|
| 80 | + | $(KERNEL_TEST): $(KERNEL_SRC) $(KERNEL_TEST_SRC) $(KERNEL_ASM) $(STD_LIB) $(RAD_BIN) |
|
| 80 | 81 | @echo "radiance kernel tests => $@" |
|
| 81 | - | @$(RADIANCE) -test $(STD_TEST) $(KERNEL) $(patsubst %,-mod %,$(KERNEL_TEST_SRC)) -entry kernel -o $@ |
|
| 82 | + | @$(RADIANCE) -test $(STD_TEST) $(KERNEL) $(patsubst %,-mod %,$(KERNEL_TEST_SRC) $(KERNEL_ASM)) -entry kernel -o $@ |
|
| 83 | + | ||
| 84 | + | # Native kernel image and machine boot checks. |
|
| 85 | + | kernel-boot-test: $(BIN_DIR)/kernel.rv64 |
|
| 86 | + | @RAD_EMULATOR="$(EMU)" sh test/boot/run |
|
| 87 | + | ||
| 88 | + | $(BIN_DIR)/kernel.ril: $(KERNEL_SRC) $(RAD_BIN) |
|
| 89 | + | @$(RADIANCE) $(KERNEL) -entry kernel -ril $(BIN_DIR) |
|
| 90 | + | ||
| 91 | + | $(BIN_DIR)/kernel.build.rv64: kernel/tools/build.rad $(STD_LIB) $(RAD_BIN) |
|
| 92 | + | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 93 | + | ||
| 94 | + | $(BIN_DIR)/kernel.rv64: $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.build.rv64 $(KERNEL_ASM) |
|
| 95 | + | @cat $(KERNEL_ASM) > $(BIN_DIR)/kernel.ras |
|
| 96 | + | @$(EMU) -run $(BIN_DIR)/kernel.build.rv64 -- $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.ras $@ |
|
| 82 | 97 | ||
| 83 | 98 | # Separate binary package emission and decode checks. |
|
| 84 | 99 | package-test: emulator $(BIN_DIR)/packages.check.rv64 |
|
| 85 | 100 | @sh test/packages/run |
|
| 86 | 101 |
compiler/radiance.rad
+29 -4
| 944 | 944 | pkgLog(pkg, &["asm:", "parsing", "(", path, ")", ".."]); |
|
| 945 | 945 | ||
| 946 | 946 | let sourceLen = unix::readFile(path, &mut ASM_SOURCE_BUF[..]) else { |
|
| 947 | 947 | throw error(&["error reading assembly file"]); |
|
| 948 | 948 | }; |
|
| 949 | - | let source = &ASM_SOURCE_BUF[..sourceLen]; |
|
| 950 | - | if source.len == ASM_SOURCE_BUF.len { |
|
| 949 | + | let input = &ASM_SOURCE_BUF[..sourceLen]; |
|
| 950 | + | if input.len == ASM_SOURCE_BUF.len { |
|
| 951 | 951 | throw error(&["fatal:", "assembly source too large:", path]); |
|
| 952 | 952 | } |
|
| 953 | + | // Assembly symbols borrow source bytes until final linking. |
|
| 954 | + | let buffer = try alloc::allocSlice(arena, 1, 1, input.len) catch { |
|
| 955 | + | throw error(&["assembly source workspace exhausted"]); |
|
| 956 | + | }; |
|
| 957 | + | let source = buffer as *mut [u8]; |
|
| 958 | + | try! mem::copy(source, input); |
|
| 953 | 959 | let program = try asm::assemble( |
|
| 954 | 960 | asm::scanner::SourceKind::File { path }, |
|
| 955 | 961 | source, |
|
| 956 | 962 | &mut ASM_TEXT_BUF[..], |
|
| 957 | 963 | &mut ASM_DATA_BUF[..], |
| 1046 | 1052 | else => {} |
|
| 1047 | 1053 | } |
|
| 1048 | 1054 | if let path = codegenOptions.logPath { |
|
| 1049 | 1055 | pkgLog(entryPkg, &["generating code", "(", path, ")", ".."]); |
|
| 1050 | 1056 | } |
|
| 1051 | - | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch { |
|
| 1052 | - | throw error(&["code generation failed: capacity, symbol, relocation, or image layout"]); |
|
| 1057 | + | return try rv64::finishProgram(&mut generator, low.data, storage, asmData, &mut RO_DATA_BUF[..], &mut RW_DATA_BUF[..]) catch err { |
|
| 1058 | + | match err { |
|
| 1059 | + | case rv64::Error::Allocation => throw error(&["code generation workspace exhausted"]), |
|
| 1060 | + | case rv64::Error::Capacity => throw error(&["code generation output capacity exceeded"]), |
|
| 1061 | + | case rv64::Error::Symbol => { |
|
| 1062 | + | for call in &generator.e.pendingCalls[..] { |
|
| 1063 | + | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 1064 | + | io::print("undefined function: "); io::printLn(call.target); |
|
| 1065 | + | } |
|
| 1066 | + | } |
|
| 1067 | + | for jump in &generator.e.pendingJumps[..] { |
|
| 1068 | + | if dict::get(&generator.e.labels.funcs, jump.target) == nil { |
|
| 1069 | + | io::print("undefined assembly target: "); io::printLn(jump.target); |
|
| 1070 | + | } |
|
| 1071 | + | } |
|
| 1072 | + | throw error(&["code generation has an unresolved symbol"]); |
|
| 1073 | + | }, |
|
| 1074 | + | case rv64::Error::Relocation => throw error(&["code generation relocation is out of range"]), |
|
| 1075 | + | case rv64::Error::Image(_) => throw error(&["code generation image layout is invalid"]), |
|
| 1076 | + | case rv64::Error::Data(_) => throw error(&["code generation data layout is invalid"]), |
|
| 1077 | + | } |
|
| 1053 | 1078 | }; |
|
| 1054 | 1079 | } |
|
| 1055 | 1080 | ||
| 1056 | 1081 | /// Source exports selected for one binary RIL package. |
|
| 1057 | 1082 | record PackageExports: Copy { |
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
+64 -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 { continue; } |
|
| 26 | + | if device.memory.end - device.memory.start < 6 { return; } |
|
| 27 | + | for byte in message { |
|
| 28 | + | let mut polls: u32 = 0; |
|
| 29 | + | while (read8(device.memory.start + 5) & 32) == 0 { |
|
| 30 | + | set polls += 1; |
|
| 31 | + | if polls == 1000000 { return; } |
|
| 32 | + | } |
|
| 33 | + | write8(device.memory.start, byte); |
|
| 34 | + | } |
|
| 35 | + | return; |
|
| 36 | + | } |
|
| 37 | + | } |
|
| 38 | + | ||
| 39 | + | /// Initialize one hart from firmware arguments. Return true for the last hart. |
|
| 40 | + | /// Firmware supplies a mapped FDT and a disjoint, reserved stack for each hart. |
|
| 41 | + | export unsafe fn enter(hart: u64, tree: *u8, stackTop: u64) -> bool { |
|
| 42 | + | let treeAddress = tree as u64; |
|
| 43 | + | assert hart < limits::HARTS as u64; |
|
| 44 | + | if hart == 0 { |
|
| 45 | + | assert (treeAddress & 7) == 0 and treeAddress <= 0xffffffffffffffff - 65536; |
|
| 46 | + | let prefix = @sliceOf(tree, 40); |
|
| 47 | + | let size = try! fdt::integer(&prefix[..], 4, 4) as u32; |
|
| 48 | + | assert size >= 40 and size <= 65536; |
|
| 49 | + | let blob = @sliceOf(tree, size); |
|
| 50 | + | try! platform::decode(&blob[..], &mut PLATFORM); |
|
| 51 | + | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 52 | + | assert platform::inRam(&PLATFORM, treeRange); |
|
| 53 | + | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 54 | + | print("kernel: platform ready\n"); |
|
| 55 | + | sync::storeRelease(&mut READY, 1); |
|
| 56 | + | } else { |
|
| 57 | + | while sync::loadAcquire(&READY) == 0 {} |
|
| 58 | + | } |
|
| 59 | + | assert (PLATFORM.harts & (1 << hart as u32)) <> 0; |
|
| 60 | + | assert stackTop == PLATFORM.stacks[hart as u32].end and (stackTop & 15) == 0; |
|
| 61 | + | let mut count: u64 = 0; |
|
| 62 | + | for id in 0..limits::HARTS { if (PLATFORM.harts & (1 << id)) <> 0 { set count += 1; } } |
|
| 63 | + | return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count; |
|
| 64 | + | } |
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
+183 -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 { throw Error::Invalid; } |
|
| 64 | + | if offset > bytes.len or width > bytes.len - offset { throw Error::Truncated; } |
|
| 65 | + | let mut value: u64 = 0; |
|
| 66 | + | for i in 0..width { set value = (value << 8) | bytes[offset + i] as u64; } |
|
| 67 | + | return value; |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | /// Check a block's extent without addition overflow. |
|
| 71 | + | fn block(total: u32, start: u32, size: u32) -> Span throws (Error) { |
|
| 72 | + | if start < 40 or start > total or size > total - start { throw Error::Truncated; } |
|
| 73 | + | return Span { start, end: start + size }; |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Check whether two nonempty spans overlap. |
|
| 77 | + | fn overlaps(a: Span, b: Span) -> bool { |
|
| 78 | + | return a.start < a.end and b.start < b.end and a.start < b.end and b.start < a.end; |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Validate a version-17 header, disjoint blocks, and all memory reservations. |
|
| 82 | + | export fn header(bytes: &[u8]) -> Header throws (Error) { |
|
| 83 | + | if bytes.len < 40 { throw Error::Truncated; } |
|
| 84 | + | if try integer(bytes, 0, 4) <> 0xd00dfeed { throw Error::Invalid; } |
|
| 85 | + | let size = try integer(bytes, 4, 4) as u32; |
|
| 86 | + | if size > bytes.len or size < 40 { throw Error::Truncated; } |
|
| 87 | + | if try integer(bytes, 20, 4) <> 17 or try integer(bytes, 24, 4) > 17 { throw Error::Invalid; } |
|
| 88 | + | let structure = try block(size, try integer(bytes, 8, 4) as u32, try integer(bytes, 36, 4) as u32); |
|
| 89 | + | let strings = try block(size, try integer(bytes, 12, 4) as u32, try integer(bytes, 32, 4) as u32); |
|
| 90 | + | let reserved = try integer(bytes, 16, 4) as u32; |
|
| 91 | + | if (structure.start & 3) <> 0 or (structure.end & 3) <> 0 or (reserved & 7) <> 0 or reserved < 40 { |
|
| 92 | + | throw Error::Invalid; |
|
| 93 | + | } |
|
| 94 | + | let mut end = reserved; |
|
| 95 | + | loop { |
|
| 96 | + | if end > size or size - end < 16 { throw Error::Truncated; } |
|
| 97 | + | let address = try integer(bytes, end, 8); |
|
| 98 | + | let length = try integer(bytes, end + 8, 8); |
|
| 99 | + | if address == 0 and length == 0 { break; } |
|
| 100 | + | if range::new(address, length) == nil { throw Error::Invalid; } |
|
| 101 | + | set end += 16; |
|
| 102 | + | } |
|
| 103 | + | let reservations = Span { start: reserved, end: end + 16 }; |
|
| 104 | + | if overlaps(structure, strings) or overlaps(structure, reservations) or overlaps(strings, reservations) { |
|
| 105 | + | throw Error::Invalid; |
|
| 106 | + | } |
|
| 107 | + | return Header { size, structure, strings, reservations: Span { start: reserved, end } }; |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Start traversal at the first structure token. |
|
| 111 | + | export fn cursor(tree: Header) -> Cursor { |
|
| 112 | + | return Cursor { offset: tree.structure.start, depth: 0, started: false, finished: false, children: 0 }; |
|
| 113 | + | } |
|
| 114 | + | ||
| 115 | + | /// Find a terminated string inside one validated block. |
|
| 116 | + | fn name(bytes: &[u8], start: u32, end: u32) -> Span throws (Error) { |
|
| 117 | + | if end > bytes.len or start >= end { throw Error::Truncated; } |
|
| 118 | + | let mut at = start; |
|
| 119 | + | while at < end { |
|
| 120 | + | if bytes[at] == 0 { return Span { start, end: at }; } |
|
| 121 | + | set at += 1; |
|
| 122 | + | } |
|
| 123 | + | throw Error::Truncated; |
|
| 124 | + | } |
|
| 125 | + | ||
| 126 | + | /// Round an offset to the next token boundary within the structure block. |
|
| 127 | + | fn padded(bytes: &[u8], end: u32, limit: u32) -> u32 throws (Error) { |
|
| 128 | + | let padding = (4 - (end & 3)) & 3; |
|
| 129 | + | if end > limit or padding > limit - end { throw Error::Truncated; } |
|
| 130 | + | for i in end..end + padding { if bytes[i] <> 0 { throw Error::Invalid; } } |
|
| 131 | + | return end + padding; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Read one structure event, skipping NOP tokens. Nesting is bounded to 32 nodes. |
|
| 135 | + | export fn next(bytes: &[u8], tree: Header, state: &mut Cursor) -> Event throws (Error) { |
|
| 136 | + | if state.finished { return Event::End; } |
|
| 137 | + | loop { |
|
| 138 | + | if state.offset > tree.structure.end or tree.structure.end - state.offset < 4 { throw Error::Truncated; } |
|
| 139 | + | let token = try integer(bytes, state.offset, 4); |
|
| 140 | + | set state.offset += 4; |
|
| 141 | + | match token { |
|
| 142 | + | case 1 => { |
|
| 143 | + | if state.depth == 32 { throw Error::Depth; } |
|
| 144 | + | if state.depth == 0 and state.started { throw Error::Invalid; } |
|
| 145 | + | let value = try name(bytes, state.offset, tree.structure.end); |
|
| 146 | + | if state.depth == 0 and value.start <> value.end { throw Error::Invalid; } |
|
| 147 | + | if state.depth > 0 and value.start == value.end { throw Error::Invalid; } |
|
| 148 | + | if state.depth > 0 { set state.children |= 1 << (state.depth - 1); } |
|
| 149 | + | set state.children &= ~(1 << state.depth); |
|
| 150 | + | set state.depth += 1; |
|
| 151 | + | set state.started = true; |
|
| 152 | + | set state.offset = try padded(bytes, value.end + 1, tree.structure.end); |
|
| 153 | + | return Event::Begin(value); |
|
| 154 | + | }, |
|
| 155 | + | case 2 => { |
|
| 156 | + | if state.depth == 0 { throw Error::Invalid; } |
|
| 157 | + | set state.depth -= 1; |
|
| 158 | + | return Event::EndNode; |
|
| 159 | + | }, |
|
| 160 | + | case 3 => { |
|
| 161 | + | if state.depth == 0 or (state.children & (1 << (state.depth - 1))) <> 0 { throw Error::Invalid; } |
|
| 162 | + | if tree.structure.end - state.offset < 8 { throw Error::Truncated; } |
|
| 163 | + | let size = try integer(bytes, state.offset, 4) as u32; |
|
| 164 | + | let offset = try integer(bytes, state.offset + 4, 4) as u32; |
|
| 165 | + | set state.offset += 8; |
|
| 166 | + | if size > tree.structure.end - state.offset { throw Error::Truncated; } |
|
| 167 | + | if offset >= tree.strings.end - tree.strings.start { throw Error::Invalid; } |
|
| 168 | + | let key = try name(bytes, tree.strings.start + offset, tree.strings.end); |
|
| 169 | + | if key.start == key.end { throw Error::Invalid; } |
|
| 170 | + | let value = Span { start: state.offset, end: state.offset + size }; |
|
| 171 | + | set state.offset = try padded(bytes, value.end, tree.structure.end); |
|
| 172 | + | return Event::Property { name: key, value }; |
|
| 173 | + | }, |
|
| 174 | + | case 4 => {}, |
|
| 175 | + | case 9 => { |
|
| 176 | + | if state.depth <> 0 or not state.started or state.offset <> tree.structure.end { throw Error::Invalid; } |
|
| 177 | + | set state.finished = true; |
|
| 178 | + | return Event::End; |
|
| 179 | + | }, |
|
| 180 | + | else => throw Error::Invalid, |
|
| 181 | + | } |
|
| 182 | + | } |
|
| 183 | + | } |
kernel/kernel/platform.rad
added
+270 -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 { return false; } |
|
| 93 | + | for i in 0..value.len { if bytes[span.start + i] <> value[i] { return false; } } |
|
| 94 | + | return true; |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Find an exact string in a zero-terminated property string list. |
|
| 98 | + | fn string(bytes: &[u8], span: fdt::Span, value: &[u8]) -> bool { |
|
| 99 | + | let mut start = span.start; |
|
| 100 | + | for i in span.start..span.end { |
|
| 101 | + | if bytes[i] == 0 { |
|
| 102 | + | if equal(bytes, fdt::Span { start, end: i }, value) { return true; } |
|
| 103 | + | set start = i + 1; |
|
| 104 | + | } |
|
| 105 | + | } |
|
| 106 | + | return false; |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | /// Read a cell count supported by the native 64-bit platform. |
|
| 110 | + | fn scalar(bytes: &[u8], value: fdt::Span) -> u32 throws (Error) { |
|
| 111 | + | if value.end - value.start <> 4 { throw Error::Invalid; } |
|
| 112 | + | let result = try fdt::integer(bytes, value.start, 4) catch err { throw Error::Tree(err); }; |
|
| 113 | + | return result as u32; |
|
| 114 | + | } |
|
| 115 | + | ||
| 116 | + | /// Read a nonempty physical range from one register tuple. |
|
| 117 | + | fn extent(bytes: &[u8], at: u32, addressCells: u32, sizeCells: u32) -> range::Range throws (Error) { |
|
| 118 | + | if addressCells < 1 or addressCells > 2 or sizeCells < 1 or sizeCells > 2 { throw Error::Invalid; } |
|
| 119 | + | let address = try fdt::integer(bytes, at, addressCells * 4) catch err { throw Error::Tree(err); }; |
|
| 120 | + | let size = try fdt::integer(bytes, at + addressCells * 4, sizeCells * 4) catch err { throw Error::Tree(err); }; |
|
| 121 | + | let result = range::new(address, size) else { throw Error::Invalid; }; |
|
| 122 | + | return result; |
|
| 123 | + | } |
|
| 124 | + | ||
| 125 | + | /// Append a reservation. Identical entries from both FDT representations share one slot. |
|
| 126 | + | export fn protect(platform: &mut Platform, value: range::Range) throws (Error) { |
|
| 127 | + | for i in 0..platform.reservedCount { if platform.reserved[i] == value { return; } } |
|
| 128 | + | if platform.reservedCount == RESERVATIONS { throw Error::Capacity; } |
|
| 129 | + | set platform.reserved[platform.reservedCount] = value; |
|
| 130 | + | set platform.reservedCount += 1; |
|
| 131 | + | } |
|
| 132 | + | ||
| 133 | + | /// Test a physical extent for overlap. |
|
| 134 | + | fn overlaps(left: range::Range, right: range::Range) -> bool { |
|
| 135 | + | return left.start < right.end and right.start < left.end; |
|
| 136 | + | } |
|
| 137 | + | ||
| 138 | + | /// Test whether one complete extent belongs to a RAM bank. |
|
| 139 | + | export fn inRam(platform: &Platform, value: range::Range) -> bool { |
|
| 140 | + | for i in 0..platform.ramCount { if range::contains(platform.ram[i], value) { return true; } } |
|
| 141 | + | return false; |
|
| 142 | + | } |
|
| 143 | + | ||
| 144 | + | /// Check whether a nonempty physical extent is RAM with no reserved byte. |
|
| 145 | + | export fn available(platform: &Platform, value: range::Range) -> bool { |
|
| 146 | + | if not inRam(platform, value) { return false; } |
|
| 147 | + | for i in 0..platform.reservedCount { if overlaps(platform.reserved[i], value) { return false; } } |
|
| 148 | + | return true; |
|
| 149 | + | } |
|
| 150 | + | ||
| 151 | + | /// Record resources only after all properties of their node are known. |
|
| 152 | + | fn finish(bytes: &[u8], node: Node, platform: &mut Platform) throws (Error) { |
|
| 153 | + | if not node.enabled { return; } |
|
| 154 | + | let registers = node.registers else { return; }; |
|
| 155 | + | if let typeName = node.deviceType { |
|
| 156 | + | if string(bytes, typeName, &"cpu"[..]) { |
|
| 157 | + | if node.sizeCells <> 0 or node.addressCells < 1 or node.addressCells > 2 |
|
| 158 | + | or registers.end - registers.start <> node.addressCells * 4 { throw Error::Invalid; } |
|
| 159 | + | let hart = try fdt::integer(bytes, registers.start, node.addressCells * 4) catch err { throw Error::Tree(err); }; |
|
| 160 | + | if hart >= limits::HARTS as u64 or (platform.harts & (1 << hart as u32)) <> 0 { throw Error::Invalid; } |
|
| 161 | + | set platform.harts |= 1 << hart as u32; |
|
| 162 | + | return; |
|
| 163 | + | } |
|
| 164 | + | } |
|
| 165 | + | let width = (node.addressCells + node.sizeCells) * 4; |
|
| 166 | + | if width == 0 or (registers.end - registers.start) % width <> 0 { throw Error::Invalid; } |
|
| 167 | + | let mut at = registers.start; |
|
| 168 | + | while at < registers.end { |
|
| 169 | + | let value = try extent(bytes, at, node.addressCells, node.sizeCells); |
|
| 170 | + | if node.reserved { try protect(platform, value); } |
|
| 171 | + | else { |
|
| 172 | + | let mut memory = false; |
|
| 173 | + | if let typeName = node.deviceType { |
|
| 174 | + | set memory = string(bytes, typeName, &"memory"[..]); |
|
| 175 | + | } |
|
| 176 | + | if memory { |
|
| 177 | + | if platform.ramCount == RAM_BANKS { throw Error::Capacity; } |
|
| 178 | + | for i in 0..platform.ramCount { if overlaps(platform.ram[i], value) { throw Error::Invalid; } } |
|
| 179 | + | set platform.ram[platform.ramCount] = value; |
|
| 180 | + | set platform.ramCount += 1; |
|
| 181 | + | } else { |
|
| 182 | + | let mut kind: ?Kind = nil; |
|
| 183 | + | if let names = node.compatible { |
|
| 184 | + | if string(bytes, names, &"ns16550a"[..]) { set kind = Kind::Uart; } |
|
| 185 | + | else if string(bytes, names, &"riscv,clint0"[..]) { set kind = Kind::Clint; } |
|
| 186 | + | else if string(bytes, names, &"riscv,plic0"[..]) { set kind = Kind::Plic; } |
|
| 187 | + | else if string(bytes, names, &"radiant,finish"[..]) { set kind = Kind::Finish; } |
|
| 188 | + | } |
|
| 189 | + | if let deviceKind = kind { |
|
| 190 | + | if platform.deviceCount == limits::DEVICES { throw Error::Capacity; } |
|
| 191 | + | set platform.devices[platform.deviceCount] = Device { kind: deviceKind, memory: value }; |
|
| 192 | + | set platform.deviceCount += 1; |
|
| 193 | + | } |
|
| 194 | + | try protect(platform, value); |
|
| 195 | + | } |
|
| 196 | + | } |
|
| 197 | + | set at += width; |
|
| 198 | + | } |
|
| 199 | + | } |
|
| 200 | + | ||
| 201 | + | /// Extract native platform resources. Discard the output if decoding fails. |
|
| 202 | + | export unsafe fn decode(bytes: &[u8], platform: &mut Platform) throws (Error) { |
|
| 203 | + | set platform.ramCount = 0; set platform.reservedCount = 0; set platform.deviceCount = 0; |
|
| 204 | + | set platform.harts = 0; set platform.timebase = 0; set platform.stackCount = 0; |
|
| 205 | + | let tree = try fdt::header(bytes) catch err { throw Error::Tree(err); }; |
|
| 206 | + | let mut at = tree.reservations.start; |
|
| 207 | + | while at < tree.reservations.end { |
|
| 208 | + | try protect(platform, try extent(bytes, at, 2, 2)); set at += 16; |
|
| 209 | + | } |
|
| 210 | + | let mut nodes: [Node; 32] = undefined; |
|
| 211 | + | let mut state = fdt::cursor(tree); |
|
| 212 | + | loop { |
|
| 213 | + | let event = try fdt::next(bytes, tree, &mut state) catch err { throw Error::Tree(err); }; |
|
| 214 | + | match event { |
|
| 215 | + | case fdt::Event::Begin(name) => { |
|
| 216 | + | let mut addressCells: u32 = 2; |
|
| 217 | + | let mut sizeCells: u32 = 1; |
|
| 218 | + | let mut reserved = false; |
|
| 219 | + | let mut enabled = true; |
|
| 220 | + | if state.depth > 1 { |
|
| 221 | + | let parent = nodes[state.depth - 2]; |
|
| 222 | + | set addressCells = parent.childAddress; set sizeCells = parent.childSize; |
|
| 223 | + | set reserved = parent.reserved; |
|
| 224 | + | set enabled = parent.enabled; |
|
| 225 | + | } |
|
| 226 | + | if state.depth == 2 and equal(bytes, name, &"reserved-memory"[..]) { set reserved = true; } |
|
| 227 | + | set nodes[state.depth - 1] = Node { name, addressCells, sizeCells, childAddress: 2, childSize: 1, |
|
| 228 | + | registers: nil, compatible: nil, deviceType: nil, enabled, reserved }; |
|
| 229 | + | }, |
|
| 230 | + | case fdt::Event::Property { name, value } => { |
|
| 231 | + | if equal(bytes, name, &"#address-cells"[..]) { |
|
| 232 | + | let count = try scalar(bytes, value); |
|
| 233 | + | if count > 2 { throw Error::Invalid; } set nodes[state.depth - 1].childAddress = count; |
|
| 234 | + | } else if equal(bytes, name, &"#size-cells"[..]) { |
|
| 235 | + | let count = try scalar(bytes, value); |
|
| 236 | + | if count > 2 { throw Error::Invalid; } set nodes[state.depth - 1].childSize = count; |
|
| 237 | + | } else if equal(bytes, name, &"reg"[..]) { set nodes[state.depth - 1].registers = value; } |
|
| 238 | + | else if equal(bytes, name, &"compatible"[..]) { set nodes[state.depth - 1].compatible = value; } |
|
| 239 | + | else if equal(bytes, name, &"device_type"[..]) { set nodes[state.depth - 1].deviceType = value; } |
|
| 240 | + | else if equal(bytes, name, &"status"[..]) { |
|
| 241 | + | set nodes[state.depth - 1].enabled = nodes[state.depth - 1].enabled and (string(bytes, value, &"okay"[..]) or string(bytes, value, &"ok"[..])); |
|
| 242 | + | } else if equal(bytes, name, &"ranges"[..]) and value.start <> value.end { throw Error::Invalid; } |
|
| 243 | + | else if state.depth == 2 and equal(bytes, nodes[state.depth - 1].name, &"cpus"[..]) and equal(bytes, name, &"timebase-frequency"[..]) { |
|
| 244 | + | set platform.timebase = try scalar(bytes, value); |
|
| 245 | + | } else if state.depth == 2 and equal(bytes, nodes[state.depth - 1].name, &"chosen"[..]) and equal(bytes, name, &"radiance,kernel-stacks"[..]) { |
|
| 246 | + | let size = value.end - value.start; |
|
| 247 | + | if size % 16 <> 0 or size / 16 > limits::HARTS { throw Error::Invalid; } |
|
| 248 | + | set platform.stackCount = size / 16; |
|
| 249 | + | for i in 0..platform.stackCount { |
|
| 250 | + | let stack = try extent(bytes, value.start + i * 16, 2, 2); |
|
| 251 | + | if (stack.start & 15) <> 0 or (stack.end & 15) <> 0 { throw Error::Invalid; } |
|
| 252 | + | set platform.stacks[i] = stack; try protect(platform, stack); |
|
| 253 | + | } |
|
| 254 | + | } |
|
| 255 | + | }, |
|
| 256 | + | case fdt::Event::EndNode => try finish(bytes, nodes[state.depth], platform), |
|
| 257 | + | case fdt::Event::End => break, |
|
| 258 | + | } |
|
| 259 | + | } |
|
| 260 | + | if platform.ramCount == 0 or platform.harts == 0 or platform.timebase == 0 { throw Error::Invalid; } |
|
| 261 | + | for hart in 0..limits::HARTS { |
|
| 262 | + | if (platform.harts & (1 << hart)) == 0 { continue; } |
|
| 263 | + | if hart >= platform.stackCount or not inRam(platform, platform.stacks[hart]) { throw Error::Invalid; } |
|
| 264 | + | for other in 0..hart { |
|
| 265 | + | if (platform.harts & (1 << other)) <> 0 and overlaps(platform.stacks[hart], platform.stacks[other]) { |
|
| 266 | + | throw Error::Invalid; |
|
| 267 | + | } |
|
| 268 | + | } |
|
| 269 | + | } |
|
| 270 | + | } |
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
+126 -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 { set bytes[offset + i] = (value >> ((width - i - 1) * 8) as u64) as u8; } |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Build one root property and one high-address memory reservation. |
|
| 12 | + | fn fixture(bytes: &mut [u8]) { |
|
| 13 | + | for i in 0..bytes.len { set bytes[i] = 0; } |
|
| 14 | + | put(bytes, 0, 4, 0xd00dfeed); put(bytes, 4, 4, 108); |
|
| 15 | + | put(bytes, 8, 4, 72); put(bytes, 12, 4, 104); put(bytes, 16, 4, 40); |
|
| 16 | + | put(bytes, 20, 4, 17); put(bytes, 24, 4, 16); |
|
| 17 | + | put(bytes, 32, 4, 4); put(bytes, 36, 4, 32); |
|
| 18 | + | put(bytes, 40, 8, 0x80000000); put(bytes, 48, 8, 4096); |
|
| 19 | + | put(bytes, 72, 4, 1); |
|
| 20 | + | put(bytes, 80, 4, 3); put(bytes, 84, 4, 4); put(bytes, 88, 4, 0); |
|
| 21 | + | put(bytes, 92, 4, 42); put(bytes, 96, 4, 2); put(bytes, 100, 4, 9); |
|
| 22 | + | set bytes[104] = 'r'; set bytes[105] = 'e'; set bytes[106] = 'g'; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Validate the complete tree, including the final structure token. |
|
| 26 | + | fn validate(bytes: &[u8]) throws (fdt::Error) { |
|
| 27 | + | let tree = try fdt::header(bytes); |
|
| 28 | + | let mut state = fdt::cursor(tree); |
|
| 29 | + | loop { |
|
| 30 | + | match try fdt::next(bytes, tree, &mut state) { |
|
| 31 | + | case fdt::Event::End => return, |
|
| 32 | + | else => {}, |
|
| 33 | + | } |
|
| 34 | + | } |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Require malformed input to fail through a bounded decoder error. |
|
| 38 | + | fn rejects(bytes: &[u8]) throws (testing::TestError) { |
|
| 39 | + | let mut failed = false; |
|
| 40 | + | try validate(bytes) catch { set failed = true; }; |
|
| 41 | + | try testing::expect(failed); |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Decode big-endian ranges and the ordered structure events. |
|
| 45 | + | @test unsafe fn traversal() throws (testing::TestError) { |
|
| 46 | + | let mut bytes: [u8; 108] = undefined; |
|
| 47 | + | fixture(&mut bytes[..]); |
|
| 48 | + | let tree = try! fdt::header(&bytes[..]); |
|
| 49 | + | try testing::expect(tree.reservations.start == 40 and tree.reservations.end == 56); |
|
| 50 | + | try testing::expect(try! fdt::integer(&bytes[..], 40, 8) == 0x80000000); |
|
| 51 | + | let mut state = fdt::cursor(tree); |
|
| 52 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 53 | + | case fdt::Event::Begin(name) => try testing::expect(name.start == name.end), |
|
| 54 | + | else => try testing::expect(false), |
|
| 55 | + | } |
|
| 56 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 57 | + | case fdt::Event::Property { name, value } => { |
|
| 58 | + | try testing::expect(name.end - name.start == 3 and bytes[name.start] == 'r'); |
|
| 59 | + | try testing::expect(value.end - value.start == 4); |
|
| 60 | + | try testing::expect(try! fdt::integer(&bytes[..], value.start, 4) == 42); |
|
| 61 | + | }, |
|
| 62 | + | else => try testing::expect(false), |
|
| 63 | + | } |
|
| 64 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 65 | + | case fdt::Event::EndNode => {}, else => try testing::expect(false), |
|
| 66 | + | } |
|
| 67 | + | match try! fdt::next(&bytes[..], tree, &mut state) { |
|
| 68 | + | case fdt::Event::End => {}, else => try testing::expect(false), |
|
| 69 | + | } |
|
| 70 | + | try testing::expect(state.finished and state.depth == 0); |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Reject every truncated prefix and invalid block or reservation extents. |
|
| 74 | + | @test unsafe fn malformedHeader() throws (testing::TestError) { |
|
| 75 | + | let mut bytes: [u8; 108] = undefined; |
|
| 76 | + | fixture(&mut bytes[..]); |
|
| 77 | + | for size in 0..bytes.len { try rejects(&bytes[..size]); } |
|
| 78 | + | for offset in &[0 as u32, 4, 8, 12, 16, 20, 24, 32, 36] { |
|
| 79 | + | fixture(&mut bytes[..]); put(&mut bytes[..], offset, 4, 0xffffffff); |
|
| 80 | + | try rejects(&bytes[..]); |
|
| 81 | + | } |
|
| 82 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 12, 4, 80); |
|
| 83 | + | try rejects(&bytes[..]); |
|
| 84 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 40, 8, 0xfffffffffffff800); |
|
| 85 | + | try rejects(&bytes[..]); |
|
| 86 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 48, 8, 0); |
|
| 87 | + | try rejects(&bytes[..]); |
|
| 88 | + | } |
|
| 89 | + | ||
| 90 | + | /// Reject missing names, oversized properties, bad tokens, and unbalanced nodes. |
|
| 91 | + | @test unsafe fn malformedStructure() throws (testing::TestError) { |
|
| 92 | + | let mut bytes: [u8; 108] = undefined; |
|
| 93 | + | for offset in &[72 as u32, 80, 84, 88, 96, 100] { |
|
| 94 | + | fixture(&mut bytes[..]); put(&mut bytes[..], offset, 4, 0xffffffff); |
|
| 95 | + | try rejects(&bytes[..]); |
|
| 96 | + | } |
|
| 97 | + | fixture(&mut bytes[..]); set bytes[107] = 1; |
|
| 98 | + | try rejects(&bytes[..]); |
|
| 99 | + | fixture(&mut bytes[..]); set bytes[77] = 1; |
|
| 100 | + | try rejects(&bytes[..]); |
|
| 101 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 96, 4, 4); |
|
| 102 | + | try rejects(&bytes[..]); |
|
| 103 | + | fixture(&mut bytes[..]); put(&mut bytes[..], 80, 4, 2); |
|
| 104 | + | try rejects(&bytes[..]); |
|
| 105 | + | } |
|
| 106 | + | ||
| 107 | + | /// Excessive nesting fails before any shift or stack index exceeds its bound. |
|
| 108 | + | @test unsafe fn depthLimit() throws (testing::TestError) { |
|
| 109 | + | let mut bytes: [u8; 512] = undefined; |
|
| 110 | + | fixture(&mut bytes[..]); |
|
| 111 | + | put(&mut bytes[..], 4, 4, 512); put(&mut bytes[..], 12, 4, 512); |
|
| 112 | + | put(&mut bytes[..], 32, 4, 0); put(&mut bytes[..], 36, 4, 440); |
|
| 113 | + | for i in 72..512 { set bytes[i] = 0; } |
|
| 114 | + | for i in 0..33 { |
|
| 115 | + | put(&mut bytes[..], 72 + i * 8, 4, 1); |
|
| 116 | + | if i > 0 { set bytes[76 + i * 8] = 'x'; } |
|
| 117 | + | } |
|
| 118 | + | let tree = try! fdt::header(&bytes[..]); |
|
| 119 | + | let mut state = fdt::cursor(tree); |
|
| 120 | + | for i in 0..32 { let event = try! fdt::next(&bytes[..], tree, &mut state); } |
|
| 121 | + | let mut failed = false; |
|
| 122 | + | try fdt::next(&bytes[..], tree, &mut state) catch err { |
|
| 123 | + | try testing::expect(err == fdt::Error::Depth); set failed = true; |
|
| 124 | + | }; |
|
| 125 | + | try testing::expect(failed); |
|
| 126 | + | } |
kernel/kernel/tests/platform.rad
added
+130 -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 { set f.bytes[offset + i] = (value >> ((width - i - 1) * 8) as u64) as u8; } |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Append one structure token. |
|
| 25 | + | fn word(f: &mut Fixture, value: u32) { |
|
| 26 | + | let offset = f.offset; put(f, offset, 4, value as u64); set f.offset += 4; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | /// Append a node name and its terminator. |
|
| 30 | + | fn begin(f: &mut Fixture, name: &[u8]) { |
|
| 31 | + | word(f, 1); |
|
| 32 | + | for i in 0..name.len { set f.bytes[f.offset + i] = name[i]; } |
|
| 33 | + | set f.offset = (f.offset + name.len + 4) & ~3; |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Append a property header and allocate its zeroed payload. |
|
| 37 | + | fn property(f: &mut Fixture, name: &[u8], size: u32) -> u32 { |
|
| 38 | + | let nameOffset = f.strings - 3072; |
|
| 39 | + | word(f, 3); word(f, size); word(f, nameOffset); |
|
| 40 | + | for i in 0..name.len { set f.bytes[f.strings + i] = name[i]; } |
|
| 41 | + | set f.strings += name.len + 1; |
|
| 42 | + | let offset = f.offset; |
|
| 43 | + | set f.offset = (f.offset + size + 3) & ~3; |
|
| 44 | + | return offset; |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Append a one-cell property. |
|
| 48 | + | fn scalar(f: &mut Fixture, name: &[u8], value: u32) { |
|
| 49 | + | let offset = property(f, name, 4); put(f, offset, 4, value as u64); |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Append a zero-terminated string property. |
|
| 53 | + | fn string(f: &mut Fixture, name: &[u8], value: &[u8]) { |
|
| 54 | + | let offset = property(f, name, value.len + 1); |
|
| 55 | + | for i in 0..value.len { set f.bytes[offset + i] = value[i]; } |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Append a two-address-cell, two-size-cell range. |
|
| 59 | + | fn region(f: &mut Fixture, name: &[u8], start: u64, size: u64) -> u32 { |
|
| 60 | + | let offset = property(f, name, 16); put(f, offset, 8, start); put(f, offset + 8, 8, size); |
|
| 61 | + | return offset; |
|
| 62 | + | } |
|
| 63 | + | ||
| 64 | + | /// Construct the supported one-hart boot contract. |
|
| 65 | + | fn build(f: &mut Fixture) { |
|
| 66 | + | for i in 0..f.bytes.len { set f.bytes[i] = 0; } |
|
| 67 | + | set f.offset = 56; set f.strings = 3072; |
|
| 68 | + | begin(f, &""[..]); |
|
| 69 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 70 | + | begin(f, &"chosen"[..]); |
|
| 71 | + | set f.stack = region(f, &"radiance,kernel-stacks"[..], 0x80008000, 0x2000); |
|
| 72 | + | word(f, 2); |
|
| 73 | + | begin(f, &"memory@80000000"[..]); |
|
| 74 | + | string(f, &"device_type"[..], &"memory"[..]); |
|
| 75 | + | let ram = region(f, &"reg"[..], 0x80000000, 0x10000); word(f, 2); |
|
| 76 | + | begin(f, &"cpus"[..]); |
|
| 77 | + | scalar(f, &"#address-cells"[..], 1); scalar(f, &"#size-cells"[..], 0); |
|
| 78 | + | scalar(f, &"timebase-frequency"[..], 10000000); |
|
| 79 | + | begin(f, &"cpu@0"[..]); string(f, &"device_type"[..], &"cpu"[..]); |
|
| 80 | + | scalar(f, &"reg"[..], 0); word(f, 2); word(f, 2); |
|
| 81 | + | begin(f, &"reserved-memory"[..]); |
|
| 82 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 83 | + | let ranges = property(f, &"ranges"[..], 0); |
|
| 84 | + | begin(f, &"image@80002000"[..]); |
|
| 85 | + | let image = region(f, &"reg"[..], 0x80002000, 0x3000); word(f, 2); word(f, 2); |
|
| 86 | + | begin(f, &"soc"[..]); |
|
| 87 | + | scalar(f, &"#address-cells"[..], 2); scalar(f, &"#size-cells"[..], 2); |
|
| 88 | + | let bus = property(f, &"ranges"[..], 0); |
|
| 89 | + | begin(f, &"uart@10000000"[..]); |
|
| 90 | + | string(f, &"compatible"[..], &"ns16550a"[..]); |
|
| 91 | + | let uart = region(f, &"reg"[..], 0x10000000, 0x100); word(f, 2); word(f, 2); |
|
| 92 | + | word(f, 2); word(f, 9); |
|
| 93 | + | let total = f.strings; let size = f.offset - 56; |
|
| 94 | + | put(f, 0, 4, 0xd00dfeed); put(f, 4, 4, total as u64); |
|
| 95 | + | put(f, 8, 4, 56); put(f, 12, 4, 3072); put(f, 16, 4, 40); |
|
| 96 | + | put(f, 20, 4, 17); put(f, 24, 4, 16); |
|
| 97 | + | put(f, 32, 4, (total - 3072) as u64); put(f, 36, 4, size as u64); |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Resources and their reservations come from the tree rather than fixed addresses. |
|
| 101 | + | @test unsafe fn discovery() throws (testing::TestError) { |
|
| 102 | + | let mut f: Fixture = undefined; build(&mut f); |
|
| 103 | + | let mut p: platform::Platform = undefined; |
|
| 104 | + | try! platform::decode(&f.bytes[..f.strings], &mut p); |
|
| 105 | + | try testing::expect(p.ramCount == 1 and p.harts == 1 and p.timebase == 10000000); |
|
| 106 | + | try testing::expect(p.deviceCount == 1 and p.devices[0].kind == platform::Kind::Uart); |
|
| 107 | + | try testing::expect(p.devices[0].memory.start == 0x10000000); |
|
| 108 | + | try testing::expect(p.stackCount == 1 and p.stacks[0].end == 0x8000a000); |
|
| 109 | + | try testing::expect(p.reservedCount == 3); |
|
| 110 | + | for i in 0..16 { |
|
| 111 | + | let page = range::new(0x80000000 + i as u64 * 4096, 4096) else panic "valid page"; |
|
| 112 | + | let free = i < 2 or (i >= 5 and i < 8) or i >= 10; |
|
| 113 | + | try testing::expect(platform::available(&p, page) == free); |
|
| 114 | + | } |
|
| 115 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x7ffff000, end: 0x80001000 })); |
|
| 116 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x80001000, end: 0x80003000 })); |
|
| 117 | + | try testing::expect(not platform::available(&p, range::Range { start: 0x80010000, end: 0x80011000 })); |
|
| 118 | + | } |
|
| 119 | + | ||
| 120 | + | /// Misaligned, overflowing, and non-RAM kernel stacks cannot become boot state. |
|
| 121 | + | @test unsafe fn invalidStacks() throws (testing::TestError) { |
|
| 122 | + | let mut f: Fixture = undefined; |
|
| 123 | + | let mut p: platform::Platform = undefined; |
|
| 124 | + | for start in &[0x80008001 as u64, 0x70000000, 0xfffffffffffff000] { |
|
| 125 | + | build(&mut f); let stack = f.stack; put(&mut f, stack, 8, start); |
|
| 126 | + | let mut failed = false; |
|
| 127 | + | try platform::decode(&f.bytes[..f.strings], &mut p) catch { set failed = true; }; |
|
| 128 | + | try testing::expect(failed); |
|
| 129 | + | } |
|
| 130 | + | } |
kernel/tools/build.rad
added
+79 -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 { rv64::generateFunction(&mut generator, func, &mut scratch); alloc::reset(&mut scratch); } |
|
| 63 | + | for call in &generator.e.pendingCalls[..] { |
|
| 64 | + | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 65 | + | io::print("undefined kernel function: "); io::printLn(call.target); return 1; |
|
| 66 | + | } |
|
| 67 | + | } |
|
| 68 | + | let output = try! rv64::finishProgram(&mut generator, package.program.data, |
|
| 69 | + | rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 70 | + | &[], &mut RO[..], &mut RW[..]); |
|
| 71 | + | let header = try! image::header(output.layout); |
|
| 72 | + | let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 73 | + | assert fd >= 0; |
|
| 74 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4)) |
|
| 75 | + | and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]); |
|
| 76 | + | let closed = unix::close(fd) == 0; |
|
| 77 | + | assert written and closed; |
|
| 78 | + | return 0; |
|
| 79 | + | } |
lib/std/arch/rv64.rad
+2 -2
| 110 | 110 | export constant INSTR_SIZE: i32 = 4; |
|
| 111 | 111 | /// Stack alignment requirement in bytes. |
|
| 112 | 112 | export constant STACK_ALIGNMENT: i32 = 16; |
|
| 113 | 113 | ||
| 114 | 114 | /// Minimum blit size (in bytes) to use a loop instead of inline copy. |
|
| 115 | - | /// Blits below this threshold are fully unrolled as LD/SD pairs. |
|
| 116 | - | export constant BLIT_LOOP_THRESHOLD: i32 = 256; |
|
| 115 | + | /// Blits below this threshold use unrolled byte loads and stores. |
|
| 116 | + | export constant BLIT_LOOP_THRESHOLD: i32 = 33; |
|
| 117 | 117 | ||
| 118 | 118 | ///////////////////////// |
|
| 119 | 119 | // Codegen Allocation // |
|
| 120 | 120 | ///////////////////////// |
|
| 121 | 121 |
lib/std/arch/rv64/isel.rad
+31 -124
| 461 | 461 | else => |
|
| 462 | 462 | panic "selectInstr: invalid reserve operand", |
|
| 463 | 463 | } |
|
| 464 | 464 | }, |
|
| 465 | 465 | case il::Instr::Blit { dst, src, size } => { |
|
| 466 | - | let case il::Val::Imm(staticSize) = size |
|
| 467 | - | else panic "selectInstr: blit requires immediate size"; |
|
| 468 | - | ||
| 469 | - | let bothSpilled = regalloc::spill::isSpilled(&s.ralloc.spill, dst) |
|
| 470 | - | and regalloc::spill::isSpilled(&s.ralloc.spill, src); |
|
| 471 | - | ||
| 472 | - | // When both are spilled, offsets must fit 12-bit immediates |
|
| 473 | - | // since we can't advance base registers (they live in spill |
|
| 474 | - | // slots, not real registers we can mutate). |
|
| 475 | - | assert not (bothSpilled and staticSize as i32 > super::MAX_IMM), "selectInstr: blit both-spilled with large size"; |
|
| 476 | - | ||
| 477 | - | // Resolve dst/src base registers. |
|
| 478 | - | let mut rdst = super::SCRATCH2; |
|
| 479 | - | let mut rsrc = super::SCRATCH1; |
|
| 480 | - | let mut srcReload: ?i32 = nil; |
|
| 481 | - | ||
| 482 | - | if bothSpilled { |
|
| 483 | - | let dstSlot = regalloc::spill::spillSlot(&s.ralloc.spill, dst) else { |
|
| 484 | - | panic "selectInstr: blit dst not spilled"; |
|
| 485 | - | }; |
|
| 486 | - | let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) else { |
|
| 487 | - | panic "selectInstr: blit src not spilled"; |
|
| 488 | - | }; |
|
| 489 | - | emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot)); |
|
| 490 | - | set srcReload = spillOffset(s, srcSlot); |
|
| 491 | - | } else { |
|
| 492 | - | set rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 493 | - | set rsrc = getSrcReg(s, src, super::SCRATCH2); |
|
| 494 | - | } |
|
| 495 | - | let mut offset: i32 = 0; |
|
| 496 | - | let mut remaining = staticSize as i32; |
|
| 497 | - | ||
| 498 | - | // For large blits where both pointers are in real registers, |
|
| 499 | - | // use an inline loop instead of unrolled LD/SD pairs. |
|
| 500 | - | let dwordBytes = remaining & ~(super::DWORD_SIZE - 1); |
|
| 501 | - | let canLoop = not bothSpilled |
|
| 502 | - | and *rsrc <> *super::SCRATCH1 and *rsrc <> *super::SCRATCH2 |
|
| 503 | - | and *rdst <> *super::SCRATCH1 and *rdst <> *super::SCRATCH2; |
|
| 504 | - | ||
| 505 | - | if canLoop and dwordBytes >= super::BLIT_LOOP_THRESHOLD { |
|
| 506 | - | emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes); |
|
| 507 | - | ||
| 508 | - | let loopStart = s.e.codeLen; |
|
| 509 | - | ||
| 510 | - | emit::emitLd(s.e, super::SCRATCH2, rsrc, 0); |
|
| 511 | - | emit::emitSd(s.e, super::SCRATCH2, rdst, 0); |
|
| 512 | - | emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE)); |
|
| 513 | - | ||
| 514 | - | if *rdst <> *rsrc { |
|
| 515 | - | emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE)); |
|
| 516 | - | } |
|
| 517 | - | let brOff = (loopStart as i32 - s.e.codeLen as i32) * super::INSTR_SIZE; |
|
| 518 | - | ||
| 519 | - | emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff)); |
|
| 520 | - | set remaining -= dwordBytes; |
|
| 466 | + | let case il::Val::Imm(staticSize) = size else { |
|
| 467 | + | set s.e.error = super::Error::Capacity; return; |
|
| 468 | + | }; |
|
| 469 | + | if staticSize < 0 or staticSize > 0x7fffffff { |
|
| 470 | + | set s.e.error = super::Error::Capacity; return; |
|
| 521 | 471 | } |
|
| 522 | - | ||
| 523 | - | // Copy remaining: 8 bytes, then 4 bytes, then 1 byte at a time. |
|
| 524 | - | // Before each load/store pair, check whether the offset is |
|
| 525 | - | // about to exceed the 12-bit signed immediate range. When |
|
| 526 | - | // it does, advance the base registers by the accumulated |
|
| 527 | - | // offset and reset to zero. |
|
| 528 | - | while remaining >= super::DWORD_SIZE { |
|
| 529 | - | if offset > super::MAX_IMM - super::DWORD_SIZE { |
|
| 530 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 531 | - | if *rdst <> *rsrc { |
|
| 532 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 533 | - | } |
|
| 534 | - | set offset = 0; |
|
| 535 | - | } |
|
| 536 | - | if let off = srcReload { |
|
| 537 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 538 | - | emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 539 | - | } else { |
|
| 540 | - | emit::emitLd(s.e, super::SCRATCH1, rsrc, offset); |
|
| 541 | - | } |
|
| 542 | - | emit::emitSd(s.e, super::SCRATCH1, rdst, offset); |
|
| 543 | - | set offset += super::DWORD_SIZE; |
|
| 544 | - | set remaining -= super::DWORD_SIZE; |
|
| 545 | - | } |
|
| 546 | - | if remaining >= super::WORD_SIZE { |
|
| 547 | - | if offset > super::MAX_IMM - super::WORD_SIZE { |
|
| 548 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 549 | - | if *rdst <> *rsrc { |
|
| 550 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 551 | - | } |
|
| 552 | - | set offset = 0; |
|
| 553 | - | } |
|
| 554 | - | if let off = srcReload { |
|
| 555 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 556 | - | emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 557 | - | } else { |
|
| 558 | - | emit::emitLw(s.e, super::SCRATCH1, rsrc, offset); |
|
| 559 | - | } |
|
| 560 | - | emit::emitSw(s.e, super::SCRATCH1, rdst, offset); |
|
| 561 | - | set offset += super::WORD_SIZE; |
|
| 562 | - | set remaining -= super::WORD_SIZE; |
|
| 563 | - | } |
|
| 564 | - | while remaining > 0 { |
|
| 565 | - | if offset > super::MAX_IMM - 1 { |
|
| 566 | - | emit::emitAddImm(s.e, rsrc, rsrc, offset); |
|
| 567 | - | if *rdst <> *rsrc { |
|
| 568 | - | emit::emitAddImm(s.e, rdst, rdst, offset); |
|
| 569 | - | } |
|
| 570 | - | set offset = 0; |
|
| 571 | - | } |
|
| 572 | - | if let off = srcReload { |
|
| 573 | - | emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off); |
|
| 574 | - | emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset); |
|
| 575 | - | } else { |
|
| 576 | - | emit::emitLb(s.e, super::SCRATCH1, rsrc, offset); |
|
| 577 | - | } |
|
| 578 | - | emit::emitSb(s.e, super::SCRATCH1, rdst, offset); |
|
| 579 | - | set offset += 1; |
|
| 580 | - | set remaining -= 1; |
|
| 581 | - | } |
|
| 582 | - | // Restore base registers if they were advanced (never happens |
|
| 583 | - | // in the both-spilled case since size <= MAX_IMM). |
|
| 584 | - | if not bothSpilled { |
|
| 585 | - | let advanced = staticSize as i32 - offset; |
|
| 586 | - | if advanced <> 0 { |
|
| 587 | - | emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced); |
|
| 588 | - | if *rdst <> *rsrc { |
|
| 589 | - | emit::emitAddImm(s.e, rdst, rdst, 0 - advanced); |
|
| 590 | - | } |
|
| 472 | + | if staticSize == 0 { return; } |
|
| 473 | + | let rdst = getSrcReg(s, dst, super::SCRATCH2); |
|
| 474 | + | let rsrc = getSrcReg(s, src, super::SCRATCH1); |
|
| 475 | + | // Blit addresses have byte alignment. Small copies need no loop state. |
|
| 476 | + | if staticSize < super::BLIT_LOOP_THRESHOLD as i64 { |
|
| 477 | + | for offset in 0..staticSize as u32 { |
|
| 478 | + | emit::emitLb(s.e, super::ADDR_SCRATCH, rsrc, offset as i32); |
|
| 479 | + | emit::emitSb(s.e, super::ADDR_SCRATCH, rdst, offset as i32); |
|
| 591 | 480 | } |
|
| 481 | + | } else { |
|
| 482 | + | // Private cursors preserve both input pointers. Save the count |
|
| 483 | + | // register because it can hold a live allocated value. |
|
| 484 | + | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, rsrc, 0)); |
|
| 485 | + | emit::emit(s.e, encode::addi(super::SCRATCH2, rdst, 0)); |
|
| 486 | + | emit::emit(s.e, encode::addi(super::SP, super::SP, -16)); |
|
| 487 | + | emit::emitSd(s.e, super::T3, super::SP, 0); |
|
| 488 | + | emit::loadImm(s.e, super::T3, staticSize); |
|
| 489 | + | let start = s.e.codeLen; |
|
| 490 | + | emit::emitLb(s.e, super::SCRATCH1, super::ADDR_SCRATCH, 0); |
|
| 491 | + | emit::emitSb(s.e, super::SCRATCH1, super::SCRATCH2, 0); |
|
| 492 | + | emit::emit(s.e, encode::addi(super::ADDR_SCRATCH, super::ADDR_SCRATCH, 1)); |
|
| 493 | + | emit::emit(s.e, encode::addi(super::SCRATCH2, super::SCRATCH2, 1)); |
|
| 494 | + | emit::emit(s.e, encode::addi(super::T3, super::T3, -1)); |
|
| 495 | + | let offset = (start as i32 - s.e.codeLen as i32) * super::INSTR_SIZE; |
|
| 496 | + | emit::emit(s.e, encode::bne(super::T3, super::ZERO, offset)); |
|
| 497 | + | emit::emitLd(s.e, super::T3, super::SP, 0); |
|
| 498 | + | emit::emit(s.e, encode::addi(super::SP, super::SP, 16)); |
|
| 592 | 499 | } |
|
| 593 | 500 | }, |
|
| 594 | 501 | case il::Instr::Zext { typ, dst, val } => { |
|
| 595 | 502 | let rd = getDstReg(s, dst, super::SCRATCH1); |
|
| 596 | 503 | let rs = resolveVal(s, super::SCRATCH1, val); |
lib/std/lang/resolver.rad
+1 -0
| 4472 | 4472 | ||
| 4473 | 4473 | /// Analyze a `use` statement and create a symbol for the imported module. |
|
| 4474 | 4474 | unsafe fn resolveUse(self: &mut Resolver, node: *ast::Node, decl: ast::Use) -> Type |
|
| 4475 | 4475 | throws (ResolveError) |
|
| 4476 | 4476 | { |
|
| 4477 | + | if not shouldAnalyzeModule(self, decl.attrs) { return Type::Void; } |
|
| 4477 | 4478 | let resolved = try resolveModulePath(self, decl.path); |
|
| 4478 | 4479 | let attrMask = resolveAttributes(self, decl.attrs); |
|
| 4479 | 4480 | ||
| 4480 | 4481 | if decl.wildcard { |
|
| 4481 | 4482 | // Import all public symbols from the target module. |
lib/std/lang/resolver/tests.rad
+12 -0
| 6341 | 6341 | @test unsafe fn testLocalReferenceDeclarationContext() throws (testing::TestError) { |
|
| 6342 | 6342 | let mut a = testResolver(); |
|
| 6343 | 6343 | let result = try resolveProgramStr(&mut a, "let n: u32 = 1; let p: &u32 = &n;"); |
|
| 6344 | 6344 | try expectErrorKind(&result, super::ErrorKind::InvalidRefPosition); |
|
| 6345 | 6345 | } |
|
| 6346 | + | ||
| 6347 | + | /// Test-only imports do not require their modules in a normal build. |
|
| 6348 | + | @test unsafe fn testConditionalUse() throws (testing::TestError) { |
|
| 6349 | + | let mut a = testResolver(); |
|
| 6350 | + | set a.config.buildTest = false; |
|
| 6351 | + | let source = "@test use missing::testing; fn value() -> u32 { return 1; }"; |
|
| 6352 | + | let result = try resolveProgramStr(&mut a, source); |
|
| 6353 | + | try expectNoErrors(&result); |
|
| 6354 | + | set a.config.buildTest = true; |
|
| 6355 | + | let enabled = try resolveProgramStr(&mut a, source); |
|
| 6356 | + | try testing::expect(enabled.diagnostics.errors.len > 0); |
|
| 6357 | + | } |
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' |