kernel: own and clear contiguous physical allocations

71bd6f97ea1d457327d38dae8d7293c13285b60e5185a4ca6747a3795fa09268
Verified: make -C kernel check with the machine-capable emulator; all pass.
Alexis Sellier committed ago 1 parent d88ae1a1
kernel/Makefile +2 -2
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 := core/fdt.rad core/platform.rad
6 +
MODULES := core/fdt.rad core/platform.rad core/frames.rad
7 7
CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES))
8 -
CHECK_MODULES := check/boot.rad check/fixture.rad
8 +
CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad
9 9
10 10
.PHONY: all check clean compiler-check
11 11
all: kernel.rv64
12 12
13 13
compiler-check:
kernel/NOTES.md +12 -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 2 of the 22-step plan.
5 +
record the contracts established through step 3 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.
31 31
- Decode firmware reservations and /reserved-memory. MMIO is not allocatable RAM.
32 32
  CLINT, PLIC, and radiant,finish are machine resources, not user Device authority.
33 33
- Hardware hart identifiers fit the eight-entry build limit. CLINT banks and
34 34
  PLIC contexts come from interrupts-extended, not the hart identifier itself.
35 35
36 +
## Physical frame ownership
37 +
38 +
- Frame indexes and counts use u32: MAX_PAGES = 65536 is not representable in u16.
39 +
- Frame indices address the first 256 MiB of physical memory. Discovered RAM
40 +
  must fit in this build's physical address range.
41 +
- Allocation clears frames before a new domain receives access. Kernel/image,
42 +
  FDT, boot data, firmware reservations, and MMIO regions are excluded.
43 +
- Contiguous allocations validate complete ranges and fail without partial
44 +
  ownership when memory or metadata capacity is unavailable.
