kernel: Allocate and split physical memory

5a24e04e0f1b0fe955db5bec3265bef99236f0f37f065479d4c4142c4e31b294
Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 79aa6df5
Makefile +10 -6
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test
26 +
test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
# Emulator command check
66 66
		lib/std.test.rv64.s \
67 67
		lib/std.test.rv64.o
68 68
69 69
# Kernel modules and tests use a separate package and test entry point.
70 70
KERNEL_SRC := kernel/kernel.rad $(shell find kernel/kernel -name '*.rad' ! -name 'tests.rad' ! -path '*/tests/*' 2>/dev/null)
71 -
KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras
71 +
KERNEL_ASM := kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras
72 72
KERNEL_TEST_SRC := kernel/kernel/tests.rad $(shell find kernel/kernel/tests -name '*.rad' 2>/dev/null)
73 73
KERNEL := -pkg kernel $(patsubst %,-mod %,$(sort $(KERNEL_SRC)))
74 74
KERNEL_TEST := $(BIN_DIR)/kernel.test.rv64
75 75
76 76
kernel-test: emulator $(KERNEL_TEST)
83 83
84 84
# Native kernel image and machine boot checks.
85 85
kernel-boot-test: $(BIN_DIR)/kernel.rv64
86 86
	@RAD_EMULATOR="$(EMU)" sh test/boot/run
87 87
88 -
$(BIN_DIR)/kernel.ril: $(KERNEL_SRC) $(RAD_BIN)
89 -
	@$(RADIANCE) $(KERNEL) -entry kernel -ril $(BIN_DIR)
88 +
$(BIN_DIR)/kernel.ril $(BIN_DIR)/std.ril &: $(KERNEL_SRC) $(STD_LIB) $(RAD_BIN)
89 +
	@$(RADIANCE) $(STD) $(KERNEL) -entry kernel -ril $(BIN_DIR)
90 90
91 91
$(BIN_DIR)/kernel.build.rv64: kernel/tools/build.rad $(STD_LIB) $(RAD_BIN)
92 92
	@$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@
93 93
94 -
$(BIN_DIR)/kernel.rv64: $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.build.rv64 $(KERNEL_ASM)
94 +
$(BIN_DIR)/kernel.rv64: $(BIN_DIR)/std.ril $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.build.rv64 $(KERNEL_ASM)
95 95
	@cat $(KERNEL_ASM) > $(BIN_DIR)/kernel.ras
96 -
	@$(EMU) -run $(BIN_DIR)/kernel.build.rv64 -- $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.ras $@
96 +
	@$(EMU) $(EMU_FLAGS) -run $(BIN_DIR)/kernel.build.rv64 -- $(BIN_DIR)/std.ril $(BIN_DIR)/kernel.ril $(BIN_DIR)/kernel.ras $@
97 97
98 98
# Separate binary package emission and decode checks.
99 99
package-test: emulator $(BIN_DIR)/packages.check.rv64
100 100
	@sh test/packages/run
101 101
192 192
.SILENT:
193 193
194 194
# Integer trap contexts and nested machine interrupts.
195 195
trap-test: $(BIN_DIR)/sync.build.rv64
196 196
	@RAD_EMULATOR="$(EMU)" sh test/trap/run
197 +
198 +
# Physical frame zeroing at native addresses.
199 +
page-test: $(BIN_DIR)/sync.build.rv64
200 +
	@RAD_EMULATOR="$(EMU)" sh test/pages/run
kernel/kernel.rad +5 -2
7 7
export mod limits;
8 8
export mod slots;
9 9
export mod range;
10 10
export mod sync;
11 11
export mod platform;
12 -
@test export mod tests;
13 12
export mod trap;
14 -
export mod boot;
15 13
export mod capability;
14 +
export mod frames;
15 +
export mod backing;
16 +
export mod pages;
17 +
export mod boot;
18 +
@test export mod tests;
kernel/kernel/backing.rad added +121 -0
1 +
//! Allocation lifetimes shared by split pages and exposed domains.
2 +
3 +
use std::lang::gen::bitset;
4 +
use super::abi;
5 +
use super::limits;
6 +
use super::slots;
7 +
use super::frames;
8 +
9 +
/// Words required for one exposure bit per domain slot.
10 +
constant EXPOSURE_WORDS: u32 = (limits::DOMAINS + 31) / 32;
11 +
12 +
/// A physical allocation retained independently of its current page objects.
13 +
export record Allocation: Copy {
14 +
    /// Complete physical frame ownership, unchanged by page splits.
15 +
    run: frames::Run,
16 +
    /// Number of page objects with live handles.
17 +
    pages: u32,
18 +
    /// Number of live domains with lifetime exposure.
19 +
    exposed: u32,
20 +
    /// Domain-index exposure bitmap. Domain exit clears bits before slot reuse.
21 +
    exposures: [u32; EXPOSURE_WORDS],
22 +
}
23 +
24 +
/// Fixed allocation records and generation-checked exposure-domain membership.
25 +
export record Store: Copy {
26 +
    /// Physical frames owned by backing allocations.
27 +
    pool: frames::Pool,
28 +
    /// Backing-record lifetimes.
29 +
    slots: [slots::Slot; limits::ALLOCATIONS],
30 +
    /// Payloads for live backing records.
31 +
    records: [Allocation; limits::ALLOCATIONS],
32 +
    /// Live domain generation, or zero after all of that domain's exposures end.
33 +
    domains: [u32; limits::DOMAINS],
34 +
}
35 +
36 +
/// Initialize fresh backing and exposure-domain tables.
37 +
export fn initialize(store: &mut Store) {
38 +
    set store.pool.count = 0;
39 +
    slots::initialize(&mut store.slots[..]);
40 +
    for i in 0..limits::DOMAINS { set store.domains[i] = 0; }
41 +
}
42 +
43 +
/// Register a domain before it can receive allocation exposure.
44 +
export fn registerDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) {
45 +
    if domain.index >= limits::DOMAINS or domain.generation == 0 { throw abi::Error::InvalidArg; }
46 +
    if store.domains[domain.index] <> 0 { throw abi::Error::Busy; }
47 +
    set store.domains[domain.index] = domain.generation;
48 +
}
49 +
50 +
/// Test exposure-domain generation without reading beyond the fixed table.
51 +
export fn domainLive(store: &Store, domain: abi::Ref) -> bool {
52 +
    return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation;
53 +
}
54 +
55 +
/// Publish reserved metadata with one page object and its live origin exposure.
56 +
/// The caller owns the committed run and has validated the origin domain.
57 +
export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, run: frames::Run, origin: abi::Ref) -> abi::Ref {
58 +
    assert domainLive(store, origin);
59 +
    let object = slots::reference(&reservation);
60 +
    assert slots::matches(&store.slots[..], object, slots::State::Reserved);
61 +
    set store.records[object.index].run = run;
62 +
    set store.records[object.index].pages = 1;
63 +
    set store.records[object.index].exposed = 1;
64 +
    let mut bits = bitset::init(&mut store.records[object.index].exposures[..]);
65 +
    bitset::put(&mut bits, origin.index);
66 +
    return try! slots::commit(&mut store.slots[..], reservation);
67 +
}
68 +
69 +
/// Require a live generation-bearing backing reference.
70 +
fn require(store: &Store, object: abi::Ref) throws (abi::Error) {
71 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; }
72 +
}
73 +
74 +
/// Add one split page to the allocation's lifetime count.
75 +
export fn retainPage(store: &mut Store, object: abi::Ref) throws (abi::Error) {
76 +
    try require(store, object);
77 +
    if store.records[object.index].pages == 0 { throw abi::Error::BadHandle; }
78 +
    if store.records[object.index].pages == 0xffffffff { throw abi::Error::Exhausted; }
79 +
    set store.records[object.index].pages += 1;
80 +
}
81 +
82 +
/// Record access for a domain for the rest of that domain generation's lifetime.
83 +
export unsafe fn expose(store: &mut Store, object: abi::Ref, domain: abi::Ref) throws (abi::Error) {
84 +
    try require(store, object);
85 +
    if store.records[object.index].pages == 0 or not domainLive(store, domain) { throw abi::Error::BadHandle; }
86 +
    let mut bits = bitset::new(&mut store.records[object.index].exposures[..]);
87 +
    if bitset::contains(&bits, domain.index) { return; }
88 +
    bitset::put(&mut bits, domain.index);
89 +
    set store.records[object.index].exposed += 1;
90 +
}
91 +
92 +
/// Reclaim only when no page object and no domain exposure remains.
93 +
fn reclaim(store: &mut Store, object: abi::Ref) {
94 +
    if store.records[object.index].pages <> 0 or store.records[object.index].exposed <> 0 { return; }
95 +
    let run = store.records[object.index].run;
96 +
    try! frames::release(&mut store.pool, run);
97 +
    try! slots::release(&mut store.slots[..], object);
98 +
}
99 +
100 +
/// Release one page object after its final handle disappears.
101 +
export fn releasePage(store: &mut Store, object: abi::Ref) throws (abi::Error) {
102 +
    try require(store, object);
103 +
    if store.records[object.index].pages == 0 { throw abi::Error::BadHandle; }
104 +
    set store.records[object.index].pages -= 1;
105 +
    reclaim(store, object);
106 +
}
107 +
108 +
/// End all exposure for one domain before its slot can be registered again.
109 +
/// Callers serialize this bounded scan with allocation and capability changes.
110 +
export unsafe fn endDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) {
111 +
    if not domainLive(store, domain) { throw abi::Error::BadHandle; }
112 +
    for i in 0..limits::ALLOCATIONS {
113 +
        if store.slots[i].state <> slots::State::Live { continue; }
114 +
        let mut bits = bitset::new(&mut store.records[i].exposures[..]);
115 +
        if not bitset::contains(&bits, domain.index) { continue; }
116 +
        bitset::clear(&mut bits, domain.index);
117 +
        set store.records[i].exposed -= 1;
118 +
        reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation });
