kernel: route exclusive interrupts through masked PLIC claims

566537b1fc0d5dfa9947666ede25ead59fc9d6b996964bb2b6008fd5341f6ff7
Verified: make -C kernel check with the machine-capable emulator; all pass.
Alexis Sellier committed ago 1 parent e7e57c61
kernel/Makefile +9 -5
5 5
COMPILE := $(HOST_EMU) -memory-size=385024 -data-size=348160 -stack-size=512 -run $(COMPILER)
6 6
MODULES := core/fdt.rad core/platform.rad core/frames.rad core/abi.rad core/handles.rad \
7 7
	core/events.rad core/domains.rad core/resources.rad core/capabilities.rad \
8 8
	core/memory.rad core/state.rad core/pages.rad core/atomic.rad core/cpu.rad \
9 9
	core/budgets.rad core/contexts.rad core/clock.rad core/budget_caps.rad \
10 -
	core/timers.rad core/notifications.rad
11 -
CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras
10 +
	core/timers.rad core/notifications.rad core/devices.rad core/interrupts.rad core/mmio.rad
11 +
CORE_ASM := arch/atomic.ras arch/context.ras arch/clock.ras arch/mmio.ras
12 12
CORE := -pkg core -mod core.rad $(addprefix -mod ,$(MODULES) $(CORE_ASM))
13 13
CHECK_MODULES := check/boot.rad check/fixture.rad check/frames.rad check/handles.rad \
14 14
	check/domains.rad check/capabilities.rad check/pages.rad check/events.rad \
15 -
	check/budgets.rad check/budget_caps.rad check/timers.rad check/notifications.rad
15 +
	check/budgets.rad check/budget_caps.rad check/timers.rad check/notifications.rad check/devices.rad
16 16
17 17
.PHONY: all check clean compiler-check
18 18
all: kernel.rv64
19 19
20 20
compiler-check:
29 29
	$(COMPILE) $(CORE) -pkg check -mod check.rad $(addprefix -mod ,$(CHECK_MODULES)) -entry check -o $@
30 30
31 31
context.rv64: context.rad context/wait.rad check/context.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER)
32 32
	$(COMPILE) $(CORE) -pkg context -start arch/entry.ras -mod context.rad -mod context/wait.rad -mod check/context.ras -entry context -o $@
33 33
34 -
check: all check.rv64 context.rv64
34 +
interrupt.rv64: interrupt.rad check/interrupt.ras arch/entry.ras core.rad $(MODULES) $(CORE_ASM) $(COMPILER)
35 +
	$(COMPILE) $(CORE) -pkg interrupt -start arch/entry.ras -mod interrupt.rad -mod check/interrupt.ras -entry interrupt -o $@
36 +
37 +
check: all check.rv64 context.rv64 interrupt.rv64
35 38
	$(HOST_EMU) -run check.rv64
36 39
	$(EMU) -machine -no-guard-stack -max-steps=100000000 -count-instructions -run kernel.rv64
37 40
	$(EMU) -machine -no-guard-stack -max-steps=1000000 -count-instructions -run context.rv64
41 +
	$(EMU) -machine -harts=2 -no-guard-stack -irq=3 -irq-at=4000000 -uart-rx=52 -uart-rx-at=8000000 -max-steps=100000000 -count-instructions -run interrupt.rv64
38 42
39 43
clean:
40 -
	rm -f kernel.rv64 check.rv64 context.rv64
44 +
	rm -f kernel.rv64 check.rv64 context.rv64 interrupt.rv64
kernel/NOTES.md +18 -2
1 1
# Kernel implementation decisions
2 2
3 3
The specification at https://radiant.computer/system/kernel takes precedence
4 4
for fixed call numbers, handle layout, rights, and object behavior. These notes
5 -
record the contracts established through step 13 of the 22-step plan.
5 +
record the contracts established through step 14 of the 22-step plan.
6 6
7 7
## Source and trust boundary
8 8
9 9
- Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register
10 10
  state, atomics, and MMIO. Hosted checks exercise the same mechanism modules.
173 173
- Wait validates consumer progress before blocking. Resumption requires an
174 174
  unread event and positive retained budget. Wakeup requires an explicit Wake