45 +
36 46
## Validation
37 47
38 48
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
39 49
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
40 50
checks compiler dependencies. From the repository root, run:
41 51
42 52
```sh
43 53
make -C kernel check
44 54
```
45 55
46 -
Exercise bounded FDT topology discovery, malformed sections, every truncated prefix, reservations, and disabled CPU nodes.
56 +
Exercise contiguous allocation, clearing, reservation exclusion, exhaustion, and exact physical boundaries.
47 57
48 58
The entry probe uses explicit M-mode success/fault finish writes; secondary
49 59
harts idle. This checks machine entry, not user-domain execution. Finish writes
50 60
are a check protocol, not a domain-exit operation.
kernel/check.rad +2 -0
1 1
//! Hosted entry for kernel mechanism checks.
2 2
3 3
mod boot;
4 4
export mod fixture;
5 +
mod frames;
5 6
6 7
/// Run the available kernel mechanism checks.
7 8
@default fn main() -> u32 {
9 +
    frames::run();
8 10
    boot::run();
9 11
    return 0;
10 12
}
kernel/check/frames.rad added +73 -0
1 +
//! Physical allocator bounds, fragmentation, and data-isolation checks.
2 +
3 +
use core::fdt;
4 +
use core::frames;
5 +
use core::platform;
6 +
7 +
/// Test-owned physical memory spanning more than one bitmap word.
8 +
static MEMORY: [u8; 68 * frames::PAGE_SIZE] = undefined;
9 +
10 +
/// Clear only the physical range supplied by the allocator.
11 +
fn clear(base: u64, size: u32) {
12 +
    let start = base as u32;
13 +
    assert base < MEMORY.len as u64;
14 +
    assert size <= MEMORY.len - start;
15 +
    for i in start..start + size { set MEMORY[i] = 0; }
16 +
}
17 +
18 +
/// Require a contiguous allocation request to fail without taking frames.
19 +
fn exhausted(pool: *mut frames::Pool, count: u32) {
20 +
    let available = pool.available;
21 +
    let allocation = try frames::allocate(pool, count, clear) catch {
22 +
        assert pool.available == available;
23 +
        return;
24 +
    };
25 +
    frames::discard(pool, allocation);
26 +
    panic "exhausted: allocation unexpectedly succeeded";
27 +
}
28 +
29 +
/// Check unaligned RAM, reserved overlaps, bitmap boundaries, and zeroing.
30 +
export fn run() {
31 +
    for i in 0..MEMORY.len { set MEMORY[i] = 0xa5; }
32 +
    let mut machine: platform::Platform = undefined;
33 +
    set machine.memoryCount = 1;
34 +
    set machine.memory[0] = fdt::Range {
35 +
        base: frames::PAGE_SIZE as u64 + 1,
36 +
        size: frames::PAGE_SIZE as u64 * 66 - 1,
37 +
    };
38 +
    set machine.reservedCount = 1;
39 +
    set machine.reserved[0] = fdt::Range {
40 +
        base: frames::PAGE_SIZE as u64 * 3 + 1,
41 +
        size: frames::PAGE_SIZE as u64,
42 +
    };
43 +
    let mut pool: frames::Pool = undefined;
44 +
    try! frames::init(&mut pool, &machine);
45 +
    assert pool.available == 63;
46 +
    let allocation = try! frames::allocate(&mut pool, 2, clear);
47 +
    let run = frames::install(allocation);
48 +
    assert run.first == 5 and run.count == 2;
49 +
    for i in 5 * frames::PAGE_SIZE..7 * frames::PAGE_SIZE {
50 +
        assert MEMORY[i] == 0;
51 +
    }
52 +
    assert MEMORY[5 * frames::PAGE_SIZE - 1] == 0xa5;
53 +
    assert MEMORY[7 * frames::PAGE_SIZE] == 0xa5;
54 +
    exhausted(&mut pool, 61);
55 +
    let rest = try! frames::allocate(&mut pool, 60, clear);
56 +
    let last = try! frames::allocate(&mut pool, 1, clear);
57 +
    assert pool.available == 0;
58 +
    exhausted(&mut pool, 1);
59 +
    frames::discard(&mut pool, last);
60 +
    frames::discard(&mut pool, rest);
61 +
    frames::reclaim(&mut pool, run);
62 +
    assert pool.available == 63;
63 +
64 +
    set machine.memory[0] = fdt::Range { base: 0, size: MEMORY.len as u64 };
65 +
    set machine.reservedCount = 0;
66 +
    try! frames::init(&mut pool, &machine);
67 +
    let whole = try! frames::allocate(&mut pool, 68, clear);
68 +
    assert pool.available == 0;
69 +
    frames::discard(&mut pool, whole);
70 +
    assert pool.available == 68;
71 +
    exhausted(&mut pool, 0);
72 +
    exhausted(&mut pool, frames::MAX_FRAMES + 1);
73 +
}
kernel/core.rad +1 -0
1 1
//! Safe kernel mechanisms shared by machine execution and hosted checks.
2 2
3 3
export mod fdt;
4 4
export mod platform;
5 +
export mod frames;
kernel/core/frames.rad added +144 -0
1 +
//! Contiguous physical frames with explicit allocation ownership.
2 +
3 +
use core::fdt;
4 +
use core::platform;
5 +
6 +
/// Physical allocation unit in bytes.
7 +
export constant PAGE_SIZE: u32 = 4096;
8 +
/// Number of physical frames supported by this build.
9 +
export constant MAX_FRAMES: u32 = 65536;
10 +
/// Words in the frame availability bitmap.
11 +
constant BITMAP_WORDS: u32 = MAX_FRAMES / 64;
12 +
13 +
/// Invalid allocation input or insufficient contiguous frames.
14 +
export union Error: Copy {
15 +
    /// The request has zero frames or exceeds the supported physical range.
16 +
    Invalid,
17 +
    /// No contiguous free range satisfies the request.
18 +
    OutOfMemory,
19 +
}
20 +
21 +
/// Physical range metadata held by kernel page objects.
22 +
export record Run: Copy {
23 +
    /// Index of the first physical frame.
24 +
    first: u32,
25 +
    /// Number of contiguous frames.
26 +
    count: u32,
27 +
}
28 +
29 +
/// An allocation that must be installed into a kernel object or returned.
30 +
export union Allocation: Once {
31 +
    /// Cleared frames exclusively held by this obligation.
32 +
    Frames(Run),
33 +
}
34 +
35 +
/// A bitmap of available physical frames.
36 +
export record Pool: Copy {
37 +
    /// A set bit identifies a complete, unreserved, unallocated RAM frame.
38 +
    free: [u64; BITMAP_WORDS],
39 +
    /// Exclusive high frame index of discovered RAM.
40 +
    limit: u32,
41 +
    /// Number of currently available frames.
42 +
    available: u32,
43 +
}
44 +
45 +
/// Initialize available frames from physical RAM and boot reservations.
46 +
export fn init(pool: *mut Pool, machine: *platform::Platform) throws (Error) {
47 +
    for i in 0..BITMAP_WORDS { set pool.free[i] = 0; }
48 +
    set pool.limit = 0;
49 +
    set pool.available = 0;
50 +
    let physicalLimit = MAX_FRAMES as u64 * PAGE_SIZE as u64;
51 +
    for i in 0..machine.memoryCount {
52 +
        let range = machine.memory[i];
53 +
        if range.base > physicalLimit or range.size > physicalLimit - range.base {
54 +
            throw Error::Invalid;
55 +
        }
56 +
        let first = ((range.base + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64) as u32;
57 +
        let end = ((range.base + range.size) / PAGE_SIZE as u64) as u32;
58 +
        for frame in first..end {
59 +
            let bit = 1 as u64 << (frame as u64 & 63);
60 +
            if pool.free[frame / 64] & bit == 0 {
61 +
                set pool.free[frame / 64] |= bit;
62 +
                set pool.available += 1;
63 +
            }
64 +
        }
65 +
        if end > pool.limit { set pool.limit = end; }
66 +
    }
67 +
    for i in 0..machine.reservedCount {
68 +
        let range = machine.reserved[i];
69 +
        if range.size == 0 or range.base > 0xffffffffffffffff - range.size {
70 +
            throw Error::Invalid;
71 +
        }
72 +
        let first = range.base / PAGE_SIZE as u64;
73 +
        let endByte = range.base + range.size;
74 +
        let mut end = endByte / PAGE_SIZE as u64;
75 +
        if endByte % PAGE_SIZE as u64 <> 0 { set end += 1; }
76 +
        if first >= pool.limit as u64 { continue; }
77 +
        if end > pool.limit as u64 { set end = pool.limit as u64; }
78 +
        for frame in first as u32..end as u32 {
79 +
            let bit = 1 as u64 << (frame as u64 & 63);
80 +
            if pool.free[frame / 64] & bit <> 0 {
81 +
                set pool.free[frame / 64] &= ~bit;
82 +
                set pool.available -= 1;
83 +
            }
84 +
        }
85 +
    }
86 +
}
87 +
88 +
/// Take contiguous frames and clear them before returning their ownership.
89 +
/// The clear function must write exactly the supplied physical byte range.
90 +
export fn allocate(pool: *mut Pool, count: u32, clear: fn(u64, u32)) -> Allocation throws (Error) {
91 +
    if count == 0 or count > MAX_FRAMES { throw Error::Invalid; }
92 +
    if count > pool.available { throw Error::OutOfMemory; }
93 +
    let mut length: u32 = 0;
94 +
    let mut frame: u32 = 0;
95 +
    while frame < pool.limit {
96 +
        let word = pool.free[frame / 64];
97 +
        if word == 0 {
98 +
            set length = 0;
99 +
            set frame = (frame + 64) & ~63;
100 +
            continue;
101 +
        }
102 +
        if word & (1 as u64 << (frame as u64 & 63)) <> 0 {
103 +
            set length += 1;
104 +
            if length == count {
105 +
                let first = frame + 1 - count;
106 +
                for current in first..frame + 1 {
107 +
                    set pool.free[current / 64] &= ~(1 as u64 << (current as u64 & 63));
108 +
                }
109 +
                set pool.available -= count;
110 +
                clear(first as u64 * PAGE_SIZE as u64, count * PAGE_SIZE);
111 +
                return Allocation::Frames(Run { first, count });
112 +
            }
113 +
        } else {
114 +
            set length = 0;
115 +
        }
116 +
        set frame += 1;
117 +
    }
118 +
    throw Error::OutOfMemory;
119 +
}
120 +
121 +
/// Consume allocation ownership when a page object assumes its lifetime.
122 +
export fn install(allocation: Allocation) -> Run {
123 +
    match allocation {
124 +
        case Allocation::Frames(run) => return run,
125 +
    }
126 +
}
127 +
128 +
/// Return frames after all object and persistent-grant lifetimes have ended.
129 +
export fn reclaim(pool: *mut Pool, run: Run) {
130 +
    assert run.count > 0 and run.first <= pool.limit;
131 +
    assert run.count <= pool.limit - run.first;
132 +
    for frame in run.first..run.first + run.count {
133 +
        let bit = 1 as u64 << (frame as u64 & 63);
134 +
        assert pool.free[frame / 64] & bit == 0;
135 +
        set pool.free[frame / 64] |= bit;
136 +
    }
137 +
    set pool.available += run.count;
138 +
}
139 +
140 +
/// Return an allocation that was not installed into a page object.
141 +
export fn discard(pool: *mut Pool, allocation: Allocation) {
142 +
    let run = install(allocation);
143 +
    reclaim(pool, run);
144 +
}