kernel: Boot from platform data

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