kernel: Boot from platform data

3661ec1fdb56529e0a301a0b1380fbd9bf158921e5c6365db01eaebeb6c4205b
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 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'