175 175
  handle and records the sender identity. Idle waiting does not consume ticks
176 176
  from the suspended context's budget.
177 177
178 +
## Devices and external interrupts
179 +
180 +
- Device access validates live authority, rights, complete byte range, overflow,
181 +
  and physical alignment. Its admitted address is consumed by one MMIO operation;
182 +
  it cannot become a user pointer.
183 +
- One FDT-selected PLIC machine context services external interrupts; other
184 +
  contexts remain masked. Access only implemented source words and fence memory
185 +
  and device I/O.
186 +
- A claimed source stays masked and active until notification consumption. The
187 +
  driver drains the device first. Completion briefly enables the route because
188 +
  the PLIC may require its enable bit. Claims survive queue backpressure and
189 +
  exclusive receiver transfer.
190 +
- Missing, dead, or stale receiver authority leaves the source masked. Invalid
191 +
  consumer progress cannot release a claim or rearm a source. The two-hart
192 +
  IRQ/UART check exercises the primary PLIC route, not multicore dispatch.
193 +
178 194
## Validation
179 195
180 196
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
181 197
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
182 198
checks compiler dependencies. From the repository root, run:
183 199
184 200
```sh
185 201
make -C kernel check
186 202
```
187 203
188 -
Exercise Timeout, Wait, machine idle, timer expiry, event consumption, retained notifications, and unchanged budget during idle.
204 +
Run the two-hart IRQ/UART scenario with IRQ 3 at tick 4,000,000 and UART byte R at tick 8,000,000. Check retained-claim transfer through a full queue, one delivery, draining, dropped authority, and the next timer wake.
189 205
190 206
Run the context reservation probe with an emulator that retains LR/SC
191 207
reservations across traps. This checks the kernel's reservation invalidation.
kernel/arch/mmio.ras added +14 -0
1 +
// Full I/O and memory fences surround each native 32-bit controller access.
2 +
.text;
3 +
.export @"core::mmio::read32";
4 +
@"core::mmio::read32"
5 +
	fence;
6 +
	lwu    %a0    0(%a0);
7 +
	fence;
8 +
	ret;
9 +
.export @"core::mmio::write32";
10 +
@"core::mmio::write32"
11 +
	fence;
12 +
	sw     %a1    0(%a0);
13 +
	fence;
14 +
	ret;