119 +
    }
120 +
    set store.domains[domain.index] = 0;
121 +
}
kernel/kernel/boot.rad +2 -0
4 4
use super::fdt;
5 5
use super::range;
6 6
use super::limits;
7 7
use super::sync;
8 8
use super::trap;
9 +
use super::pages;
9 10
10 11
/// Platform data published by hart zero before secondary initialization.
11 12
export unsafe static PLATFORM: platform::Platform = undefined;
12 13
/// Release/acquire publication flag for PLATFORM.
13 14
static READY: u64 = 0;
58 59
        let blob = @sliceOf(tree, size);
59 60
        try! platform::decode(&blob[..], &mut PLATFORM);
60 61
        let treeRange = range::new(treeAddress, size as u64) else panic "FDT range";
61 62
        assert platform::inRam(&PLATFORM, treeRange);
62 63
        try! platform::protect(&mut PLATFORM, treeRange);
64 +
        try! pages::initialize(&mut pages::STORE, &PLATFORM);
63 65
        print("kernel: platform ready\n");
64 66
        sync::storeRelease(&mut READY, 1);
65 67
    } else {
66 68
        while sync::loadAcquire(&READY) == 0 {}
67 69
    }
kernel/kernel/boot.ras +2 -1
17 17
    csrw mstatus %zero;
18 18
    csrw mscratch %sp;
19 19
    mv %a2 %sp;
20 20
    addi %sp %sp -16;
21 21
    sd %ra 0(%sp);
22 -
    call @kernel::boot::enter;
22 +
    la %t0 @kernel::boot::enter;
23 +
    jalr %ra %t0 0;
23 24
    ld %ra 0(%sp);
24 25
    addi %sp %sp 16;
25 26
    ret;
