kernel: Bootstrap user-space scheduling

5f30e2cdc4faa3700d3602beec6fa6cd3330a3ec99fe3509c2057096baac9113
Alexis Sellier committed ago 1 parent 407a0145
kernel/kernel.rad +2 -0
12 12
export mod trap;
13 13
export mod capability;
14 14
export mod transactions;
15 15
export mod events;
16 16
export mod interrupts;
17 +
export mod devices;
17 18
export mod plic;
18 19
export mod registry;
19 20
export mod frames;
20 21
export mod backing;
21 22
export mod pages;
28 29
export mod timers;
29 30
export mod remote;
30 31
export mod calls;
31 32
export mod sys;
32 33
export mod boot;
34 +
export mod startup;
33 35
@test export mod tests;
kernel/kernel/abi.rad +32 -0
15 15
    eventsHandle: u64,
16 16
    /// Address of the domain's shared event ring.
17 17
    eventsPointer: u64,
18 18
}
19 19
20 +
/// Initial root resources copied into root-owned startup memory.
21 +
export record BootInfo: Copy {
22 +
    /// Number of Device handles after the self and Events slots.
23 +
    devices: u64,
24 +
    /// Number of Interrupt handles following the Device handles.
25 +
    interrupts: u64,
26 +
    /// Bit mask of online harts, whose Budget handles follow in hart order.
27 +
    harts: u64,
28 +
    /// Machine timer ticks per second.
29 +
    timebase: u64,
30 +
    /// Image used to construct root and additional instances of its program.
31 +
    rootImage: Handle,
32 +
    /// Initial user-space scheduler image.
33 +
    schedulerImage: Handle,
34 +
    /// Read-only machine-time device, or zero when absent.
35 +
    clock: Handle,
36 +
    /// Platform shutdown device, or zero when absent.
37 +
    shutdown: Handle,
38 +
    /// Page that owns this header and the copied handle array.
39 +
    arguments: Handle,
40 +
    /// Copied capability values in their initial slot order.
41 +
    handles: *unsafe mut [u64],
42 +
}
43 +
20 44
/// Physical extent returned by QueryPage.
21 45
export record PageInfo: Copy {
22 46
    /// Address of the first physical frame.
23 47
    base: u64,
24 48
    /// Number of contiguous physical frames.
25 49
    count: u64,
26 50
}
27 51
52 +
/// Physical MMIO extent returned by QueryDevice.
53 +
export record DeviceInfo: Copy {
54 +
    /// Physical base address of the register region.
55 +
    base: u64,
56 +
    /// Size of the register region in bytes.
57 +
    size: u64,
58 +
}
59 +
28 60
/// Physical source returned by QueryInterrupt.
29 61
export record InterruptInfo: Copy {
30 62
    /// PLIC source number; zero does not name an interrupt.
31 63
    number: u32,
32 64
}
kernel/kernel/boot.rad +22 -1
1 1
//! Machine entry boundary and published platform state.
2 2
3 3
use super::platform;
4 +
use super::startup;
4 5
use super::fdt;
5 6
use super::range;
6 7
use super::limits;
7 8
use super::sync;
8 9
use super::trap;
13 14
use super::budgets;
14 15
use super::dispatch;
15 16
use super::timers;
16 17
use super::remote;
17 18
use super::interrupts;
19 +
use super::devices;
18 20
use super::plic;
19 21
use std::arch::rv64::shared::catalog;
20 22
21 23
/// Platform data published by hart zero before secondary initialization.
22 24
export unsafe static PLATFORM: platform::Platform = undefined;
23 25
/// Release/acquire publication flag for PLATFORM.
24 26
static READY: u64 = 0;
25 27
/// Number of harts that validated their machine stack.
26 28
static ARRIVED: u64 = 0;
29 +
/// Root resource publication before user dispatch begins.
30 +
static STARTED: u64 = 0;
27 31
/// Trap anchors owned by their physical harts.
28 32
unsafe static HARTS: [trap::Hart; limits::HARTS] = undefined;
29 33
30 34
/// Dispatch interrupts, user calls, and user faults; halt on kernel faults.
31 35
unsafe fn unexpected(frame: &mut trap::Frame, hart: &mut trap::Hart) {
93 97
        budgets::initialize(&mut budgets::STORE);
94 98
        timers::initialize(&mut timers::STORE);
95 99
        remote::initialize(&mut remote::STORE, PLATFORM.harts);
96 100
        interrupts::initialize(&mut interrupts::STORE, PLATFORM.irqSources);
97 101
        try! plic::initialize(&PLATFORM);
98 -
        assert entryCount == 2;
102 +
        try! devices::initialize(&mut devices::STORE, &PLATFORM);
103 +
        assert entryCount >= 2 and entryCount <= limits::PACKAGES;
99 104
        registry::initialize(&mut registry::STORE);
100 105
        let bootCatalog = @sliceOf(entries, entryCount);
101 106
        try! registry::boot(&mut registry::STORE, &bootCatalog[..]);
102 107
        let library = registry::find(&registry::STORE, &"std"[..]) else panic "boot std";
103 108
        let kernel = registry::find(&registry::STORE, &"kernel"[..]) else panic "boot kernel";
128 133
        }
129 134
    }
130 135
    return sync::fetchAdd(&mut ARRIVED, 1) + 1 == count;