kernel/check.rad +2 -0
10 10
mod events;
11 11
mod budgets;
12 12
mod budget_caps;
13 13
mod timers;
14 14
mod notifications;
15 +
mod devices;
15 16
16 17
/// Run the available kernel mechanism checks.
17 18
@default fn main() -> u32 {
18 19
    frames::run();
19 20
    boot::run();
24 25
    events::run();
25 26
    budgets::run();
26 27
    budget_caps::run();
27 28
    timers::run();
28 29
    notifications::run();
30 +
    devices::run();
29 31
    return 0;
30 32
}
kernel/check/devices.rad added +88 -0
1 +
//! Device materialization authority and byte-range boundary checks; no hardware I/O.
2 +
3 +
use core::abi;
4 +
use core::capabilities;
5 +
use core::contexts;
6 +
use core::devices;
7 +
use core::domains;
8 +
use core::fdt;
9 +
use core::frames;
10 +
use core::handles;
11 +
use core::memory;
12 +
use core::platform;
13 +
use core::resources;
14 +
use core::state;
15 +
16 +
/// One caller and its device authority.
17 +
static DOMAINS: [domains::Domain; 1] = undefined;
18 +
/// Device windows, including deliberately invalid physical bounds.
19 +
static OBJECTS: [resources::Slot; 4] = undefined;
20 +
/// Context storage required by shared kernel state.
21 +
static CONTEXTS: [contexts::Context; 1] = undefined;
22 +
/// Empty RAM pool; materialization must not access physical device bytes.
23 +
static POOL: frames::Pool = undefined;
24 +
/// Unused frame metadata backing.
25 +
static PINS: [u16; 1] = undefined;
26 +
/// Unused Page claim backing.
27 +
static ASSIGNED: [bool; 1] = undefined;
28 +
/// Domain grant backing.
29 +
static GRANTS: [u64; 1] = undefined;
30 +
/// Shared state for the checked materialization entry point.
31 +
static KERNEL: state::State = undefined;
32 +
33 +
/// Install a real device resource and read-only capability without touching MMIO.
34 +
fn device(base: u64, size: u64) -> abi::Handle {
35 +
    let object = try! resources::create(&mut OBJECTS[..], resources::Value::Device(fdt::Range { base, size }));
36 +
    resources::retain(&mut OBJECTS[..], object);
37 +
    let slot = try! handles::vacant(&DOMAINS[0].handles);
38 +
    return handles::install(&mut DOMAINS[0].handles, slot, object, abi::READ);
39 +
}
40 +
41 +
/// Observe the public error result rather than private validation steps.
42 +
fn error(handle: abi::Handle, offset: u64, width: u64, rights: u16) -> abi::Error {
43 +
    let _address = try devices::access(&KERNEL, 0, handle, offset, width, rights) catch result {
44 +
        return result;
45 +
    };
46 +
    return abi::Error::Ok;
47 +
}
48 +
49 +
/// Check exact-end bounds, physical alignment, arithmetic overflow, and authority.
50 +
export fn run() {
51 +
    domains::init(&mut DOMAINS[..]);
52 +
    resources::init(&mut OBJECTS[..]);
53 +
    contexts::init(&mut CONTEXTS[0], 0);
54 +
    let mut machine: platform::Platform = undefined;
55 +
    set machine.memoryCount = 0;
56 +
    set machine.reservedCount = 0;
57 +
    try! frames::init(&mut POOL, &machine);
58 +
    let mut ram: memory::Memory = undefined;
59 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 1);
60 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
61 +
    let root = domains::root(&mut DOMAINS[..], 0);
62 +
    let registers = device(0x1000, 16);
63 +
    assert try! devices::access(&KERNEL, 0, registers, 8, 8, abi::READ) == 0x1008;
64 +
    assert error(registers, 16, 1, abi::READ) == abi::Error::InvalidArg;
65 +
    assert error(registers, 12, 8, abi::READ) == abi::Error::InvalidArg;
66 +
    assert error(registers, 0xffffffffffffffff, 2, abi::READ) == abi::Error::InvalidArg;
67 +
    assert error(registers, 0, 0, abi::READ) == abi::Error::InvalidArg;
68 +
    assert error(registers, 0, 3, abi::READ) == abi::Error::InvalidArg;
69 +
    assert error(registers, 1, 2, abi::READ) == abi::Error::InvalidArg;
70 +
    assert error(registers, 0, 1, 0) == abi::Error::InvalidArg;
71 +
    assert error(registers, 0, 1, abi::READ | abi::EXECUTE) == abi::Error::InvalidArg;
72 +
    assert error(registers, 0, 1, abi::READ | abi::WRITE) == abi::Error::Denied;
73 +
    assert error(root, 0, 1, abi::READ) == abi::Error::BadHandle;
74 +
75 +
    // Alignment belongs to the physical address, not just the region offset.
76 +
    let unaligned = device(0x2001, 8);
77 +
    assert try! devices::access(&KERNEL, 0, unaligned, 3, 4, abi::READ) == 0x2004;
78 +
    assert error(unaligned, 0, 4, abi::READ) == abi::Error::InvalidArg;
79 +
    let overflowing = device(0xfffffffffffffffc, 16);
80 +
    assert error(overflowing, 4, 1, abi::READ) == abi::Error::InvalidArg;
81 +
82 +
    // A still-packed handle cannot name another incarnation of its resource slot.
83 +
    let entry = try! capabilities::lookup(&KERNEL, 0, registers);
84 +
    set OBJECTS[entry.object.index].epoch += 1;
85 +
    assert error(registers, 0, 1, abi::READ) == abi::Error::BadHandle;
86 +
    try! capabilities::drop(&mut KERNEL, 0, unaligned);
87 +
    assert error(unaligned, 0, 1, abi::READ) == abi::Error::BadHandle;