26 27
27 28
@kernel::boot::read8
kernel/kernel/capability.rad +7 -0
35 35
/// Invalid metadata or exhausted capacity leaves the table unchanged.
36 36
export fn install(table: &mut Table, entry: Entry) -> abi::Handle throws (abi::Error) {
37 37
    if entry.kind == abi::Kind::Empty or entry.object.generation == 0 { throw abi::Error::InvalidArg; }
38 38
    let rights = try abi::rights(*entry.rights as u64);
39 39
    let reservation = try slots::reserve(&mut table.slots[..]);
40 +
    return publish(table, reservation, entry);
41 +
}
42 +
43 +
/// Publish valid authority into a reservation from this table.
44 +
/// The caller has validated metadata and acquired the object's lifetime reference.
45 +
export fn publish(table: &mut Table, reservation: slots::Reservation, entry: Entry) -> abi::Handle {
40 46
    let object = slots::reference(&reservation);
47 +
    assert slots::matches(&table.slots[..], object, slots::State::Reserved);
41 48
    set table.entries[object.index] = entry;
42 49
    let live = try! slots::commit(&mut table.slots[..], reservation);
43 50
    return try! abi::handle(entry.kind, live);
44 51
}
45 52
kernel/kernel/frames.rad added +131 -0
1 +
//! Bounded physical-frame reservations. Callers serialize pool mutations.
2 +
3 +
use super::abi;
4 +
use super::limits;
5 +
use super::platform;
6 +
use super::range;
7 +
8 +
/// A contiguous allocation represented by indices in the physical-frame map.
9 +
export record Run: Copy {
10 +
    /// First frame-map index.
11 +
    first: u32,
12 +
    /// Number of physically adjacent frames.
13 +
    count: u32,
14 +
}
15 +
16 +
/// Reserved frames that must be committed or cancelled in the same pool.
17 +
export union Reservation: Once {
18 +
    /// Frames excluded from subsequent allocation scans.
19 +
    Held(Run),
20 +
}
21 +
22 +
/// Physical addresses and occupancy of allocatable frames, sorted by address.
23 +
export record Pool: Copy {
24 +
    /// Physical base address of each eligible frame.
25 +
    addresses: [u64; limits::FRAMES],
26 +
    /// True when the frame is available for reservation.
27 +
    free: [bool; limits::FRAMES],
28 +
    /// Number of valid frame-map entries.
29 +
    count: u32,
30 +
}
31 +
32 +
/// Insert one range into an address-sorted prefix of fixed storage.
33 +
fn insert(ranges: &mut [range::Range], count: u32, value: range::Range) {
34 +
    let mut at = count;
35 +
    while at > 0 and ranges[at - 1].start > value.start {
36 +
        set ranges[at] = ranges[at - 1]; set at -= 1;
37 +
    }
38 +
    set ranges[at] = value;
39 +
}
40 +
41 +
/// Build a fresh frame map from complete RAM pages with no reserved byte.
42 +
/// The pool has zero valid entries if initialization fails.
43 +
export unsafe fn initialize(pool: &mut Pool, machine: &platform::Platform) throws (abi::Error) {
44 +
    set pool.count = 0;
45 +
    if machine.ramCount > platform::RAM_BANKS or machine.reservedCount > platform::RESERVATIONS {
46 +
        throw abi::Error::InvalidArg;
47 +
    }
48 +
    let mut reserved: [range::Range; platform::RESERVATIONS] = undefined;
49 +
    for i in 0..machine.reservedCount {
50 +
        let value = machine.reserved[i];
51 +
        if value.start >= value.end { throw abi::Error::InvalidArg; }
52 +
        insert(&mut reserved[..], i, value);
53 +
    }
54 +
    let mut banks: [range::Range; platform::RAM_BANKS] = undefined;
55 +
    for i in 0..machine.ramCount {
56 +
        let bank = machine.ram[i];
57 +
        if bank.start >= bank.end { throw abi::Error::InvalidArg; }
58 +
        insert(&mut banks[..], i, bank);
59 +
    }
60 +
    let mut count: u32 = 0;
61 +
    let mut next: u32 = 0;
62 +
    for i in 0..machine.ramCount {
63 +
        let bank = banks[i];
64 +
        if i > 0 and banks[i - 1].end > bank.start { throw abi::Error::InvalidArg; }
65 +
        let padding = (limits::FRAME_SIZE - (bank.start & (limits::FRAME_SIZE - 1))) & (limits::FRAME_SIZE - 1);
66 +
        if padding > bank.end - bank.start { continue; }
67 +
        let mut address = bank.start + padding;
68 +
        let end = bank.end & ~(limits::FRAME_SIZE - 1);
69 +
        while address < end {
70 +
            while next < machine.reservedCount and reserved[next].end <= address { set next += 1; }
71 +
            if next < machine.reservedCount and reserved[next].start < address + limits::FRAME_SIZE {
72 +
                if reserved[next].end >= end { break; }
73 +
                set address = (reserved[next].end + limits::FRAME_SIZE - 1) & ~(limits::FRAME_SIZE - 1);
74 +
                continue;
75 +
            }
76 +
            if count == limits::FRAMES { throw abi::Error::Exhausted; }
77 +
            set pool.addresses[count] = address;
78 +
            set pool.free[count] = true;
79 +
            set count += 1;
80 +
            set address += limits::FRAME_SIZE;
81 +
        }
82 +
    }
83 +
    set pool.count = count;
84 +
}
85 +
86 +
/// Reserve the first physically contiguous free run with a bounded scan.
87 +
export fn reserve(pool: &mut Pool, count: u32) -> Reservation throws (abi::Error) {
88 +
    if count == 0 { throw abi::Error::InvalidArg; }
89 +
    let mut first: u32 = 0;
90 +
    let mut found: u32 = 0;
91 +
    let mut previous: u64 = 0;
92 +
    for i in 0..pool.count {
93 +
        if not pool.free[i] { set found = 0; continue; }
94 +
        if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { set first = i; set found = 0; }
95 +
        set found += 1;
96 +
        set previous = pool.addresses[i];
97 +
        if found == count {
98 +
            for j in first..first + count { set pool.free[j] = false; }
99 +
            return Reservation::Held(Run { first, count });
100 +
        }
101 +
    }
102 +
    throw abi::Error::OutOfMemory;
103 +
}
104 +
105 +
/// Consume a reservation into a backing allocation's frame ownership.
106 +
export fn commit(reservation: Reservation) -> Run {
107 +
    match reservation { case Reservation::Held(run) => return run, }
108 +
}
109 +
110 +
/// Resolve a complete physical run and reject map gaps or invalid indices.
111 +
export fn extent(pool: &Pool, run: Run) -> range::Range throws (abi::Error) {
112 +
    if run.count == 0 or run.first >= pool.count or run.count > pool.count - run.first { throw abi::Error::BadHandle; }
113 +
    let start = pool.addresses[run.first];
114 +
    let length = run.count as u64 * limits::FRAME_SIZE;
115 +
    let result = range::new(start, length) else { throw abi::Error::BadHandle; };
116 +
    if pool.addresses[run.first + run.count - 1] <> result.end - limits::FRAME_SIZE { throw abi::Error::BadHandle; }
117 +
    return result;
118 +
}
119 +
120 +
/// Return a backing allocation's frames after all handles and exposures end.
121 +
/// Only the owner of a committed run may release it.
122 +
export fn release(pool: &mut Pool, run: Run) throws (abi::Error) {
123 +
    let memory = try extent(pool, run);
124 +
    for i in run.first..run.first + run.count { if pool.free[i] { throw abi::Error::BadHandle; } }
125 +
    for i in run.first..run.first + run.count { set pool.free[i] = true; }
126 +
}
127 +
128 +
/// Cancel unpublished frame ownership and restore its capacity.
129 +
export fn cancel(pool: &mut Pool, reservation: Reservation) throws (abi::Error) {
130 +
    match reservation { case Reservation::Held(run) => try release(pool, run), }
131 +
}
kernel/kernel/pages.rad added +132 -0
1 +
//! Physical page objects, allocation, and exclusive splitting.
2 +
3 +
use super::abi;
4 +
use super::limits;
5 +
use super::slots;
6 +
use super::frames;
7 +
use super::backing;
8 +
use super::capability;
9 +
use super::platform;
10 +
11 +
/// Page capability rights created by physical allocation.
12 +
export constant DEFAULT_RIGHTS: u16 = abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER;
13 +
14 +
/// One page-aligned segment of a retained backing allocation.
15 +
export record Page: Copy {
16 +
    /// Common backing record, including the complete allocation's exposures.
17 +
    backing: abi::Ref,
18 +
    /// First physical byte of this segment.
19 +
    base: u64,
20 +
    /// Number of contiguous frames in this segment.
21 +
    count: u32,
22 +
    /// Domain that created the original allocation.
23 +
    origin: abi::Ref,
24 +
    /// Number of live capabilities that name this page object.
25 +
    handles: u32,
26 +
}
27 +
28 +
/// Fixed physical-memory metadata. Callers serialize shared mutations.
29 +
export record Store: Copy {
30 +
    /// Page-object slot lifetimes.
31 +
    slots: [slots::Slot; limits::PAGES],
32 +
    /// Live page-object payloads.
33 +
    records: [Page; limits::PAGES],
34 +
    /// Backing allocations and domain exposures.
35 +
    backings: backing::Store,
36 +
}
37 +
38 +
/// Physical-memory state shared by the kernel.
39 +
export unsafe static STORE: Store = undefined;
40 +
41 +
/// Zero complete, mapped physical frames. The base must be page-aligned.
42 +
fn zero(base: u64, count: u32);
43 +
44 +
/// Initialize physical-memory metadata before creating domains or pages.
45 +
export unsafe fn initialize(store: &mut Store, machine: &platform::Platform) throws (abi::Error) {
46 +
    backing::initialize(&mut store.backings);
47 +
    try frames::initialize(&mut store.backings.pool, machine);
48 +
    slots::initialize(&mut store.slots[..]);
49 +
}
50 +
51 +
/// Read a live page object through its internal generation-bearing reference.
52 +
export fn get(store: &Store, object: abi::Ref) -> Page throws (abi::Error) {
53 +
    if not slots::matches(&store.slots[..], object, slots::State::Live) { throw abi::Error::BadHandle; }
54 +
    return store.records[object.index];
55 +
}
56 +
57 +
/// Allocate and zero frames before publishing a page and its capability.
58 +
/// Allocation authority must name the calling domain. Failure cancels reservations.
59 +
export unsafe fn allocate(store: &mut Store, table: &mut capability::Table, authority: abi::Handle, count: u64)
60 +
    -> abi::Handle throws (abi::Error)
61 +
{
62 +
    if count == 0 { throw abi::Error::InvalidArg; }
63 +
    if count > limits::FRAMES as u64 { throw abi::Error::OutOfMemory; }
64 +
    let owner = table.owner;
65 +
    let permission = try capability::authority(table, authority, abi::Rights(abi::ALLOCATE));
66 +
    if permission.object <> owner { throw abi::Error::Denied; }
67 +
    if not backing::domainLive(&store.backings, owner) { throw abi::Error::BadHandle; }
68 +
    let handleSlot = try slots::reserve(&mut table.slots[..]);
69 +
    let pageSlot = try slots::reserve(&mut store.slots[..]) catch err {
70 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
71 +
    };
72 +
    let backingSlot = try slots::reserve(&mut store.backings.slots[..]) catch err {
73 +
        try! slots::cancel(&mut store.slots[..], pageSlot);
74 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
75 +
    };
76 +
    let memory = try frames::reserve(&mut store.backings.pool, count as u32) catch err {
77 +
        try! slots::cancel(&mut store.backings.slots[..], backingSlot);
78 +
        try! slots::cancel(&mut store.slots[..], pageSlot);
79 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
80 +
    };
81 +
    let run = frames::commit(memory);
82 +
    let extent = try! frames::extent(&store.backings.pool, run);
83 +
    zero(extent.start, run.count);
84 +
    let allocation = backing::publish(&mut store.backings, backingSlot, run, owner);
85 +
    let object = slots::reference(&pageSlot);
86 +
    set store.records[object.index] = Page { backing: allocation, base: extent.start, count: run.count, origin: owner, handles: 1 };
87 +
    let page = try! slots::commit(&mut store.slots[..], pageSlot);
88 +
    return capability::publish(table, handleSlot, capability::Entry {
89 +
        kind: abi::Kind::Page, object: page, rights: abi::Rights(DEFAULT_RIGHTS),
90 +
    });
91 +
}
92 +
93 +
/// Split an exclusive page into two segments with the same backing and rights.
94 +
export fn split(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, leftCount: u64)
95 +
    -> abi::Handle throws (abi::Error)
