kernel: discover bounded firmware platform resources
d88ae1a155a416b4e2052aa8ed816884ae65d499a7065c2db12f37676d78274c
Verified: make -C kernel check; hosted firmware cases and machine entry pass.
1 parent
1f842c56
kernel/Makefile
+7 -5
| 1 | 1 | # Freestanding kernel and hosted mechanism checks. |
|
| 2 | 2 | EMU ?= $(or $(RAD_EMULATOR),emulator) |
|
| 3 | 3 | HOST_EMU ?= $(EMU) |
|
| 4 | 4 | COMPILER := ../bin/radiance.rv64.dev |
|
| 5 | 5 | COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER) |
|
| 6 | - | MODULES := |
|
| 6 | + | MODULES := core/fdt.rad core/platform.rad |
|
| 7 | + | CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES)) |
|
| 8 | + | CHECK_MODULES := check/boot.rad check/fixture.rad |
|
| 7 | 9 | ||
| 8 | 10 | .PHONY: all check clean compiler-check |
|
| 9 | 11 | all: kernel.rv64 |
|
| 10 | 12 | ||
| 11 | 13 | compiler-check: |
|
| 12 | 14 | ||
| 13 | 15 | $(COMPILER): compiler-check |
|
| 14 | 16 | $(MAKE) -C .. RAD_EMULATOR=$(abspath $(shell command -v $(HOST_EMU))) |
|
| 15 | 17 | ||
| 16 | - | kernel.rv64: main.rad arch/entry.ras $(MODULES) $(COMPILER) |
|
| 17 | - | $(COMPILE) -pkg kernel -start arch/entry.ras -mod main.rad $(addprefix -mod ,$(MODULES)) -o $@ |
|
| 18 | + | kernel.rv64: main.rad arch/entry.ras core.rad $(MODULES) $(COMPILER) |
|
| 19 | + | $(COMPILE) $(CORE) -pkg kernel -start arch/entry.ras -mod main.rad -entry kernel -o $@ |
|
| 18 | 20 | ||
| 19 | - | check.rv64: check.rad $(MODULES) $(COMPILER) |
|
| 20 | - | $(COMPILE) -pkg kernel -mod check.rad $(addprefix -mod ,$(MODULES)) -o $@ |
|
| 21 | + | check.rv64: check.rad core.rad $(MODULES) $(CHECK_MODULES) $(COMPILER) |
|
| 22 | + | $(COMPILE) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@ |
|
| 21 | 23 | ||
| 22 | 24 | check: all check.rv64 |
|
| 23 | 25 | $(HOST_EMU) -run check.rv64 |
|
| 24 | 26 | $(EMU) -machine -no-guard-stack -max-steps=1000000 -count-instructions -run kernel.rv64 |
|
| 25 | 27 |
kernel/NOTES.md
+13 -2
| 1 | 1 | # Kernel implementation decisions |
|
| 2 | 2 | ||
| 3 | 3 | The specification at https://radiant.computer/system/kernel takes precedence |
|
| 4 | 4 | for fixed call numbers, handle layout, rights, and object behavior. These notes |
|
| 5 | - | record the contracts established through step 1 of the 22-step plan. |
|
| 5 | + | record the contracts established through step 2 of the 22-step plan. |
|
| 6 | 6 | ||
| 7 | 7 | ## Source and trust boundary |
|
| 8 | 8 | ||
| 9 | 9 | - Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register |
|
| 10 | 10 | state, atomics, and MMIO. Hosted checks exercise the same mechanism modules. |
| 20 | 20 | immutable; writable image state is an instance resource. |
|
| 21 | 21 | - The reference machine uses RAD0 and the sibling emulator, not an assumed |
|
| 22 | 22 | QEMU `virt` memory map. Emulator execution is not evidence of real-hardware |
|
| 23 | 23 | cache behavior or timing bounds. Report instruction counts, not host timings. |
|
| 24 | 24 | ||
| 25 | + | ## Firmware discovery |
|
| 26 | + | ||
| 27 | + | - Accept version-17 FDT blobs up to 64 KiB, at most 256 nodes, and 32 nesting |
|
| 28 | + | levels. Support one- and two-cell addresses, empty bus ranges, and explicit |
|
| 29 | + | translations. Reject duplicate names/properties/phandles, overlapping sections, |
|
| 30 | + | incomplete register tuples, and truncated inputs. |
|
| 31 | + | - Decode firmware reservations and /reserved-memory. MMIO is not allocatable RAM. |
|
| 32 | + | CLINT, PLIC, and radiant,finish are machine resources, not user Device authority. |
|
| 33 | + | - Hardware hart identifiers fit the eight-entry build limit. CLINT banks and |
|
| 34 | + | PLIC contexts come from interrupts-extended, not the hart identifier itself. |
|
| 35 | + | ||
| 25 | 36 | ## Validation |
|
| 26 | 37 | ||
| 27 | 38 | Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass |
|
| 28 | 39 | `EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build |
|
| 29 | 40 | checks compiler dependencies. From the repository root, run: |
|
| 30 | 41 | ||
| 31 | 42 | ```sh |
|
| 32 | 43 | make -C kernel check |
|
| 33 | 44 | ``` |
|
| 34 | 45 | ||
| 35 | - | The linked freestanding entry point and hosted runner must execute; an assertion failure must fail the check. |
|
| 46 | + | Exercise bounded FDT topology discovery, malformed sections, every truncated prefix, reservations, and disabled CPU nodes. |
|
| 36 | 47 | ||
| 37 | 48 | The entry probe uses explicit M-mode success/fault finish writes; secondary |
|
| 38 | 49 | harts idle. This checks machine entry, not user-domain execution. Finish writes |
|
| 39 | 50 | are a check protocol, not a domain-exit operation. |
kernel/check.rad
+4 -0
| 1 | 1 | //! Hosted entry for kernel mechanism checks. |
|
| 2 | 2 | ||
| 3 | + | mod boot; |
|
| 4 | + | export mod fixture; |
|
| 5 | + | ||
| 3 | 6 | /// Run the available kernel mechanism checks. |
|
| 4 | 7 | @default fn main() -> u32 { |
|
| 8 | + | boot::run(); |
|
| 5 | 9 | return 0; |
|
| 6 | 10 | } |
kernel/check/boot.rad
added
+66 -0
| 1 | + | //! Device-tree parsing and physical-platform behavior checks. |
|
| 2 | + | ||
| 3 | + | use core::fdt; |
|
| 4 | + | use core::platform; |
|
| 5 | + | use check::fixture; |
|
| 6 | + | ||
| 7 | + | /// Set a big-endian word in a test-owned buffer. |
|
| 8 | + | fn put(bytes: *mut [u8], offset: u32, value: u32) { |
|
| 9 | + | set bytes[offset] = (value >> 24) as u8; |
|
| 10 | + | set bytes[offset + 1] = (value >> 16) as u8; |
|
| 11 | + | set bytes[offset + 2] = (value >> 8) as u8; |
|
| 12 | + | set bytes[offset + 3] = value as u8; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | /// Require malformed input to fail without exposing partially decoded state. |
|
| 16 | + | fn reject(bytes: *[u8]) { |
|
| 17 | + | let mut tree: fdt::Tree = undefined; |
|
| 18 | + | try fdt::decode(bytes, &mut tree) catch { return; }; |
|
| 19 | + | panic "reject: malformed FDT accepted"; |
|
| 20 | + | } |
|
| 21 | + | ||
| 22 | + | /// Check platform resources and reject truncated or overlapping sections. |
|
| 23 | + | export fn run() { |
|
| 24 | + | let bytes = &fixture::BYTES[..]; |
|
| 25 | + | let mut tree: fdt::Tree = undefined; |
|
| 26 | + | try! fdt::decode(bytes, &mut tree); |
|
| 27 | + | let mut machine: platform::Platform = undefined; |
|
| 28 | + | try! platform::discover(&tree, &mut machine); |
|
| 29 | + | assert machine.memoryCount == 1; |
|
| 30 | + | assert machine.memory[0].base == 0; |
|
| 31 | + | assert machine.memory[0].size == 0x8000000; |
|
| 32 | + | assert machine.reserved[0].size == 0x1000000; |
|
| 33 | + | assert machine.hartCount == 2; |
|
| 34 | + | assert machine.harts[1].id == 1; |
|
| 35 | + | assert machine.harts[1].clintIndex == 1; |
|
| 36 | + | assert machine.harts[1].plicContext == 1; |
|
| 37 | + | assert machine.clint.base == 0x2000000; |
|
| 38 | + | assert machine.plic.base == 0xc000000; |
|
| 39 | + | assert machine.devices[0].region.base == 0x10000000; |
|
| 40 | + | assert machine.devices[0].irq == 10; |
|
| 41 | + | for n in 0..bytes.len { reject(&bytes[..n]); } |
|
| 42 | + | ||
| 43 | + | let mut corrupt = fixture::BYTES; |
|
| 44 | + | put(&mut corrupt[..], 8, 0xfffffff0); |
|
| 45 | + | reject(&corrupt[..]); |
|
| 46 | + | set corrupt = fixture::BYTES; |
|
| 47 | + | put(&mut corrupt[..], 12, 72); |
|
| 48 | + | reject(&corrupt[..]); |
|
| 49 | + | set corrupt = fixture::BYTES; |
|
| 50 | + | put(&mut corrupt[..], 16, 72); |
|
| 51 | + | reject(&corrupt[..]); |
|
| 52 | + | set corrupt = fixture::BYTES; |
|
| 53 | + | put(&mut corrupt[..], 72, 2); |
|
| 54 | + | reject(&corrupt[..]); |
|
| 55 | + | ||
| 56 | + | // A disabled hart must not receive a route in the online set. |
|
| 57 | + | try! fdt::decode(bytes, &mut tree); |
|
| 58 | + | for i in 0..tree.count { |
|
| 59 | + | if fdt::equal(tree.nodes[i].name, "cpu@1") { |
|
| 60 | + | set tree.nodes[i].status = "disabled\0"; |
|
| 61 | + | } |
|
| 62 | + | } |
|
| 63 | + | try! platform::discover(&tree, &mut machine); |
|
| 64 | + | assert machine.hartCount == 1; |
|
| 65 | + | assert machine.harts[0].id == 0; |
|
| 66 | + | } |
kernel/check/fixture.rad
added
+76 -0
| 1 | + | //! Encoded two-hart platform used by device-tree behavior checks. |
|
| 2 | + | ||
| 3 | + | /// A complete version-17 FDT with RAM, CLINT, PLIC, and UART. |
|
| 4 | + | export constant BYTES: [u8; 1136] = [ |
|
| 5 | + | 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x04, 0x70, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x03, 0xcc, |
|
| 6 | + | 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, |
|
| 7 | + | 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
| 8 | + | 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
| 9 | + | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, |
|
| 10 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, |
|
| 11 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, |
|
| 12 | + | 0x00, 0x00, 0x00, 0x01, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00, |
|
| 13 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1b, 0x6d, 0x65, 0x6d, 0x6f, |
|
| 14 | + | 0x72, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x27, |
|
| 15 | + | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, |
|
| 16 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x63, 0x70, 0x75, 0x73, 0x00, 0x00, 0x00, 0x00, |
|
| 17 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, |
|
| 18 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, |
|
| 19 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x0f, 0x42, 0x40, |
|
| 20 | + | 0x00, 0x00, 0x00, 0x01, 0x63, 0x70, 0x75, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, |
|
| 21 | + | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1b, 0x63, 0x70, 0x75, 0x00, 0x00, 0x00, 0x00, 0x03, |
|
| 22 | + | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, |
|
| 23 | + | 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, |
|
| 24 | + | 0x6c, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0f, |
|
| 25 | + | 0x00, 0x00, 0x00, 0x3e, 0x72, 0x69, 0x73, 0x63, 0x76, 0x2c, 0x63, 0x70, 0x75, 0x2d, 0x69, 0x6e, |
|
| 26 | + | 0x74, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x49, |
|
| 27 | + | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x51, |
|
| 28 | + | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, |
|
| 29 | + | 0x63, 0x70, 0x75, 0x40, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, |
|
| 30 | + | 0x00, 0x00, 0x00, 0x1b, 0x63, 0x70, 0x75, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, |
|
| 31 | + | 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6e, 0x74, 0x65, |
|
| 32 | + | 0x72, 0x72, 0x75, 0x70, 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, |
|
| 33 | + | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3e, |
|
| 34 | + | 0x72, 0x69, 0x73, 0x63, 0x76, 0x2c, 0x63, 0x70, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x63, 0x00, 0x00, |
|
| 35 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x02, |
|
| 36 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, |
|
| 37 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, |
|
| 38 | + | 0x73, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, |
|
| 39 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0f, |
|
| 40 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, |
|
| 41 | + | 0x00, 0x00, 0x00, 0x01, 0x63, 0x6c, 0x69, 0x6e, 0x74, 0x40, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, |
|
| 42 | + | 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3e, |
|
| 43 | + | 0x72, 0x69, 0x73, 0x63, 0x76, 0x2c, 0x63, 0x6c, 0x69, 0x6e, 0x74, 0x30, 0x00, 0x00, 0x00, 0x00, |
|
| 44 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, |
|
| 45 | + | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, |
|
| 46 | + | 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, |
|
| 47 | + | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, |
|
| 48 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, |
|
| 49 | + | 0x70, 0x6c, 0x69, 0x63, 0x40, 0x63, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, |
|
| 50 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x72, 0x69, 0x73, 0x63, |
|
| 51 | + | 0x76, 0x2c, 0x70, 0x6c, 0x69, 0x63, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, |
|
| 52 | + | 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|
| 53 | + | 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x49, |
|
| 54 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x51, |
|
| 55 | + | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7d, |
|
| 56 | + | 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x69, |
|
| 57 | + | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, |
|
| 58 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x75, 0x61, 0x72, 0x74, 0x40, 0x31, 0x30, 0x30, |
|
| 59 | + | 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, |
|
| 60 | + | 0x00, 0x00, 0x00, 0x3e, 0x6e, 0x73, 0x31, 0x36, 0x35, 0x35, 0x30, 0x61, 0x00, 0x00, 0x00, 0x00, |
|
| 61 | + | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, |
|
| 62 | + | 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, |
|
| 63 | + | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, |
|
| 64 | + | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, |
|
| 65 | + | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x23, 0x61, 0x64, 0x64, |
|
| 66 | + | 0x72, 0x65, 0x73, 0x73, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x23, 0x73, 0x69, 0x7a, 0x65, |
|
| 67 | + | 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x79, |
|
| 68 | + | 0x70, 0x65, 0x00, 0x72, 0x65, 0x67, 0x00, 0x74, 0x69, 0x6d, 0x65, 0x62, 0x61, 0x73, 0x65, 0x2d, |
|
| 69 | + | 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x00, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, |
|
| 70 | + | 0x69, 0x62, 0x6c, 0x65, 0x00, 0x70, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x00, 0x23, 0x69, 0x6e, |
|
| 71 | + | 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x72, 0x61, |
|
| 72 | + | 0x6e, 0x67, 0x65, 0x73, 0x00, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x73, 0x2d, |
|
| 73 | + | 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x00, 0x72, 0x69, 0x73, 0x63, 0x76, 0x2c, 0x6e, |
|
| 74 | + | 0x64, 0x65, 0x76, 0x00, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x2d, 0x70, 0x61, |
|
| 75 | + | 0x72, 0x65, 0x6e, 0x74, 0x00, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x75, 0x70, 0x74, 0x73, 0x00, |
|
| 76 | + | ]; |
kernel/core.rad
added
+4 -0
| 1 | + | //! Safe kernel mechanisms shared by machine execution and hosted checks. |
|
| 2 | + | ||
| 3 | + | export mod fdt; |
|
| 4 | + | export mod platform; |
kernel/core/fdt.rad
added
+323 -0
| 1 | + | //! Bounded decoding of flattened device trees, version 17. |
|
| 2 | + | ||
| 3 | + | /// Maximum nodes accepted from platform firmware. |
|
| 4 | + | export constant MAX_NODES: u32 = 256; |
|
| 5 | + | /// Maximum nesting depth accepted from platform firmware. |
|
| 6 | + | constant MAX_DEPTH: u32 = 32; |
|
| 7 | + | /// Maximum firmware reservation entries. |
|
| 8 | + | export constant MAX_RESERVED: u32 = 64; |
|
| 9 | + | /// Maximum complete device-tree size accepted at boot. |
|
| 10 | + | export constant MAX_BYTES: u32 = 65536; |
|
| 11 | + | /// Maximum properties retained across the active parser stack. |
|
| 12 | + | constant MAX_PROPERTIES: u32 = 256; |
|
| 13 | + | /// Parent index of the root node. |
|
| 14 | + | export constant NO_PARENT: u32 = 0xffffffff; |
|
| 15 | + | /// Device tree header magic. |
|
| 16 | + | constant MAGIC: u32 = 0xd00dfeed; |
|
| 17 | + | ||
| 18 | + | /// Invalid firmware input or exhausted decoder storage. |
|
| 19 | + | export union Error: Copy { |
|
| 20 | + | /// Input violates the flattened tree format or supported cell widths. |
|
| 21 | + | Invalid, |
|
| 22 | + | /// Input exceeds a fixed decoder limit. |
|
| 23 | + | Exhausted, |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// One physical half-open address range. |
|
| 27 | + | export record Range: Copy { |
|
| 28 | + | /// Physical start address. |
|
| 29 | + | base: u64, |
|
| 30 | + | /// Number of bytes in the range. |
|
| 31 | + | size: u64, |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Properties used to discover physical resources. |
|
| 35 | + | export record Node: Copy { |
|
| 36 | + | /// Node name without its terminating zero byte. |
|
| 37 | + | name: *[u8], |
|
| 38 | + | /// Parent node index, or NO_PARENT for the root. |
|
| 39 | + | parent: u32, |
|
| 40 | + | /// Number of address cells used by children. |
|
| 41 | + | addressCells: u32, |
|
| 42 | + | /// Number of size cells used by children. |
|
| 43 | + | sizeCells: u32, |
|
| 44 | + | /// Physical register tuples encoded with parent cell widths. |
|
| 45 | + | reg: *[u8], |
|
| 46 | + | /// Bus address translations. |
|
| 47 | + | ranges: ?*[u8], |
|
| 48 | + | /// Zero-terminated compatible strings. |
|
| 49 | + | compatible: *[u8], |
|
| 50 | + | /// Device type string. |
|
| 51 | + | deviceType: *[u8], |
|
| 52 | + | /// Status string; empty means enabled. |
|
| 53 | + | status: *[u8], |
|
| 54 | + | /// Interrupt-controller identifier. |
|
| 55 | + | phandle: u32, |
|
| 56 | + | /// Interrupt-parent identifier; zero inherits from the parent. |
|
| 57 | + | interruptParent: u32, |
|
| 58 | + | /// Number of cells in interrupt specifiers. |
|
| 59 | + | interruptCells: u32, |
|
| 60 | + | /// Interrupt source specifiers. |
|
| 61 | + | interrupts: *[u8], |
|
| 62 | + | /// Interrupt specifiers with controller identifiers. |
|
| 63 | + | extended: *[u8], |
|
| 64 | + | /// Number of PLIC interrupt sources. |
|
| 65 | + | sources: u32, |
|
| 66 | + | /// Timer ticks per second for the CPU bus. |
|
| 67 | + | frequency: u32, |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | /// Decoded nodes and firmware reservations borrowed from the input. |
|
| 71 | + | export record Tree: Copy { |
|
| 72 | + | /// Nodes in structure-block order. |
|
| 73 | + | nodes: [Node; MAX_NODES], |
|
| 74 | + | /// Number of initialized node entries. |
|
| 75 | + | count: u32, |
|
| 76 | + | /// Physical reservations from the header reservation map. |
|
| 77 | + | reserved: [Range; MAX_RESERVED], |
|
| 78 | + | /// Number of initialized reservation entries. |
|
| 79 | + | reservedCount: u32, |
|
| 80 | + | /// Total byte size of the firmware blob. |
|
| 81 | + | size: u32, |
|
| 82 | + | } |
|
| 83 | + | ||
| 84 | + | /// Read a big-endian word after checking the complete range. |
|
| 85 | + | export fn word(bytes: *[u8], offset: u32) -> u32 throws (Error) { |
|
| 86 | + | if offset > bytes.len or bytes.len - offset < 4 { |
|
| 87 | + | throw Error::Invalid; |
|
| 88 | + | } |
|
| 89 | + | return (bytes[offset] as u32 << 24) | (bytes[offset + 1] as u32 << 16) |
|
| 90 | + | | (bytes[offset + 2] as u32 << 8) | bytes[offset + 3] as u32; |
|
| 91 | + | } |
|
| 92 | + | ||
| 93 | + | /// Read a one-cell or two-cell integer. |
|
| 94 | + | export fn cells(bytes: *[u8], offset: u32, count: u32) -> u64 throws (Error) { |
|
| 95 | + | if count == 0 { return 0; } |
|
| 96 | + | if count == 1 { return try word(bytes, offset) as u64; } |
|
| 97 | + | if count <> 2 { throw Error::Invalid; } |
|
| 98 | + | let high = try word(bytes, offset); |
|
| 99 | + | let low = try word(bytes, offset + 4); |
|
| 100 | + | return (high as u64 << 32) | low as u64; |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Compare byte strings without reading beyond either slice. |
|
| 104 | + | export fn equal(a: *[u8], b: *[u8]) -> bool { |
|
| 105 | + | if a.len <> b.len { return false; } |
|
| 106 | + | for byte, i in a { |
|
| 107 | + | if byte <> b[i] { return false; } |
|
| 108 | + | } |
|
| 109 | + | return true; |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | /// Return a bounded zero-terminated string without its terminator. |
|
| 113 | + | fn string(bytes: *[u8], offset: u32) -> *[u8] throws (Error) { |
|
| 114 | + | if offset >= bytes.len { throw Error::Invalid; } |
|
| 115 | + | let mut end = offset; |
|
| 116 | + | while bytes[end] <> 0 { |
|
| 117 | + | set end += 1; |
|
| 118 | + | if end == bytes.len { throw Error::Invalid; } |
|
| 119 | + | } |
|
| 120 | + | return &bytes[offset..end]; |
|
| 121 | + | } |
|
| 122 | + | ||
| 123 | + | /// Check for an exact item in a sequence of zero-terminated strings. |
|
| 124 | + | export fn contains(bytes: *[u8], item: *[u8]) -> bool { |
|
| 125 | + | let mut offset: u32 = 0; |
|
| 126 | + | while offset < bytes.len { |
|
| 127 | + | let value = try string(bytes, offset) catch { return false; }; |
|
| 128 | + | if equal(value, item) { return true; } |
|
| 129 | + | set offset += value.len + 1; |
|
| 130 | + | } |
|
| 131 | + | return false; |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Test whether firmware enables a node and all its parents. |
|
| 135 | + | export fn enabled(tree: *Tree, index: u32) -> bool { |
|
| 136 | + | let mut current = index; |
|
| 137 | + | while current <> NO_PARENT { |
|
| 138 | + | let node = &tree.nodes[current]; |
|
| 139 | + | if node.status.len <> 0 and not equal(node.status, "okay\0") |
|
| 140 | + | and not equal(node.status, "ok\0") { |
|
| 141 | + | return false; |
|
| 142 | + | } |
|
| 143 | + | set current = node.parent; |
|
| 144 | + | } |
|
| 145 | + | return true; |
|
| 146 | + | } |
|
| 147 | + | ||
| 148 | + | /// Check a section range against the complete input. |
|
| 149 | + | fn section(bytes: *[u8], offset: u32, size: u32) -> *[u8] throws (Error) { |
|
| 150 | + | if offset > bytes.len or size > bytes.len - offset { throw Error::Invalid; } |
|
| 151 | + | return &bytes[offset..offset + size]; |
|
| 152 | + | } |
|
| 153 | + | ||
| 154 | + | /// Store one relevant property after validating its representation. |
|
| 155 | + | fn property(node: *mut Node, name: *[u8], value: *[u8]) throws (Error) { |
|
| 156 | + | if equal(name, "#address-cells") { |
|
| 157 | + | if value.len <> 4 { throw Error::Invalid; } |
|
| 158 | + | set node.addressCells = try word(value, 0); |
|
| 159 | + | if node.addressCells == 0 or node.addressCells > 2 { throw Error::Invalid; } |
|
| 160 | + | } else if equal(name, "#size-cells") { |
|
| 161 | + | if value.len <> 4 { throw Error::Invalid; } |
|
| 162 | + | set node.sizeCells = try word(value, 0); |
|
| 163 | + | if node.sizeCells > 2 { throw Error::Invalid; } |
|
| 164 | + | } else if equal(name, "reg") { |
|
| 165 | + | set node.reg = value; |
|
| 166 | + | } else if equal(name, "ranges") { |
|
| 167 | + | set node.ranges = value; |
|
| 168 | + | } else if equal(name, "compatible") { |
|
| 169 | + | if value.len == 0 or value[value.len - 1] <> 0 { throw Error::Invalid; } |
|
| 170 | + | set node.compatible = value; |
|
| 171 | + | } else if equal(name, "device_type") { |
|
| 172 | + | set node.deviceType = value; |
|
| 173 | + | } else if equal(name, "status") { |
|
| 174 | + | set node.status = value; |
|
| 175 | + | } else if equal(name, "interrupts") { |
|
| 176 | + | set node.interrupts = value; |
|
| 177 | + | } else if equal(name, "interrupts-extended") { |
|
| 178 | + | set node.extended = value; |
|
| 179 | + | } else { |
|
| 180 | + | let scalar = equal(name, "phandle") or equal(name, "linux,phandle") |
|
| 181 | + | or equal(name, "interrupt-parent") or equal(name, "#interrupt-cells") |
|
| 182 | + | or equal(name, "riscv,ndev") or equal(name, "timebase-frequency"); |
|
| 183 | + | if scalar { |
|
| 184 | + | if value.len <> 4 { throw Error::Invalid; } |
|
| 185 | + | let number = try word(value, 0); |
|
| 186 | + | if equal(name, "phandle") or equal(name, "linux,phandle") { |
|
| 187 | + | if number == 0 or number == 0xffffffff { throw Error::Invalid; } |
|
| 188 | + | if node.phandle <> 0 and node.phandle <> number { throw Error::Invalid; } |
|
| 189 | + | set node.phandle = number; |
|
| 190 | + | } else if equal(name, "interrupt-parent") { |
|
| 191 | + | set node.interruptParent = number; |
|
| 192 | + | } else if equal(name, "#interrupt-cells") { |
|
| 193 | + | set node.interruptCells = number; |
|
| 194 | + | } else if equal(name, "riscv,ndev") { |
|
| 195 | + | set node.sources = number; |
|
| 196 | + | } else { |
|
| 197 | + | set node.frequency = number; |
|
| 198 | + | } |
|
| 199 | + | } |
|
| 200 | + | } |
|
| 201 | + | } |
|
| 202 | + | ||
| 203 | + | /// Decode a complete FDT into caller-owned fixed storage. |
|
| 204 | + | export fn decode(bytes: *[u8], tree: *mut Tree) throws (Error) { |
|
| 205 | + | if bytes.len < 40 or try word(bytes, 0) <> MAGIC { throw Error::Invalid; } |
|
| 206 | + | let size = try word(bytes, 4); |
|
| 207 | + | if size < 40 or size > bytes.len or size > MAX_BYTES { throw Error::Invalid; } |
|
| 208 | + | let blob = &bytes[..size]; |
|
| 209 | + | let structureOffset = try word(blob, 8); |
|
| 210 | + | let stringsOffset = try word(blob, 12); |
|
| 211 | + | let reserveOffset = try word(blob, 16); |
|
| 212 | + | if try word(blob, 20) <> 17 or try word(blob, 24) > 17 { |
|
| 213 | + | throw Error::Invalid; |
|
| 214 | + | } |
|
| 215 | + | let stringsSize = try word(blob, 32); |
|
| 216 | + | let structureSize = try word(blob, 36); |
|
| 217 | + | if structureOffset < 40 or stringsOffset < 40 or reserveOffset < 40 |
|
| 218 | + | or (structureOffset & 3) <> 0 or (reserveOffset & 7) <> 0 { |
|
| 219 | + | throw Error::Invalid; |
|
| 220 | + | } |
|
| 221 | + | let structure = try section(blob, structureOffset, structureSize); |
|
| 222 | + | let strings = try section(blob, stringsOffset, stringsSize); |
|
| 223 | + | if structureOffset < stringsOffset + stringsSize |
|
| 224 | + | and stringsOffset < structureOffset + structureSize { throw Error::Invalid; } |
|
| 225 | + | set tree.count = 0; |
|
| 226 | + | set tree.reservedCount = 0; |
|
| 227 | + | set tree.size = size; |
|
| 228 | + | let mut pos = reserveOffset; |
|
| 229 | + | loop { |
|
| 230 | + | let entry = try section(blob, pos, 16); |
|
| 231 | + | let base = try cells(entry, 0, 2); |
|
| 232 | + | let length = try cells(entry, 8, 2); |
|
| 233 | + | set pos += 16; |
|
| 234 | + | if base == 0 and length == 0 { break; } |
|
| 235 | + | if length == 0 or base > 0xffffffffffffffff - length { throw Error::Invalid; } |
|
| 236 | + | if tree.reservedCount == MAX_RESERVED { throw Error::Exhausted; } |
|
| 237 | + | set tree.reserved[tree.reservedCount] = Range { base, size: length }; |
|
| 238 | + | set tree.reservedCount += 1; |
|
| 239 | + | } |
|
| 240 | + | if reserveOffset < structureOffset + structureSize and structureOffset < pos |
|
| 241 | + | or reserveOffset < stringsOffset + stringsSize and stringsOffset < pos { |
|
| 242 | + | throw Error::Invalid; |
|
| 243 | + | } |
|
| 244 | + | let mut stack: [u32; MAX_DEPTH] = undefined; |
|
| 245 | + | let mut depth: u32 = 0; |
|
| 246 | + | let mut childSeen: [bool; MAX_DEPTH] = undefined; |
|
| 247 | + | let mut propertyNames: [*[u8]; MAX_PROPERTIES] = undefined; |
|
| 248 | + | let mut propertyStart: [u32; MAX_DEPTH] = undefined; |
|
| 249 | + | let mut propertyCount: u32 = 0; |
|
| 250 | + | set pos = 0; |
|
| 251 | + | loop { |
|
| 252 | + | let token = try word(structure, pos); |
|
| 253 | + | set pos += 4; |
|
| 254 | + | if token == 1 { |
|
| 255 | + | if depth == MAX_DEPTH or tree.count == MAX_NODES { throw Error::Exhausted; } |
|
| 256 | + | if depth == 0 and tree.count <> 0 { throw Error::Invalid; } |
|
| 257 | + | let name = try string(structure, pos); |
|
| 258 | + | if tree.count == 0 and name.len <> 0 { throw Error::Invalid; } |
|
| 259 | + | let padded = (name.len + 4) & ~3; |
|
| 260 | + | let _nameBytes = try section(structure, pos, padded); |
|
| 261 | + | set pos += padded; |
|
| 262 | + | let mut parent = NO_PARENT; |
|
| 263 | + | if depth <> 0 { |
|
| 264 | + | set parent = stack[depth - 1]; |
|
| 265 | + | set childSeen[depth - 1] = true; |
|
| 266 | + | } |
|
| 267 | + | for i in 0..tree.count { |
|
| 268 | + | if tree.nodes[i].parent == parent and equal(tree.nodes[i].name, name) { |
|
| 269 | + | throw Error::Invalid; |
|
| 270 | + | } |
|
| 271 | + | } |
|
| 272 | + | set tree.nodes[tree.count] = Node { |
|
| 273 | + | name, parent, addressCells: 2, sizeCells: 1, |
|
| 274 | + | reg: "", ranges: nil, compatible: "", deviceType: "", status: "", |
|
| 275 | + | phandle: 0, interruptParent: 0, interruptCells: 0, |
|
| 276 | + | interrupts: "", extended: "", sources: 0, frequency: 0, |
|
| 277 | + | }; |
|
| 278 | + | set stack[depth] = tree.count; |
|
| 279 | + | set childSeen[depth] = false; |
|
| 280 | + | set propertyStart[depth] = propertyCount; |
|
| 281 | + | set depth += 1; |
|
| 282 | + | set tree.count += 1; |
|
| 283 | + | } else if token == 2 { |
|
| 284 | + | if depth == 0 { throw Error::Invalid; } |
|
| 285 | + | set depth -= 1; |
|
| 286 | + | set propertyCount = propertyStart[depth]; |
|
| 287 | + | } else if token == 3 { |
|
| 288 | + | if depth == 0 or childSeen[depth - 1] { throw Error::Invalid; } |
|
| 289 | + | let length = try word(structure, pos); |
|
| 290 | + | let nameOffset = try word(structure, pos + 4); |
|
| 291 | + | set pos += 8; |
|
| 292 | + | let name = try string(strings, nameOffset); |
|
| 293 | + | if name.len == 0 { throw Error::Invalid; } |
|
| 294 | + | for i in propertyStart[depth - 1]..propertyCount { |
|
| 295 | + | if equal(propertyNames[i], name) { throw Error::Invalid; } |
|
| 296 | + | } |
|
| 297 | + | if propertyCount == MAX_PROPERTIES { throw Error::Exhausted; } |
|
| 298 | + | set propertyNames[propertyCount] = name; |
|
| 299 | + | set propertyCount += 1; |
|
| 300 | + | let value = try section(structure, pos, length); |
|
| 301 | + | if length > 0xfffffffc { throw Error::Invalid; } |
|
| 302 | + | let padded = (length + 3) & ~3; |
|
| 303 | + | let _valueBytes = try section(structure, pos, padded); |
|
| 304 | + | set pos += padded; |
|
| 305 | + | try property(&mut tree.nodes[stack[depth - 1]], name, value); |
|
| 306 | + | } else if token == 4 { |
|
| 307 | + | // NOP tokens can occur between structure entries. |
|
| 308 | + | } else if token == 9 { |
|
| 309 | + | if depth <> 0 or tree.count == 0 or pos <> structure.len { throw Error::Invalid; } |
|
| 310 | + | for i in 0..tree.count { |
|
| 311 | + | let id = tree.nodes[i].phandle; |
|
| 312 | + | if id <> 0 { |
|
| 313 | + | for j in 0..i { |
|
| 314 | + | if tree.nodes[j].phandle == id { throw Error::Invalid; } |
|
| 315 | + | } |
|
| 316 | + | } |
|
| 317 | + | } |
|
| 318 | + | return; |
|
| 319 | + | } else { |
|
| 320 | + | throw Error::Invalid; |
|
| 321 | + | } |
|
| 322 | + | } |
|
| 323 | + | } |
kernel/core/platform.rad
added
+276 -0
| 1 | + | //! Physical resources and interrupt topology from a validated device tree. |
|
| 2 | + | ||
| 3 | + | use core::fdt; |
|
| 4 | + | ||
| 5 | + | /// Maximum online harts in this build. |
|
| 6 | + | export constant MAX_HARTS: u32 = 8; |
|
| 7 | + | /// Maximum disjoint RAM regions in the boot description. |
|
| 8 | + | export constant MAX_MEMORY: u32 = 16; |
|
| 9 | + | /// Maximum user-visible MMIO regions. |
|
| 10 | + | export constant MAX_DEVICES: u32 = 128; |
|
| 11 | + | /// Maximum PLIC source identifier, including unused source zero. |
|
| 12 | + | export constant MAX_IRQS: u32 = 128; |
|
| 13 | + | ||
| 14 | + | /// One enabled hart and its machine interrupt-controller routes. |
|
| 15 | + | export record Hart: Copy { |
|
| 16 | + | /// Hardware hart identifier. |
|
| 17 | + | id: u32, |
|
| 18 | + | /// Local CPU interrupt-controller phandle. |
|
| 19 | + | controller: u32, |
|
| 20 | + | /// CLINT register bank index. |
|
| 21 | + | clintIndex: u32, |
|
| 22 | + | /// PLIC machine-context index. |
|
| 23 | + | plicContext: u32, |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// One user-visible device region. |
|
| 27 | + | export record Device: Copy { |
|
| 28 | + | /// Physical register window. |
|
| 29 | + | region: fdt::Range, |
|
| 30 | + | /// PLIC source, or zero for a device without interrupts. |
|
| 31 | + | irq: u32, |
|
| 32 | + | } |
|
| 33 | + | ||
| 34 | + | /// Fixed storage for all discovered platform resources. |
|
| 35 | + | export record Platform: Copy { |
|
| 36 | + | /// Physical RAM banks. |
|
| 37 | + | memory: [fdt::Range; MAX_MEMORY], |
|
| 38 | + | /// Number of initialized RAM banks. |
|
| 39 | + | memoryCount: u32, |
|
| 40 | + | /// Firmware reservations. |
|
| 41 | + | reserved: [fdt::Range; fdt::MAX_RESERVED], |
|
| 42 | + | /// Number of initialized reservations. |
|
| 43 | + | reservedCount: u32, |
|
| 44 | + | /// Online harts in device-tree order. |
|
| 45 | + | harts: [Hart; MAX_HARTS], |
|
| 46 | + | /// Number of online harts. |
|
| 47 | + | hartCount: u32, |
|
| 48 | + | /// User-visible devices in device-tree order. |
|
| 49 | + | devices: [Device; MAX_DEVICES], |
|
| 50 | + | /// Number of initialized device regions. |
|
| 51 | + | deviceCount: u32, |
|
| 52 | + | /// CLINT register window. |
|
| 53 | + | clint: fdt::Range, |
|
| 54 | + | /// PLIC register window. |
|
| 55 | + | plic: fdt::Range, |
|
| 56 | + | /// Number of usable PLIC sources. |
|
| 57 | + | irqCount: u32, |
|
| 58 | + | /// Monotonic timer frequency in ticks per second. |
|
| 59 | + | frequency: u32, |
|
| 60 | + | } |
|
| 61 | + | ||
| 62 | + | /// Translate a range through its parent bus mappings. |
|
| 63 | + | fn translate(tree: *fdt::Tree, parent: u32, range: fdt::Range) -> fdt::Range throws (fdt::Error) { |
|
| 64 | + | let mut result = range; |
|
| 65 | + | let mut bus = parent; |
|
| 66 | + | while tree.nodes[bus].parent <> fdt::NO_PARENT { |
|
| 67 | + | let node = &tree.nodes[bus]; |
|
| 68 | + | let ancestor = &tree.nodes[node.parent]; |
|
| 69 | + | let ranges = node.ranges else { throw fdt::Error::Invalid; }; |
|
| 70 | + | if ranges.len <> 0 { |
|
| 71 | + | let tupleSize = (node.addressCells + ancestor.addressCells + node.sizeCells) * 4; |
|
| 72 | + | if tupleSize == 0 or ranges.len % tupleSize <> 0 { throw fdt::Error::Invalid; } |
|
| 73 | + | let mut found = false; |
|
| 74 | + | let mut offset: u32 = 0; |
|
| 75 | + | while offset < ranges.len { |
|
| 76 | + | let child = try fdt::cells(ranges, offset, node.addressCells); |
|
| 77 | + | let target = try fdt::cells(ranges, offset + node.addressCells * 4, ancestor.addressCells); |
|
| 78 | + | let size = try fdt::cells(ranges, offset + (node.addressCells + ancestor.addressCells) * 4, node.sizeCells); |
|
| 79 | + | if result.base >= child and result.base - child <= size |
|
| 80 | + | and result.size <= size - (result.base - child) { |
|
| 81 | + | let delta = result.base - child; |
|
| 82 | + | if target > 0xffffffffffffffff - delta |
|
| 83 | + | or result.size > 0xffffffffffffffff - (target + delta) { |
|
| 84 | + | throw fdt::Error::Invalid; |
|
| 85 | + | } |
|
| 86 | + | set result.base = target + delta; |
|
| 87 | + | set found = true; |
|
| 88 | + | break; |
|
| 89 | + | } |
|
| 90 | + | set offset += tupleSize; |
|
| 91 | + | } |
|
| 92 | + | if not found { throw fdt::Error::Invalid; } |
|
| 93 | + | } |
|
| 94 | + | set bus = node.parent; |
|
| 95 | + | } |
|
| 96 | + | return result; |
|
| 97 | + | } |
|
| 98 | + | ||
| 99 | + | /// Decode and translate one register tuple. |
|
| 100 | + | fn region(tree: *fdt::Tree, index: u32, offset: u32) -> fdt::Range throws (fdt::Error) { |
|
| 101 | + | let node = &tree.nodes[index]; |
|
| 102 | + | if node.parent == fdt::NO_PARENT { throw fdt::Error::Invalid; } |
|
| 103 | + | let parent = &tree.nodes[node.parent]; |
|
| 104 | + | let base = try fdt::cells(node.reg, offset, parent.addressCells); |
|
| 105 | + | let size = try fdt::cells(node.reg, offset + parent.addressCells * 4, parent.sizeCells); |
|
| 106 | + | if size == 0 or base > 0xffffffffffffffff - size { throw fdt::Error::Invalid; } |
|
| 107 | + | return try translate(tree, node.parent, fdt::Range { base, size }); |
|
| 108 | + | } |
|
| 109 | + | ||
| 110 | + | /// Resolve CPU-local interrupt routing for CLINT and PLIC. |
|
| 111 | + | fn routes(tree: *fdt::Tree, node: *fdt::Node, platform: *mut Platform, plic: bool) throws (fdt::Error) { |
|
| 112 | + | if node.extended.len == 0 or node.extended.len % 8 <> 0 { throw fdt::Error::Invalid; } |
|
| 113 | + | let mut offset: u32 = 0; |
|
| 114 | + | while offset < node.extended.len { |
|
| 115 | + | let controller = try fdt::word(node.extended, offset); |
|
| 116 | + | let cause = try fdt::word(node.extended, offset + 4); |
|
| 117 | + | let mut known = false; |
|
| 118 | + | for i in 0..tree.count { |
|
| 119 | + | if tree.nodes[i].phandle == controller and tree.nodes[i].interruptCells == 1 { |
|
| 120 | + | set known = true; |
|
| 121 | + | } |
|
| 122 | + | } |
|
| 123 | + | if not known { throw fdt::Error::Invalid; } |
|
| 124 | + | for i in 0..platform.hartCount { |
|
| 125 | + | let hart = &mut platform.harts[i]; |
|
| 126 | + | if hart.controller == controller { |
|
| 127 | + | if plic and cause == 11 { |
|
| 128 | + | if hart.plicContext <> fdt::NO_PARENT { throw fdt::Error::Invalid; } |
|
| 129 | + | set hart.plicContext = offset / 8; |
|
| 130 | + | } else if not plic and cause == 3 { |
|
| 131 | + | if hart.clintIndex <> fdt::NO_PARENT { throw fdt::Error::Invalid; } |
|
| 132 | + | if offset + 16 > node.extended.len |
|
| 133 | + | or try fdt::word(node.extended, offset + 8) <> controller |
|
| 134 | + | or try fdt::word(node.extended, offset + 12) <> 7 { |
|
| 135 | + | throw fdt::Error::Invalid; |
|
| 136 | + | } |
|
| 137 | + | set hart.clintIndex = offset / 16; |
|
| 138 | + | } |
|
| 139 | + | } |
|
| 140 | + | } |
|
| 141 | + | set offset += 8; |
|
| 142 | + | } |
|
| 143 | + | } |
|
| 144 | + | ||
| 145 | + | /// Discover enabled RAM, harts, devices, and their interrupt routes. |
|
| 146 | + | export fn discover(tree: *fdt::Tree, platform: *mut Platform) throws (fdt::Error) { |
|
| 147 | + | set platform.memoryCount = 0; |
|
| 148 | + | set platform.hartCount = 0; |
|
| 149 | + | set platform.deviceCount = 0; |
|
| 150 | + | set platform.reservedCount = tree.reservedCount; |
|
| 151 | + | set platform.frequency = 0; |
|
| 152 | + | set platform.clint = fdt::Range { base: 0, size: 0 }; |
|
| 153 | + | set platform.plic = fdt::Range { base: 0, size: 0 }; |
|
| 154 | + | set platform.irqCount = 0; |
|
| 155 | + | for i in 0..tree.reservedCount { set platform.reserved[i] = tree.reserved[i]; } |
|
| 156 | + | let mut clintNode = fdt::NO_PARENT; |
|
| 157 | + | let mut plicNode = fdt::NO_PARENT; |
|
| 158 | + | for i in 0..tree.count { |
|
| 159 | + | let node = &tree.nodes[i]; |
|
| 160 | + | if not fdt::enabled(tree, i) { continue; } |
|
| 161 | + | if fdt::equal(node.name, "cpus") { |
|
| 162 | + | set platform.frequency = node.frequency; |
|
| 163 | + | } |
|
| 164 | + | if fdt::equal(node.deviceType, "cpu\0") { |
|
| 165 | + | if platform.hartCount == MAX_HARTS or node.parent == fdt::NO_PARENT { |
|
| 166 | + | throw fdt::Error::Exhausted; |
|
| 167 | + | } |
|
| 168 | + | let cells = tree.nodes[node.parent].addressCells; |
|
| 169 | + | if node.reg.len <> cells * 4 { throw fdt::Error::Invalid; } |
|
| 170 | + | let id = try fdt::cells(node.reg, 0, cells); |
|
| 171 | + | if id >= MAX_HARTS as u64 { throw fdt::Error::Invalid; } |
|
| 172 | + | for j in 0..platform.hartCount { |
|
| 173 | + | if platform.harts[j].id == id as u32 { throw fdt::Error::Invalid; } |
|
| 174 | + | } |
|
| 175 | + | let mut controller: u32 = 0; |
|
| 176 | + | for j in 0..tree.count { |
|
| 177 | + | let child = &tree.nodes[j]; |
|
| 178 | + | if child.parent == i and fdt::contains(child.compatible, "riscv,cpu-intc") { |
|
| 179 | + | if controller <> 0 or child.phandle == 0 or child.interruptCells <> 1 { |
|
| 180 | + | throw fdt::Error::Invalid; |
|
| 181 | + | } |
|
| 182 | + | set controller = child.phandle; |
|
| 183 | + | } |
|
| 184 | + | } |
|
| 185 | + | if controller == 0 { throw fdt::Error::Invalid; } |
|
| 186 | + | set platform.harts[platform.hartCount] = Hart { |
|
| 187 | + | id: id as u32, controller, clintIndex: fdt::NO_PARENT, plicContext: fdt::NO_PARENT, |
|
| 188 | + | }; |
|
| 189 | + | set platform.hartCount += 1; |
|
| 190 | + | continue; |
|
| 191 | + | } |
|
| 192 | + | if node.reg.len == 0 { continue; } |
|
| 193 | + | if node.parent == fdt::NO_PARENT { throw fdt::Error::Invalid; } |
|
| 194 | + | let parent = &tree.nodes[node.parent]; |
|
| 195 | + | if parent.sizeCells == 0 { continue; } |
|
| 196 | + | let tupleSize = (parent.addressCells + parent.sizeCells) * 4; |
|
| 197 | + | if node.reg.len % tupleSize <> 0 { throw fdt::Error::Invalid; } |
|
| 198 | + | let memory = fdt::equal(node.deviceType, "memory\0"); |
|
| 199 | + | let reserved = fdt::equal(parent.name, "reserved-memory"); |
|
| 200 | + | let clint = fdt::contains(node.compatible, "riscv,clint0"); |
|
| 201 | + | let plic = fdt::contains(node.compatible, "riscv,plic0") |
|
| 202 | + | or fdt::contains(node.compatible, "sifive,plic-1.0.0"); |
|
| 203 | + | let mut offset: u32 = 0; |
|
| 204 | + | while offset < node.reg.len { |
|
| 205 | + | let range = try region(tree, i, offset); |
|
| 206 | + | if not memory { |
|
| 207 | + | if platform.reservedCount == fdt::MAX_RESERVED { throw fdt::Error::Exhausted; } |
|
| 208 | + | set platform.reserved[platform.reservedCount] = range; |
|
| 209 | + | set platform.reservedCount += 1; |
|
| 210 | + | } |
|
| 211 | + | if memory { |
|
| 212 | + | if platform.memoryCount == MAX_MEMORY { throw fdt::Error::Exhausted; } |
|
| 213 | + | for j in 0..platform.memoryCount { |
|
| 214 | + | let other = platform.memory[j]; |
|
| 215 | + | if range.base < other.base + other.size and other.base < range.base + range.size { |
|
| 216 | + | throw fdt::Error::Invalid; |
|
| 217 | + | } |
|
| 218 | + | } |
|
| 219 | + | set platform.memory[platform.memoryCount] = range; |
|
| 220 | + | set platform.memoryCount += 1; |
|
| 221 | + | } else if clint { |
|
| 222 | + | if clintNode <> fdt::NO_PARENT or range.size < 0xc000 { throw fdt::Error::Invalid; } |
|
| 223 | + | set platform.clint = range; |
|
| 224 | + | set clintNode = i; |
|
| 225 | + | } else if plic { |
|
| 226 | + | if plicNode <> fdt::NO_PARENT or range.size < 0x200008 |
|
| 227 | + | or node.sources == 0 or node.sources >= MAX_IRQS { throw fdt::Error::Invalid; } |
|
| 228 | + | set platform.plic = range; |
|
| 229 | + | set platform.irqCount = node.sources; |
|
| 230 | + | set plicNode = i; |
|
| 231 | + | } else if not reserved and node.compatible.len <> 0 |
|
| 232 | + | and not fdt::contains(node.compatible, "radiant,finish") { |
|
| 233 | + | // User-visible devices exclude machine interrupt controllers. |
|
| 234 | + | if platform.deviceCount == MAX_DEVICES { throw fdt::Error::Exhausted; } |
|
| 235 | + | let mut irq: u32 = 0; |
|
| 236 | + | if node.interrupts.len <> 0 { |
|
| 237 | + | if node.interrupts.len <> 4 { throw fdt::Error::Invalid; } |
|
| 238 | + | set irq = try fdt::word(node.interrupts, 0); |
|
| 239 | + | } |
|
| 240 | + | set platform.devices[platform.deviceCount] = Device { region: range, irq }; |
|
| 241 | + | set platform.deviceCount += 1; |
|
| 242 | + | } |
|
| 243 | + | set offset += tupleSize; |
|
| 244 | + | } |
|
| 245 | + | } |
|
| 246 | + | if platform.memoryCount == 0 or platform.hartCount == 0 or platform.frequency == 0 |
|
| 247 | + | or clintNode == fdt::NO_PARENT or plicNode == fdt::NO_PARENT { |
|
| 248 | + | throw fdt::Error::Invalid; |
|
| 249 | + | } |
|
| 250 | + | try routes(tree, &tree.nodes[clintNode], platform, false); |
|
| 251 | + | try routes(tree, &tree.nodes[plicNode], platform, true); |
|
| 252 | + | for i in 0..platform.hartCount { |
|
| 253 | + | let hart = &platform.harts[i]; |
|
| 254 | + | if hart.clintIndex == fdt::NO_PARENT or hart.plicContext == fdt::NO_PARENT |
|
| 255 | + | or 0x4008 + hart.clintIndex as u64 * 8 > platform.clint.size |
|
| 256 | + | or 0x200008 + hart.plicContext as u64 * 0x1000 > platform.plic.size { |
|
| 257 | + | throw fdt::Error::Invalid; |
|
| 258 | + | } |
|
| 259 | + | } |
|
| 260 | + | for i in 0..platform.deviceCount { |
|
| 261 | + | if platform.devices[i].irq > platform.irqCount { throw fdt::Error::Invalid; } |
|
| 262 | + | } |
|
| 263 | + | for i in 0..tree.count { |
|
| 264 | + | let node = &tree.nodes[i]; |
|
| 265 | + | if not fdt::enabled(tree, i) or node.interrupts.len == 0 { continue; } |
|
| 266 | + | let mut current = i; |
|
| 267 | + | let mut controller: u32 = 0; |
|
| 268 | + | while current <> fdt::NO_PARENT and controller == 0 { |
|
| 269 | + | set controller = tree.nodes[current].interruptParent; |
|
| 270 | + | set current = tree.nodes[current].parent; |
|
| 271 | + | } |
|
| 272 | + | if controller == 0 or controller <> tree.nodes[plicNode].phandle { |
|
| 273 | + | throw fdt::Error::Invalid; |
|
| 274 | + | } |
|
| 275 | + | } |
|
| 276 | + | } |
kernel/main.rad
+1 -0
| 1 | 1 | //! Freestanding machine initialization. |
|
| 2 | 2 | ||
| 3 | + | ||
| 3 | 4 | /// Maximum online harts in this kernel build. |
|
| 4 | 5 | constant MAX_HARTS: u64 = 8; |
|
| 5 | 6 | ||
| 6 | 7 | /// Validate the entry hart before machine initialization. |
|
| 7 | 8 | @default fn main(hart: u64) -> u32 { |