88 +
}
kernel/check/interrupt.ras added +14 -0
1 +
// Byte MMIO for the trusted physical-device regression only.
2 +
.text;
3 +
.export @"interrupt::read8";
4 +
@"interrupt::read8"
5 +
	fence;
6 +
	lbu    %a0    0(%a0);
7 +
	fence;
8 +
	ret;
9 +
.export @"interrupt::write8";
10 +
@"interrupt::write8"
11 +
	fence;
12 +
	sb     %a1    0(%a0);
13 +
	fence;
14 +
	ret;
kernel/core.rad +3 -0
18 18
export mod contexts;
19 19
export mod clock;
20 20
export mod budget_caps;
21 21
export mod timers;
22 22
export mod notifications;
23 +
export mod devices;
24 +
export mod interrupts;
25 +
export mod mmio;
kernel/core/devices.rad added +32 -0
1 +
//! Capability-checked MMIO address materialization; never device I/O.
2 +
3 +
use core::abi;
4 +
use core::capabilities;
5 +
use core::resources;
6 +
use core::state;
7 +
8 +
/// Materialize one loader-verified MMIO instruction's complete byte range.
9 +
/// The returned address is not Page authority and is consumed by that instruction.
10 +
export fn access(kernel: *state::State, caller: u32, handle: abi::Handle, offset: u64, width: u64, rights: u16) -> u64 throws (abi::Error) {
11 +
    let entry = try capabilities::lookup(kernel, caller, handle);
12 +
    if entry.object.kind <> abi::Kind::Device { throw abi::Error::BadHandle; }
13 +
    if rights == 0 or rights & ~(abi::READ | abi::WRITE) <> 0 {
14 +
        throw abi::Error::InvalidArg;
15 +
    }
16 +
    if entry.rights & rights <> rights { throw abi::Error::Denied; }
17 +
    if width <> 1 and width <> 2 and width <> 4 and width <> 8 {
18 +
        throw abi::Error::InvalidArg;
19 +
    }
20 +
    let case resources::Value::Device(region) = kernel.resources[entry.object.index].value else {
21 +
        panic "access: device kind mismatch";
22 +
    };
23 +
    if offset > region.size or width > region.size - offset
24 +
        or region.base > 0xffffffffffffffff - offset {
25 +
        throw abi::Error::InvalidArg;
26 +
    }
27 +
    let address = region.base + offset;
28 +
    if address > 0xffffffffffffffff - (width - 1) or address % width <> 0 {
29 +
        throw abi::Error::InvalidArg;
30 +
    }
31 +
    return address;
32 +
}
kernel/core/interrupts.rad added +180 -0
1 +
//! Exclusive PLIC routing with retained claims and bounded queue backpressure.
2 +
3 +
use core::abi;
4 +
use core::domains;
5 +
use core::events;
6 +
use core::mmio;
7 +
use core::platform;
8 +
use core::resources;
9 +
use core::state;
10 +
11 +
/// Private state for one source; source zero is never enabled or published.
12 +
export record Source: Copy {
13 +
    /// Installed Interrupt resource identity, not its mutable receiver.
14 +
    object: abi::Object,
15 +
    /// A real hardware claim still awaiting publication or consumer progress.
16 +
    claimed: bool,
17 +
    /// Domain incarnation whose queue contains this claim, or the empty identity.
18 +
    published: abi::Object,
19 +
}
20 +
21 +
/// One machine-context PLIC service route, serialized by the kernel lock.
22 +
/// All online harts may call poll; only the selected hart handles external traps.
23 +
/// Domain execution routing and remote wakeups are separate from this controller.
24 +
export record Controller: Copy {
25 +
    /// Validated physical PLIC register base.
26 +
    base: u64,
27 +
    /// Actual FDT machine-context index, never derived from the hardware hart ID.
28 +
    context: u32,
29 +
    /// Largest usable source identifier; strictly less than MAX_IRQS.
30 +
    limit: u32,
31 +
    /// Shadow of the selected context's enable words; sole writer is this module.
32 +
    enabled: [u32; platform::MAX_IRQS / 32],
33 +
    /// Constant-space resource mapping and notification state, indexed by source.
34 +
    sources: [Source; platform::MAX_IRQS],
35 +
}
36 +
37 +
/// Initialize once before external interrupts or secondary kernel entry are enabled.
38 +
/// machine must be a validated platform and hartIndex an online-hart array index.
39 +
/// Sources stay disabled until register followed by poll observes a live receiver.
40 +
export unsafe fn init(controller: *mut Controller, machine: *platform::Platform, hartIndex: u32) {
41 +
    assert machine.hartCount > 0 and machine.hartCount <= platform::MAX_HARTS;
42 +
    assert hartIndex < machine.hartCount;
43 +
    assert machine.irqCount > 0 and machine.irqCount < platform::MAX_IRQS;
44 +
    set controller.base = machine.plic.base;
45 +
    set controller.context = machine.harts[hartIndex].plicContext;
46 +
    set controller.limit = machine.irqCount;
47 +
    set controller.enabled = [0; platform::MAX_IRQS / 32];
48 +
    for source in 0..platform::MAX_IRQS {
49 +
        set controller.sources[source] = Source {
50 +
            object: domains::none(), claimed: false, published: domains::none(),
51 +
        };
52 +
    }
53 +
    for hart in 0..machine.hartCount {
54 +
        let context = machine.harts[hart].plicContext as u64;
55 +
        // Touch only implemented source words; reserved PLIC words may fault.
56 +
        for word in 0..controller.limit / 32 + 1 {
57 +
            mmio::write32(controller.base + 0x2000 + context * 0x80 + word as u64 * 4, 0);
58 +
        }
59 +
        // Priority one is eligible only on the selected service context.
60 +
        let mut threshold: u32 = 1;
61 +
        if hart == hartIndex { set threshold = 0; }
62 +
        mmio::write32(controller.base + 0x200000 + context * 0x1000, threshold);
63 +
    }
64 +
    for source in 1..controller.limit + 1 {
65 +
        mmio::write32(controller.base + source as u64 * 4, 1);
66 +
    }
67 +
}
68 +
69 +
/// Bind an installed exclusive IRQ resource before its first poll; no MMIO occurs.
70 +
/// The mapping cannot be replaced, so resource-slot reuse never inherits a claim.
71 +
export fn register(controller: *mut Controller, source: u32, object: abi::Object) {
72 +
    assert source > 0 and source <= controller.limit;
73 +
    assert object.kind == abi::Kind::Interrupt and object.epoch <> 0;
74 +
    assert controller.sources[source].object.kind == abi::Kind::Empty;
75 +
    set controller.sources[source].object = object;
76 +
}
77 +
78 +
/// Change one selected-context enable bit without disturbing other source masks.
79 +
unsafe fn enable(controller: *mut Controller, source: u32, active: bool) {
80 +
    let word = source / 32;
81 +
    let bit: u32 = 1 << (source % 32);
82 +
    let previous = controller.enabled[word];
83 +
    let mut next = previous & ~bit;
84 +
    if active { set next = previous | bit; }
85 +
    if next == previous { return; }
86 +
    set controller.enabled[word] = next;
87 +
    mmio::write32(controller.base + 0x2000 + controller.context as u64 * 0x80 + word as u64 * 4, next);
88 +
}
89 +
90 +
/// Complete a retained hardware claim after user service, while its route is enabled.
91 +
/// A PLIC can ignore completion through a disabled route.
92 +
unsafe fn complete(controller: *mut Controller, source: u32) {
93 +
    enable(controller, source, true);
94 +
    mmio::write32(controller.base + 0x200004 + controller.context as u64 * 0x1000, source);
95 +
}
96 +
97 +
/// Resolve the current exclusive holder, checking both resource and domain epochs.
98 +
fn target(controller: *Controller, kernel: *state::State, source: u32) -> abi::Object {
99 +
    let object = controller.sources[source].object;
100 +
    if object.kind <> abi::Kind::Interrupt
101 +
        or not resources::live(kernel.resources, kernel.domains, object) {
102 +
        return domains::none();
103 +
    }
104 +
    let slot = &kernel.resources[object.index];
105 +
    let case resources::Value::Interrupt(irq) = slot.value else { return domains::none(); };
106 +
    if slot.references <> 1 or irq.number <> source or not domains::live(kernel.domains, irq.target) {
107 +
        return domains::none();
108 +
    }
109 +
    return irq.target;
110 +
}
111 +
112 +
/// Test the authoritative coalescing bit only after refresh accepted consumer head.
113 +
fn outstanding(queue: *events::Queue, source: u32) -> bool {
114 +
    return queue.pending[source / 64] & (1 << (source as u64 % 64)) <> 0;
115 +
}
116 +
117 +
/// Reconcile retained claims with current ownership and validated consumer progress.
118 +
/// Never claims hardware or fabricates a notification merely because a source exists.
119 +
/// A transfer republishes retained work to the new incarnation, coalescing if needed.
120 +
/// Drop or domain death discards work and leaves the source masked. Invalid consumer
121 +
/// progress also leaves it masked, but preserves the claim for a later valid head.
122 +
export unsafe fn poll(controller: *mut Controller, kernel: *mut state::State) {
123 +
    for source in 1..controller.limit + 1 {
124 +
        let receiver = target(controller, kernel, source);
125 +
        let pending = &mut controller.sources[source];
126 +
        if receiver.kind == abi::Kind::Empty {
127 +
            if pending.claimed { complete(controller, source); }
128 +
            set pending.claimed = false;
129 +
            set pending.published = domains::none();
130 +
            enable(controller, source, false);
131 +
            continue;
132 +
        }
133 +
        let queue = &mut kernel.domains[receiver.index].events;
134 +
        try events::refresh(queue) catch {
135 +
            enable(controller, source, false);
136 +
            continue;
137 +
        };
138 +
        if pending.claimed {
139 +
            let previous = pending.published;
140 +
            if previous.kind == abi::Kind::Domain and previous.index == receiver.index
141 +
                and previous.epoch == receiver.epoch {
142 +
                if not outstanding(queue, source) {
143 +
                    complete(controller, source);
144 +
                    set pending.claimed = false;
145 +
                    set pending.published = domains::none();
146 +
                }
147 +
            } else {
148 +
                // Keep the real claim across Busy and transfers until push succeeds.
149 +
                try events::push(queue, events::Event { kind: 1, reserved: 0, code: source, value: 0 }, events::Class::Interrupt(source)) catch {
150 +
                    enable(controller, source, false);
151 +
                    continue;
152 +
                };
153 +
                set pending.published = receiver;
154 +
            }
155 +
        }
156 +
        enable(controller, source, not pending.claimed and not outstanding(queue, source)
157 +
            and queue.ordinary < events::CAPACITY - events::CRITICAL);
158 +
    }
159 +
}
160 +
161 +
/// Claim at most limit sources, masking each until its notification is consumed.
162 +
/// The gateway stays active while the driver services the device. Consumer
163 +
/// progress permits completion and rearming without a held-level interrupt loop.
164 +
/// Call under the kernel lock; poll also runs before every subsequent user dispatch.
165 +
export unsafe fn handle(controller: *mut Controller, kernel: *mut state::State) {
166 +
    let claim = controller.base + 0x200004 + controller.context as u64 * 0x1000;
167 +
    for count in 0..controller.limit {
168 +
        let source = mmio::read32(claim);
169 +
        if source == 0 { break; }
170 +
        // Initialization disables every out-of-range source; never index by bad MMIO.
171 +
        if source > controller.limit {
172 +
            mmio::write32(claim, source);
173 +
            break;
174 +
        }
175 +
        enable(controller, source, false);
176 +
        set controller.sources[source].claimed = true;
177 +
        set controller.sources[source].published = domains::none();
178 +
    }
179 +
    poll(controller, kernel);
180 +
}
kernel/core/mmio.rad added +7 -0
1 +
//! Machine-only access to validated, naturally aligned controller registers.
2 +
3 +
/// Read one mapped 32-bit register, ordered before and after all memory and I/O.
4 +
export unsafe fn read32(address: u64) -> u32;
5 +
6 +
/// Write one mapped 32-bit register, ordered before and after all memory and I/O.
7 +
export unsafe fn write32(address: u64, value: u32);
kernel/interrupt.rad added +148 -0
1 +
//! PLIC claims, level-triggered UART service, and exclusive receiver transfer.
2 +
3 +
use core::abi;
4 +
use core::atomic;
5 +
use core::capabilities;
6 +
use core::clock;
7 +
use core::contexts;
8 +
use core::cpu;
9 +
use core::devices;
10 +
use core::domains;
11 +
use core::events;
12 +
use core::fdt;
13 +
use core::frames;
14 +
use core::handles;
15 +
use core::interrupts;
16 +
use core::memory;
17 +
use core::mmio;
18 +
use core::platform;
19 +
use core::resources;
20 +
use core::state;
21 +
22 +
/// One ordered byte access to an already checked device address.
23 +
unsafe fn read8(address: u64) -> u8;
24 +
/// One ordered byte write to an already checked device address.
25 +
unsafe fn write8(address: u64, value: u8);
26 +
/// Independent interrupt receivers.
27 +
static DOMAINS: [domains::Domain; 2] = undefined;
28 +
/// One UART device and two exclusive interrupt sources.
29 +
static OBJECTS: [resources::Slot; 3] = undefined;
30 +
/// No user context is needed for this physical notification check.
31 +
static CONTEXTS: [contexts::Context; 1] = undefined;
32 +
/// Empty RAM accounting; this check does not allocate user Pages.
33 +
static POOL: frames::Pool = undefined;
34 +
/// Empty-pool pins.
35 +
static PINS: [u16; 1] = undefined;
36 +
/// Empty-pool claims.
37 +
static ASSIGNED: [bool; 1] = undefined;
38 +
/// Empty-pool grants.
39 +
static GRANTS: [u64; 2] = undefined;
40 +
/// Private PLIC ownership and coalescing state.
41 +
static PLIC: interrupts::Controller = undefined;
42 +
/// Serialized control-plane state.
43 +
static KERNEL: state::State = undefined;
44 +
45 +
/// Install exclusive authority for one real PLIC source.
46 +
fn interrupt(source: u32, root: abi::Object) -> abi::Handle {
47 +
    let object = try! resources::create(&mut OBJECTS[..], resources::Value::Interrupt(resources::Interrupt { number: source, target: root }));
48 +
    resources::retain(&mut OBJECTS[..], object);
49 +
    interrupts::register(&mut PLIC, source, object);
50 +
    return handles::install(&mut DOMAINS[0].handles, try! handles::vacant(&DOMAINS[0].handles), object, abi::RIGHTS);
51 +
}
52 +
53 +
/// Observe exactly one IRQ event, then release it to the producer.
54 +
fn consume(receiver: u32, source: u32) {
55 +
    let ring = &mut DOMAINS[receiver].events.ring;
56 +
    let head = atomic::load(&ring.head);
57 +
    assert atomic::load(&ring.tail) - head == 1;
58 +
    let event = ring.data[head & (events::CAPACITY - 1)];
59 +
    assert event.kind == 1 and event.code == source and event.value == 0;
60 +
    atomic::store(&mut ring.head, head + 1);
61 +
}
62 +
63 +
/// Read the physical enable register, not the controller's private shadow.
64 +
unsafe fn enabled(source: u32) -> bool {
65 +
    let address = PLIC.base + 0x2000 + PLIC.context as u64 * 0x80 + (source / 32) as u64 * 4;
66 +
    return mmio::read32(address) & (1 << (source % 32)) <> 0;
67 +
}
68 +
69 +
/// Wake from the next external interrupt and run the actual PLIC service path.
70 +
unsafe fn receive(frame: *mut cpu::Frame) {
71 +
    clock::interrupts(2048);
72 +
    cpu::idle(frame);
73 +
    clock::interrupts(0);
74 +
    assert frame.cause == 0x800000000000000b;
75 +
    interrupts::handle(&mut PLIC, &mut KERNEL);
76 +
}
77 +
78 +
/// Exercise an injected edge at tick 4,000,000 and UART byte R at tick 8,000,000.
79 +
@default unsafe fn main(hart: u64, description: *u8) -> u32 {
80 +
    assert hart == 0;
81 +
    let size = try! fdt::word(@sliceOf(description, 40), 4);
82 +
    assert size >= 40 and size <= fdt::MAX_BYTES;
83 +
    let mut tree: fdt::Tree = undefined;
84 +
    try! fdt::decode(@sliceOf(description, size), &mut tree);
85 +
    let mut machine: platform::Platform = undefined;
86 +
    try! platform::discover(&tree, &mut machine);
87 +
    assert machine.harts[0].id == 0 and machine.devices[0].irq == 10;
88 +
    interrupts::init(&mut PLIC, &machine, 0);
89 +
    domains::init(&mut DOMAINS[..]);
90 +
    resources::init(&mut OBJECTS[..]);
91 +
    contexts::init(&mut CONTEXTS[0], 0);
92 +
    let mut empty: platform::Platform = undefined;
93 +
    set empty.memoryCount = 0;
94 +
    set empty.reservedCount = 0;
95 +
    try! frames::init(&mut POOL, &empty);
96 +
    let mut ram: memory::Memory = undefined;
97 +
    memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], 2);