96 +
{
97 +
    let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0));
98 +
    let source = try get(store, entry.object);
99 +
    if leftCount == 0 or leftCount >= source.count as u64 { throw abi::Error::InvalidArg; }
100 +
    if source.handles <> 1 { throw abi::Error::Busy; }
101 +
    let handleSlot = try slots::reserve(&mut table.slots[..]);
102 +
    let pageSlot = try slots::reserve(&mut store.slots[..]) catch err {
103 +
        try! slots::cancel(&mut table.slots[..], handleSlot); throw err;
104 +
    };
105 +
    let object = slots::reference(&pageSlot);
106 +
    try! backing::retainPage(&mut store.backings, source.backing);
107 +
    set store.records[object.index] = Page {
108 +
        backing: source.backing, base: source.base + leftCount * limits::FRAME_SIZE,
109 +
        count: source.count - leftCount as u32, origin: source.origin, handles: 1,
110 +
    };
111 +
    set store.records[entry.object.index].count = leftCount as u32;
112 +
    let page = try! slots::commit(&mut store.slots[..], pageSlot);
113 +
    return capability::publish(table, handleSlot, capability::Entry { kind: abi::Kind::Page, object: page, rights: entry.rights });
114 +
}
115 +
116 +
/// Add a capability reference and lifetime exposure before publishing that capability.
117 +
export unsafe fn retain(store: &mut Store, object: abi::Ref, receiver: abi::Ref) throws (abi::Error) {
118 +
    let page = try get(store, object);
119 +
    if page.handles == 0xffffffff { throw abi::Error::Exhausted; }
120 +
    try backing::expose(&mut store.backings, page.backing, receiver);
121 +
    set store.records[object.index].handles += 1;
122 +
}
123 +
124 +
/// Release a capability reference. The backing retains all lifetime exposures.
125 +
export fn release(store: &mut Store, object: abi::Ref) throws (abi::Error) {
126 +
    let page = try get(store, object);
127 +
    assert page.handles > 0;
128 +
    set store.records[object.index].handles -= 1;
129 +
    if page.handles > 1 { return; }
130 +
    try! slots::release(&mut store.slots[..], object);
131 +
    try! backing::releasePage(&mut store.backings, page.backing);
132 +
}
kernel/kernel/pages.ras added +15 -0
1 +
//! Zero physical frames before capability publication.
2 +
.text;
3 +
.export @kernel::pages::zero;
4 +
5 +
// Physical frame bases and lengths are multiples of 4096 bytes.
6 +
@kernel::pages::zero
7 +
    beqz %a1 @done;
8 +
    slli %a1 %a1 12;
9 +
    add %t0 %a0 %a1;
10 +
@clear
11 +
    sd %zero 0(%a0);
12 +
    addi %a0 %a0 8;
13 +
    bne %a0 %t0 @clear;
14 +
@done
15 +
    ret;