131 136
}
132 137
138 +
/// Publish configured root resources once, then enter dispatch on every hart.
139 +
export unsafe fn launch(last: bool) -> ! {
140 +
    if last {
141 +
        if let root = registry::find(&registry::STORE, &"root"[..]) {
142 +
            let scheduler = registry::find(&registry::STORE, &"scheduler"[..]) else panic "missing scheduler image";
143 +
            let domain = try! startup::create(&PLATFORM, root, scheduler);
144 +
            print("kernel: root ready\n");
145 +
        }
146 +
        sync::storeRelease(&mut STARTED, 1);
147 +
    } else {
148 +
        while sync::loadAcquire(&STARTED) == 0 {
149 +
        }
150 +
    }
151 +
    run();
152 +
}
153 +
133 154
/// Enter dispatch after every online hart publishes its initialized state.
134 155
export unsafe fn run() -> ! {
135 156
    let mut count: u64 = 0;
136 157
    for id in 0..limits::HARTS {
137 158
        if (PLATFORM.harts & (1 << id)) <> 0 {
kernel/kernel/boot.ras +9 -1
5 5
.export @kernel::boot::read8;
6 6
.export @kernel::boot::write8;
7 7
8 8
@kernel::boot::start
9 9
    call @kernel::boot::initialize;
10 -
    call @kernel::boot::run;
10 +
    call @kernel::boot::launch;
11 11
    ebreak;
12 12
13 13
// Initialize machine CSRs and preserve the firmware stack across Radiance entry.
14 14
@kernel::boot::initialize
15 15
    csrw mie %zero;
34 34
@kernel::boot::write8
35 35
    fence iorw iorw;
36 36
    sb %a1 0(%a0);
37 37
    fence iorw iorw;
38 38
    ret;
39 +
40 +
.export @kernel::startup::info;
41 +
.export @kernel::startup::words;
42 +
// Resolve root-owned startup memory after physical allocation.
43 +
@kernel::startup::info
44 +
    ret;
45 +
@kernel::startup::words
46 +
    ret;
kernel/kernel/calls.rad +20 -1
1 1
//! Direct-call execution and checked copies into domain-owned memory.
2 2
3 3
use super::abi;
4 +
use std::lang::il;
4 5
use super::slots;
5 6
use super::limits;
6 7
use super::capability;
7 8
use super::pages;
8 9
use super::domains;
15 16
use super::dispatch;
16 17
use super::timers;
17 18
use super::sync;
18 19
use super::remote;
19 20
use super::interrupts;
21 +
use super::devices;
20 22
use super::plic;
21 23
use super::lifecycle;
22 24
23 25
/// Resolve the owning hart for an operation that can change CPU authority.
24 26
unsafe fn budgetHart(table: &capability::Table, operation: u64, arguments: &[u64]) -> ?u32 throws (abi::Error) {
61 63
            let object = try domains::get(&domains::STORE, entry.object);
62 64
            if object.state == domains::Lifecycle::Dead {
63 65
                throw abi::Error::BadHandle;
64 66
            }
65 67
        },
68 +
        case abi::Kind::Device => {
69 +
            let object = try devices::get(&devices::STORE, entry.object);
70 +
        },
66 71
        case abi::Kind::Image => {
67 72
            let object = try registry::get(&registry::STORE, entry.object);
68 73
        },
69 74
        else => throw abi::Error::BadHandle,
70 75
    }
249 254
        case 22, 43, 49 => {
250 255
            let stopped = try termination(owner, operation, arguments);
251 256
            return 0;
252 257
        },
253 258
        case 10, 11 => return *try delegate(&mut domain, handle, abi::Handle(arguments[1]), arguments[2], operation == 11, now),
259 +
        case il::DEVICE_ACCESS => {
260 +
            let flags = arguments[2];
261 +
            if (flags & ~0x1ff) <> 0 {
262 +
                throw abi::Error::InvalidArg;
263 +
            }
264 +
            return try devices::address(&devices::STORE, &domain.memory.table, handle,
265 +
                arguments[1], flags & 0xff, (flags & 0x100) <> 0);
266 +
        },
254 267
        case 12 => {
255 268
            try drop(&mut domain, handle);
256 269
            return 0;
257 270
        },
258 271
        case 20 => return *try domains::create(&mut domains::STORE, &mut pages::STORE.backings,
298 311
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Page, abi::Rights(0));
299 312
            let page = try pages::get(&pages::STORE, entry.object);
300 313
            try reply(&domain, arguments, &[page.base, page.count as u64]);
301 314
            return 0;
302 315
        },
316 +
        case 45 => {
317 +
            let entry = try capability::lookup(&domain.memory.table, handle, abi::Kind::Device, abi::Rights(0));
318 +
            let device = try devices::get(&devices::STORE, entry.object);
319 +
            try reply(&domain, arguments, &[device.info.base, device.info.size]);
320 +
            return 0;
321 +
        },
303 322
        case 46 => {
304 323
            let number = try interrupts::query(&interrupts::STORE, &domain.memory.table, handle);
305 324
            if arguments[2] <> @sizeOf(abi::InterruptInfo) as u64 or arguments[3] <> 0
306 325
                or not buffer(&domain, arguments[1], 4, abi::Rights(abi::WRITE), 4) {
307 326
                    throw abi::Error::InvalidArg;
665 684
    assert trap::fromUser(frame) and frame.cause == 8;
666 685
    assert trap::advanceCall(frame);
667 686
    let args = [frame.registers[10], frame.registers[11], frame.registers[12], frame.registers[13]];
668 687
    let result = try execute(owner, frame.registers[17], &args[..], now) catch error {
669 688
        let operation = frame.registers[17];
670 -
        if operation >= 44 and operation <= 48 and error == abi::Error::BadHandle {
689 +
        if operation == il::DEVICE_ACCESS or (operation >= 44 and operation <= 48 and error == abi::Error::BadHandle) {
671 690
            try execute(owner, 43, &[0, 0, 0, 0], now) catch stopped {
672 691
                assert stopped == abi::Error::BadHandle;
673 692
            };
674 693
        }
675 694
        set frame.registers[10] = (0 as u64) - error as u64;
kernel/kernel/devices.rad added +99 -0
1 +
//! Immutable MMIO regions and domain-relative device authority.
2 +
use super::abi;
3 +
use super::limits;
4 +
use super::platform;
5 +
use super::capability;
6 +
7 +
/// One platform register region available to user-space drivers.
8 +
export record Region: Copy {
9 +
    /// Platform mechanism used during bootstrap handoff.
10 +
    kind: platform::Kind,
11 +
    /// Physical register extent returned by QueryDevice.
12 +
    info: abi::DeviceInfo,
13 +
    /// Maximum rights installed by bootstrap.
14 +
    rights: abi::Rights,
15 +
}
16 +
17 +
/// Device identities retained for the complete boot lifetime.
18 +
export record Store: Copy {
19 +
    /// Exported regions in device-tree order.
20 +
    regions: [Region; limits::DEVICES],
21 +
    /// Number of published regions.
22 +
    count: u32,
23 +
}
24 +
25 +
/// Global device metadata published before any user context starts.
26 +
export unsafe static STORE: Store = undefined;
27 +
28 +
/// Publish driver regions and the read-only CLINT time register.
29 +
/// Interrupt-controller and timer-programming registers stay kernel-private.
30 +
export fn initialize(store: &mut Store, machine: &platform::Platform) throws (abi::Error) {
31 +
    set store.count = 0;
32 +
    if machine.deviceCount > limits::DEVICES {
33 +
        throw abi::Error::InvalidArg;
34 +
    }
35 +
    let mut count: u32 = 0;
36 +
    for i in 0..machine.deviceCount {
37 +
        let device = machine.devices[i];
38 +
        if device.memory.start >= device.memory.end {
39 +
            throw abi::Error::InvalidArg;
40 +
        }
41 +
        if device.kind == platform::Kind::Plic {
42 +
            continue;
43 +
        }
44 +
        let mut base = device.memory.start;
45 +
        let mut size = device.memory.end - base;
46 +
        let mut rights = abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER;
47 +
        if device.kind == platform::Kind::Clint {
48 +
            if size < 0xc000 {
49 +
                throw abi::Error::InvalidArg;
50 +
            }
51 +
            set base += 0xbff8;
52 +
            set size = 8;
53 +
            set rights &= ~abi::WRITE;
54 +
        }
55 +
        set store.regions[count] = Region {
56 +
            kind: device.kind, info: abi::DeviceInfo { base, size }, rights: abi::Rights(rights),
57 +
        };
58 +
        set count += 1;
59 +
    }
60 +
    set store.count = count;
61 +
}
62 +
63 +
/// Resolve an immutable device identity; physical regions use generation one.
64 +
export fn get(store: &Store, object: abi::Ref) -> Region throws (abi::Error) {
65 +
    if object.index >= store.count or object.generation <> 1 {
66 +
        throw abi::Error::BadHandle;
67 +
    }
68 +
    return store.regions[object.index];
69 +
}
70 +
71 +
/// Install bootstrap authority for one published physical device.
72 +
export fn install(store: &Store, table: &mut capability::Table, index: u32) -> abi::Handle throws (abi::Error) {
73 +
    let object = abi::Ref { index, generation: 1 };
74 +
    let region = try get(store, object);
75 +
    return try capability::install(table, capability::Entry { kind: abi::Kind::Device, object, rights: region.rights });
76 +
}
77 +
78 +
/// Validate one register access and return its physical address to generated code.
79 +
export fn address(store: &Store, table: &capability::Table, handle: abi::Handle,
80 +
    offset: u64, width: u64, writing: bool) -> u64 throws (abi::Error)
81 +
{
82 +
    let mut right = abi::READ;
83 +
    if writing {
84 +
        set right = abi::WRITE;
85 +
    }
86 +
    let entry = try capability::lookup(table, handle, abi::Kind::Device, abi::Rights(right));
87 +
    let region = (try get(store, entry.object)).info;
88 +
    if width <> 1 and width <> 2 and width <> 4 and width <> 8 {
89 +
        throw abi::Error::InvalidArg;
90 +
    }
91 +
    if offset > region.size or width > region.size - offset or region.base > 0xffffffffffffffff - offset {
92 +
        throw abi::Error::InvalidArg;
93 +
    }
94 +
    let address = region.base + offset;
95 +
    if (address & (width - 1)) <> 0 {
96 +
        throw abi::Error::InvalidArg;
97 +
    }
98 +
    return address;
99 +
}
kernel/kernel/domains.rad +65 -7
725 725
    match reservation { case Reservation::Held(creation) => try prepareCreation(packages, creation), }
726 726
}
727 727
728 728
/// Initialize retained domain storage outside the metadata lock.
729 729
export unsafe fn prepareCreation(packages: &registry::Store, creation: &Creation) throws (abi::Error) {
730 -
    let mut storage = creation.storage;
730 +
    try prepareStorage(packages, creation.storage, creation.domain);
731 +
}
732 +
733 +
/// Initialize one domain's private allocations before capability publication.
734 +
unsafe fn prepareStorage(packages: &registry::Store, storage: Prepared, owner: abi::Ref) throws (abi::Error) {
735 +
    let mut storage = storage;
731 736
    zero(storage.memory as u64, storage.allocation.count);
732 737
    zero(storage.kernelStack.start, storage.kernelFrames.count);
733 738
    try instances::fill(packages, &storage.graph.graph, storage.graph.base);
734 -
    capability::initialize(&mut storage.memory.table, creation.domain);
739 +
    capability::initialize(&mut storage.memory.table, owner);
735 740
}
736 741
737 742
/// Check the unpublished capacity retained by one creation transaction.
738 743
fn require(store: &Store, table: &capability::Table, creation: &Creation) {
739 744
    assert table.owner == creation.owner;
752 757
    release: fn(&mut frames::Pool, frames::Run) throws (abi::Error))
753 758
{
754 759
    match reservation {
755 760
        case Reservation::Held(creation) => {
756 761
            require(store, table, &creation);
762 +
            try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(creation.handle));
757 763
            discard(&mut backings.pool, creation.storage, release);
758 764
            try! slots::cancel(&mut store.contextSlots[..], slots::Reservation::Held(creation.context));
759 765
            try! slots::cancel(&mut store.slots[..], slots::Reservation::Held(creation.domain));
760 -
            try! slots::cancel(&mut table.slots[..], slots::Reservation::Held(creation.handle));
761 766
        },
762 767
    }
763 768
}
764 769
765 770
/// Publish an initialized domain and its Events under metadata serialization.
767 772
    -> abi::Handle throws (abi::Error)
768 773
{
769 774
    match reservation {
770 775
        case Reservation::Held(creation) => {
771 776
            require(store, table, &creation);
772 -
            if not backing::domainLive(backings, creation.owner) {
777 +
            let root = creation.owner == creation.domain;
778 +
            if not root and not backing::domainLive(backings, creation.owner) {
773 779
                cancel(store, backings, table, Reservation::Held(creation));
774 780
                throw abi::Error::BadHandle;
775 781
            }
776 782
            let object = creation.domain;
777 783
            let initial = creation.context;
795 801
                owner: object, state: ContextState::Ready, hart: nil, pending: nil, reservation: false, kernelFrames: prepared.kernelFrames, kernelStack: prepared.kernelStack,
796 802
                userStack: range::Range { start: 0, end: 0 }, frame,
797 803
            };
798 804
            set store.reclaimed[object.index] = false;
799 805
            set store.reaping[object.index] = false;
806 +
            let mut parent: ?abi::Ref = nil;
807 +
            if not root {
808 +
                set parent = creation.owner;
809 +
            }
800 810
            set store.records[object.index] = Domain {
801 -
                state: Lifecycle::Pending, creator: creation.owner, parent: creation.owner, image: creation.image, initial,
811 +
                state: Lifecycle::Pending, creator: creation.owner, parent, image: creation.image, initial,
802 812
                allocation: prepared.allocation, memory: prepared.memory, graph, events: queue,
803 813
            };
804 -
            set store.ancestors[object.index] = store.ancestors[creation.owner.index];
805 -
            set store.ancestors[object.index][creation.owner.index / 32] |= 1 << (creation.owner.index % 32);
814 +
            if not root {
815 +
                set store.ancestors[object.index] = store.ancestors[creation.owner.index];
816 +
                set store.ancestors[object.index][creation.owner.index / 32] |= 1 << (creation.owner.index % 32);
817 +
            }
806 818
            let context = try! slots::commit(&mut store.contextSlots[..], slots::Reservation::Held(initial));
807 819
            let domain = try! slots::commit(&mut store.slots[..], slots::Reservation::Held(object));
808 820
            return capability::publish(table, slots::Reservation::Held(creation.handle), capability::Entry {
809 821
                kind: abi::Kind::Domain, object: domain, rights: creation.rights,
810 822
            });
811 823
        },
812 824
    }
813 825
}
814 826
827 +
/// Create the first domain with its self capability in slot zero and Events in slot one.
828 +
/// Boot calls this before any domain, context, or exposure membership is published.
829 +
export unsafe fn bootstrap(store: &mut Store, backings: &mut backing::Store, packages: &registry::Store, image: abi::Ref)
830 +
    -> abi::Ref throws (abi::Error)
831 +
{
832 +
    for slot in store.slots {
833 +
        if slot.state <> slots::State::Free {
834 +
            throw abi::Error::Busy;
835 +
        }
836 +
    }
837 +
    let package = try registry::get(packages, image);
838 +
    let entry = package.entry else {
839 +
        throw abi::Error::VerifyFailed;
840 +
    };
841 +
    let domainSlot = try slots::reserve(&mut store.slots[..]);
842 +
    let contextSlot = try slots::reserve(&mut store.contextSlots[..]) catch error {
843 +
        try! slots::cancel(&mut store.slots[..], domainSlot); throw error;
844 +
    };
845 +
    let object = slots::reference(&domainSlot);
846 +
    let mut storage = try allocate(&mut backings.pool, packages, image) catch error {
847 +
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
848 +
        try! slots::cancel(&mut store.slots[..], domainSlot); throw error;
849 +
    };
850 +
    try prepareStorage(packages, storage, object) catch error {
851 +
        discard(&mut backings.pool, storage, frames::release);
852 +
        try! slots::cancel(&mut store.contextSlots[..], contextSlot);
853 +
        try! slots::cancel(&mut store.slots[..], domainSlot); throw error;
854 +
    };
855 +
    let handleSlot = try! slots::reserve(&mut storage.memory.table.slots[..]);
856 +
    assert slots::reference(&handleSlot).index == 0;
857 +
    match domainSlot {
858 +
        case slots::Reservation::Held(domain) => match contextSlot {
859 +
            case slots::Reservation::Held(context) => match handleSlot {
860 +
                case slots::Reservation::Held(handle) => {
861 +
                    let result = try publish(store, backings, &mut storage.memory.table, Reservation::Held(Creation {
862 +
                        owner: object, handle, domain, context,
863 +
                        rights: abi::Rights(MANAGEMENT_RIGHTS | abi::CREATE | abi::ALLOCATE), image, entry, storage,
864 +
                    }));
865 +
                    assert (try! abi::decode(result)).object.index == 0;
866 +
                    return object;
867 +
                },
868 +
            },
869 +
        },
870 +
    }
871 +
}
872 +
815 873
/// Create a private domain while the caller has exclusive access to metadata.
816 874
export unsafe fn create(store: &mut Store, backings: &mut backing::Store, packages: &registry::Store,
817 875
    table: &mut capability::Table, authority: abi::Handle, image: abi::Handle) -> abi::Handle throws (abi::Error)
818 876
{
819 877
    let pending = try reserve(store, backings, packages, table, authority, image);
kernel/kernel/startup.rad added +82 -0
1 +
//! Initial root resources and execution authority.
2 +
use super::abi;
3 +
use super::platform;
4 +
use super::limits;
5 +
use super::domains;
6 +
use super::pages;
7 +
use super::registry;
8 +
use super::devices;
9 +
use super::interrupts;
10 +
use super::budgets;
11 +
use super::dispatch;
12 +
use super::slots;
13 +
use super::capability;
14 +
15 +
/// Map a root-owned startup allocation to its ABI record.
16 +
fn info(address: u64) -> *mut abi::BootInfo;
17 +
/// Map an aligned root-owned startup extent to copied handle words.
18 +
fn words(address: u64) -> *unsafe mut u64;
19 +
20 +
/// Install root's fixed resource slots and activate its initial context on hart zero.
21 +
/// Boot calls this after global and per-hart initialization, before user dispatch.
22 +
export unsafe fn create(machine: &platform::Platform, image: abi::Ref, scheduler: abi::Ref) -> abi::Ref throws (abi::Error) {
23 +
    if (machine.harts & 1) == 0 or machine.timebase == 0 {
24 +
        throw abi::Error::InvalidArg;
25 +
    }
26 +
    let root = try domains::bootstrap(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, image);
27 +
    let mut domain = try! domains::get(&domains::STORE, root);
28 +
    let table = &mut domain.memory.table;
29 +
    let self = try! abi::handle(abi::Kind::Domain, abi::Ref { index: 0, generation: table.slots[0].generation });
30 +
    let mut clock = abi::Handle(0);
31 +
    let mut shutdown = abi::Handle(0);
32 +
    for i in 0..devices::STORE.count {
33 +
        let handle = try devices::install(&devices::STORE, &mut *table, i);
34 +
        if devices::STORE.regions[i].kind == platform::Kind::Clint {
35 +
            set clock = handle;
36 +
        }
37 +
        if devices::STORE.regions[i].kind == platform::Kind::Finish {
38 +
            set shutdown = handle;
39 +
        }
40 +
    }
41 +
    for number in 1..interrupts::STORE.count + 1 {
42 +
        let handle = try interrupts::seed(&mut interrupts::STORE, &mut *table, number);
43 +
    }
44 +
    let now = dispatch::clock();
45 +
    let second = machine.timebase as u64;
46 +
    if now > 0xffffffffffffffff - second * 61 {
47 +
        throw abi::Error::InvalidArg;
48 +
    }
49 +
    let start = now + second;
50 +
    let end = start + second * 60;
51 +
    let mut count = 2 + devices::STORE.count + interrupts::STORE.count;
52 +
    for hart in 0..limits::HARTS {
53 +
        if (machine.harts & (1 << hart)) == 0 {
54 +
            continue;
55 +
        }
56 +
        let handle = try budgets::seed(&mut budgets::STORE, &mut *table, hart, start, end);
57 +
        if hart == 0 {
58 +
            let bound = try budgets::bind(&mut budgets::STORE, &domains::STORE, &mut *table,
59 +
                budgets::Binding { budget: handle, domain: self, context: domain.initial }, now);
60 +
        }
61 +
        set count += 1;
62 +
    }
63 +
    let rootImage = try registry::install(&registry::STORE, &mut *table, image);
64 +
    let schedulerImage = try registry::install(&registry::STORE, &mut *table, scheduler);
65 +
    let stack = try pages::allocate(&mut pages::STORE, &mut *table, self, 16);
66 +
    let arguments = try pages::allocate(&mut pages::STORE, &mut *table, self, 1);
67 +
    let stackPage = try! pages::get(&pages::STORE, (try! capability::get(&*table, stack)).object);
68 +
    let argumentPage = try! pages::get(&pages::STORE, (try! capability::get(&*table, arguments)).object);
69 +
    assert @sizeOf(abi::BootInfo) as u64 + count as u64 * 8 <= limits::FRAME_SIZE;
70 +
    let handles = @sliceOf(words(argumentPage.base + @sizeOf(abi::BootInfo) as u64), count);
71 +
    for i in 0..count {
72 +
        assert table.slots[i].state == slots::State::Live;
73 +
        set handles[i] = *try! abi::handle(table.entries[i].kind, abi::Ref { index: i, generation: table.slots[i].generation });
74 +
    }
75 +
    set *info(argumentPage.base) = abi::BootInfo {
76 +
        devices: devices::STORE.count as u64, interrupts: interrupts::STORE.count as u64,
77 +
        harts: machine.harts as u64, timebase: second, rootImage, schedulerImage, clock, shutdown, arguments, handles,
78 +
    };
79 +
    try domains::activate(&mut domains::STORE, &pages::STORE, &mut *table, self,
80 +
        stackPage.base + stackPage.count as u64 * limits::FRAME_SIZE, argumentPage.base, @sizeOf(abi::BootInfo) as u64);
81 +
    return root;
82 +
}
kernel/kernel/sys.rad +10 -0
146 146
        try! result(ecall(abi::Operation::QueryPage as u32, *handle as i64, (&mut info) as i64, @sizeOf(abi::PageInfo) as i64, 0));
147 147
        return info;
148 148
    }
149 149
}
150 150
151 +
/// Read Device metadata. An invalid handle is a program error.
152 +
export fn queryDevice(handle: abi::Handle) -> abi::DeviceInfo {
153 +
    unsafe {
154 +
        let mut info: abi::DeviceInfo = undefined;
155 +
        try! result(ecall(abi::Operation::QueryDevice as u32, *handle as i64,
156 +
            (&mut info) as i64, @sizeOf(abi::DeviceInfo) as i64, 0));
157 +
        return info;
158 +
    }
159 +
}
160 +
151 161
/// Read a physical interrupt source. An invalid handle is a program error.
152 162
export fn queryInterrupt(handle: abi::Handle) -> abi::InterruptInfo {
153 163
    unsafe {
154 164
        let mut info: abi::InterruptInfo = undefined;
155 165
        try! result(ecall(abi::Operation::QueryInterrupt as u32, *handle as i64,
kernel/kernel/tests.rad +1 -0
11 11
export mod backing;
12 12
export mod pages;
13 13
export mod transactions;
14 14
export mod events;
15 15
export mod interrupts;
16 +
export mod devices;
16 17
export mod registry;
17 18
export mod loader;
18 19
export mod instances;
19 20
export mod domains;
20 21
export mod lifecycle;
kernel/kernel/tests/calls.rad +25 -1
14 14
use kernel::trap;
15 15
use kernel::budgets;
16 16
use kernel::remote;
17 17
use kernel::interrupts;
18 18
use kernel::events;
19 +
use kernel::devices;
20 +
use kernel::platform;
19 21
20 22
/// Event storage for administrative destruction targets.
21 23
unsafe static TARGETS: [domains::Memory; 2] = undefined;
22 24
23 25
/// Retained dead-domain records cannot be queried or delegated through handles.
381 383
    try testing::expect(budgets::STORE.slots[0].state == slots::State::Free);
382 384
    let installed = try! capability::get(&MEMORY.table, queue);
383 385
    try testing::expect(installed.kind == abi::Kind::Events);
384 386
}
385 387
388 +
/// Device queries copy only metadata into checked caller-owned memory.
389 +
@test unsafe fn deviceRecord() throws (testing::TestError) {
390 +
    let owner = initialize();
391 +
    let storage = try! calls::invoke(owner, 30, &[0, 1, 0, 0], 0);
392 +
    set devices::STORE.count = 1;
393 +
    set devices::STORE.regions[0] = devices::Region {
394 +
        kind: platform::Kind::Uart, info: { base: 0x10000000, size: 256 }, rights: abi::Rights(abi::READ),
395 +
    };
396 +
    let device = try! devices::install(&devices::STORE, &mut MEMORY.table, 0);
397 +
    let base = (&RAM[OFFSET]) as u64;
398 +
    set RAM[OFFSET + 2] = 123;
399 +
    assert try! calls::invoke(owner, 45, &[*device, base, 16, 0], 0) == 0;
400 +
    assert RAM[OFFSET] == 0x10000000 and RAM[OFFSET + 1] == 256 and RAM[OFFSET + 2] == 123;
401 +
    let mut rejected: u32 = 0;
402 +
    for args in &[[*device, base + 1, 16, 0], [*device, base, 8, 0], [*device, base, 16, 1]] {
403 +
        try calls::invoke(owner, 45, &args[..], 0) catch error {
404 +
            assert error == abi::Error::InvalidArg; set rejected += 1;
405 +
        };
406 +
    }
407 +
    assert rejected == 3 and RAM[OFFSET + 2] == 123;
408 +
}
409 +
386 410
/// A query copies exactly its record into writable memory owned by the caller.
387 411
@test unsafe fn records() throws (testing::TestError) {
388 412
    let owner = initialize();
389 413
    let handle = try! calls::invoke(owner, 30, &[0, 1, 0, 0], 0);
390 414
    let base = (&RAM[OFFSET]) as u64;
431 455
    try testing::expect(rejected == 6 and domains::STORE.contextSlots[0].state == slots::State::Free);
432 456
}
433 457
434 458
/// Invalid infallible queries terminate the caller at the syscall boundary.
435 459
@test unsafe fn invalidQueryAborts() throws (testing::TestError) {
436 -
    for operation in [44 as u64, 46, 47, 48] {
460 +
    for operation in [44 as u64, 45, 46, 47, 48] {
437 461
        let owner = initialize();
438 462
        try! events::open(&mut domains::STORE.events, owner, &mut MEMORY.ring);
439 463
        let mut frame = trap::Frame { registers: [0; 32], pc: 0x80000000, status: 0x80, cause: 8, value: 0 };
440 464
        set frame.registers[17] = operation;
441 465
        calls::handle(owner, &mut frame, 0, calls::invoke);
kernel/kernel/tests/devices.rad added +117 -0
1 +
//! Device identity, exported regions, and capability delegation.
2 +
use std::testing;
3 +
use kernel::abi;
4 +
use kernel::devices;
5 +
use kernel::platform;
6 +
use kernel::capability;
7 +
use kernel::calls;
8 +
use kernel::domains;
9 +
use kernel::slots;
10 +
11 +
/// Device metadata under test.
12 +
unsafe static STORE: devices::Store = undefined;
13 +
/// Synthetic validated platform regions.
14 +
unsafe static MACHINE: platform::Platform = undefined;
15 +
/// Independent caller and receiver capability storage.
16 +
unsafe static MEMORY: [domains::Memory; 2] = undefined;
17 +
18 +
/// Describe driver devices around kernel-owned interrupt and timer registers.
19 +
unsafe fn initialize() {
20 +
    set MACHINE.deviceCount = 4;
21 +
    set MACHINE.devices[0] = { kind: platform::Kind::Uart, memory: { start: 0x10000000, end: 0x10000100 } };
22 +
    set MACHINE.devices[1] = { kind: platform::Kind::Plic, memory: { start: 0x0c000000, end: 0x10000000 } };
23 +
    set MACHINE.devices[2] = { kind: platform::Kind::Clint, memory: { start: 0x02000000, end: 0x02010000 } };
24 +
    set MACHINE.devices[3] = { kind: platform::Kind::Finish, memory: { start: 0x10001000, end: 0x10002000 } };
25 +
    try! devices::initialize(&mut STORE, &MACHINE);
26 +
}
27 +
28 +
/// Driver regions retain tree order and expose only read-only timer state.
29 +
@test unsafe fn regions() throws (testing::TestError) {
30 +
    initialize();
31 +
    assert STORE.count == 3;
32 +
    assert STORE.regions[0].kind == platform::Kind::Uart;
33 +
    let timer = abi::DeviceInfo { base: 0x0200bff8, size: 8 };
34 +
    assert STORE.regions[1].info == timer;
35 +
    assert not abi::permits(STORE.regions[1].rights, abi::Rights(abi::WRITE));
36 +
    assert STORE.regions[2].kind == platform::Kind::Finish;
37 +
    let mut rejected: u32 = 0;
38 +
    for object in [abi::Ref { index: 3, generation: 1 }, abi::Ref { index: 0, generation: 2 }] {
39 +
        try devices::get(&STORE, object) catch error {
40 +
            assert error == abi::Error::BadHandle; set rejected += 1;
41 +
        };
42 +
    }
43 +
    assert rejected == 2;
44 +
    set MACHINE.devices[2].memory.end = 0x0200bfff;
45 +
    try devices::initialize(&mut STORE, &MACHINE) catch error {
46 +
        assert error == abi::Error::InvalidArg; set rejected += 1;
47 +
    };
48 +
    assert rejected == 3 and STORE.count == 0;
49 +
}
50 +
51 +
/// Device authority can move to a surviving domain and can only lose rights.
52 +
@test unsafe fn delegation() throws (testing::TestError) {
53 +
    initialize();
54 +
    set devices::STORE = STORE;
55 +
    domains::initialize(&mut domains::STORE);
56 +
    let mut owners: [abi::Ref; 2] = undefined;
57 +
    for i in 0..2 {
58 +
        let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
59 +
        let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
60 +
        set owners[i] = owner;
61 +
        capability::initialize(&mut MEMORY[i].table, owner);
62 +
        set domains::STORE.records[owner.index].state = domains::Lifecycle::Active;
63 +
        set domains::STORE.records[owner.index].memory = &mut MEMORY[i];
64 +
    }
65 +
    let target = try! capability::install(&mut MEMORY[0].table, capability::Entry {
66 +
        kind: abi::Kind::Domain, object: owners[1], rights: abi::Rights(0),
67 +
    });
68 +
    let device = try! devices::install(&STORE, &mut MEMORY[0].table, 2);
69 +
    let original = try! capability::get(&MEMORY[0].table, device);
70 +
    let granted = abi::Handle(try! calls::invoke(owners[0], 10, &[*device, *target, abi::READ as u64, 0], 0));
71 +
    let reader = try! capability::get(&MEMORY[1].table, granted);
72 +
    assert reader.object == original.object and reader.rights == abi::Rights(abi::READ);
73 +
    let moved = abi::Handle(try! calls::invoke(owners[0], 11, &[*device, *target, *original.rights as u64, 0], 0));
74 +
    assert (try! capability::get(&MEMORY[1].table, moved)) == original;
75 +
    let old = try! abi::decode(device);
76 +
    assert MEMORY[0].table.slots[old.object.index].state == slots::State::Free;
77 +
    let dropped = try! calls::invoke(owners[1], 12, &[*moved, 0, 0, 0], 0);
78 +
    assert dropped == 0;
79 +
    assert (try! devices::get(&devices::STORE, reader.object)).info.base == 0x10001000;
80 +
}
81 +
82 +
/// Every register access checks current authority and the complete physical extent.
83 +
@test unsafe fn access() throws (testing::TestError) {
84 +
    initialize();
85 +
    capability::initialize(&mut MEMORY[0].table, abi::Ref { index: 0, generation: 1 });
86 +
    let device = try! devices::install(&STORE, &mut MEMORY[0].table, 0);
87 +
    for width in [1 as u64, 2, 4, 8] {
88 +
        assert (try! devices::address(&STORE, &MEMORY[0].table, device, 256 - width, width, false)) == 0x10000100 - width;
89 +
        assert (try! devices::address(&STORE, &MEMORY[0].table, device, 0, width, true)) == 0x10000000;
90 +
    }
91 +
    let mut rejected: u32 = 0;
92 +
    for offset in [1 as u64, 253, 256, 0xffffffffffffffff] {
93 +
        try devices::address(&STORE, &MEMORY[0].table, device, offset, 4, false) catch error {
94 +
            assert error == abi::Error::InvalidArg; set rejected += 1;
95 +
        };
96 +
    }
97 +
    for width in [0 as u64, 3, 16, 0x100000004] {
98 +
        try devices::address(&STORE, &MEMORY[0].table, device, 0, width, false) catch error {
99 +
            assert error == abi::Error::InvalidArg; set rejected += 1;
100 +
        };
101 +
    }
102 +
    let clock = try! devices::install(&STORE, &mut MEMORY[0].table, 1);
103 +
    try devices::address(&STORE, &MEMORY[0].table, clock, 0, 8, true) catch error {
104 +
        assert error == abi::Error::Denied; set rejected += 1;
105 +
    };
106 +
    let removed = try! capability::invalidate(&mut MEMORY[0].table, device);
107 +
    let replacement = try! devices::install(&STORE, &mut MEMORY[0].table, 0);
108 +
    assert replacement <> device;
109 +
    try devices::address(&STORE, &MEMORY[0].table, device, 0, 1, false) catch error {
110 +
        assert error == abi::Error::BadHandle; set rejected += 1;
111 +
    };
112 +
    set STORE.regions[0].info.base = 0x10000001;
113 +
    try devices::address(&STORE, &MEMORY[0].table, replacement, 0, 4, false) catch error {
114 +
        assert error == abi::Error::InvalidArg; set rejected += 1;
115 +
    };
116 +
    assert rejected == 11;
117 +
}
kernel/kernel/tests/domains.rad +55 -0
71 71
        template: &[7], memory: 8, alignment: 8, relocations: &[],
72 72
    });
73 73
    set IMAGE = try! registry::install(&PACKAGES, &mut TABLE, object);
74 74
}
75 75
76 +
/// Failed root construction restores capacity and permits a fresh attempt.
77 +
@test unsafe fn bootstrapRollback() throws (testing::TestError) {
78 +
    initialize(abi::CREATE);
79 +
    let image = (try! capability::get(&TABLE, IMAGE)).object;
80 +
    domains::initialize(&mut DOMAINS);
81 +
    set BACKINGS.domains[0] = 0;
82 +
    let capacity = BACKINGS.pool.count;
83 +
    for count in [0 as u32, 1, 8, 32] {
84 +
        set BACKINGS.pool.count = count;
85 +
        let mut failed = false;
86 +
        try domains::bootstrap(&mut DOMAINS, &mut BACKINGS, &PACKAGES, image) catch error {
87 +
            assert error == abi::Error::OutOfMemory; set failed = true;
88 +
        };
89 +
        assert failed and DOMAINS.slots[0].state == slots::State::Free;
90 +
        assert DOMAINS.contextSlots[0].state == slots::State::Free;
91 +
        assert BACKINGS.domains[0] == 0 and DOMAINS.events.queues[0].generation == 0;
92 +
        for i in 0..count {
93 +
            assert frames::available(&BACKINGS.pool, i);
94 +
        }
95 +
    }
96 +
    set BACKINGS.pool.count = capacity;
97 +
    let root = try! domains::bootstrap(&mut DOMAINS, &mut BACKINGS, &PACKAGES, image);
98 +
    assert root.index == 0 and root.generation > 1;
99 +
    assert (try! domains::get(&DOMAINS, root)).parent == nil;
100 +
}
101 +
102 +
/// Root construction owns slot zero and installs the fixed bootstrap handles.
103 +
@test unsafe fn bootstrap() throws (testing::TestError) {
104 +
    initialize(abi::CREATE);
105 +
    let image = (try! capability::get(&TABLE, IMAGE)).object;
106 +
    domains::initialize(&mut DOMAINS);
107 +
    set BACKINGS.domains[0] = 0;
108 +
    let root = try! domains::bootstrap(&mut DOMAINS, &mut BACKINGS, &PACKAGES, image);
109 +
    assert root.index == 0 and root.generation == 1;
110 +
    let domain = try! domains::get(&DOMAINS, root);
111 +
    assert domain.creator == root and domain.parent == nil and domain.state == domains::Lifecycle::Pending;
112 +
    assert domain.memory.table.slots[0].state == slots::State::Live;
113 +
    let self = domain.memory.table.entries[0];
114 +
    assert self.kind == abi::Kind::Domain and self.object == root;
115 +
    assert *self.rights == (domains::MANAGEMENT_RIGHTS | abi::CREATE | abi::ALLOCATE);
116 +
    let queue = try! abi::decode(domain.events);
117 +
    assert queue.object.index == 1 and queue.kind == abi::Kind::Events;
118 +
    assert domain.memory.table.entries[1].object == root;
119 +
    assert DOMAINS.contexts[domain.initial.index].frame.pc == 0x80000000;
120 +
    assert DOMAINS.contexts[domain.initial.index].frame.registers[3] == domain.graph.table.ptr as u64;
121 +
    for word in DOMAINS.ancestors[0] {
122 +
        assert word == 0;
123 +
    }
124 +
    let mut busy = false;
125 +
    try domains::bootstrap(&mut DOMAINS, &mut BACKINGS, &PACKAGES, image) catch error {
126 +
        assert error == abi::Error::Busy; set busy = true;
127 +
    };
128 +
    assert busy and DOMAINS.slots[1].state == slots::State::Free;
129 +
}
130 +
76 131
/// Interrupted creation returns all private storage after its caller releases its hart.
77 132
@test unsafe fn cancelledCreation() throws (testing::TestError) {
78 133
    initialize(abi::CREATE);
79 134
    lifecycle::initialize(&mut CALLS);
80 135
    let handle = try! domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
kernel/mmio.rad added +62 -0
1 +
//! Fixed-width register access through live Device capabilities.
2 +
use super::abi;
3 +
4 +
/// Compiler primitive for one checked 8-bit register read.
5 +
@intrinsic fn deviceRead8(handle: u64, offset: u64) -> u8;
6 +
/// Compiler primitive for one checked 8-bit register write.
7 +
@intrinsic fn deviceWrite8(handle: u64, offset: u64, value: u8);
8 +
9 +
/// Read an aligned 8-bit register at a byte offset within the device.
10 +
export fn read8(handle: abi::Handle, offset: u64) -> u8 {
11 +
    return deviceRead8(*handle, offset);
12 +
}
13 +
14 +
/// Write an aligned 8-bit register at a byte offset within the device.
15 +
export fn write8(handle: abi::Handle, offset: u64, value: u8) {
16 +
    deviceWrite8(*handle, offset, value);
17 +
}
18 +
19 +
/// Compiler primitive for one checked 16-bit register read.
20 +
@intrinsic fn deviceRead16(handle: u64, offset: u64) -> u16;
21 +
/// Compiler primitive for one checked 16-bit register write.
22 +
@intrinsic fn deviceWrite16(handle: u64, offset: u64, value: u16);
23 +
24 +
/// Read an aligned 16-bit register at a byte offset within the device.
25 +
export fn read16(handle: abi::Handle, offset: u64) -> u16 {
26 +
    return deviceRead16(*handle, offset);
27 +
}
28 +
29 +
/// Write an aligned 16-bit register at a byte offset within the device.
30 +
export fn write16(handle: abi::Handle, offset: u64, value: u16) {
31 +
    deviceWrite16(*handle, offset, value);
32 +
}
33 +
34 +
/// Compiler primitive for one checked 32-bit register read.
35 +
@intrinsic fn deviceRead32(handle: u64, offset: u64) -> u32;
36 +
/// Compiler primitive for one checked 32-bit register write.
37 +
@intrinsic fn deviceWrite32(handle: u64, offset: u64, value: u32);
38 +
39 +
/// Read an aligned 32-bit register at a byte offset within the device.
40 +
export fn read32(handle: abi::Handle, offset: u64) -> u32 {
41 +
    return deviceRead32(*handle, offset);
42 +
}
43 +
44 +
/// Write an aligned 32-bit register at a byte offset within the device.
45 +
export fn write32(handle: abi::Handle, offset: u64, value: u32) {
46 +
    deviceWrite32(*handle, offset, value);
47 +
}
48 +
49 +
/// Compiler primitive for one checked 64-bit register read.
50 +
@intrinsic fn deviceRead64(handle: u64, offset: u64) -> u64;
51 +
/// Compiler primitive for one checked 64-bit register write.
52 +
@intrinsic fn deviceWrite64(handle: u64, offset: u64, value: u64);
53 +
54 +
/// Read an aligned 64-bit register at a byte offset within the device.
55 +
export fn read64(handle: abi::Handle, offset: u64) -> u64 {
56 +
    return deviceRead64(*handle, offset);
57 +
}
58 +
59 +
/// Write an aligned 64-bit register at a byte offset within the device.
60 +
export fn write64(handle: abi::Handle, offset: u64, value: u64) {
61 +
    deviceWrite64(*handle, offset, value);
62 +
}
kernel/root.rad added +62 -0
1 +
//! Initial resource handoff to the user-space scheduler.
2 +
export mod abi;
3 +
export mod sys;
4 +
5 +
/// Root startup memory supplied by the kernel.
6 +
record Env: Copy {
7 +
    /// Writable header and copied resource handles.
8 +
    argsPointer: *unsafe mut abi::BootInfo,
9 +
    /// Header size in bytes.
10 +
    argsSize: u64,
11 +
    /// Root's event capability.
12 +
    eventsHandle: u64,
13 +
    /// Root's event-ring address.
14 +
    eventsPointer: u64,
15 +
}
16 +
17 +
/// Transfer root's resource bundle and parent duties before normal exit.
18 +
@default unsafe fn main(env: *Env) -> ! {
19 +
    assert env.argsSize == @sizeOf(abi::BootInfo) as u64;
20 +
    let mut info = env.argsPointer;
21 +
    let self = abi::Handle(info.handles[0]);
22 +
    let scheduler = try! sys::domainCreate(self, info.schedulerImage);
23 +
    let context = try! sys::queryContext(scheduler, 0);
24 +
    let authority = try! sys::capabilityGrant(scheduler, scheduler, abi::RIGHTS_MASK as u64);
25 +
    let stack = try! sys::pageAllocate(self, 16);
26 +
    let stackInfo = sys::queryPage(stack);
27 +
    let sharedStack = try! sys::capabilityTransfer(stack, scheduler, abi::RIGHTS_MASK as u64);
28 +
    let mut handles = info.handles;
29 +
    let clock = info.clock;
30 +
    let shutdown = info.shutdown;
31 +
    let budgets = 2 + info.devices as u32 + info.interrupts as u32;
32 +
    for i in 2..budgets {
33 +
        let old = abi::Handle(handles[i]);
34 +
        let moved = try! sys::capabilityTransfer(old, scheduler, abi::RIGHTS_MASK as u64);
35 +
        set handles[i] = *moved;
36 +
        if old == clock {
37 +
            set info.clock = moved;
38 +
        }
39 +
        if old == shutdown {
40 +
            set info.shutdown = moved;
41 +
        }
42 +
    }
43 +
    for i in budgets..handles.len {
44 +
        let budget = abi::Handle(handles[i]);
45 +
        let window = try! sys::queryBudget(budget);
46 +
        if window.hart == 0 {
47 +
            let future = try! sys::budgetSplit(budget, window.start + info.timebase * 2);
48 +
            set handles[i] = *try! sys::budgetBind(future, scheduler, context.context);
49 +
        } else {
50 +
            set handles[i] = *try! sys::capabilityTransfer(budget, scheduler, abi::RIGHTS_MASK as u64);
51 +
        }
52 +
    }
53 +
    set info.rootImage = try! sys::capabilityTransfer(info.rootImage, scheduler, abi::RIGHTS_MASK as u64);
54 +
    set info.schedulerImage = try! sys::capabilityTransfer(info.schedulerImage, scheduler, abi::RIGHTS_MASK as u64);
55 +
    set info.arguments = try! sys::capabilityTransfer(info.arguments, scheduler, abi::RIGHTS_MASK as u64);
56 +
    set handles[0] = *authority;
57 +
    set handles[1] = 0;
58 +
    try! sys::domainReparent(self, scheduler);
59 +
    try! sys::domainActivate(scheduler, stackInfo.base + stackInfo.count * 4096,
60 +
        info as *unsafe opaque, @sizeOf(abi::BootInfo) as u64);
61 +
    sys::exit(0);
62 +
}
kernel/scheduler.rad added +47 -0
1 +
//! User-space CPU windows and bounded worker execution.
2 +
export mod abi;
3 +
export mod sys;
4 +
export mod mmio;
5 +
6 +
/// Scheduler or worker startup values.
7 +
record Env: Copy {
8 +
    /// Shared resource bundle for the scheduler.
9 +
    argsPointer: *abi::BootInfo,
10 +
    /// Zero for a worker, or the resource header size for the scheduler.
11 +
    argsSize: u64,
12 +
    /// Installed event capability.
13 +
    eventsHandle: u64,
14 +
    /// Shared event-ring address.
15 +
    eventsPointer: u64,
16 +
}
17 +
18 +
/// Run a worker or distribute four successive CPU windows from the boot bundle.
19 +
@default unsafe fn main(env: *Env) -> ! {
20 +
    if env.argsSize == 0 {
21 +
        sys::exit(7);
22 +
    }
23 +
    assert env.argsSize == @sizeOf(abi::BootInfo) as u64;
24 +
    let info = env.argsPointer;
25 +
    let self = abi::Handle(info.handles[0]);
26 +
    let started = mmio::read64(info.clock, 0);
27 +
    assert sys::queryDomain(self).state == 1;
28 +
    for turn in 0..4 {
29 +
        let current = sys::currentContext();
30 +
        let budget = abi::Handle(current.budget);
31 +
        let window = try! sys::queryBudget(budget);
32 +
        let later = try! sys::budgetSplit(budget, window.start + info.timebase);
33 +
        let worker = try! sys::domainCreate(self, info.schedulerImage);
34 +
        let stack = try! sys::pageAllocate(self, 16);
35 +
        let extent = sys::queryPage(stack);
36 +
        let transferred = try! sys::capabilityTransfer(stack, worker, abi::RIGHTS_MASK as u64);
37 +
        try! sys::domainActivate(worker, extent.base + extent.count * 4096, (&*env) as *unsafe opaque, 0);
38 +
        try! sys::yield(worker);
39 +
        assert sys::currentContext().budget == *later;
40 +
    }
41 +
    assert mmio::read64(info.clock, 0) > started;
42 +
    if info.shutdown <> abi::Handle(0) {
43 +
        mmio::write32(info.shutdown, 0, 0x5555);
44 +
        panic "shutdown returned";
45 +
    }
46 +
    sys::exit(0);
47 +
}
kernel/tools/build.rad +46 -21
17 17
use std::arch::rv64::shared;
18 18
use std::arch::rv64::shared::catalog;
19 19
20 20
/// Native entry page, above the platform firmware data.
21 21
constant CODE_ADDRESS: u64 = 0x81000000;
22 +
/// Maximum shared packages in the boot image.
23 +
constant PACKAGES: u32 = 4;
22 24
/// Package code arenas retained until image output completes.
23 -
unsafe static CODE: [[u8; 16777216]; 2] = undefined;
25 +
unsafe static CODE: [[u8; 16777216]; PACKAGES] = undefined;
24 26
/// Reusable function workspace.
25 27
static SCRATCH: [u8; 16777216] = [0; 16777216];
26 28
/// Persistent decoded RIL storage.
27 29
static DECODE: [u8; 67108864] = [0; 67108864];
28 30
/// Exact package bytes retained in the native catalog.
29 -
unsafe static INPUT: [[u8; 8388608]; 2] = undefined;
31 +
unsafe static INPUT: [[u8; 8388608]; PACKAGES] = undefined;
30 32
/// Combined startup and boundary assembly source.
31 33
static SOURCE: [u8; 65536] = [0; 65536];
32 34
/// Assembler workspace.
33 35
static ASSEMBLY: [u8; 4194304] = [0; 4194304];
34 36
/// Assembled startup words.
35 37
static TEXT: [u32; 16384] = [0; 16384];
36 38
/// Assembly identifiers retained through package linking.
37 39
unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 };
38 40
/// Per-package data layout workspaces.
39 -
unsafe static DATA: [[data::DataSym; 4096]; 2] = undefined;
41 +
unsafe static DATA: [[data::DataSym; 4096]; PACKAGES] = undefined;
40 42
/// Local function and data definitions.
41 -
unsafe static SYMBOLS: [[shared::Symbol; 4096]; 2] = undefined;
43 +
unsafe static SYMBOLS: [[shared::Symbol; 4096]; PACKAGES] = undefined;
42 44
/// Public package symbols.
43 -
unsafe static EXPORTS: [[shared::Symbol; 4096]; 2] = undefined;
45 +
unsafe static EXPORTS: [[shared::Symbol; 4096]; PACKAGES] = undefined;
46 +
/// Exports from dependencies compiled before the current package.
47 +
unsafe static IMPORTS: [shared::Symbol; 16384] = undefined;
44 48
/// Private-state relocation records.
45 -
unsafe static RELOCS: [[shared::Relocation; 4096]; 2] = undefined;
49 +
unsafe static RELOCS: [[shared::Relocation; 4096]; PACKAGES] = undefined;
46 50
/// Initialized package templates.
47 -
unsafe static TEMPLATES: [[u8; 1048576]; 2] = undefined;
51 +
unsafe static TEMPLATES: [[u8; 1048576]; PACKAGES] = undefined;
48 52
/// Read-only catalog and retained package payloads.
49 53
static RO: [u8; 16777216] = [0; 16777216];
50 54
/// Kernel state table and initialized private package graph.
51 55
static RW: [u8; 33554432] = [0; 33554432];
52 56
/// Contiguous native code segment, including entry trampoline and alignment padding.
83 87
            template: &mut TEMPLATES[slot][..], relocations: &mut RELOCS[slot][..],
84 88
        }, &mut arena, &mut scratch);
85 89
}
86 90
87 91
/// Emit a trampoline that preserves firmware arguments and supplies the native catalog.
88 -
unsafe fn trampoline(kernel: u64, ro: u64, rw: u64) {
92 +
unsafe fn trampoline(kernel: u64, ro: u64, rw: u64, count: u32) {
89 93
    let mut arena = alloc::new(&mut SCRATCH[..]);
90 94
    let mut e = try! emit::emitter(&mut arena, false);
91 95
    emit::loadImm(&mut e, rv64::GP, rw as i64);
92 96
    emit::loadImm(&mut e, rv64::A3, ro as i64);
93 -
    emit::loadImm(&mut e, rv64::A4, 2);
97 +
    emit::loadImm(&mut e, rv64::A4, count as i64);
94 98
    emit::loadImm(&mut e, rv64::T0, kernel as i64);
95 99
    emit::emit(&mut e, encode::jalr(rv64::ZERO, rv64::T0, 0));
96 100
    assert e.codeLen <= 1024;
97 101
    for word, i in emit::getCode(&e) {
98 102
        set NATIVE[i] = word;
99 103
    }
100 104
}
101 105
102 106
/// Link shared boot packages, retain their catalog, and instantiate the kernel graph.
103 107
@default unsafe fn main(env: *sys::Env) -> i32 {
104 -
    assert env.args.len == 5;
108 +
    assert env.args.len >= 5 and env.args.len <= PACKAGES + 3;
109 +
    let count = env.args.len - 3;
105 110
    let mut decoder = alloc::new(&mut DECODE[..]);
106 111
    let libraryBytesLength = unix::readFile(env.args[1], &mut INPUT[0][..]) else panic "std RIL";
107 112
    let libraryBytes = &INPUT[0][..libraryBytesLength];
108 113
    let libraryInput = try! program::decode(libraryBytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
109 114
    let kernelBytesLength = unix::readFile(env.args[2], &mut INPUT[1][..]) else panic "kernel RIL";
113 118
    assert libraryInput.dependencies.len == 0;
114 119
    for dependency in kernelInput.dependencies {
115 120
        assert mem::eq(dependency, "std");
116 121
    }
117 122
    let sourceLength = unix::readFile(env.args[3], &mut SOURCE[..]) else panic "kernel assembly";
123 +
    let source = &SOURCE[..sourceLength];
118 124
    let mut assemblyArena = alloc::new(&mut ASSEMBLY[..]);
119 125
    let empty: *mut [u8] = &mut [];
120 -
    let assembly = try! asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..sourceLength],
126 +
    let assembly = try! asm::assemble(asm::scanner::SourceKind::String, source,
121 127
        &mut TEXT[..], &mut empty[..], &mut assemblyArena, &mut STRINGS, 0);
122 128
    let library = try compile(&libraryInput, 0, CODE_ADDRESS + 4096, &[],
123 129
        asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error {
124 130
            report(error);
125 131
            return 1;
127 133
    let kernelAddress = aligned(library.codeAddress + library.code.len as u64 * 4, 4096);
128 134
    let kernel = try compile(&kernelInput, 1, kernelAddress, library.exports, assembly) catch error {
129 135
        report(error);
130 136
        return 1;
131 137
    };
132 -
    let entries = [
133 -
        catalog::Entry { source: libraryBytes, package: library },
134 -
        catalog::Entry { source: kernelBytes, package: kernel },
135 -
    ];
136 -
    let codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4;
138 +
    let mut catalogEntries: [catalog::Entry; PACKAGES] = undefined;
139 +
    set catalogEntries[0] = catalog::Entry { source: libraryBytes, package: library };
140 +
    set catalogEntries[1] = catalog::Entry { source: kernelBytes, package: kernel };
141 +
    let mut codeEnd = kernel.codeAddress + kernel.code.len as u64 * 4;
142 +
    for slot in 2..count {
143 +
        let bytesLength = unix::readFile(env.args[slot + 3], &mut INPUT[slot][..]) else panic "boot package RIL";
144 +
        let bytes = &INPUT[slot][..bytesLength];
145 +
        let input = try! program::decode(bytes, &mut decoder, binary::Limits { registers: 8192, blocks: 4096 });
146 +
        let mut imported: u32 = 0;
147 +
        for previous in 0..slot {
148 +
            for symbol in catalogEntries[previous].package.exports {
149 +
                assert imported < IMPORTS.len;
150 +
                set IMPORTS[imported] = symbol;
151 +
                set imported += 1;
152 +
            }
153 +
        }
154 +
        let package = try compile(&input, slot, aligned(codeEnd, 4096), &IMPORTS[..imported],
155 +
            asm::Program { text: &[], data: &[], symbols: &[], externalFixups: &[] }) catch error {
156 +
                report(error);
157 +
                return 1;
158 +
            };
159 +
        set catalogEntries[slot] = catalog::Entry { source: bytes, package };
160 +
        set codeEnd = package.codeAddress + package.code.len as u64 * 4;
161 +
    }
137 162
    let codeSize = (codeEnd - CODE_ADDRESS) as u32;
138 163
    assert codeEnd - CODE_ADDRESS <= @sizeOf([u32; 4194304]) as u64;
139 164
    let roAddress = aligned(codeEnd, 4096);
140 -
    let roSize = try catalog::pack(&entries[..], roAddress, &mut RO[..]) catch error {
165 +
    let roSize = try catalog::pack(&catalogEntries[..count], roAddress, &mut RO[..]) catch error {
141 166
        report(error);
142 167
        return 1;
143 168
    };
144 169
    let rwAddress = aligned(roAddress + roSize as u64, 4096);
145 170
    let mut bases: [u64; shared::MAX_PACKAGES] = [0; shared::MAX_PACKAGES];
146 171
    let mut rwSize: u32 = @sizeOf([u64; shared::MAX_PACKAGES]);
147 -
    for entry in &entries[..] {
172 +
    for entry in &catalogEntries[..2] {
148 173
        let at = aligned(rwAddress + rwSize as u64, entry.package.alignment);
149 174
        assert at - rwAddress <= RW.len as u64 and entry.package.memory <= RW.len - (at - rwAddress) as u32;
150 175
        set bases[entry.package.slot] = at;
151 176
        set rwSize = (at - rwAddress) as u32 + entry.package.memory;
152 177
    }
153 178
    try! mem::copy(&mut RW[..@sizeOf([u64; shared::MAX_PACKAGES])],
154 179
        @sliceOf(&bases[0] as *unsafe u8, @sizeOf([u64; shared::MAX_PACKAGES])));
155 -
    for entry in &entries[..] {
180 +
    for entry in &catalogEntries[..2] {
156 181
        let at = (bases[entry.package.slot] - rwAddress) as u32;
157 182
        try shared::instantiate(&entry.package, &bases[..], &mut RW[at..at + entry.package.memory])
158 183
            catch error {
159 184
                report(error);
160 185
                return 1;
161 186
            };
162 187
    }
163 188
    for i in 0..codeSize / 4 {
164 189
        set NATIVE[i] = encode::nop();
165 190
    }
166 -
    for entry in &entries[..] {
191 +
    for entry in &catalogEntries[..count] {
167 192
        let at = ((entry.package.codeAddress - CODE_ADDRESS) / 4) as u32;
168 193
        for word, i in entry.package.code {
169 194
            set NATIVE[at + i] = word;
170 195
        }
171 196
    }
172 -
    trampoline(kernelAddress, roAddress, rwAddress);
197 +
    trampoline(kernelAddress, roAddress, rwAddress, count);
173 198
    let header = try! image::header(image::Layout {
174 199
        entry: CODE_ADDRESS,
175 200
        code: image::Segment { address: CODE_ADDRESS, initialized: codeSize, memory: codeSize },
176 201
        roData: image::Segment { address: roAddress, initialized: roSize, memory: roSize },
177 202
        rwData: image::Segment { address: rwAddress, initialized: rwSize, memory: rwSize },
test/acceptance/replay added +27 -0
1 +
#!/bin/sh
2 +
# Compare complete output from two executions of the same native image.
3 +
set -eu
4 +
if [ "$#" -lt 4 ]; then
5 +
    printf 'usage: replay profile emulator image harts [machine-options...]\n' >&2
6 +
    exit 1
7 +
fi
8 +
profile=$1
9 +
emulator=$2
10 +
image=$3
11 +
harts=$4
12 +
shift 4
13 +
mkdir -p bin/acceptance/traces
14 +
prefix="bin/acceptance/traces/$profile-$harts"
15 +
for attempt in 1 2; do
16 +
    if ! "$emulator" -machine -harts="$harts" -memory-size=262144 -max-steps=2000000000 "$@" -run "$image" > "$prefix.$attempt.log" 2>&1; then
17 +
        cat "$prefix.$attempt.log"
18 +
        exit 1
19 +
    fi
20 +
done
21 +
if ! cmp -s "$prefix.1.log" "$prefix.2.log"; then
22 +
    diff -u "$prefix.1.log" "$prefix.2.log" || true
23 +
    printf 'replay: %s %s-hart output differs\n' "$profile" "$harts" >&2
24 +
    exit 1
25 +
fi
26 +
cat "$prefix.1.log"
27 +
printf 'replay: %s %s-hart output matched\n' "$profile" "$harts"
test/boot/run +5 -3
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 7
cat test/boot/machine.ras kernel/kernel/*.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"
8 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -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 10
    # The deterministic emulator shares its tick budget across all harts.
11 11
    steps=$((10000000 * harts))
12 12
    if ! "$emulator" -machine -max-steps="$steps" -harts="$harts" -run "$work/boot.rv64" > "$work/log" 2>&1; then
13 13
        cat "$work/log" >&2
21 21
        cat "$work/log" >&2
22 22
        exit 1
23 23
    fi
24 24
    printf 'kernel boot: %s harts passed\n' "$harts"
25 25
done
26 +
cat kernel/kernel/*.ras > "$work/idle.ras"
27 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/idle.ras" "$work/idle.rv64"
26 28
status=0
27 -
"$emulator" -machine -harts=8 -max-steps=80000000 -run bin/kernel.rv64 > "$work/log" 2>&1 || status=$?
29 +
"$emulator" -machine -harts=8 -max-steps=80000000 -run "$work/idle.rv64" > "$work/log" 2>&1 || status=$?
28 30
if [ "$status" -ne 2 ] || [ "$(grep -c 'wfi=1 mcause=0x0' "$work/log")" -ne 8 ]; then
29 31
    cat "$work/log" >&2
30 32
    exit 1
31 33
fi
32 34
printf 'kernel startup: all eight harts reached machine idle without traps\n'
33 35
cat test/boot/trap.ras kernel/kernel/*.ras > "$work/trap.ras"
34 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
36 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 -- bin/std.ril bin/kernel.ril "$work/trap.ras" "$work/trap.rv64"
35 37
status=0
36 38
"$emulator" -machine -harts=2 -max-steps=20000000 -run "$work/trap.rv64" > "$work/log" 2>&1 || status=$?
37 39
if [ "$status" -ne 2 ] || [ "$(grep -c '^kernel: unexpected trap$' "$work/log")" -ne 1 ] \
38 40
    || [ "$(grep -c 'wfi=1' "$work/log")" -ne 2 ]; then
39 41
    cat "$work/log" >&2
test/bootstrap/kernel/dispatchcheck.rad added +88 -0
1 +
//! Native root construction, exit, and continued resource authority.
2 +
use kernel::abi;
3 +
use kernel::boot;
4 +
use kernel::registry;
5 +
use kernel::domains;
6 +
use kernel::capability;
7 +
use kernel::pages;
8 +
use kernel::budgets;
9 +
use kernel::dispatch;
10 +
use kernel::calls;
11 +
use kernel::events;
12 +
use kernel::sync;
13 +
use kernel::startup;
14 +
use kernel::devices;
15 +
use kernel::interrupts;
16 +
17 +
/// Initial root identity retained by the terminal event.
18 +
unsafe static ROOT: abi::Ref = undefined;
19 +
/// Surviving controller domain with inherited resource authority.
20 +
unsafe static CONTROLLER: abi::Ref = undefined;
21 +
/// Image capability installed in the controller's own table.
22 +
unsafe static IMAGE: abi::Handle = undefined;
23 +
/// Current kernel package-state table.
24 +
fn kernelGp() -> u64;
25 +
/// Controller entry address.
26 +
fn completion() -> u64;
27 +
/// Signal successful completion to the emulator.
28 +
fn finish();
29 +
/// Map an assembly-compatible physical byte address.
30 +
fn memory(address: u64) -> *mut u8;
31 +
32 +
/// Construct root from the boot catalog and give its child a later CPU window.
33 +
export unsafe fn setup() {
34 +
    let image = registry::find(&registry::STORE, &"loaded"[..]) else panic "missing boot user package";
35 +
    let service = registry::find(&registry::STORE, &"service"[..]) else panic "missing boot service package";
36 +
    set ROOT = try! startup::create(&boot::PLATFORM, image, service);
37 +
    assert ROOT.index == 0;
38 +
    let mut root = try! domains::get(&domains::STORE, ROOT);
39 +
    let self = try! abi::handle(abi::Kind::Domain, abi::Ref { index: 0, generation: root.memory.table.slots[0].generation });
40 +
    assert (try! abi::decode(root.events)).object.index == 1;
41 +
    let executable = try! registry::install(&registry::STORE, &mut root.memory.table, image);
42 +
    let childHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings,
43 +
        &registry::STORE, &mut root.memory.table, self, executable);
44 +
    set CONTROLLER = (try! capability::get(&root.memory.table, childHandle)).object;
45 +
    let mut child = try! domains::get(&domains::STORE, CONTROLLER);
46 +
    let inherited = domains::MANAGEMENT_RIGHTS | abi::CREATE | abi::ALLOCATE;
47 +
    let authority = try! calls::invoke(ROOT, 10, &[*childHandle, *childHandle, inherited as u64, 0], 0);
48 +
    assert service.index == 3 and image.index == 2;
49 +
    set IMAGE = try! registry::install(&registry::STORE, &mut child.memory.table, service);
50 +
    try! domains::reparent(&mut domains::STORE, &root.memory.table, self, childHandle);
51 +
    set domains::STORE.records[CONTROLLER.index].state = domains::Lifecycle::Active;
52 +
    let frame = &mut domains::STORE.contexts[child.initial.index].frame;
53 +
    set frame.pc = completion(); set frame.status = 0x1880;
54 +
    set frame.registers[2] = domains::STORE.contexts[child.initial.index].kernelStack.end;
55 +
    set frame.registers[3] = kernelGp();
56 +
    let now = dispatch::clock();
57 +
    let index = 2 + devices::STORE.count + interrupts::STORE.count;
58 +
    let budget = try! abi::handle(abi::Kind::Budget, abi::Ref { index, generation: root.memory.table.slots[index].generation });
59 +
    let window = try! budgets::query(&budgets::STORE, &root.memory.table, budget);
60 +
    let start = window.start;
61 +
    let later = try! budgets::split(&mut budgets::STORE, &mut root.memory.table, budget, start + 10000000, now);
62 +
    let running = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut root.memory.table,
63 +
        budgets::Binding { budget, domain: self, context: root.initial }, now);
64 +
    let transferred = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut root.memory.table,
65 +
        budgets::Binding { budget: later, domain: childHandle, context: child.initial }, now);
66 +
}
67 +
68 +
/// Root exit leaves the survivor able to allocate and create new domains.
69 +
export unsafe fn verify() {
70 +
    let guard = sync::enter();
71 +
    let mut child = try! domains::get(&domains::STORE, CONTROLLER);
72 +
    let terminal = try! events::pop(&mut child.memory.ring) else panic "missing root exit";
73 +
    assert terminal.kind == events::CHILD_EXIT and terminal.code == 0 and terminal.value == abi::id(ROOT);
74 +
    assert child.state == domains::Lifecycle::Active and child.parent == nil;
75 +
    try! events::refresh(&mut domains::STORE.events, CONTROLLER, &child.memory.ring);
76 +
    sync::leave(guard);
77 +
    let page = abi::Handle(try! calls::synchronized(CONTROLLER, 30, &[0, 1, 0, 0], dispatch::clock()));
78 +
    let domain = abi::Handle(try! calls::synchronized(CONTROLLER, 20, &[0, *IMAGE, 0, 0], dispatch::clock()));
79 +
    let checking = sync::enter();
80 +
    let allocated = try! capability::get(&child.memory.table, page);
81 +
    assert (try! pages::get(&pages::STORE, allocated.object)).count == 1;
82 +
    let object = (try! capability::get(&child.memory.table, domain)).object;
83 +
    let created = try! domains::get(&domains::STORE, object);
84 +
    assert created.creator == CONTROLLER and created.parent == CONTROLLER;
85 +
    assert created.state == domains::Lifecycle::Pending;
86 +
    sync::leave(checking);
87 +
    finish();
88 +
}
test/bootstrap/machine.ras added +25 -0
1 +
//! Boot one hart and check user termination through production dispatch.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
.export @kernel::dispatchcheck::memory;
7 +
.export @kernel::dispatchcheck::kernelGp;
8 +
.export @kernel::dispatchcheck::completion;
9 +
.export @kernel::dispatchcheck::finish;
10 +
@kernel::dispatchcheck::memory
11 +
    ret;
12 +
@kernel::dispatchcheck::kernelGp
13 +
    mv %a0 %gp;
14 +
    ret;
15 +
@kernel::dispatchcheck::completion
16 +
    la %a0 @complete;
17 +
    ret;
18 +
@complete
19 +
    call @kernel::dispatchcheck::verify;
20 +
    ebreak;
21 +
@kernel::dispatchcheck::finish
22 +
    li %t0 0x10001000;
23 +
    li %t1 0x5555;
24 +
    sw %t1 0(%t0);
25 +
    ebreak;
test/bootstrap/root.rad added +48 -0
1 +
//! Validate the root startup bundle from user mode.
2 +
export mod abi;
3 +
export mod sys;
4 +
5 +
/// Typed startup view used by the boot root package.
6 +
record Env: Copy {
7 +
    /// Root-owned resource bundle.
8 +
    argsPointer: *abi::BootInfo,
9 +
    /// Byte size of the bundle header.
10 +
    argsSize: u64,
11 +
    /// Installed Events capability.
12 +
    eventsHandle: u64,
13 +
    /// Shared event-ring address.
14 +
    eventsPointer: u64,
15 +
}
16 +
17 +
/// Check root identity, resource ordering, and image authority before returning.
18 +
@default unsafe fn main(env: *Env) {
19 +
    assert env.argsSize == @sizeOf(abi::BootInfo) as u64;
20 +
    let info = env.argsPointer;
21 +
    let handles = info.handles;
22 +
    let self = sys::queryDomain(abi::Handle(handles[0]));
23 +
    assert (self.id & 0xffffffff) == 0 and self.creator == self.id;
24 +
    assert handles[1] == env.eventsHandle and info.timebase > 0;
25 +
    let mut harts: u64 = 0;
26 +
    for hart in 0..8 {
27 +
        if (info.harts & (1 << hart as u64)) <> 0 {
28 +
            set harts += 1;
29 +
        }
30 +
    }
31 +
    assert handles.len as u64 == 2 + info.devices + info.interrupts + harts;
32 +
    for i in 0..info.devices as u32 {
33 +
        let device = sys::queryDevice(abi::Handle(handles[2 + i]));
34 +
        assert device.size > 0;
35 +
    }
36 +
    for i in 0..info.interrupts as u32 {
37 +
        let irq = sys::queryInterrupt(abi::Handle(handles[2 + info.devices as u32 + i]));
38 +
        assert irq.number == i + 1;
39 +
    }
40 +
    assert sys::queryDevice(info.clock).size == 8;
41 +
    assert sys::queryDevice(info.shutdown).size >= 4;
42 +
    assert (try! sys::queryImage(info.rootImage)).entry <> 0;
43 +
    assert (try! sys::queryImage(info.schedulerImage)).entry <> 0;
44 +
    let current = sys::currentContext();
45 +
    let first = 2 + info.devices as u32 + info.interrupts as u32;
46 +
    assert current.hart == 0 and current.budget == handles[first];
47 +
    assert (try! sys::queryBudget(abi::Handle(handles[first]))).hart == 0;
48 +
}
test/bootstrap/run added +28 -0
1 +
#!/bin/sh
2 +
# Construct root from a boot package and retain authority after root exits.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
work=$(mktemp -d)
6 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 +
mkdir "$work/loaded"
8 +
cp test/bootstrap/root.rad "$work/loaded.rad"
9 +
cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/loaded/"
10 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/radiance.rv64.dev \
11 +
    -pkg loaded -mod "$work/loaded.rad" -mod "$work/loaded/abi.rad" -mod "$work/loaded/sys.rad" -entry loaded -ril "$work"
12 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/radiance.rv64.dev \
13 +
    -pkg service -mod test/bootstrap/service.rad -entry service -ril "$work"
14 +
cp kernel/kernel.rad "$work/kernel.rad"
15 +
printf '\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
16 +
mkdir "$work/kernel"
17 +
cp kernel/kernel/*.rad "$work/kernel/"
18 +
cp test/bootstrap/kernel/dispatchcheck.rad "$work/kernel/"
19 +
sh test/acceptance/compile "$emulator" "$work"
20 +
cat test/bootstrap/machine.ras kernel/kernel/*.ras > "$work/bootstrap.ras"
21 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 \
22 +
    -- "$work/std.ril" "$work/kernel.ril" "$work/bootstrap.ras" "$work/bootstrap.rv64" "$work/loaded.ril" "$work/service.ril"
23 +
if [ "${KERNEL_REPLAY:-0}" = 1 ]; then
24 +
    sh test/acceptance/replay bootstrap "$emulator" "$work/bootstrap.rv64" 1
25 +
else
26 +
    "$emulator" -machine -harts=1 -memory-size=262144 -max-steps=2000000000 -run "$work/bootstrap.rv64"
27 +
fi
28 +
printf 'bootstrap: root exit preserves allocation and creation authority\n'
test/bootstrap/service.rad added +6 -0
1 +
//! Independent boot package used for creation after root exit.
2 +
3 +
/// Return a small status from the service entry.
4 +
@default fn main() -> u64 {
5 +
    return 7;
6 +
}
test/dispatch/run +20 -4
8 8
work=$(mktemp -d)
9 9
trap 'rm -rf "$work"' EXIT HUP INT TERM
10 10
mkdir "$work/spin"
11 11
cp "test/$fixture/spin.rad" "$work/spin.rad"
12 12
cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/"
13 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
14 -
    -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad" -entry spin -ril "$work"
13 +
set -- -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad"
14 +
if [ "$fixture" = mmio ]; then
15 +
    cp kernel/mmio.rad "$work/spin/"
16 +
    set -- "$@" -mod "$work/spin/mmio.rad"
17 +
fi
18 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/radiance.rv64.dev \
19 +
    "$@" -entry spin -ril "$work"
15 20
cp kernel/kernel.rad "$work/kernel.rad"
16 21
printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
17 22
mkdir "$work/kernel"
18 23
cp kernel/kernel/*.rad "$work/kernel/"
19 -
cp "test/$fixture/kernel/dispatchcheck.rad" "$work/kernel/"
24 +
for source in "test/$fixture"/kernel/*.rad; do
25 +
    cp "$source" "$work/kernel/"
26 +
    module=${source##*/}
27 +
    module=${module%.rad}
28 +
    if [ "$module" != dispatchcheck ]; then
29 +
        printf '\nexport mod %s;\n' "$module" >> "$work/kernel.rad"
30 +
    fi
31 +
done
20 32
length=$(wc -c < "$work/spin.ril")
21 33
printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad"
22 34
od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
23 35
printf '];\n' >> "$work/kernel/dispatchinput.rad"
24 36
sh test/acceptance/compile "$emulator" "$work"
25 37
cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras"
26 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
38 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 \
27 39
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
28 40
set --
29 41
if [ "$irq" -ne 0 ]; then set -- "-irq=$irq" "-irq-at=1"; fi
30 42
for count in $harts; do
43 +
    if [ "${KERNEL_REPLAY:-0}" = 1 ]; then
44 +
        sh test/acceptance/replay "$fixture" "$emulator" "$work/dispatch.rv64" "$count" "$@"
45 +
    else
31 46
    "$emulator" -machine -harts="$count" -memory-size=262144 -max-steps=2000000000 "$@" -run "$work/dispatch.rv64"
47 +
    fi
32 48
    printf '%s: %s-hart execution passed\n' "$fixture" "$count"
33 49
done
test/loader/run +3 -3
2 2
# Build a kernel fixture and call code produced by its runtime loader.
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 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
7 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
8 8
    -run bin/radiance.rv64.dev -pkg loaded -mod test/loader/loaded.rad -entry loaded -ril "$work"
9 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
9 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
10 10
    -run bin/radiance.rv64.dev -pkg loaded -mod test/loader/loaded.rad -entry loaded -o "$work/hosted.rv64"
11 11
status=0
12 12
"$emulator" -run "$work/hosted.rv64" || status=$?
13 13
test "$status" -eq 43
14 14
cat kernel/kernel.rad > "$work/kernel.rad"
20 20
printf '//! Binary package used by both execution paths.\n/// Complete trusted binary RIL input.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/loadinput.rad"
21 21
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/loadinput.rad"
22 22
printf '];\n' >> "$work/kernel/loadinput.rad"
23 23
sh test/acceptance/compile "$emulator" "$work"
24 24
cat test/loader/machine.ras kernel/kernel/*.ras > "$work/loader.ras"
25 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
25 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
26 26
    -run bin/kernel.build.rv64 -- "$work/std.ril" "$work/kernel.ril" "$work/loader.ras" "$work/loader.rv64"
27 27
"$emulator" -machine -memory-size=262144 -max-steps=200000000 -run "$work/loader.rv64"
28 28
printf 'runtime loader: hosted and loaded entries agree after workspace release\n'
test/mmio/kernel/dispatchcheck.rad added +149 -0
1 +
//! Native fixed-width MMIO and authority rejection before register access.
2 +
use std::mem;
3 +
use kernel::abi;
4 +
use kernel::slots;
5 +
use kernel::backing;
6 +
use kernel::pages;
7 +
use kernel::capability;
8 +
use kernel::registry;
9 +
use kernel::loader;
10 +
use kernel::domains;
11 +
use kernel::budgets;
12 +
use kernel::dispatch;
13 +
use kernel::boot;
14 +
use kernel::events;
15 +
use kernel::sync;
16 +
use kernel::dispatchinput;
17 +
use kernel::devices;
18 +
use kernel::platform;
19 +
20 +
/// Register storage exposed only through a synthetic Device capability.
21 +
static REGISTERS: [u64; 512] = [0; 512];
22 +
23 +
/// Bootstrap creation, allocation, and budget authority.
24 +
unsafe static TABLE: capability::Table = undefined;
25 +
/// Child domain identities retained by terminal events.
26 +
unsafe static CHILDREN: [abi::Ref; 9] = undefined;
27 +
/// Surviving event receiver and native completion checker.
28 +
unsafe static PARENT: abi::Ref = undefined;
29 +
/// Map a validated physical page.
30 +
fn memory(address: u64) -> *mut u8;
31 +
/// Current kernel package-state base.
32 +
fn kernelGp() -> u64;
33 +
/// Native entry for the completion checker.
34 +
fn completion() -> u64;
35 +
/// Signal successful completion to the machine fixture.
36 +
fn finish();
37 +
38 +
/// Give each child a disjoint window, followed by the surviving parent.
39 +
export unsafe fn setup() {
40 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
41 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
42 +
    capability::initialize(&mut TABLE, owner);
43 +
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
44 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
45 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
46 +
    });
47 +
    let source = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (dispatchinput::INPUT.len as u64 + 4095) / 4096);
48 +
    let page = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, source)).object);
49 +
    let bytes = @sliceOf(memory(page.base), page.count * 4096);
50 +
    let length = try! mem::copy(&mut bytes[..], &dispatchinput::INPUT[..]);
51 +
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
52 +
        loader::Request { authority, source, offset: 0, length: length as u64 });
53 +
    let parentHandle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
54 +
    set PARENT = (try! capability::get(&TABLE, parentHandle)).object;
55 +
    let parent = try! domains::get(&domains::STORE, PARENT);
56 +
    set domains::STORE.records[PARENT.index].state = domains::Lifecycle::Active;
57 +
    set domains::STORE.contexts[parent.initial.index].frame.pc = completion();
58 +
    set domains::STORE.contexts[parent.initial.index].frame.status = 0x1880;
59 +
    set domains::STORE.contexts[parent.initial.index].frame.registers[2] = domains::STORE.contexts[parent.initial.index].kernelStack.end;
60 +
    set domains::STORE.contexts[parent.initial.index].frame.registers[3] = kernelGp();
61 +
    let scratch = devices::STORE.count;
62 +
    set devices::STORE.regions[scratch] = {
63 +
        kind: platform::Kind::Uart,
64 +
        info: { base: (&mut REGISTERS[0]) as u64, size: 4096 },
65 +
        rights: abi::Rights(abi::READ | abi::WRITE | abi::GRANT | abi::TRANSFER),
66 +
    };
67 +
    set devices::STORE.count += 1;
68 +
    let mut clockDevice: u32 = 0;
69 +
    let mut finish: u32 = 0;
70 +
    for i in 0..scratch {
71 +
        if devices::STORE.regions[i].kind == platform::Kind::Clint {
72 +
            set clockDevice = i;
73 +
        }
74 +
        if devices::STORE.regions[i].kind == platform::Kind::Finish {
75 +
            set finish = i;
76 +
        }
77 +
    }
78 +
    let mut handles: [abi::Handle; 9] = undefined;
79 +
    let mut contexts: [abi::Ref; 9] = undefined;
80 +
    for i in 0..9 {
81 +
        let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
82 +
        let object = (try! capability::get(&TABLE, handle)).object;
83 +
        let mut child = try! domains::get(&domains::STORE, object);
84 +
        set CHILDREN[i] = object;
85 +
        set handles[i] = handle;
86 +
        set contexts[i] = child.initial;
87 +
        try! domains::reparent(&mut domains::STORE, &TABLE, handle, parentHandle);
88 +
        let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
89 +
        let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64);
90 +
        let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object);
91 +
        let mut region = scratch;
92 +
        if i == 2 or i == 6 {
93 +
            set region = finish;
94 +
        }
95 +
        let mut device = try! devices::install(&devices::STORE, &mut child.memory.table, region);
96 +
        let slot = (try! abi::decode(device)).object.index;
97 +
        if i == 2 {
98 +
            set child.memory.table.entries[slot].rights = abi::Rights(abi::READ);
99 +
        }
100 +
        if i == 3 {
101 +
            set child.memory.table.entries[slot].rights = abi::Rights(abi::WRITE);
102 +
        }
103 +
        if i == 1 {
104 +
            set device = abi::Handle(0);
105 +
        }
106 +
        if i == 7 {
107 +
            set device = granted;
108 +
        }
109 +
        let timerHandle = try! devices::install(&devices::STORE, &mut child.memory.table, clockDevice);
110 +
        let args = @sliceOf(memory(storage.base) as *mut u64, 2);
111 +
        set args[0] = *device; set args[1] = *timerHandle;
112 +
        let size = 16 + i as u64;
113 +
        try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, size);
114 +
    }
115 +
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
116 +
    let clock = dispatch::now(timer);
117 +
    let start = clock + 1000000;
118 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 20000000);
119 +
    for i in 0..9 {
120 +
        let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * 2000000, clock);
121 +
        let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
122 +
            budgets::Binding { budget: remaining, domain: handles[i], context: contexts[i] }, clock);
123 +
        set remaining = next;
124 +
    }
125 +
    let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
126 +
        budgets::Binding { budget: remaining, domain: parentHandle, context: parent.initial }, clock);
127 +
}
128 +
129 +
/// Check each terminal kind, status, and generation while the parent continues to run.
130 +
export unsafe fn verify() {
131 +
    let guard = sync::enter();
132 +
    let mut parent = try! domains::get(&domains::STORE, PARENT);
133 +
    let codes = [61 as u32, 256, 256, 256, 256, 256, 256, 256, 256];
134 +
    for i in 0..9 {
135 +
        let event = try! events::pop(&mut parent.memory.ring) else panic "missing termination event";
136 +
        assert event.value == abi::id(CHILDREN[i]) and event.code == codes[i];
137 +
        if i == 0 {
138 +
            assert event.kind == events::CHILD_EXIT;
139 +
        }
140 +
        else {
141 +
            assert event.kind == events::FAULT;
142 +
        }
143 +
        assert domains::STORE.records[CHILDREN[i].index].state == domains::Lifecycle::Dead;
144 +
    }
145 +
    try! events::refresh(&mut domains::STORE.events, PARENT, &parent.memory.ring);
146 +
    assert try! events::pop(&mut parent.memory.ring) == nil;
147 +
    sync::leave(guard);
148 +
    finish();
149 +
}
test/mmio/machine.ras added +25 -0
1 +
//! Boot one hart and check user termination through production dispatch.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
.export @kernel::dispatchcheck::memory;
7 +
.export @kernel::dispatchcheck::kernelGp;
8 +
.export @kernel::dispatchcheck::completion;
9 +
.export @kernel::dispatchcheck::finish;
10 +
@kernel::dispatchcheck::memory
11 +
    ret;
12 +
@kernel::dispatchcheck::kernelGp
13 +
    mv %a0 %gp;
14 +
    ret;
15 +
@kernel::dispatchcheck::completion
16 +
    la %a0 @complete;
17 +
    ret;
18 +
@complete
19 +
    call @kernel::dispatchcheck::verify;
20 +
    ebreak;
21 +
@kernel::dispatchcheck::finish
22 +
    li %t0 0x10001000;
23 +
    li %t1 0x5555;
24 +
    sw %t1 0(%t0);
25 +
    ebreak;
test/mmio/spin.rad added +66 -0
1 +
//! User-mode register widths and rejected MMIO authority.
2 +
export mod abi;
3 +
export mod sys;
4 +
export mod mmio;
5 +
6 +
/// Device values installed in the calling domain's table.
7 +
record Arguments: Copy {
8 +
    /// Register interface under test.
9 +
    device: abi::Handle,
10 +
    /// Read-only machine timer.
11 +
    clock: abi::Handle,
12 +
}
13 +
14 +
/// Case selection and readable arguments supplied by the test parent.
15 +
record Env: Copy {
16 +
    /// Installed device handles.
17 +
    argsPointer: *Arguments,
18 +
    /// Sixteen plus the test case number.
19 +
    argsSize: u64,
20 +
    /// Installed event capability.
21 +
    eventsHandle: u64,
22 +
    /// Shared ring address.
23 +
    eventsPointer: u64,
24 +
}
25 +
26 +
/// Execute each width, or require a kernel fault before an unauthorized access.
27 +
@default fn main(env: *Env) {
28 +
    let device = env.argsPointer.device;
29 +
    let scenario = env.argsSize - 16;
30 +
    if scenario == 0 {
31 +
        let before = mmio::read64(env.argsPointer.clock, 0);
32 +
        mmio::write8(device, 0, 0x81);
33 +
        mmio::write16(device, 2, 0x8123);
34 +
        mmio::write32(device, 4, 0x81234567);
35 +
        mmio::write64(device, 8, 0x8123456789abcdef);
36 +
        assert mmio::read8(device, 0) == 0x81;
37 +
        assert mmio::read16(device, 2) == 0x8123;
38 +
        assert mmio::read32(device, 4) == 0x81234567;
39 +
        assert mmio::read64(device, 8) == 0x8123456789abcdef;
40 +
        assert mmio::read32(env.argsPointer.clock, 0) as u64 >= before;
41 +
        sys::exit(61);
42 +
    }
43 +
    if scenario == 1 or scenario == 2 {
44 +
        mmio::write32(device, 0, 0x5555);
45 +
    }
46 +
    if scenario == 3 {
47 +
        let value = mmio::read8(device, 0);
48 +
    }
49 +
    if scenario == 4 {
50 +
        mmio::write32(device, 1, 1);
51 +
    }
52 +
    if scenario == 5 {
53 +
        let value = mmio::read8(device, 4096);
54 +
    }
55 +
    if scenario == 6 {
56 +
        try! sys::capabilityDrop(device);
57 +
        mmio::write32(device, 0, 0x5555);
58 +
    }
59 +
    if scenario == 7 {
60 +
        let value = mmio::read8(device, 0);
61 +
    }
62 +
    if scenario == 8 {
63 +
        mmio::write64(device, 0xfffffffffffffff8, 1);
64 +
    }
65 +
    sys::exit(99);
66 +
}
test/modules/run +1 -1
13 13
    set -- "$@" -mod "$work/capacity/m$index.rad"
14 14
    index=$((index + 1))
15 15
done
16 16
printf 'use capacity::m126;\n/// Check a package root beyond module index 127.\n@default fn main() -> u64 { assert m126::value() == 126; return 0; }\n' > "$work/entry.rad"
17 17
set -- "$@" -pkg entry -mod "$work/entry.rad"
18 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
18 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
19 19
    -run bin/radiance.rv64.dev "$@" -entry entry -o "$work/capacity.rv64"
20 20
"$emulator" -run "$work/capacity.rv64"
21 21
printf 'module scopes: references beyond index 127 passed\n'
test/packages/run +2 -2
4 4
emulator=${RAD_EMULATOR:-emulator}
5 5
work=$(mktemp -d)
6 6
trap 'rm -rf "$work"' EXIT HUP INT TERM
7 7
mkdir "$work/first" "$work/second"
8 8
for output in first second; do
9 -
    "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
9 +
    "$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
10 10
        -run bin/radiance.rv64.dev \
11 11
        -pkg support -mod test/packages/support.rad \
12 12
        -pkg app -mod test/packages/app.rad -entry app -ril "$work/$output"
13 13
done
14 14
cmp "$work/first/support.ril" "$work/second/support.ril"
15 15
cmp "$work/first/app.ril" "$work/second/app.ril"
16 16
"$emulator" -run bin/packages.check.rv64 -- "$work/first/support.ril" "$work/first/app.ril"
17 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
17 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
18 18
    -run bin/radiance.rv64.dev \
19 19
    -pkg support -mod test/packages/support.rad \
20 20
    -pkg app -mod test/packages/app.rad -entry app -o "$work/hosted.rv64"
21 21
status=0
22 22
"$emulator" -run "$work/hosted.rv64" || status=$?
test/runtime/run +8 -4
6 6
work=$(mktemp -d)
7 7
trap 'rm -rf "$work"' EXIT HUP INT TERM
8 8
mkdir "$work/spin"
9 9
cp "test/$fixture/spin.rad" "$work/spin.rad"
10 10
cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/"
11 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
11 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/radiance.rv64.dev \
12 12
    -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad" -entry spin -ril "$work"
13 13
cp test/loader/loaded.rad "$work/loaded.rad"
14 14
index=0
15 15
while [ "$index" -lt 128 ]; do
16 16
    printf '\n/// Compute a distinct exported value.\nexport fn value%s(input: u64) -> u64 { return input + %s; }\n' "$index" "$index" >> "$work/loaded.rad"
17 17
    index=$((index + 1))
18 18
done
19 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
19 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/radiance.rv64.dev \
20 20
    -pkg loaded -mod "$work/loaded.rad" -entry loaded -ril "$work"
21 21
cp kernel/kernel.rad "$work/kernel.rad"
22 22
printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
23 23
mkdir "$work/kernel"
24 24
cp kernel/kernel/*.rad "$work/kernel/"
31 31
printf '\n/// Package compiled by scheduled user calls.\nexport static PAYLOAD: [u8; %s] = [\n' "$length" >> "$work/kernel/dispatchinput.rad"
32 32
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
33 33
printf '];\n' >> "$work/kernel/dispatchinput.rad"
34 34
sh test/acceptance/compile "$emulator" "$work"
35 35
cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras"
36 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
36 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 \
37 37
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
38 -
"$emulator" -machine -harts=1 -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64"
38 +
if [ "${KERNEL_REPLAY:-0}" = 1 ]; then
39 +
    sh test/acceptance/replay runtime "$emulator" "$work/dispatch.rv64" 1
40 +
else
41 +
    "$emulator" -machine -harts=1 -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64"
42 +
fi
39 43
printf 'runtime: scheduled cancellation, retry, and publication passed\n'
test/scheduling/kernel/dispatchcheck.rad added +76 -0
1 +
//! Observe user-space bootstrap and scheduling at the trap boundary.
2 +
use kernel::abi;
3 +
use std::lang::il;
4 +
use kernel::trap;
5 +
use kernel::dispatch;
6 +
use kernel::domains;
7 +
use kernel::budgets;
8 +
use kernel::events;
9 +
use kernel::sync;
10 +
11 +
/// Root has requested normal termination.
12 +
static ROOT_EXITED: bool = false;
13 +
/// Workers that returned after root released its authority.
14 +
static WORKERS: u32 = 0;
15 +
/// Write one byte to the test UART.
16 +
fn put(value: u8);
17 +
/// Most recent user operation for fault diagnosis.
18 +
static LAST_CALL: u64 = 0;
19 +
20 +
/// Print a complete hexadecimal diagnostic word.
21 +
fn number(value: u64) {
22 +
    let digits = "0123456789abcdef";
23 +
    for i in 0..16 {
24 +
        put(digits[((value >> ((15 - i) as u64 * 4)) & 15) as u32]);
25 +
    }
26 +
    put(10);
27 +
}
28 +
29 +
/// Check terminal calls without changing user-domain resources or execution.
30 +
export unsafe fn observe(frame: &trap::Frame) {
31 +
    if not trap::fromUser(frame) {
32 +
        return;
33 +
    }
34 +
    let cause = trap::classify(frame.cause);
35 +
    if cause == trap::Cause::Timer or cause == trap::Cause::Software or cause == trap::Cause::External {
36 +
        return;
37 +
    }
38 +
    if cause <> trap::Cause::UserCall {
39 +
        number(frame.cause); number(frame.pc); number(LAST_CALL); number(WORKERS as u64);
40 +
        assert false;
41 +
    }
42 +
    set LAST_CALL = frame.registers[17];
43 +
    assert frame.registers[17] <> abi::Operation::Abort as u64;
44 +
    let shutdown = frame.registers[17] == il::DEVICE_ACCESS as u64 and frame.registers[12] == 0x104;
45 +
    if frame.registers[17] <> abi::Operation::Exit as u64 and not shutdown {
46 +
        return;
47 +
    }
48 +
    let current = dispatch::current();
49 +
    let context = abi::reference(current.context);
50 +
    let guard = sync::enter();
51 +
    let owner = domains::STORE.contexts[context.index].owner;
52 +
    if owner.index == 0 {
53 +
        assert frame.registers[10] == 0;
54 +
        set ROOT_EXITED = true;
55 +
    } else if owner.index == 1 {
56 +
        assert ROOT_EXITED and WORKERS == 4 and shutdown and frame.registers[13] == 0x5555;
57 +
        let scheduler = try! domains::get(&domains::STORE, owner);
58 +
        assert scheduler.parent == nil;
59 +
        let ring = &scheduler.memory.ring;
60 +
        assert ring.tail - ring.head == 5;
61 +
        assert ring.data[ring.head & ring.mask].kind == events::CHILD_EXIT;
62 +
        assert ring.data[ring.head & ring.mask].code == 0;
63 +
        for i in 1..5 {
64 +
            let event = ring.data[(ring.head + i) & ring.mask];
65 +
            assert event.kind == events::CHILD_EXIT and event.code == 7;
66 +
        }
67 +
        let future = try! budgets::query(&budgets::STORE, &scheduler.memory.table, abi::Handle(current.budget));
68 +
        assert future.end > dispatch::clock();
69 +
        sync::leave(guard);
70 +
        return;
71 +
    } else {
72 +
        assert ROOT_EXITED and frame.registers[10] == 7;
73 +
        set WORKERS += 1;
74 +
    }
75 +
    sync::leave(guard);
76 +
}
test/scheduling/machine.ras added +10 -0
1 +
//! Run production boot and finish after user-space scheduling checks.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::boot::launch;
5 +
    ebreak;
6 +
.export @kernel::dispatchcheck::put;
7 +
@kernel::dispatchcheck::put
8 +
    li %t0 0x10000000;
9 +
    sb %a0 0(%t0);
10 +
    ret;
test/scheduling/run added +25 -0
1 +
#!/bin/sh
2 +
# Execute root handoff and successive user-space yields with the production packages.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
harts=${1:-1 2 8}
6 +
work=$(mktemp -d)
7 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
8 +
cp kernel/kernel.rad "$work/kernel.rad"
9 +
printf '\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
10 +
mkdir "$work/kernel"
11 +
cp kernel/kernel/*.rad "$work/kernel/"
12 +
cp test/scheduling/kernel/dispatchcheck.rad "$work/kernel/"
13 +
sed -i '/let cause = trap::classify(frame.cause);/a\    super::dispatchcheck::observe(frame);' "$work/kernel/boot.rad"
14 +
sh test/acceptance/compile "$emulator" "$work"
15 +
cat test/scheduling/machine.ras kernel/kernel/*.ras > "$work/scheduling.ras"
16 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 -run bin/kernel.build.rv64 \
17 +
    -- "$work/std.ril" "$work/kernel.ril" "$work/scheduling.ras" "$work/scheduling.rv64" bin/root.ril bin/scheduler.ril
18 +
for count in $harts; do
19 +
    if [ "${KERNEL_REPLAY:-0}" = 1 ]; then
20 +
        sh test/acceptance/replay scheduling "$emulator" "$work/scheduling.rv64" "$count"
21 +
    else
22 +
    "$emulator" -machine -harts="$count" -memory-size=262144 -max-steps=2000000000 -run "$work/scheduling.rv64"
23 +
    fi
24 +
    printf 'scheduling: %s-hart root handoff, four user-space yields, and checked shutdown passed\n' "$count"
25 +
done
test/shared/run +1 -1
2 2
# Compile two packages once and execute them with two private state graphs.
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 -
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
7 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
8 8
    -run bin/radiance.rv64.dev \
9 9
    -pkg support -mod test/shared/support.rad \
10 10
    -pkg app -mod test/shared/app.rad -entry app -ril "$work"
11 11
"$emulator" -run bin/shared.build.rv64 -- "$work/support.ril" "$work/app.ril" "$work/native.rv64"
12 12
"$emulator" -machine -run "$work/native.rv64"
test/slots/run +1 -1
8 8
for name in duplicate abandon; do
9 9
    case "$name" in
10 10
        duplicate) diagnostic='linear value used after consumption' ;;
11 11
        abandon) diagnostic='linear value is not consumed' ;;
12 12
    esac
13 -
    if "$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 \
13 +
    if "$emulator" -memory-size=385024 -data-size=348160 -stack-size=1024 \
14 14
        -run bin/radiance.rv64.dev "$@" -test \
15 15
        -pkg "$name" -mod "test/slots/$name.rad" -entry "$name" \
16 16
        -o "$work/test.rv64" > "$work/log" 2>&1; then
17 17
        printf 'reservation misuse compiled: %s\n' "$name" >&2
18 18
        exit 1
test/termination/kernel/dispatchcheck.rad +29 -11
12 12
use kernel::dispatch;
13 13
use kernel::boot;
14 14
use kernel::events;
15 15
use kernel::sync;
16 16
use kernel::dispatchinput;
17 +
use kernel::devices;
18 +
use kernel::platform;
17 19
18 20
/// Bootstrap creation, allocation, and budget authority.
19 21
unsafe static TABLE: capability::Table = undefined;
20 22
/// Child domain identities retained by terminal events.
21 -
static CHILDREN: [abi::Ref; 5] = undefined;
23 +
unsafe static CHILDREN: [abi::Ref; 6] = undefined;
22 24
/// Surviving event receiver and native completion checker.
23 25
unsafe static PARENT: abi::Ref = undefined;
24 26
/// Map a validated physical page.
25 27
fn memory(address: u64) -> *mut u8;
26 28
/// Current kernel package-state base.
28 30
/// Native entry for the completion checker.
29 31
fn completion() -> u64;
30 32
/// Signal successful completion to the machine fixture.
31 33
fn finish();
32 34
33 -
/// Give four children disjoint windows, followed by the surviving parent.
35 +
/// Give each child a disjoint window, followed by the surviving parent.
34 36
export unsafe fn setup() {
35 37
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
36 38
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
37 39
    capability::initialize(&mut TABLE, owner);
38 40
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
51 53
    set domains::STORE.records[PARENT.index].state = domains::Lifecycle::Active;
52 54
    set domains::STORE.contexts[parent.initial.index].frame.pc = completion();
53 55
    set domains::STORE.contexts[parent.initial.index].frame.status = 0x1880;
54 56
    set domains::STORE.contexts[parent.initial.index].frame.registers[2] = domains::STORE.contexts[parent.initial.index].kernelStack.end;
55 57
    set domains::STORE.contexts[parent.initial.index].frame.registers[3] = kernelGp();
56 -
    let mut handles: [abi::Handle; 5] = undefined;
57 -
    let mut contexts: [abi::Ref; 5] = undefined;
58 -
    for i in 0..5 {
58 +
    let mut handles: [abi::Handle; 6] = undefined;
59 +
    let mut contexts: [abi::Ref; 6] = undefined;
60 +
    for i in 0..6 {
59 61
        let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
60 62
        let object = (try! capability::get(&TABLE, handle)).object;
61 63
        let mut child = try! domains::get(&domains::STORE, object);
62 64
        set CHILDREN[i] = object;
63 65
        set handles[i] = handle;
64 66
        set contexts[i] = child.initial;
65 67
        try! domains::reparent(&mut domains::STORE, &TABLE, handle, parentHandle);
66 68
        let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
67 69
        let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64);
68 70
        let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object);
69 -
        try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, i as u64);
71 +
        let mut size = i as u64;
72 +
        if i == 5 {
73 +
            let mut installed = false;
74 +
            for index in 0..devices::STORE.count {
75 +
                if devices::STORE.regions[index].kind <> platform::Kind::Finish {
76 +
                    continue;
77 +
                }
78 +
                let device = try! devices::install(&devices::STORE, &mut child.memory.table, index);
79 +
                let args = memory(storage.base) as *mut u64;
80 +
                set *args = *device;
81 +
                set installed = true;
82 +
                break;
83 +
            }
84 +
            assert installed;
85 +
            set size = 8;
86 +
        }
87 +
        try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, size);
70 88
    }
71 89
    let timer = try! dispatch::timer(&boot::PLATFORM, 0);
72 90
    let clock = dispatch::now(timer);
73 91
    let start = clock + 1000000;
74 -
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 12000000);
75 -
    for i in 0..5 {
92 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, start + 14000000);
93 +
    for i in 0..6 {
76 94
        let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + (i as u64 + 1) * 2000000, clock);
77 95
        let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
78 96
            budgets::Binding { budget: remaining, domain: handles[i], context: contexts[i] }, clock);
79 97
        set remaining = next;
80 98
    }
84 102
85 103
/// Check each terminal kind, status, and generation while the parent continues to run.
86 104
export unsafe fn verify() {
87 105
    let guard = sync::enter();
88 106
    let mut parent = try! domains::get(&domains::STORE, PARENT);
89 -
    let codes = [37 as u32, abi::FAULT_ABORT, 3, 0, abi::FAULT_ABORT];
90 -
    for i in 0..5 {
107 +
    let codes = [37 as u32, abi::FAULT_ABORT, 3, 0, abi::FAULT_ABORT, 51];
108 +
    for i in 0..6 {
91 109
        let event = try! events::pop(&mut parent.memory.ring) else panic "missing termination event";
92 110
        assert event.value == abi::id(CHILDREN[i]) and event.code == codes[i];
93 -
        if i == 0 or i == 3 {
111 +
        if i == 0 or i == 3 or i == 5 {
94 112
            assert event.kind == events::CHILD_EXIT;
95 113
        }
96 114
        else {
97 115
            assert event.kind == events::FAULT;
98 116
        }
test/termination/spin.rad +6 -1
6 6
@intrinsic fn ecall(operation: u32, a0: i64, a1: i64, a2: i64, a3: i64) -> i64;
7 7
8 8
/// Startup values used to select the termination operation.
9 9
record Env: Copy {
10 10
    /// Optional argument bytes supplied by the parent.
11 -
    argsPointer: *u8,
11 +
    argsPointer: *abi::Handle,
12 12
    /// Termination case selected by the parent.
13 13
    argsSize: u64,
14 14
    /// Installed event handle.
15 15
    eventsHandle: u64,
16 16
    /// Shared event-ring address.
30 30
    }
31 31
    if env.argsSize == 4 {
32 32
        let result = ecall(47, 0, 0, 0, 0);
33 33
        sys::exit(99);
34 34
    }
35 +
    if env.argsSize == 8 {
36 +
        let device = sys::queryDevice(*env.argsPointer);
37 +
        assert device.base == 0x10001000 and device.size >= 4;
38 +
        sys::exit(51);
39 +
    }
35 40
    assert false;
36 41
}