98 +
    set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram };
99 +
    let own = domains::root(&mut DOMAINS[..], 0);
100 +
    let root = try! domains::resolve(&DOMAINS[..], 0, own, abi::CREATE);
101 +
    let child = try! domains::create(&mut DOMAINS[..], root, 0);
102 +
    let target = handles::install(&mut DOMAINS[0].handles, 2, child, abi::DOMAIN_RIGHTS);
103 +
    let device = try! resources::create(&mut OBJECTS[..], resources::Value::Device(machine.devices[0].region));
104 +
    resources::retain(&mut OBJECTS[..], device);
105 +
    let uart = handles::install(&mut DOMAINS[0].handles, 3, device, abi::READ | abi::WRITE);
106 +
    let edge = interrupt(3, root);
107 +
    let _uartIrq = interrupt(10, root);
108 +
    write8(try! devices::access(&KERNEL, 0, uart, 1, 1, abi::WRITE), 1);
109 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
110 +
    assert enabled(3) and enabled(10);
111 +
    let mut frame: cpu::Frame = undefined;
112 +
    cpu::init(&mut frame, 0, 0, 0, 0);
113 +
    receive(&mut frame);
114 +
    assert not enabled(3);
115 +
    for i in 0..events::CAPACITY - events::CRITICAL {
116 +
        try! events::push(&mut DOMAINS[1].events, events::Event { kind: 5, reserved: 0, code: i, value: 0 }, events::Class::Ordinary);
117 +
    }