kernel/kernel/tests.rad +3 -0
5 5
export mod slots;
6 6
export mod fdt;
7 7
export mod platform;
8 8
export mod trap;
9 9
export mod capability;
10 +
export mod frames;
11 +
export mod backing;
12 +
export mod pages;
kernel/kernel/tests/backing.rad added +95 -0
1 +
//! Backing allocations outlive page objects until every exposure ends.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::frames;
6 +
use kernel::backing;
7 +
use kernel::slots;
8 +
9 +
/// Fixed backing-store workspace.
10 +
unsafe static STORE: backing::Store = undefined;
11 +
12 +
/// Initialize two domains and four synthetic physical frames.
13 +
unsafe fn initialize() {
14 +
    backing::initialize(&mut STORE);
15 +
    try! backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
16 +
    try! backing::registerDomain(&mut STORE, abi::Ref { index: 1, generation: 1 });
17 +
    set STORE.pool.count = 4;
18 +
    for i in 0..4 { set STORE.pool.addresses[i] = 0x80000000 + i as u64 * 4096; set STORE.pool.free[i] = true; }
19 +
}
20 +
21 +
/// Publish one backing record with its initial page and origin exposure.
22 +
unsafe fn allocate() -> abi::Ref {
23 +
    let slot = try! slots::reserve(&mut STORE.slots[..]);
24 +
    let reservation = try! frames::reserve(&mut STORE.pool, 4);
25 +
    let run = frames::commit(reservation);
26 +
    return backing::publish(&mut STORE, slot, run, abi::Ref { index: 0, generation: 1 });
27 +
}
28 +
29 +
/// Dropping all page handles preserves capacity while an exposed domain lives.
30 +
@test unsafe fn exposures() throws (testing::TestError) {
31 +
    initialize();
32 +
    let object = allocate();
33 +
    let receiver = abi::Ref { index: 1, generation: 1 };
34 +
    try! backing::expose(&mut STORE, object, receiver);
35 +
    try! backing::expose(&mut STORE, object, receiver);
36 +
    try testing::expect(STORE.records[object.index].exposed == 2);
37 +
    try! backing::releasePage(&mut STORE, object);
38 +
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
39 +
    try testing::expect(not STORE.pool.free[0] and slots::matches(&STORE.slots[..], object, slots::State::Live));
40 +
    try! backing::endDomain(&mut STORE, receiver);
41 +
    for i in 0..4 { try testing::expect(STORE.pool.free[i]); }
42 +
    try testing::expect(not slots::matches(&STORE.slots[..], object, slots::State::Live));
43 +
}
44 +
45 +
/// Splits keep their common backing alive until every page object is gone.
46 +
@test unsafe fn splitLifetime() throws (testing::TestError) {
47 +
    initialize();
48 +
    let object = allocate();
49 +
    try! backing::retainPage(&mut STORE, object);
50 +
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
51 +
    try! backing::releasePage(&mut STORE, object);
52 +
    try testing::expect(not STORE.pool.free[0] and STORE.records[object.index].pages == 1);
53 +
    try! backing::releasePage(&mut STORE, object);
54 +
    try testing::expect(STORE.pool.free[0]);
55 +
    let mut stale = false;
56 +
    try backing::releasePage(&mut STORE, object) catch err {
57 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
58 +
    };
59 +
    try testing::expect(stale);
60 +
}
61 +
62 +
/// An ended domain generation cannot gain exposures through a reused domain slot.
63 +
@test unsafe fn domainGeneration() throws (testing::TestError) {
64 +
    initialize();
65 +
    let object = allocate();
66 +
    let old = abi::Ref { index: 1, generation: 1 };
67 +
    try! backing::expose(&mut STORE, object, old);
68 +
    try! backing::endDomain(&mut STORE, old);
69 +
    try! backing::registerDomain(&mut STORE, abi::Ref { index: 1, generation: 2 });
70 +
    let mut stale = false;
71 +
    try backing::expose(&mut STORE, object, old) catch err {
72 +
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
73 +
    };
74 +
    try testing::expect(stale and STORE.records[object.index].exposed == 1);
75 +
    try! backing::releasePage(&mut STORE, object);
76 +
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
77 +
    try testing::expect(STORE.pool.free[0]);
78 +
}
79 +
80 +
/// Retained backing metadata cannot create new page authority after the last page ends.
81 +
@test unsafe fn noResurrection() throws (testing::TestError) {
82 +
    initialize();
83 +
    let object = allocate();
84 +
    try! backing::releasePage(&mut STORE, object);
85 +
    let mut failures: u32 = 0;
86 +
    try backing::retainPage(&mut STORE, object) catch err {
87 +
        try testing::expect(err == abi::Error::BadHandle); set failures += 1;
88 +
    };
89 +
    try backing::expose(&mut STORE, object, abi::Ref { index: 1, generation: 1 }) catch err {
90 +
        try testing::expect(err == abi::Error::BadHandle); set failures += 1;
91 +
    };
92 +
    try testing::expect(failures == 2 and STORE.records[object.index].exposed == 1);
93 +
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
94 +
    try testing::expect(STORE.pool.free[0]);
95 +
}
kernel/kernel/tests/frames.rad added +171 -0
1 +
//! Contiguous frame reservation and reserved-memory exclusion.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::frames;
6 +
use kernel::platform;
7 +
use kernel::range;
8 +
9 +
/// Frame-map workspace is static because its size exceeds a small kernel stack.
10 +
unsafe static POOL: frames::Pool = undefined;
11 +
12 +
/// Build eight available frames around a reserved two-frame hole.
13 +
unsafe fn initialize() {
14 +
    let mut machine: platform::Platform = undefined;
15 +
    set machine.ramCount = 1;
16 +
    set machine.ram[0] = range::Range { start: 0x80000000, end: 0x8000a000 };
17 +
    set machine.reservedCount = 1;
18 +
    set machine.reserved[0] = range::Range { start: 0x80003000, end: 0x80005000 };
19 +
    try! frames::initialize(&mut POOL, &machine);
20 +
}
21 +
22 +
/// Adjacent physical frames form runs; a reserved hole cannot be crossed.
23 +
@test unsafe fn fragmentation() throws (testing::TestError) {
24 +
    initialize();
25 +
    try testing::expect(POOL.count == 8);
26 +
    let left = try! frames::reserve(&mut POOL, 3);
27 +
    let leftRun = frames::commit(left);
28 +
    let right = try! frames::reserve(&mut POOL, 5);
29 +
    let rightRun = frames::commit(right);
30 +
    let first = try! frames::extent(&POOL, leftRun);
31 +
    let second = try! frames::extent(&POOL, rightRun);
32 +
    try testing::expect(first.start == 0x80000000 and first.end == 0x80003000);
33 +
    try testing::expect(second.start == 0x80005000 and second.end == 0x8000a000);
34 +
    try! frames::release(&mut POOL, leftRun);
35 +
    try! frames::release(&mut POOL, rightRun);
36 +
    let mut failed = false;
37 +
    try reserveAndCancel(6) catch err {
38 +
        try testing::expect(err == abi::Error::OutOfMemory); set failed = true;
39 +
    };
40 +
    try testing::expect(failed);
41 +
}
42 +
43 +
/// Consume a successful probe reservation before returning to its caller.
44 +
unsafe fn reserveAndCancel(count: u32) throws (abi::Error) {
45 +
    let token = try frames::reserve(&mut POOL, count);
46 +
    try frames::cancel(&mut POOL, token);
47 +
}
48 +
49 +
/// Full exhaustion and cancellation preserve the pool's available capacity.
50 +
@test unsafe fn capacity() throws (testing::TestError) {
51 +
    initialize();
52 +
    let mut allocated: [frames::Run; 8] = undefined;
53 +
    for i in 0..8 {
54 +
        let token = try! frames::reserve(&mut POOL, 1);
55 +
        set allocated[i] = frames::commit(token);
56 +
    }
57 +
    let mut failed = false;
58 +
    try reserveAndCancel(1) catch err {
59 +
        try testing::expect(err == abi::Error::OutOfMemory); set failed = true;
60 +
    };
61 +
    try testing::expect(failed);
62 +
    for i in 0..8 { try! frames::release(&mut POOL, allocated[i]); }
63 +
    let pending = try! frames::reserve(&mut POOL, 5);
64 +
    try! frames::cancel(&mut POOL, pending);
65 +
    let again = try! frames::reserve(&mut POOL, 5);
66 +
    try! frames::cancel(&mut POOL, again);
67 +
    let mut empty = false;
68 +
    try reserveAndCancel(0) catch err {
69 +
        try testing::expect(err == abi::Error::InvalidArg); set empty = true;
70 +
    };
71 +
    try testing::expect(empty);
72 +
}
73 +
74 +
/// A release validates every frame before mutating any occupancy state.
75 +
@test unsafe fn invalidRelease() throws (testing::TestError) {
76 +
    initialize();
77 +
    let token = try! frames::reserve(&mut POOL, 2);
78 +
    let run = frames::commit(token);
79 +
    let mut failed = false;
80 +
    try frames::release(&mut POOL, frames::Run { first: run.first, count: 3 }) catch err {
81 +
        try testing::expect(err == abi::Error::BadHandle); set failed = true;
82 +
    };
83 +
    try testing::expect(failed);
84 +
    try! frames::release(&mut POOL, run);
85 +
    let mut duplicate = false;
86 +
    try frames::release(&mut POOL, run) catch err {
87 +
        try testing::expect(err == abi::Error::BadHandle); set duplicate = true;
88 +
    };
89 +
    try testing::expect(duplicate);
90 +
}
91 +
92 +
/// Only complete, unreserved physical pages can enter the frame pool.
93 +
@test unsafe fn partialPages() throws (testing::TestError) {
94 +
    let mut machine: platform::Platform = undefined;
95 +
    set machine.ramCount = 1;
96 +
    set machine.ram[0] = range::Range { start: 0x80000001, end: 0x80006001 };
97 +
    set machine.reservedCount = 1;
98 +
    set machine.reserved[0] = range::Range { start: 0x80002fff, end: 0x80003001 };
99 +
    try! frames::initialize(&mut POOL, &machine);
100 +
    try testing::expect(POOL.count == 3);
101 +
    let token = try! frames::reserve(&mut POOL, 2);
102 +
    let run = frames::commit(token);
103 +
    let span = try! frames::extent(&POOL, run);
104 +
    try testing::expect(span.start == 0x80004000 and span.end == 0x80006000);
105 +
}
106 +
107 +
/// Physically adjacent banks form a run regardless of device-tree order.
108 +
@test unsafe fn bankOrder() throws (testing::TestError) {
109 +
    let mut machine: platform::Platform = undefined;
110 +
    set machine.ramCount = 2; set machine.reservedCount = 0;
111 +
    set machine.ram[0] = range::Range { start: 0x80002000, end: 0x80004000 };
112 +
    set machine.ram[1] = range::Range { start: 0x80000000, end: 0x80002000 };
113 +
    try! frames::initialize(&mut POOL, &machine);
114 +
    let token = try! frames::reserve(&mut POOL, 4);
115 +
    let run = frames::commit(token);
116 +
    let memory = try! frames::extent(&POOL, run);
117 +
    try testing::expect(memory.start == 0x80000000 and memory.end == 0x80004000);
118 +
    set machine.ram[1].end = 0x80003000;
119 +
    let mut failed = false;
120 +
    try frames::initialize(&mut POOL, &machine) catch err {
121 +
        try testing::expect(err == abi::Error::InvalidArg); set failed = true;
122 +
    };
123 +
    try testing::expect(failed and POOL.count == 0);
124 +
}
125 +
126 +
/// The full 65,536-frame profile fits; one extra eligible frame fails cleanly.
127 +
@test unsafe fn maximum() throws (testing::TestError) {
128 +
    let mut machine: platform::Platform = undefined;
129 +
    set machine.ramCount = 1; set machine.reservedCount = 0;
130 +
    set machine.ram[0] = range::Range { start: 0x80000000, end: 0x90000000 };
131 +
    try! frames::initialize(&mut POOL, &machine);
132 +
    try testing::expect(POOL.count == 65536);
133 +
    let token = try! frames::reserve(&mut POOL, 65536);
134 +
    let run = frames::commit(token);
135 +
    try! frames::release(&mut POOL, run);
136 +
    set machine.ram[0].end += 4096;
137 +
    let mut failed = false;
138 +
    try frames::initialize(&mut POOL, &machine) catch err {
139 +
        try testing::expect(err == abi::Error::Exhausted); set failed = true;
140 +
    };
141 +
    try testing::expect(failed and POOL.count == 0);
142 +
    set machine.ram[0] = range::Range { start: 0xfffffffffffff001, end: 0xffffffffffffffff };
143 +
    try! frames::initialize(&mut POOL, &machine);
144 +
    try testing::expect(POOL.count == 0);
145 +
}
146 +
147 +
/// Sorted reservation skipping agrees with byte-range exclusion across banks and gaps.
148 +
@test unsafe fn reservationSweep() throws (testing::TestError) {
149 +
    let mut machine: platform::Platform = undefined;
150 +
    set machine.ramCount = 3;
151 +
    set machine.ram[0] = range::Range { start: 0x80040000, end: 0x80060000 };
152 +
    set machine.ram[1] = range::Range { start: 0x80000000, end: 0x80010000 };
153 +
    set machine.ram[2] = range::Range { start: 0x80010000, end: 0x80020000 };
154 +
    set machine.reservedCount = 6;
155 +
    set machine.reserved[0] = range::Range { start: 0x80050001, end: 0x8005f000 };
156 +
    set machine.reserved[1] = range::Range { start: 0x80001234, end: 0x80004321 };
157 +
    set machine.reserved[2] = range::Range { start: 0x80004000, end: 0x80009001 };
158 +
    set machine.reserved[3] = range::Range { start: 0x7ffff000, end: 0x80000001 };
159 +
    set machine.reserved[4] = range::Range { start: 0x80018000, end: 0x80048000 };
160 +
    set machine.reserved[5] = range::Range { start: 0x80002000, end: 0x80003000 };
161 +
    try! frames::initialize(&mut POOL, &machine);
162 +
    let mut count: u32 = 0;
163 +
    for i in 0..96 {
164 +
        let start = 0x80000000 + i as u64 * 4096;
165 +
        if platform::available(&machine, range::Range { start, end: start + 4096 }) {
166 +
            try testing::expect(count < POOL.count and POOL.addresses[count] == start and POOL.free[count]);
167 +
            set count += 1;
168 +
        }
169 +
    }
170 +
    try testing::expect(count == POOL.count);
171 +
}
kernel/kernel/tests/pages.rad added +183 -0
1 +
//! Page allocation, zeroing, split atomicity, and retained-pointer lifetimes.
2 +
3 +
use std::testing;
4 +
use kernel::abi;
5 +
use kernel::capability;
6 +
use kernel::pages;
7 +
use kernel::backing;
8 +
use kernel::slots;
9 +
use kernel::limits;
10 +
11 +
/// Page and allocation metadata workspace.
12 +
unsafe static STORE: pages::Store = undefined;
13 +
/// Test domain's capability table.
14 +
unsafe static TABLE: capability::Table = undefined;
15 +
/// Mapped bytes with room for page alignment and eight full pages.
16 +
static RAM: [u8; 36864] = [0; 36864];
17 +
/// Byte offset to the first page-aligned test frame.
18 +
static OFFSET: u32 = 0;
19 +
20 +
/// Initialize metadata over mapped hosted memory, without synthetic addresses.
21 +
unsafe fn initialize() {
22 +
    for i in 0..RAM.len { set RAM[i] = 0xa5; }
23 +
    let pointer: *u8 = &RAM[0];
24 +
    let address = pointer as u64;
25 +
    let aligned = (address + 4095) & ~4095;
26 +
    set OFFSET = (aligned - address) as u32;
27 +
    slots::initialize(&mut STORE.slots[..]);
28 +
    backing::initialize(&mut STORE.backings);
29 +
    set STORE.backings.pool.count = 8;
30 +
    for i in 0..8 { set STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; set STORE.backings.pool.free[i] = true; }
31 +
    let owner = abi::Ref { index: 0, generation: 1 };
32 +
    capability::initialize(&mut TABLE, owner);
33 +
    try! backing::registerDomain(&mut STORE.backings, owner);
34 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
35 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
36 +
    });
