kernel: Allocate and split physical memory
b13595ab029f512923e049284a5a32056419e7d21e2b5f50385e128c84babdcd
Assisted-by: Codex:gpt-6
1 parent
0d03396d
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
+147 -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 { |
|
| 41 | + | set store.domains[i] = 0; |
|
| 42 | + | } |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Register a domain before it can receive allocation exposure. |
|
| 46 | + | export fn registerDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) { |
|
| 47 | + | if domain.index >= limits::DOMAINS or domain.generation == 0 { |
|
| 48 | + | throw abi::Error::InvalidArg; |
|
| 49 | + | } |
|
| 50 | + | if store.domains[domain.index] <> 0 { |
|
| 51 | + | throw abi::Error::Busy; |
|
| 52 | + | } |
|
| 53 | + | set store.domains[domain.index] = domain.generation; |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Test exposure-domain generation without reading beyond the fixed table. |
|
| 57 | + | export fn domainLive(store: &Store, domain: abi::Ref) -> bool { |
|
| 58 | + | return domain.index < limits::DOMAINS and domain.generation <> 0 and store.domains[domain.index] == domain.generation; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | /// Publish reserved metadata with one page object and its live origin exposure. |
|
| 62 | + | /// The caller owns the committed run and has validated the origin domain. |
|
| 63 | + | export unsafe fn publish(store: &mut Store, reservation: slots::Reservation, run: frames::Run, origin: abi::Ref) -> abi::Ref { |
|
| 64 | + | assert domainLive(store, origin); |
|
| 65 | + | let object = slots::reference(&reservation); |
|
| 66 | + | assert slots::matches(&store.slots[..], object, slots::State::Reserved); |
|
| 67 | + | set store.records[object.index].run = run; |
|
| 68 | + | set store.records[object.index].pages = 1; |
|
| 69 | + | set store.records[object.index].exposed = 1; |
|
| 70 | + | let mut bits = bitset::init(&mut store.records[object.index].exposures[..]); |
|
| 71 | + | bitset::put(&mut bits, origin.index); |
|
| 72 | + | return try! slots::commit(&mut store.slots[..], reservation); |
|
| 73 | + | } |
|
| 74 | + | ||
| 75 | + | /// Require a live generation-bearing backing reference. |
|
| 76 | + | fn require(store: &Store, object: abi::Ref) throws (abi::Error) { |
|
| 77 | + | if not slots::matches(&store.slots[..], object, slots::State::Live) { |
|
| 78 | + | throw abi::Error::BadHandle; |
|
| 79 | + | } |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | /// Add one split page to the allocation's lifetime count. |
|
| 83 | + | export fn retainPage(store: &mut Store, object: abi::Ref) throws (abi::Error) { |
|
| 84 | + | try require(store, object); |
|
| 85 | + | if store.records[object.index].pages == 0 { |
|
| 86 | + | throw abi::Error::BadHandle; |
|
| 87 | + | } |
|
| 88 | + | if store.records[object.index].pages == 0xffffffff { |
|
| 89 | + | throw abi::Error::Exhausted; |
|
| 90 | + | } |
|
| 91 | + | set store.records[object.index].pages += 1; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Record access for a domain for the rest of that domain generation's lifetime. |
|
| 95 | + | export unsafe fn expose(store: &mut Store, object: abi::Ref, domain: abi::Ref) throws (abi::Error) { |
|
| 96 | + | try require(store, object); |
|
| 97 | + | if store.records[object.index].pages == 0 or not domainLive(store, domain) { |
|
| 98 | + | throw abi::Error::BadHandle; |
|
| 99 | + | } |
|
| 100 | + | let mut bits = bitset::new(&mut store.records[object.index].exposures[..]); |
|
| 101 | + | if bitset::contains(&bits, domain.index) { |
|
| 102 | + | return; |
|
| 103 | + | } |
|
| 104 | + | bitset::put(&mut bits, domain.index); |
|
| 105 | + | set store.records[object.index].exposed += 1; |
|
| 106 | + | } |
|
| 107 | + | ||
| 108 | + | /// Reclaim only when no page object and no domain exposure remains. |
|
| 109 | + | fn reclaim(store: &mut Store, object: abi::Ref) { |
|
| 110 | + | if store.records[object.index].pages <> 0 or store.records[object.index].exposed <> 0 { |
|
| 111 | + | return; |
|
| 112 | + | } |
|
| 113 | + | let run = store.records[object.index].run; |
|
| 114 | + | try! frames::release(&mut store.pool, run); |
|
| 115 | + | try! slots::release(&mut store.slots[..], object); |
|
| 116 | + | } |
|
| 117 | + | ||
| 118 | + | /// Release one page object after its final handle disappears. |
|
| 119 | + | export fn releasePage(store: &mut Store, object: abi::Ref) throws (abi::Error) { |
|
| 120 | + | try require(store, object); |
|
| 121 | + | if store.records[object.index].pages == 0 { |
|
| 122 | + | throw abi::Error::BadHandle; |
|
| 123 | + | } |
|
| 124 | + | set store.records[object.index].pages -= 1; |
|
| 125 | + | reclaim(store, object); |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// End all exposure for one domain before its slot can be registered again. |
|
| 129 | + | /// Callers serialize this bounded scan with allocation and capability changes. |
|
| 130 | + | export unsafe fn endDomain(store: &mut Store, domain: abi::Ref) throws (abi::Error) { |
|
| 131 | + | if not domainLive(store, domain) { |
|
| 132 | + | throw abi::Error::BadHandle; |
|
| 133 | + | } |
|
| 134 | + | for i in 0..limits::ALLOCATIONS { |
|
| 135 | + | if store.slots[i].state <> slots::State::Live { |
|
| 136 | + | continue; |
|
| 137 | + | } |
|
| 138 | + | let mut bits = bitset::new(&mut store.records[i].exposures[..]); |
|
| 139 | + | if not bitset::contains(&bits, domain.index) { |
|
| 140 | + | continue; |
|
| 141 | + | } |
|
| 142 | + | bitset::clear(&mut bits, domain.index); |
|
| 143 | + | set store.records[i].exposed -= 1; |
|
| 144 | + | reclaim(store, abi::Ref { index: i, generation: store.slots[i].generation }); |
|
| 145 | + | } |
|
| 146 | + | set store.domains[domain.index] = 0; |
|
| 147 | + | } |
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; |
| 64 | 65 | let blob = @sliceOf(tree, size); |
|
| 65 | 66 | try! platform::decode(&blob[..], &mut PLATFORM); |
|
| 66 | 67 | let treeRange = range::new(treeAddress, size as u64) else panic "FDT range"; |
|
| 67 | 68 | assert platform::inRam(&PLATFORM, treeRange); |
|
| 68 | 69 | try! platform::protect(&mut PLATFORM, treeRange); |
|
| 70 | + | try! pages::initialize(&mut pages::STORE, &PLATFORM); |
|
| 69 | 71 | print("kernel: platform ready\n"); |
|
| 70 | 72 | sync::storeRelease(&mut READY, 1); |
|
| 71 | 73 | } else { |
|
| 72 | 74 | while sync::loadAcquire(&READY) == 0 { |
|
| 73 | 75 | } |
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
| 37 | 37 | if entry.kind == abi::Kind::Empty or entry.object.generation == 0 { |
|
| 38 | 38 | throw abi::Error::InvalidArg; |
|
| 39 | 39 | } |
|
| 40 | 40 | let rights = try abi::rights(*entry.rights as u64); |
|
| 41 | 41 | let reservation = try slots::reserve(&mut table.slots[..]); |
|
| 42 | + | return publish(table, reservation, entry); |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Publish valid authority into a reservation from this table. |
|
| 46 | + | /// The caller has validated metadata and acquired the object's lifetime reference. |
|
| 47 | + | export fn publish(table: &mut Table, reservation: slots::Reservation, entry: Entry) -> abi::Handle { |
|
| 42 | 48 | let object = slots::reference(&reservation); |
|
| 49 | + | assert slots::matches(&table.slots[..], object, slots::State::Reserved); |
|
| 43 | 50 | set table.entries[object.index] = entry; |
|
| 44 | 51 | let live = try! slots::commit(&mut table.slots[..], reservation); |
|
| 45 | 52 | return try! abi::handle(entry.kind, live); |
|
| 46 | 53 | } |
|
| 47 | 54 |
kernel/kernel/frames.rad
added
+167 -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 { |
|
| 52 | + | throw abi::Error::InvalidArg; |
|
| 53 | + | } |
|
| 54 | + | insert(&mut reserved[..], i, value); |
|
| 55 | + | } |
|
| 56 | + | let mut banks: [range::Range; platform::RAM_BANKS] = undefined; |
|
| 57 | + | for i in 0..machine.ramCount { |
|
| 58 | + | let bank = machine.ram[i]; |
|
| 59 | + | if bank.start >= bank.end { |
|
| 60 | + | throw abi::Error::InvalidArg; |
|
| 61 | + | } |
|
| 62 | + | insert(&mut banks[..], i, bank); |
|
| 63 | + | } |
|
| 64 | + | let mut count: u32 = 0; |
|
| 65 | + | let mut next: u32 = 0; |
|
| 66 | + | for i in 0..machine.ramCount { |
|
| 67 | + | let bank = banks[i]; |
|
| 68 | + | if i > 0 and banks[i - 1].end > bank.start { |
|
| 69 | + | throw abi::Error::InvalidArg; |
|
| 70 | + | } |
|
| 71 | + | let padding = (limits::FRAME_SIZE - (bank.start & (limits::FRAME_SIZE - 1))) & (limits::FRAME_SIZE - 1); |
|
| 72 | + | if padding > bank.end - bank.start { |
|
| 73 | + | continue; |
|
| 74 | + | } |
|
| 75 | + | let mut address = bank.start + padding; |
|
| 76 | + | let end = bank.end & ~(limits::FRAME_SIZE - 1); |
|
| 77 | + | while address < end { |
|
| 78 | + | while next < machine.reservedCount and reserved[next].end <= address { |
|
| 79 | + | set next += 1; |
|
| 80 | + | } |
|
| 81 | + | if next < machine.reservedCount and reserved[next].start < address + limits::FRAME_SIZE { |
|
| 82 | + | if reserved[next].end >= end { |
|
| 83 | + | break; |
|
| 84 | + | } |
|
| 85 | + | set address = (reserved[next].end + limits::FRAME_SIZE - 1) & ~(limits::FRAME_SIZE - 1); |
|
| 86 | + | continue; |
|
| 87 | + | } |
|
| 88 | + | if count == limits::FRAMES { |
|
| 89 | + | throw abi::Error::Exhausted; |
|
| 90 | + | } |
|
| 91 | + | set pool.addresses[count] = address; |
|
| 92 | + | set pool.free[count] = true; |
|
| 93 | + | set count += 1; |
|
| 94 | + | set address += limits::FRAME_SIZE; |
|
| 95 | + | } |
|
| 96 | + | } |
|
| 97 | + | set pool.count = count; |
|
| 98 | + | } |
|
| 99 | + | ||
| 100 | + | /// Reserve the first physically contiguous free run with a bounded scan. |
|
| 101 | + | export fn reserve(pool: &mut Pool, count: u32) -> Reservation throws (abi::Error) { |
|
| 102 | + | if count == 0 { |
|
| 103 | + | throw abi::Error::InvalidArg; |
|
| 104 | + | } |
|
| 105 | + | let mut first: u32 = 0; |
|
| 106 | + | let mut found: u32 = 0; |
|
| 107 | + | let mut previous: u64 = 0; |
|
| 108 | + | for i in 0..pool.count { |
|
| 109 | + | if not pool.free[i] { |
|
| 110 | + | set found = 0; |
|
| 111 | + | continue; |
|
| 112 | + | } |
|
| 113 | + | if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { |
|
| 114 | + | set first = i; |
|
| 115 | + | set found = 0; |
|
| 116 | + | } |
|
| 117 | + | set found += 1; |
|
| 118 | + | set previous = pool.addresses[i]; |
|
| 119 | + | if found == count { |
|
| 120 | + | for j in first..first + count { |
|
| 121 | + | set pool.free[j] = false; |
|
| 122 | + | } |
|
| 123 | + | return Reservation::Held(Run { first, count }); |
|
| 124 | + | } |
|
| 125 | + | } |
|
| 126 | + | throw abi::Error::OutOfMemory; |
|
| 127 | + | } |
|
| 128 | + | ||
| 129 | + | /// Consume a reservation into a backing allocation's frame ownership. |
|
| 130 | + | export fn commit(reservation: Reservation) -> Run { |
|
| 131 | + | match reservation { case Reservation::Held(run) => return run, } |
|
| 132 | + | } |
|
| 133 | + | ||
| 134 | + | /// Resolve a complete physical run and reject map gaps or invalid indices. |
|
| 135 | + | export fn extent(pool: &Pool, run: Run) -> range::Range throws (abi::Error) { |
|
| 136 | + | if run.count == 0 or run.first >= pool.count or run.count > pool.count - run.first { |
|
| 137 | + | throw abi::Error::BadHandle; |
|
| 138 | + | } |
|
| 139 | + | let start = pool.addresses[run.first]; |
|
| 140 | + | let length = run.count as u64 * limits::FRAME_SIZE; |
|
| 141 | + | let result = range::new(start, length) else { |
|
| 142 | + | throw abi::Error::BadHandle; |
|
| 143 | + | }; |
|
| 144 | + | if pool.addresses[run.first + run.count - 1] <> result.end - limits::FRAME_SIZE { |
|
| 145 | + | throw abi::Error::BadHandle; |
|
| 146 | + | } |
|
| 147 | + | return result; |
|
| 148 | + | } |
|
| 149 | + | ||
| 150 | + | /// Return a backing allocation's frames after all handles and exposures end. |
|
| 151 | + | /// Only the owner of a committed run may release it. |
|
| 152 | + | export fn release(pool: &mut Pool, run: Run) throws (abi::Error) { |
|
| 153 | + | let memory = try extent(pool, run); |
|
| 154 | + | for i in run.first..run.first + run.count { |
|
| 155 | + | if pool.free[i] { |
|
| 156 | + | throw abi::Error::BadHandle; |
|
| 157 | + | } |
|
| 158 | + | } |
|
| 159 | + | for i in run.first..run.first + run.count { |
|
| 160 | + | set pool.free[i] = true; |
|
| 161 | + | } |
|
| 162 | + | } |
|
| 163 | + | ||
| 164 | + | /// Cancel unpublished frame ownership and restore its capacity. |
|
| 165 | + | export fn cancel(pool: &mut Pool, reservation: Reservation) throws (abi::Error) { |
|
| 166 | + | match reservation { case Reservation::Held(run) => try release(pool, run), } |
|
| 167 | + | } |
kernel/kernel/pages.rad
added
+150 -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) { |
|
| 54 | + | throw abi::Error::BadHandle; |
|
| 55 | + | } |
|
| 56 | + | return store.records[object.index]; |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Allocate and zero frames before publishing a page and its capability. |
|
| 60 | + | /// Allocation authority must name the calling domain. Failure cancels reservations. |
|
| 61 | + | export unsafe fn allocate(store: &mut Store, table: &mut capability::Table, authority: abi::Handle, count: u64) |
|
| 62 | + | -> abi::Handle throws (abi::Error) |
|
| 63 | + | { |
|
| 64 | + | if count == 0 { |
|
| 65 | + | throw abi::Error::InvalidArg; |
|
| 66 | + | } |
|
| 67 | + | if count > limits::FRAMES as u64 { |
|
| 68 | + | throw abi::Error::OutOfMemory; |
|
| 69 | + | } |
|
| 70 | + | let owner = table.owner; |
|
| 71 | + | let permission = try capability::authority(table, authority, abi::Rights(abi::ALLOCATE)); |
|
| 72 | + | if permission.object <> owner { |
|
| 73 | + | throw abi::Error::Denied; |
|
| 74 | + | } |
|
| 75 | + | if not backing::domainLive(&store.backings, owner) { |
|
| 76 | + | throw abi::Error::BadHandle; |
|
| 77 | + | } |
|
| 78 | + | let handleSlot = try slots::reserve(&mut table.slots[..]); |
|
| 79 | + | let pageSlot = try slots::reserve(&mut store.slots[..]) catch err { |
|
| 80 | + | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 81 | + | }; |
|
| 82 | + | let backingSlot = try slots::reserve(&mut store.backings.slots[..]) catch err { |
|
| 83 | + | try! slots::cancel(&mut store.slots[..], pageSlot); |
|
| 84 | + | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 85 | + | }; |
|
| 86 | + | let memory = try frames::reserve(&mut store.backings.pool, count as u32) catch err { |
|
| 87 | + | try! slots::cancel(&mut store.backings.slots[..], backingSlot); |
|
| 88 | + | try! slots::cancel(&mut store.slots[..], pageSlot); |
|
| 89 | + | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 90 | + | }; |
|
| 91 | + | let run = frames::commit(memory); |
|
| 92 | + | let extent = try! frames::extent(&store.backings.pool, run); |
|
| 93 | + | zero(extent.start, run.count); |
|
| 94 | + | let allocation = backing::publish(&mut store.backings, backingSlot, run, owner); |
|
| 95 | + | let object = slots::reference(&pageSlot); |
|
| 96 | + | set store.records[object.index] = Page { backing: allocation, base: extent.start, count: run.count, origin: owner, handles: 1 }; |
|
| 97 | + | let page = try! slots::commit(&mut store.slots[..], pageSlot); |
|
| 98 | + | return capability::publish(table, handleSlot, capability::Entry { |
|
| 99 | + | kind: abi::Kind::Page, object: page, rights: abi::Rights(DEFAULT_RIGHTS), |
|
| 100 | + | }); |
|
| 101 | + | } |
|
| 102 | + | ||
| 103 | + | /// Split an exclusive page into two segments with the same backing and rights. |
|
| 104 | + | export fn split(store: &mut Store, table: &mut capability::Table, handle: abi::Handle, leftCount: u64) |
|
| 105 | + | -> abi::Handle throws (abi::Error) |
|
| 106 | + | { |
|
| 107 | + | let entry = try capability::lookup(table, handle, abi::Kind::Page, abi::Rights(0)); |
|
| 108 | + | let source = try get(store, entry.object); |
|
| 109 | + | if leftCount == 0 or leftCount >= source.count as u64 { |
|
| 110 | + | throw abi::Error::InvalidArg; |
|
| 111 | + | } |
|
| 112 | + | if source.handles <> 1 { |
|
| 113 | + | throw abi::Error::Busy; |
|
| 114 | + | } |
|
| 115 | + | let handleSlot = try slots::reserve(&mut table.slots[..]); |
|
| 116 | + | let pageSlot = try slots::reserve(&mut store.slots[..]) catch err { |
|
| 117 | + | try! slots::cancel(&mut table.slots[..], handleSlot); throw err; |
|
| 118 | + | }; |
|
| 119 | + | let object = slots::reference(&pageSlot); |
|
| 120 | + | try! backing::retainPage(&mut store.backings, source.backing); |
|
| 121 | + | set store.records[object.index] = Page { |
|
| 122 | + | backing: source.backing, base: source.base + leftCount * limits::FRAME_SIZE, |
|
| 123 | + | count: source.count - leftCount as u32, origin: source.origin, handles: 1, |
|
| 124 | + | }; |
|
| 125 | + | set store.records[entry.object.index].count = leftCount as u32; |
|
| 126 | + | let page = try! slots::commit(&mut store.slots[..], pageSlot); |
|
| 127 | + | return capability::publish(table, handleSlot, capability::Entry { kind: abi::Kind::Page, object: page, rights: entry.rights }); |
|
| 128 | + | } |
|
| 129 | + | ||
| 130 | + | /// Add a capability reference and lifetime exposure before publishing that capability. |
|
| 131 | + | export unsafe fn retain(store: &mut Store, object: abi::Ref, receiver: abi::Ref) throws (abi::Error) { |
|
| 132 | + | let page = try get(store, object); |
|
| 133 | + | if page.handles == 0xffffffff { |
|
| 134 | + | throw abi::Error::Exhausted; |
|
| 135 | + | } |
|
| 136 | + | try backing::expose(&mut store.backings, page.backing, receiver); |
|
| 137 | + | set store.records[object.index].handles += 1; |
|
| 138 | + | } |
|
| 139 | + | ||
| 140 | + | /// Release a capability reference. The backing retains all lifetime exposures. |
|
| 141 | + | export fn release(store: &mut Store, object: abi::Ref) throws (abi::Error) { |
|
| 142 | + | let page = try get(store, object); |
|
| 143 | + | assert page.handles > 0; |
|
| 144 | + | set store.records[object.index].handles -= 1; |
|
| 145 | + | if page.handles > 1 { |
|
| 146 | + | return; |
|
| 147 | + | } |
|
| 148 | + | try! slots::release(&mut store.slots[..], object); |
|
| 149 | + | try! backing::releasePage(&mut store.backings, page.backing); |
|
| 150 | + | } |
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
+100 -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 { |
|
| 19 | + | set STORE.pool.addresses[i] = 0x80000000 + i as u64 * 4096; |
|
| 20 | + | set STORE.pool.free[i] = true; |
|
| 21 | + | } |
|
| 22 | + | } |
|
| 23 | + | ||
| 24 | + | /// Publish one backing record with its initial page and origin exposure. |
|
| 25 | + | unsafe fn allocate() -> abi::Ref { |
|
| 26 | + | let slot = try! slots::reserve(&mut STORE.slots[..]); |
|
| 27 | + | let reservation = try! frames::reserve(&mut STORE.pool, 4); |
|
| 28 | + | let run = frames::commit(reservation); |
|
| 29 | + | return backing::publish(&mut STORE, slot, run, abi::Ref { index: 0, generation: 1 }); |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Dropping all page handles preserves capacity while an exposed domain lives. |
|
| 33 | + | @test unsafe fn exposures() throws (testing::TestError) { |
|
| 34 | + | initialize(); |
|
| 35 | + | let object = allocate(); |
|
| 36 | + | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 37 | + | try! backing::expose(&mut STORE, object, receiver); |
|
| 38 | + | try! backing::expose(&mut STORE, object, receiver); |
|
| 39 | + | try testing::expect(STORE.records[object.index].exposed == 2); |
|
| 40 | + | try! backing::releasePage(&mut STORE, object); |
|
| 41 | + | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 42 | + | try testing::expect(not STORE.pool.free[0] and slots::matches(&STORE.slots[..], object, slots::State::Live)); |
|
| 43 | + | try! backing::endDomain(&mut STORE, receiver); |
|
| 44 | + | for i in 0..4 { |
|
| 45 | + | try testing::expect(STORE.pool.free[i]); |
|
| 46 | + | } |
|
| 47 | + | try testing::expect(not slots::matches(&STORE.slots[..], object, slots::State::Live)); |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Splits keep their common backing alive until every page object is gone. |
|
| 51 | + | @test unsafe fn splitLifetime() throws (testing::TestError) { |
|
| 52 | + | initialize(); |
|
| 53 | + | let object = allocate(); |
|
| 54 | + | try! backing::retainPage(&mut STORE, object); |
|
| 55 | + | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 56 | + | try! backing::releasePage(&mut STORE, object); |
|
| 57 | + | try testing::expect(not STORE.pool.free[0] and STORE.records[object.index].pages == 1); |
|
| 58 | + | try! backing::releasePage(&mut STORE, object); |
|
| 59 | + | try testing::expect(STORE.pool.free[0]); |
|
| 60 | + | let mut stale = false; |
|
| 61 | + | try backing::releasePage(&mut STORE, object) catch err { |
|
| 62 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 63 | + | }; |
|
| 64 | + | try testing::expect(stale); |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | /// An ended domain generation cannot gain exposures through a reused domain slot. |
|
| 68 | + | @test unsafe fn domainGeneration() throws (testing::TestError) { |
|
| 69 | + | initialize(); |
|
| 70 | + | let object = allocate(); |
|
| 71 | + | let old = abi::Ref { index: 1, generation: 1 }; |
|
| 72 | + | try! backing::expose(&mut STORE, object, old); |
|
| 73 | + | try! backing::endDomain(&mut STORE, old); |
|
| 74 | + | try! backing::registerDomain(&mut STORE, abi::Ref { index: 1, generation: 2 }); |
|
| 75 | + | let mut stale = false; |
|
| 76 | + | try backing::expose(&mut STORE, object, old) catch err { |
|
| 77 | + | try testing::expect(err == abi::Error::BadHandle); set stale = true; |
|
| 78 | + | }; |
|
| 79 | + | try testing::expect(stale and STORE.records[object.index].exposed == 1); |
|
| 80 | + | try! backing::releasePage(&mut STORE, object); |
|
| 81 | + | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 82 | + | try testing::expect(STORE.pool.free[0]); |
|
| 83 | + | } |
|
| 84 | + | ||
| 85 | + | /// Retained backing metadata cannot create new page authority after the last page ends. |
|
| 86 | + | @test unsafe fn noResurrection() throws (testing::TestError) { |
|
| 87 | + | initialize(); |
|
| 88 | + | let object = allocate(); |
|
| 89 | + | try! backing::releasePage(&mut STORE, object); |
|
| 90 | + | let mut failures: u32 = 0; |
|
| 91 | + | try backing::retainPage(&mut STORE, object) catch err { |
|
| 92 | + | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 93 | + | }; |
|
| 94 | + | try backing::expose(&mut STORE, object, abi::Ref { index: 1, generation: 1 }) catch err { |
|
| 95 | + | try testing::expect(err == abi::Error::BadHandle); set failures += 1; |
|
| 96 | + | }; |
|
| 97 | + | try testing::expect(failures == 2 and STORE.records[object.index].exposed == 1); |
|
| 98 | + | try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 }); |
|
| 99 | + | try testing::expect(STORE.pool.free[0]); |
|
| 100 | + | } |
kernel/kernel/tests/frames.rad
added
+173 -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 { |
|
| 63 | + | try! frames::release(&mut POOL, allocated[i]); |
|
| 64 | + | } |
|
| 65 | + | let pending = try! frames::reserve(&mut POOL, 5); |
|
| 66 | + | try! frames::cancel(&mut POOL, pending); |
|
| 67 | + | let again = try! frames::reserve(&mut POOL, 5); |
|
| 68 | + | try! frames::cancel(&mut POOL, again); |
|
| 69 | + | let mut empty = false; |
|
| 70 | + | try reserveAndCancel(0) catch err { |
|
| 71 | + | try testing::expect(err == abi::Error::InvalidArg); set empty = true; |
|
| 72 | + | }; |
|
| 73 | + | try testing::expect(empty); |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// A release validates every frame before mutating any occupancy state. |
|
| 77 | + | @test unsafe fn invalidRelease() throws (testing::TestError) { |
|
| 78 | + | initialize(); |
|
| 79 | + | let token = try! frames::reserve(&mut POOL, 2); |
|
| 80 | + | let run = frames::commit(token); |
|
| 81 | + | let mut failed = false; |
|
| 82 | + | try frames::release(&mut POOL, frames::Run { first: run.first, count: 3 }) catch err { |
|
| 83 | + | try testing::expect(err == abi::Error::BadHandle); set failed = true; |
|
| 84 | + | }; |
|
| 85 | + | try testing::expect(failed); |
|
| 86 | + | try! frames::release(&mut POOL, run); |
|
| 87 | + | let mut duplicate = false; |
|
| 88 | + | try frames::release(&mut POOL, run) catch err { |
|
| 89 | + | try testing::expect(err == abi::Error::BadHandle); set duplicate = true; |
|
| 90 | + | }; |
|
| 91 | + | try testing::expect(duplicate); |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Only complete, unreserved physical pages can enter the frame pool. |
|
| 95 | + | @test unsafe fn partialPages() throws (testing::TestError) { |
|
| 96 | + | let mut machine: platform::Platform = undefined; |
|
| 97 | + | set machine.ramCount = 1; |
|
| 98 | + | set machine.ram[0] = range::Range { start: 0x80000001, end: 0x80006001 }; |
|
| 99 | + | set machine.reservedCount = 1; |
|
| 100 | + | set machine.reserved[0] = range::Range { start: 0x80002fff, end: 0x80003001 }; |
|
| 101 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 102 | + | try testing::expect(POOL.count == 3); |
|
| 103 | + | let token = try! frames::reserve(&mut POOL, 2); |
|
| 104 | + | let run = frames::commit(token); |
|
| 105 | + | let span = try! frames::extent(&POOL, run); |
|
| 106 | + | try testing::expect(span.start == 0x80004000 and span.end == 0x80006000); |
|
| 107 | + | } |
|
| 108 | + | ||
| 109 | + | /// Physically adjacent banks form a run regardless of device-tree order. |
|
| 110 | + | @test unsafe fn bankOrder() throws (testing::TestError) { |
|
| 111 | + | let mut machine: platform::Platform = undefined; |
|
| 112 | + | set machine.ramCount = 2; set machine.reservedCount = 0; |
|
| 113 | + | set machine.ram[0] = range::Range { start: 0x80002000, end: 0x80004000 }; |
|
| 114 | + | set machine.ram[1] = range::Range { start: 0x80000000, end: 0x80002000 }; |
|
| 115 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 116 | + | let token = try! frames::reserve(&mut POOL, 4); |
|
| 117 | + | let run = frames::commit(token); |
|
| 118 | + | let memory = try! frames::extent(&POOL, run); |
|
| 119 | + | try testing::expect(memory.start == 0x80000000 and memory.end == 0x80004000); |
|
| 120 | + | set machine.ram[1].end = 0x80003000; |
|
| 121 | + | let mut failed = false; |
|
| 122 | + | try frames::initialize(&mut POOL, &machine) catch err { |
|
| 123 | + | try testing::expect(err == abi::Error::InvalidArg); set failed = true; |
|
| 124 | + | }; |
|
| 125 | + | try testing::expect(failed and POOL.count == 0); |
|
| 126 | + | } |
|
| 127 | + | ||
| 128 | + | /// The full 65,536-frame profile fits; one extra eligible frame fails cleanly. |
|
| 129 | + | @test unsafe fn maximum() throws (testing::TestError) { |
|
| 130 | + | let mut machine: platform::Platform = undefined; |
|
| 131 | + | set machine.ramCount = 1; set machine.reservedCount = 0; |
|
| 132 | + | set machine.ram[0] = range::Range { start: 0x80000000, end: 0x90000000 }; |
|
| 133 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 134 | + | try testing::expect(POOL.count == 65536); |
|
| 135 | + | let token = try! frames::reserve(&mut POOL, 65536); |
|
| 136 | + | let run = frames::commit(token); |
|
| 137 | + | try! frames::release(&mut POOL, run); |
|
| 138 | + | set machine.ram[0].end += 4096; |
|
| 139 | + | let mut failed = false; |
|
| 140 | + | try frames::initialize(&mut POOL, &machine) catch err { |
|
| 141 | + | try testing::expect(err == abi::Error::Exhausted); set failed = true; |
|
| 142 | + | }; |
|
| 143 | + | try testing::expect(failed and POOL.count == 0); |
|
| 144 | + | set machine.ram[0] = range::Range { start: 0xfffffffffffff001, end: 0xffffffffffffffff }; |
|
| 145 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 146 | + | try testing::expect(POOL.count == 0); |
|
| 147 | + | } |
|
| 148 | + | ||
| 149 | + | /// Sorted reservation skipping agrees with byte-range exclusion across banks and gaps. |
|
| 150 | + | @test unsafe fn reservationSweep() throws (testing::TestError) { |
|
| 151 | + | let mut machine: platform::Platform = undefined; |
|
| 152 | + | set machine.ramCount = 3; |
|
| 153 | + | set machine.ram[0] = range::Range { start: 0x80040000, end: 0x80060000 }; |
|
| 154 | + | set machine.ram[1] = range::Range { start: 0x80000000, end: 0x80010000 }; |
|
| 155 | + | set machine.ram[2] = range::Range { start: 0x80010000, end: 0x80020000 }; |
|
| 156 | + | set machine.reservedCount = 6; |
|
| 157 | + | set machine.reserved[0] = range::Range { start: 0x80050001, end: 0x8005f000 }; |
|
| 158 | + | set machine.reserved[1] = range::Range { start: 0x80001234, end: 0x80004321 }; |
|
| 159 | + | set machine.reserved[2] = range::Range { start: 0x80004000, end: 0x80009001 }; |
|
| 160 | + | set machine.reserved[3] = range::Range { start: 0x7ffff000, end: 0x80000001 }; |
|
| 161 | + | set machine.reserved[4] = range::Range { start: 0x80018000, end: 0x80048000 }; |
|
| 162 | + | set machine.reserved[5] = range::Range { start: 0x80002000, end: 0x80003000 }; |
|
| 163 | + | try! frames::initialize(&mut POOL, &machine); |
|
| 164 | + | let mut count: u32 = 0; |
|
| 165 | + | for i in 0..96 { |
|
| 166 | + | let start = 0x80000000 + i as u64 * 4096; |
|
| 167 | + | if platform::available(&machine, range::Range { start, end: start + 4096 }) { |
|
| 168 | + | try testing::expect(count < POOL.count and POOL.addresses[count] == start and POOL.free[count]); |
|
| 169 | + | set count += 1; |
|
| 170 | + | } |
|
| 171 | + | } |
|
| 172 | + | try testing::expect(count == POOL.count); |
|
| 173 | + | } |
kernel/kernel/tests/pages.rad
added
+196 -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 { |
|
| 23 | + | set RAM[i] = 0xa5; |
|
| 24 | + | } |
|
| 25 | + | let pointer: *u8 = &RAM[0]; |
|
| 26 | + | let address = pointer as u64; |
|
| 27 | + | let aligned = (address + 4095) & ~4095; |
|
| 28 | + | set OFFSET = (aligned - address) as u32; |
|
| 29 | + | slots::initialize(&mut STORE.slots[..]); |
|
| 30 | + | backing::initialize(&mut STORE.backings); |
|
| 31 | + | set STORE.backings.pool.count = 8; |
|
| 32 | + | for i in 0..8 { |
|
| 33 | + | set STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; |
|
| 34 | + | set STORE.backings.pool.free[i] = true; |
|
| 35 | + | } |
|
| 36 | + | let owner = abi::Ref { index: 0, generation: 1 }; |
|
| 37 | + | capability::initialize(&mut TABLE, owner); |
|
| 38 | + | try! backing::registerDomain(&mut STORE.backings, owner); |
|
| 39 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 40 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 41 | + | }); |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Drop the test's handle and its object reference in the same serialized operation. |
|
| 45 | + | unsafe fn drop(handle: abi::Handle) { |
|
| 46 | + | let entry = try! capability::invalidate(&mut TABLE, handle); |
|
| 47 | + | try! pages::release(&mut STORE, entry.object); |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// New allocations are zeroed completely and carry the documented default rights. |
|
| 51 | + | @test unsafe fn allocation() throws (testing::TestError) { |
|
| 52 | + | initialize(); |
|
| 53 | + | let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 2); |
|
| 54 | + | let entry = try! capability::get(&TABLE, handle); |
|
| 55 | + | try testing::expect(*entry.rights == (abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER)); |
|
| 56 | + | let page = try! pages::get(&STORE, entry.object); |
|
| 57 | + | try testing::expect(page.count == 2 and page.origin == TABLE.owner and page.handles == 1); |
|
| 58 | + | for i in OFFSET..OFFSET + 8192 { |
|
| 59 | + | try testing::expect(RAM[i] == 0); |
|
| 60 | + | } |
|
| 61 | + | try testing::expect(RAM[OFFSET + 8192] == 0xa5); |
|
| 62 | + | let mut invalid = false; |
|
| 63 | + | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 0) catch err { |
|
| 64 | + | try testing::expect(err == abi::Error::InvalidArg); set invalid = true; |
|
| 65 | + | }; |
|
| 66 | + | try testing::expect(invalid); |
|
| 67 | + | for count in &[65537 as u64, 0xffffffffffffffff] { |
|
| 68 | + | let mut exhausted = false; |
|
| 69 | + | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), count) catch err { |
|
| 70 | + | try testing::expect(err == abi::Error::OutOfMemory); set exhausted = true; |
|
| 71 | + | }; |
|
| 72 | + | try testing::expect(exhausted and STORE.backings.pool.free[2]); |
|
| 73 | + | } |
|
| 74 | + | } |
|
| 75 | + | ||
| 76 | + | /// Splitting preserves contents and backing identity while changing only page extents. |
|
| 77 | + | @test unsafe fn split() throws (testing::TestError) { |
|
| 78 | + | initialize(); |
|
| 79 | + | let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 4); |
|
| 80 | + | set RAM[OFFSET + 8192] = 73; |
|
| 81 | + | let original = try! capability::get(&TABLE, handle); |
|
| 82 | + | for count in &[0 as u64, 4, 5, 0xffffffffffffffff] { |
|
| 83 | + | let mut failed = false; |
|
| 84 | + | try pages::split(&mut STORE, &mut TABLE, handle, count) catch err { |
|
| 85 | + | try testing::expect(err == abi::Error::InvalidArg); set failed = true; |
|
| 86 | + | }; |
|
| 87 | + | try testing::expect(failed); |
|
| 88 | + | } |
|
| 89 | + | let rightHandle = try! pages::split(&mut STORE, &mut TABLE, handle, 2); |
|
| 90 | + | let rightEntry = try! capability::get(&TABLE, rightHandle); |
|
| 91 | + | let left = try! pages::get(&STORE, original.object); |
|
| 92 | + | let right = try! pages::get(&STORE, rightEntry.object); |
|
| 93 | + | try testing::expect(left.count == 2 and right.count == 2 and right.base == left.base + 8192); |
|
| 94 | + | try testing::expect(left.backing == right.backing and left.origin == right.origin); |
|
| 95 | + | try testing::expect(original.rights == rightEntry.rights and RAM[OFFSET + 8192] == 73); |
|
| 96 | + | try testing::expect(STORE.backings.records[left.backing.index].pages == 2); |
|
| 97 | + | try! pages::retain(&mut STORE, original.object, TABLE.owner); |
|
| 98 | + | let mut shared = false; |
|
| 99 | + | try pages::split(&mut STORE, &mut TABLE, handle, 1) catch err { |
|
| 100 | + | try testing::expect(err == abi::Error::Busy); set shared = true; |
|
| 101 | + | }; |
|
| 102 | + | try testing::expect(shared); |
|
| 103 | + | try! pages::release(&mut STORE, original.object); |
|
| 104 | + | } |
|
| 105 | + | ||
| 106 | + | /// Neither handle drop nor origin exit can release frames exposed to another live domain. |
|
| 107 | + | @test unsafe fn retainedPointers() throws (testing::TestError) { |
|
| 108 | + | initialize(); |
|
| 109 | + | let receiver = abi::Ref { index: 1, generation: 1 }; |
|
| 110 | + | try! backing::registerDomain(&mut STORE.backings, receiver); |
|
| 111 | + | let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 112 | + | let entry = try! capability::get(&TABLE, handle); |
|
| 113 | + | try! pages::retain(&mut STORE, entry.object, receiver); |
|
| 114 | + | set RAM[OFFSET] = 99; |
|
| 115 | + | drop(handle); |
|
| 116 | + | try! pages::release(&mut STORE, entry.object); |
|
| 117 | + | try! backing::endDomain(&mut STORE.backings, TABLE.owner); |
|
| 118 | + | try testing::expect(not STORE.backings.pool.free[0] and RAM[OFFSET] == 99); |
|
| 119 | + | try! backing::endDomain(&mut STORE.backings, receiver); |
|
| 120 | + | try testing::expect(STORE.backings.pool.free[0]); |
|
| 121 | + | let owner = abi::Ref { index: 0, generation: 2 }; |
|
| 122 | + | capability::initialize(&mut TABLE, owner); |
|
| 123 | + | try! backing::registerDomain(&mut STORE.backings, owner); |
|
| 124 | + | let authority = try! capability::install(&mut TABLE, capability::Entry { |
|
| 125 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE), |
|
| 126 | + | }); |
|
| 127 | + | let replacement = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 128 | + | try testing::expect(RAM[OFFSET] == 0); |
|
| 129 | + | } |
|
| 130 | + | ||
| 131 | + | /// A full capability table prevents allocation and splitting before payload mutation. |
|
| 132 | + | @test unsafe fn fullTable() throws (testing::TestError) { |
|
| 133 | + | initialize(); |
|
| 134 | + | let handle = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 4); |
|
| 135 | + | let entry = try! capability::get(&TABLE, handle); |
|
| 136 | + | let owner = TABLE.owner; |
|
| 137 | + | for i in 2..limits::HANDLES { |
|
| 138 | + | let filler = try! capability::install(&mut TABLE, capability::Entry { |
|
| 139 | + | kind: abi::Kind::Domain, object: owner, rights: abi::Rights(0), |
|
| 140 | + | }); |
|
| 141 | + | } |
|
| 142 | + | let mut failed: u32 = 0; |
|
| 143 | + | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err { |
|
| 144 | + | try testing::expect(err == abi::Error::Exhausted); set failed += 1; |
|
| 145 | + | }; |
|
| 146 | + | try pages::split(&mut STORE, &mut TABLE, handle, 2) catch err { |
|
| 147 | + | try testing::expect(err == abi::Error::Exhausted); set failed += 1; |
|
| 148 | + | }; |
|
| 149 | + | let page = try! pages::get(&STORE, entry.object); |
|
| 150 | + | try testing::expect(failed == 2 and page.count == 4 and STORE.backings.pool.free[4]); |
|
| 151 | + | } |
|
| 152 | + | ||
| 153 | + | /// Failure at each storage stage releases all earlier reservations. |
|
| 154 | + | @test unsafe fn rollback() throws (testing::TestError) { |
|
| 155 | + | initialize(); |
|
| 156 | + | let full = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8); |
|
| 157 | + | let mut failed = false; |
|
| 158 | + | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err { |
|
| 159 | + | try testing::expect(err == abi::Error::OutOfMemory); set failed = true; |
|
| 160 | + | }; |
|
| 161 | + | try testing::expect(failed and TABLE.slots[2].state == slots::State::Free); |
|
| 162 | + | try testing::expect(STORE.slots[1].state == slots::State::Free and STORE.backings.slots[1].state == slots::State::Free); |
|
| 163 | + | for stage in 0..2 { |
|
| 164 | + | initialize(); |
|
| 165 | + | if stage == 0 { |
|
| 166 | + | for i in 0..limits::ALLOCATIONS { |
|
| 167 | + | set STORE.backings.slots[i].state = slots::State::Retired; |
|
| 168 | + | } |
|
| 169 | + | } else { |
|
| 170 | + | for i in 0..limits::PAGES { |
|
| 171 | + | set STORE.slots[i].state = slots::State::Retired; |
|
| 172 | + | } |
|
| 173 | + | } |
|
| 174 | + | let mut exhausted = false; |
|
| 175 | + | try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err { |
|
| 176 | + | try testing::expect(err == abi::Error::Exhausted); set exhausted = true; |
|
| 177 | + | }; |
|
| 178 | + | try testing::expect(exhausted and TABLE.slots[1].state == slots::State::Free and STORE.backings.pool.free[0]); |
|
| 179 | + | if stage == 0 { |
|
| 180 | + | try testing::expect(STORE.slots[0].state == slots::State::Free); |
|
| 181 | + | } |
|
| 182 | + | } |
|
| 183 | + | } |
|
| 184 | + | ||
| 185 | + | /// A Domain handle with Allocate rights must name the caller to allocate RAM. |
|
| 186 | + | @test unsafe fn allocationAuthority() throws (testing::TestError) { |
|
| 187 | + | initialize(); |
|
| 188 | + | let other = try! capability::install(&mut TABLE, capability::Entry { |
|
| 189 | + | kind: abi::Kind::Domain, object: abi::Ref { index: 1, generation: 1 }, rights: abi::Rights(abi::ALLOCATE), |
|
| 190 | + | }); |
|
| 191 | + | let mut denied = false; |
|
| 192 | + | try pages::allocate(&mut STORE, &mut TABLE, other, 1) catch err { |
|
| 193 | + | try testing::expect(err == abi::Error::Denied); set denied = true; |
|
| 194 | + | }; |
|
| 195 | + | try testing::expect(denied and STORE.backings.pool.free[0] and TABLE.slots[2].state == slots::State::Free); |
|
| 196 | + | } |
kernel/tools/build.rad
+64 -15
| 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 { |
|
| 75 | + | assert mem::eq(dependency, "std"); |
|
| 76 | + | } |
|
| 77 | + | let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly"; |
|
| 49 | 78 | let mut assembly = alloc::new(&mut ASSEMBLY[..]); |
|
| 50 | 79 | let empty: *mut [u8] = &mut []; |
|
| 51 | 80 | let startup = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength], |
|
| 52 | 81 | &mut TEXT[..], &mut empty[..], &mut assembly, &mut STRINGS, 0); |
|
| 53 | 82 | let mut arena = alloc::new(&mut CODE[..]); |
|
| 54 | 83 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 55 | 84 | let mut generator = try! rv64::beginProgram(rv64::ProgramOptions { |
|
| 56 | 85 | entryPatch: rv64::EntryPatch::None, debug: false, |
|
| 57 | 86 | placement: image::Placement::Physical { |
|
| 58 | - | code: 0x80010000, roData: 0x80200000, rwData: 0x80400000, entry: 0x80010000, |
|
| 87 | + | code: CODE_ADDRESS, roData: 0, rwData: 0, entry: CODE_ADDRESS, |
|
| 59 | 88 | }, |
|
| 60 | 89 | }, &mut arena); |
|
| 61 | 90 | rv64::addAssembly(&mut generator, startup); |
|
| 62 | - | for func in package.program.fns { |
|
| 63 | - | rv64::generateFunction(&mut generator, func, &mut scratch); |
|
| 64 | - | alloc::reset(&mut scratch); |
|
| 91 | + | let mut dataCount: u32 = 0; |
|
| 92 | + | for package in &[library, kernel] { |
|
| 93 | + | for item in package.program.data { |
|
| 94 | + | assert dataCount < GLOBALS.len; |
|
| 95 | + | set GLOBALS[dataCount] = item; set dataCount += 1; |
|
| 96 | + | } |
|
| 97 | + | for func in package.program.fns { |
|
| 98 | + | rv64::generateFunction(&mut generator, func, &mut scratch); |
|
| 99 | + | if let error = generator.e.error { |
|
| 100 | + | report(error); io::printLn(func.name); return 1; |
|
| 101 | + | } |
|
| 102 | + | alloc::reset(&mut scratch); |
|
| 103 | + | } |
|
| 65 | 104 | } |
|
| 66 | 105 | for call in &generator.e.pendingCalls[..] { |
|
| 67 | 106 | if dict::get(&generator.e.labels.funcs, call.target) == nil { |
|
| 68 | 107 | io::print("undefined kernel function: "); io::printLn(call.target); return 1; |
|
| 69 | 108 | } |
|
| 70 | 109 | } |
|
| 71 | - | let output = try! rv64::finishProgram(&mut generator, package.program.data, |
|
| 110 | + | let roAddress = (CODE_ADDRESS + generator.e.codeLen as u64 * 4 + 4095) & ~4095; |
|
| 111 | + | let mut symbols: u32 = 0; |
|
| 112 | + | let roSize = try! data::layoutSection(&GLOBALS[..dataCount], &mut SYMBOLS[..], &mut symbols, roAddress, true); |
|
| 113 | + | let rwAddress = (roAddress + roSize as u64 + 4095) & ~4095; |
|
| 114 | + | set generator.placement = image::Placement::Physical { |
|
| 115 | + | code: CODE_ADDRESS, roData: roAddress, rwData: rwAddress, entry: CODE_ADDRESS, |
|
| 116 | + | }; |
|
| 117 | + | let output = try rv64::finishProgram(&mut generator, &GLOBALS[..dataCount], |
|
| 72 | 118 | rv64::Storage { dataSyms: &mut SYMBOLS[..], dataSymEntries: &mut ENTRIES[..] }, |
|
| 73 | - | &[], &mut RO[..], &mut RW[..]); |
|
| 119 | + | &[], &mut RO[..], &mut RW[..]) catch error { |
|
| 120 | + | report(error); |
|
| 121 | + | return 1; |
|
| 122 | + | }; |
|
| 74 | 123 | let header = try! image::header(output.layout); |
|
| 75 | - | let fd = unix::openOpts(env.args[3], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 124 | + | let fd = unix::openOpts(env.args[4], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 76 | 125 | assert fd >= 0; |
|
| 77 | 126 | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(output.code.ptr as *u8, output.code.len * 4)) |
|
| 78 | 127 | and unix::writeAll(fd, &RO[..output.roDataSize]) and unix::writeAll(fd, &RW[..output.rwDataSize]); |
|
| 79 | 128 | let closed = unix::close(fd) == 0; |
|
| 80 | 129 | assert written and closed; |
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' |