118 +
    let moved = try! capabilities::transfer(&mut KERNEL, 0, edge, target, abi::RIGHTS as u64);
119 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
120 +
    assert not enabled(3);
121 +
    consume(0, 3);
122 +
    atomic::store(&mut DOMAINS[1].events.ring.head, events::CAPACITY - events::CRITICAL);
123 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
124 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
125 +
    consume(1, 3);
126 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
127 +
    assert enabled(3);
128 +
    try! capabilities::drop(&mut KERNEL, 1, moved);
129 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
130 +
    assert not enabled(3);
131 +
132 +
    receive(&mut frame);
133 +
    assert not enabled(10);
134 +
    for i in 0..4 { interrupts::poll(&mut PLIC, &mut KERNEL); }
135 +
    let byte = read8(try! devices::access(&KERNEL, 0, uart, 0, 1, abi::READ));
136 +
    assert byte == 82;
137 +
    write8(try! devices::access(&KERNEL, 0, uart, 0, 1, abi::WRITE), byte);
138 +
    consume(0, 10);
139 +
    interrupts::poll(&mut PLIC, &mut KERNEL);
140 +
    assert enabled(10);
141 +
    clock::arm(machine.clint.base, machine.harts[0].clintIndex, clock::read(machine.clint.base) + 1000);
142 +
    clock::interrupts(2176);
143 +
    cpu::idle(&mut frame);
144 +
    clock::interrupts(0);
145 +
    clock::arm(machine.clint.base, machine.harts[0].clintIndex, 0xffffffffffffffff);
146 +
    assert frame.cause == 0x8000000000000007;
147 +
    return 0;
148 +
}