37 +
}
38 +
39 +
/// Drop the test's handle and its object reference in the same serialized operation.
40 +
unsafe fn drop(handle: abi::Handle) {
41 +
    let entry = try! capability::invalidate(&mut TABLE, handle);
42 +
    try! pages::release(&mut STORE, entry.object);
43 +
}
44 +
45 +
/// New allocations are zeroed completely and carry the documented default rights.
46 +
@test unsafe fn allocation() throws (testing::TestError) {
47 +
    initialize();
48 +
    let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2);
49 +
    let entry = try! capability::get(&TABLE, handle);
50 +
    try testing::expect(*entry.rights == (abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER));
51 +
    let page = try! pages::get(&STORE, entry.object);
52 +
    try testing::expect(page.count == 2 and page.origin == TABLE.owner and page.handles == 1);
53 +
    for i in OFFSET..OFFSET + 8192 { try testing::expect(RAM[i] == 0); }
54 +
    try testing::expect(RAM[OFFSET + 8192] == 0xa5);
55 +
    let mut invalid = false;
56 +
    try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 0) catch err {
57 +
        try testing::expect(err == abi::Error::InvalidArg); set invalid = true;
58 +
    };
59 +
    try testing::expect(invalid);
60 +
    for count in &[65537 as u64, 0xffffffffffffffff] {
61 +
        let mut exhausted = false;
62 +
        try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), count) catch err {
63 +
            try testing::expect(err == abi::Error::OutOfMemory); set exhausted = true;
64 +
        };
65 +
        try testing::expect(exhausted and STORE.backings.pool.free[2]);
66 +
    }
67 +
}
68 +
69 +
/// Splitting preserves contents and backing identity while changing only page extents.
70 +
@test unsafe fn split() throws (testing::TestError) {
71 +
    initialize();
72 +
    let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 4);
73 +
    set RAM[OFFSET + 8192] = 73;
74 +
    let original = try! capability::get(&TABLE, handle);
75 +
    for count in &[0 as u64, 4, 5, 0xffffffffffffffff] {
76 +
        let mut failed = false;
77 +
        try pages::split(&mut STORE, &mut TABLE, handle, count) catch err {
78 +
            try testing::expect(err == abi::Error::InvalidArg); set failed = true;
79 +
        };
80 +
        try testing::expect(failed);
81 +
    }
82 +
    let rightHandle = try! pages::split(&mut STORE, &mut TABLE, handle, 2);
83 +
    let rightEntry = try! capability::get(&TABLE, rightHandle);
84 +
    let left = try! pages::get(&STORE, original.object);
85 +
    let right = try! pages::get(&STORE, rightEntry.object);
86 +
    try testing::expect(left.count == 2 and right.count == 2 and right.base == left.base + 8192);
87 +
    try testing::expect(left.backing == right.backing and left.origin == right.origin);
88 +
    try testing::expect(original.rights == rightEntry.rights and RAM[OFFSET + 8192] == 73);
89 +
    try testing::expect(STORE.backings.records[left.backing.index].pages == 2);
90 +
    try! pages::retain(&mut STORE, original.object, TABLE.owner);
91 +
    let mut shared = false;
92 +
    try pages::split(&mut STORE, &mut TABLE, handle, 1) catch err {
93 +
        try testing::expect(err == abi::Error::Busy); set shared = true;
94 +
    };
95 +
    try testing::expect(shared);
96 +
    try! pages::release(&mut STORE, original.object);
97 +
}
98 +
99 +
/// Neither handle drop nor origin exit can release frames exposed to another live domain.
100 +
@test unsafe fn retainedPointers() throws (testing::TestError) {
101 +
    initialize();
102 +
    let receiver = abi::Ref { index: 1, generation: 1 };
103 +
    try! backing::registerDomain(&mut STORE.backings, receiver);
104 +
    let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
105 +
    let entry = try! capability::get(&TABLE, handle);
106 +
    try! pages::retain(&mut STORE, entry.object, receiver);
107 +
    set RAM[OFFSET] = 99;
108 +
    drop(handle);
109 +
    try! pages::release(&mut STORE, entry.object);
110 +
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
111 +
    try testing::expect(not STORE.backings.pool.free[0] and RAM[OFFSET] == 99);
112 +
    try! backing::endDomain(&mut STORE.backings, receiver);
113 +
    try testing::expect(STORE.backings.pool.free[0]);
114 +
    let owner = abi::Ref { index: 0, generation: 2 };
115 +
    capability::initialize(&mut TABLE, owner);
116 +
    try! backing::registerDomain(&mut STORE.backings, owner);
117 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
118 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
119 +
    });
120 +
    let replacement = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
121 +
    try testing::expect(RAM[OFFSET] == 0);
122 +
}
123 +
124 +
/// A full capability table prevents allocation and splitting before payload mutation.
125 +
@test unsafe fn fullTable() throws (testing::TestError) {
126 +
    initialize();
127 +
    let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 4);
128 +
    let entry = try! capability::get(&TABLE, handle);
129 +
    let owner = TABLE.owner;
130 +
    for i in 2..limits::HANDLES {
131 +
        let filler = try! capability::install(&mut TABLE, capability::Entry {
132 +
            kind: abi::Kind::Domain, object: owner, rights: abi::Rights(0),
133 +
        });
134 +
    }
135 +
    let mut failed: u32 = 0;
136 +
    try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err {
137 +
        try testing::expect(err == abi::Error::Exhausted); set failed += 1;
138 +
    };
139 +
    try pages::split(&mut STORE, &mut TABLE, handle, 2) catch err {
140 +
        try testing::expect(err == abi::Error::Exhausted); set failed += 1;
141 +
    };
142 +
    let page = try! pages::get(&STORE, entry.object);
143 +
    try testing::expect(failed == 2 and page.count == 4 and STORE.backings.pool.free[4]);
144 +
}
145 +
146 +
/// Failure at each storage stage releases all earlier reservations.
147 +
@test unsafe fn rollback() throws (testing::TestError) {
148 +
    initialize();
149 +
    let full = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
150 +
    let mut failed = false;
151 +
    try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err {
152 +
        try testing::expect(err == abi::Error::OutOfMemory); set failed = true;
153 +
    };
154 +
    try testing::expect(failed and TABLE.slots[2].state == slots::State::Free);
155 +
    try testing::expect(STORE.slots[1].state == slots::State::Free and STORE.backings.slots[1].state == slots::State::Free);
156 +
    for stage in 0..2 {
157 +
        initialize();
158 +
        if stage == 0 {
159 +
            for i in 0..limits::ALLOCATIONS { set STORE.backings.slots[i].state = slots::State::Retired; }
160 +
        } else {
161 +
            for i in 0..limits::PAGES { set STORE.slots[i].state = slots::State::Retired; }
162 +
        }
163 +
        let mut exhausted = false;
164 +
        try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err {
165 +
            try testing::expect(err == abi::Error::Exhausted); set exhausted = true;
166 +
        };
167 +
        try testing::expect(exhausted and TABLE.slots[1].state == slots::State::Free and STORE.backings.pool.free[0]);
168 +
        if stage == 0 { try testing::expect(STORE.slots[0].state == slots::State::Free); }
169 +
    }
170 +
}
171 +
172 +
/// A Domain handle with Allocate rights must name the caller to allocate RAM.
173 +
@test unsafe fn allocationAuthority() throws (testing::TestError) {
174 +
    initialize();
175 +
    let other = try! capability::install(&mut TABLE, capability::Entry {
176 +
        kind: abi::Kind::Domain, object: abi::Ref { index: 1, generation: 1 }, rights: abi::Rights(abi::ALLOCATE),
177 +
    });
178 +
    let mut denied = false;
179 +
    try pages::allocate(&mut STORE, &mut TABLE, other, 1) catch err {
180 +
        try testing::expect(err == abi::Error::Denied); set denied = true;
181 +
    };
182 +
    try testing::expect(denied and STORE.backings.pool.free[0] and TABLE.slots[2].state == slots::State::Free);
183 +
}
kernel/tools/build.rad +60 -13
1 1
//! Build a physical kernel image from trusted binary RIL and startup assembly.
2 2
3 3
use std::sys;
4 4
use std::io;
5 +
use std::mem;
6 +
use std::lang::il;
5 7
use std::sys::unix;
6 8
use std::lang::alloc;
7 9
use std::lang::strings;
8 10
use std::lang::il::binary;
9 11
use std::lang::il::binary::program;
11 13
use std::collections::dict;
12 14
use std::arch::rv64;
13 15
use std::arch::rv64::asm;
14 16
use std::arch::rv64::image;
15 17
18 +
/// Code segment base for the native platform profile.
19 +
constant CODE_ADDRESS: u64 = 0x81000000;
20 +
16 21
/// Persistent native code-generation workspace.
17 22
static CODE: [u8; 16777216] = [0; 16777216];
18 23
/// Reusable function workspace.
19 24
static SCRATCH: [u8; 16777216] = [0; 16777216];
20 25
/// Decoded package storage.
21 -
static DECODE: [u8; 16777216] = [0; 16777216];
26 +
static DECODE: [u8; 67108864] = [0; 67108864];
22 27
/// Binary package input.
23 -
static INPUT: [u8; 1048576] = [0; 1048576];
28 +
static INPUT: [u8; 8388608] = [0; 8388608];
29 +
/// Combined package data descriptors.
30 +
unsafe static GLOBALS: [il::Data; 4096] = undefined;
24 31
/// Combined startup and boundary assembly.
25 32
static SOURCE: [u8; 65536] = [0; 65536];
26 33
/// Assembler workspace.
27 34
static ASSEMBLY: [u8; 4194304] = [0; 4194304];
28 35
/// Assembled startup words.
29 36
static TEXT: [u32; 16384] = [0; 16384];
30 37
/// Assembly identifiers.
31 38
unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 };
32 39
/// Data symbol placement workspace.
33 -
unsafe static SYMBOLS: [data::DataSym; 1024] = undefined;
40 +
unsafe static SYMBOLS: [data::DataSym; 4096] = undefined;
34 41
/// Data name lookup workspace.
35 42
unsafe static ENTRIES: [dict::Entry; data::DATA_SYM_TABLE_SIZE] = undefined;
36 43
/// Initialized read-only bytes.
37 44
static RO: [u8; 1048576] = [0; 1048576];
38 45
/// Initialized writable bytes.
39 46
static RW: [u8; 1048576] = [0; 1048576];
40 47
48 +
/// Decode one package into persistent storage shared by the native link.
49 +
unsafe fn load(path: &[u8], arena: &mut alloc::Arena) -> binary::Package {
50 +
    let inputLength = unix::readFile(path, &mut INPUT[..]) else panic "boot RIL";
51 +
    return try! program::decode(&INPUT[..inputLength], arena, binary::Limits { registers: 8192, blocks: 4096 });
52 +
}
53 +
54 +
/// Print the bounded backend failure reported by a native build.
55 +
fn report(error: rv64::Error) {
56 +
    match error {
57 +
        case rv64::Error::Allocation => io::printLn("native build: allocation exhausted"),
58 +
        case rv64::Error::Capacity => io::printLn("native build: output capacity exhausted"),
59 +
        case rv64::Error::Symbol => io::printLn("native build: unresolved symbol"),
60 +
        case rv64::Error::Relocation => io::printLn("native build: relocation out of range"),
61 +
        case rv64::Error::Image(_) => io::printLn("native build: invalid image layout"),
62 +
        case rv64::Error::Data(_) => io::printLn("native build: invalid data layout"),
63 +
    }
64 +
}
65 +
41 66
/// Assemble entry code and lower the kernel at explicit physical addresses.
42 67
@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";
68 +
    assert env.args.len == 5;
45 69
    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";
70 +
    let library = load(env.args[1], &mut decoder);
71 +
    let kernel = load(env.args[2], &mut decoder);
72 +
    assert mem::eq(library.name, "std") and mem::eq(kernel.name, "kernel");
73 +
    assert library.dependencies.len == 0;
74 +
    for dependency in kernel.dependencies { assert mem::eq(dependency, "std"); }
75 +
    let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly";
49 76
    let mut assembly = alloc::new(&mut ASSEMBLY[..]);
50 77
    let empty: *mut [u8] = &mut [];
51 78
    let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
52 79
        &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0);
53 80
    let mut arena = alloc::new(&mut CODE[..]);
54 81
    let mut scratch = alloc::new(&mut SCRATCH[..]);
55 82
    let mut generator = try! rv64::beginProgram(rv64::ProgramOptions {
56 83
        entryPatch: rv64::EntryPatch::None, debug: false,
57 84
        placement: image::Placement::Physical {
58 -
            code: 0x80010000, roData: 0x80200000, rwData: 0x80400000, entry: 0x80010000,
85 +
            code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS,
59 86
        },
60 87
    }, &mut arena);
61 88
    rv64::addAssembly(&mut generator, startup);
62 -
    for func in package.program.fns { rv64::generateFunction(&mut generator, func, &mut scratch); alloc::reset(&mut scratch); }
89 +
    let mut dataCount: u32 = 0;
90 +
    for package in &[library, kernel] {
91 +
        for item in package.program.data {
92 +
            assert dataCount < GLOBALS.len;
93 +
            set GLOBALS[dataCount] = item; set dataCount += 1;
94 +
        }
95 +
        for func in package.program.fns {
96 +
            rv64::generateFunction(&mut generator, func, &mut scratch);
97 +
            if let error = generator.e.error {
98 +
                report(error); io::printLn(func.name); return 1;
99 +
            }
100 +
            alloc::reset(&mut scratch);
101 +
        }
102 +
    }
63 103
    for call in &generator.e.pendingCalls[..] {
64 104
        if dict::get(&generator.e.labels.funcs, call.target) == nil {
65 105
            io::print("undefined kernel function: "); io::printLn(call.target); return 1;
66 106
        }
67 107
    }
68 -
    let output = try! rv64::finishProgram(&mut generator, package.program.data,
108 +
    let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095;
109 +
    let mut symbols: u32 = 0;
110 +
    let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true);
111 +
    let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095;
112 +
    set generator.placement = image::Placement::Physical {
113 +
        code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS,
114 +
    };
115 +
    let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount],
69 116
        rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] },
70 -
        &[], &mut RO[..], &mut RW[..]);
117 +
        &[], &mut RO[..], &mut RW[..]) catch error { report(error); return 1; };
71 118
    let header = try! image::header(output.layout);
72 -
    let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420);
119 +
    let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420);
73 120
    assert fd >= 0;
74 121
    let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4))
75 122
        and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]);
76 123
    let closed = unix::close(fd) == 0;
77 124
    assert written and closed;
lib/std/lang/lower.rad +7 -0
5048 5048
        let fieldRef = try lowerFieldRef(self, access);
5049 5049
        let ptr = emitPtrOffset(self, fieldRef.base, fieldRef.offset);
5050 5050
5051 5051
        return il::Val::Reg(ptr);
5052 5052
    }
5053 +
    // A qualified constant or static uses its resolved owner's data symbol.
5054 +
    if let case ast::NodeValue::ScopeAccess(_) = addr.target.value {
5055 +
        let sym = resolver::nodeData(self.low.resolver, addr.target).sym else {
5056 +
            throw LowerError::MissingSymbol(addr.target);
5057 +
        };
5058 +
        return il::Val::Reg(emitDataAddr(self, sym));
5059 +
    }
5053 5060
    // Handle variable address: `&x`
5054 5061
    if let case ast::NodeValue::Ident(_) = addr.target.value {
5055 5062
        if let v = lookupLocalVar(self, addr.target) {
5056 5063
            let val = try useVar(self, v);
5057 5064
            let typ = try typeOf(self, addr.target);
test/boot/run +6 -4
2 2
# Boot the kernel through its production initialization on each supported hart count.
3 3
set -eu
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
work=$(mktemp -d)
6 6
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 -
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras > "$work/boot.ras"
8 -
"$emulator" -run bin/kernel.build.rv64 -- bin/kernel.ril "$work/boot.ras" "$work/boot.rv64"
7 +
cat test/boot/machine.ras kernel/kernel/boot.ras kernel/kernel/sync.ras kernel/kernel/trap.ras kernel/kernel/pages.ras > "$work/boot.ras"
8 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/boot.ras" "$work/boot.rv64"
9 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
10 +
    # The deterministic emulator shares its tick budget across all harts.
11 +
    steps=$((10000000 * harts))
12 +
    if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then
11 13
        cat "$work/log" >&2
12 14
        exit 1
13 15
    fi
14 16
    if ! grep -q '^kernel: platform ready$' "$work/log"; then
15 17
        cat "$work/log" >&2
16 18
        exit 1
17 19
    fi
18 20
    printf 'kernel boot: %s harts passed\n' "$harts"
19 21
done
20 22
status=0
21 -
"$emulator" -machine -harts=8 -max-steps=10000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$?
23 +
"$emulator" -machine -harts=8 -max-steps=80000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$?
22 24
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
23 25
    cat "$work/log" >&2
24 26
    exit 1
25 27
fi
26 28
printf 'kernel startup: all eight harts reached machine idle without traps\n'
test/pages/machine.ras added +35 -0
1 +
//! Physical page zeroing with canaries immediately outside the allocation.
2 +
.text;
3 +
    li %s0 0x40010000;
4 +
    slli %s0 %s0 1;
5 +
    li %t0 4096;
6 +
    add %s1 %s0 %t0;
7 +
    li %t0 91;
8 +
    sd %t0 -8(%s0);
9 +
    sd %t0 0(%s1);
10 +
    mv %t1 %s0;
11 +
@fill
12 +
    sd %t0 0(%t1);
13 +
    addi %t1 %t1 8;
14 +
    bne %t1 %s1 @fill;
15 +
    mv %a0 %s0;
16 +
    li %a1 1;
17 +
    call @kernel::pages::zero;
18 +
    mv %t1 %s0;
19 +
@check
20 +
    ld %t0 0(%t1);
21 +
    bnez %t0 @fail;
22 +
    addi %t1 %t1 8;
23 +
    bne %t1 %s1 @check;
24 +
    li %t0 91;
25 +
    ld %t1 -8(%s0);
26 +
    bne %t0 %t1 @fail;
27 +
    ld %t1 0(%s1);
28 +
    bne %t0 %t1 @fail;
29 +
    li %t0 0x10001000;
30 +
    li %t1 0x5555;
31 +
    sw %t1 0(%t0);
32 +
@fail
33 +
    li %t0 0x10001000;
34 +
    li %t1 0x13333;
35 +
    sw %t1 0(%t0);
test/pages/run added +10 -0
1 +
#!/bin/sh
2 +
# Check complete physical-page zeroing and both adjacent canaries.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
cat test/pages/machine.ras kernel/kernel/pages.ras > "$work/pages.ras"
8 +
"$emulator" -run bin/sync.build.rv64 -- "$work/pages.ras" "$work/pages.rv64"
9 +
"$emulator" -machine -max-steps=1000000 -run "$work/pages.rv64"
10 +
printf 'physical pages: complete zeroing and adjacent canaries passed\n'
test/tests/ref.qualified.static.rad added +12 -0
1 +
//! returns: 42
2 +
3 +
mod storage;
4 +
5 +
/// Update a statically stored scalar through a checked mutable reference.
6 +
fn update(value: &mut i32) { set *value = 42; }
7 +
8 +
/// Return the value written through the qualified reference.
9 +
@default fn main() -> i32 {
10 +
    update(&mut storage::VALUE);
11 +
    return storage::VALUE;
12 +
}
test/tests/ref.qualified.static/storage.rad added +4 -0
1 +
//! Storage for the qualified static reference test.
2 +
3 +
/// Mutable scalar used through a module-qualified checked borrow.
4 +
export static VALUE: i32 = 7;