Made PLIC more feature-complete. Still hardcoded to 32 sources and one hart for now.

This commit is contained in:
Florian Nücke
2020-09-14 17:46:30 +02:00
parent f1eac4d9e1
commit 8b44de7d88
4 changed files with 281 additions and 117 deletions

View File

@@ -63,8 +63,7 @@ public final class R5Board {
// Wire up interrupts.
clint.putHart(0, cpu);
plic.getMachineExternalInterrupt().controller = cpu;
plic.getSupervisorExternalInterrupt().controller = cpu;
plic.setHart(cpu);
uart.getInterrupt().id = 0xA;
uart.getInterrupt().controller = plic;
@@ -183,6 +182,10 @@ public final class R5Board {
return uart.getByte();
}
public void putValue(final byte b) {
uart.putByte(b);
}
private DeviceTree buildDeviceTree() {
final DeviceTree root = DeviceTreeRegistry.create(memoryMap);
root

View File

@@ -651,6 +651,7 @@ public class R5CPU implements Steppable, RealTimeCounter, InterruptController {
throw new R5IllegalInstructionException(inst);
}
}
break;
}
}
@@ -1028,8 +1029,9 @@ public class R5CPU implements Steppable, RealTimeCounter, InterruptController {
break;
}
case 0b001: { // SLLI
if ((inst & 0b1111111_00000_00000_000_00000_0000000) != 0)
if ((inst & 0b1111111_00000_00000_000_00000_0000000) != 0) {
throw new R5IllegalInstructionException(inst);
}
slli(rd, rs1, imm);

View File

@@ -78,12 +78,12 @@ public final class R5CoreLocalInterrupter implements Steppable, InterruptSource,
return msips.get(hartId).isRaised() ? 1 : 0;
} else {
LOGGER.debug("invalid sip read [{}]", Integer.toHexString(offset));
return 0;
}
} else {
LOGGER.debug("invalid sip hartid [{}]", hartId);
return 0;
}
return 0;
} else if (offset >= CLINT_TIMECMP_BASE && offset < CLINT_TIME_BASE) {
final int hartId = (offset - CLINT_TIMECMP_BASE) >>> 3;
if (mtimecmps.containsKey(hartId)) {
@@ -95,19 +95,19 @@ public final class R5CoreLocalInterrupter implements Steppable, InterruptSource,
return (int) (mtimecmp >>> 32);
} else {
LOGGER.debug("invalid timecmp read [{}]", Integer.toHexString(offset));
return 0;
}
} else {
LOGGER.debug("invalid timecmp hartid [{}]", hartId);
return 0;
}
return 0;
} else if (offset == CLINT_TIME_BASE) {
return (int) rtc.getTime();
} else if (offset == CLINT_TIME_BASE + 4) {
return (int) (rtc.getTime() >>> 32);
}
LOGGER.debug("invalid read [{}]", Integer.toHexString(offset));
LOGGER.debug("invalid read offset [{}]", Integer.toHexString(offset));
return 0;
}
@@ -130,6 +130,7 @@ public final class R5CoreLocalInterrupter implements Steppable, InterruptSource,
} else {
LOGGER.debug("invalid sip hartid [{}]", hartId);
}
return;
} else if (offset >= CLINT_TIMECMP_BASE && offset < CLINT_TIME_BASE) {
final int hartId = (offset - CLINT_TIMECMP_BASE) >>> 3;
@@ -160,10 +161,17 @@ public final class R5CoreLocalInterrupter implements Steppable, InterruptSource,
} else {
LOGGER.debug("invalid timecmp hartid [{}]", hartId);
}
return;
} else if (offset == CLINT_TIME_BASE) {
LOGGER.debug("invalid time write");
return;
} else if (offset == CLINT_TIME_BASE + 4) {
LOGGER.debug("invalid timeh write");
return;
}
LOGGER.debug("invalid write [{}]", Integer.toHexString(offset));
LOGGER.debug("invalid write offset [{}]", Integer.toHexString(offset));
}
private void checkTimeComparators() {

View File

@@ -1,137 +1,248 @@
package li.cil.circuity.vm.riscv.device;
import li.cil.circuity.api.vm.Interrupt;
import li.cil.circuity.api.vm.device.InterruptController;
import li.cil.circuity.api.vm.device.InterruptSource;
import li.cil.circuity.api.vm.device.Interrupter;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.vm.components.InterruptSourceRegistry;
import li.cil.circuity.vm.riscv.R5;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Implementation of a PLIC with 32 sources.
* Implementation of a PLIC with 31 sources supporting a single hart. It provides external
* interrupts for M and S levels.
* <p>
* See: https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc
* See: https://github.com/riscv/opensbi/blob/master/lib/utils/irqchip/plic.c
* <pre>
* base + 0x000000: Reserved (interrupt source 0 does not exist)
* base + 0x000004: Interrupt source 1 priority
* base + 0x000008: Interrupt source 2 priority
* ...
* base + 0x000FFC: Interrupt source 1023 priority
* base + 0x001000: Interrupt Pending bit 0-31
* base + 0x00107C: Interrupt Pending bit 992-1023
* ...
* base + 0x002000: Enable bits for sources 0-31 on context 0
* base + 0x002004: Enable bits for sources 32-63 on context 0
* ...
* base + 0x00207F: Enable bits for sources 992-1023 on context 0
* base + 0x002080: Enable bits for sources 0-31 on context 1
* base + 0x002084: Enable bits for sources 32-63 on context 1
* ...
* base + 0x0020FF: Enable bits for sources 992-1023 on context 1
* base + 0x002100: Enable bits for sources 0-31 on context 2
* base + 0x002104: Enable bits for sources 32-63 on context 2
* ...
* base + 0x00217F: Enable bits for sources 992-1023 on context 2
* ...
* base + 0x1F1F80: Enable bits for sources 0-31 on context 15871
* base + 0x1F1F84: Enable bits for sources 32-63 on context 15871
* base + 0x1F1FFF: Enable bits for sources 992-1023 on context 15871
* ...
* base + 0x1FFFFC: Reserved
* base + 0x200000: Priority threshold for context 0
* base + 0x200004: Claim/complete for context 0
* base + 0x200008: Reserved
* ...
* base + 0x200FFC: Reserved
* base + 0x201000: Priority threshold for context 1
* base + 0x201004: Claim/complete for context 1
* ...
* base + 0x3FFE000: Priority threshold for context 15871
* base + 0x3FFE004: Claim/complete for context 15871
* base + 0x3FFE008: Reserved
* ...
* base + 0x3FFFFFC: Reserved
* </pre>
*/
public class R5PlatformLevelInterruptController implements MemoryMappedDevice, Interrupter, InterruptSource {
private static final int PLIC_PRIORITY_BASE = 0x0;
private static final int PLIC_PENDING_BASE = 0x1000;
private static final int PLIC_ENABLE_BASE = 0x2000;
private static final Logger LOGGER = LogManager.getLogger();
private static final int PLIC_PRIORITY_BASE = 0x000004;
private static final int PLIC_PENDING_BASE = 0x001000;
private static final int PLIC_ENABLE_BASE = 0x002000;
private static final int PLIC_ENABLE_STRIDE = 0x80;
private static final int PLIC_CONTEXT_BASE = 0x200000;
private static final int PLIC_CONTEXT_STRIDE = 0x1000;
private static final int PLIC_LENGTH = 0x04000000;
private static final int PLIC_SOURCE_COUNT = 32; // Includes always off zero!
private static final int PLIC_CONTEXT_COUNT = 2; // MEIP and SEIP for one hart.
private static final int PLIC_MAX_PRIORITY = 7; // Number of priority level supported. Must have all bits set.
private final InterruptSourceRegistry interrupts = new InterruptSourceRegistry();
private int pending;
private int served;
private final Interrupt meip = new Interrupt(R5.MEIP_SHIFT);
private final Interrupt seip = new Interrupt(R5.SEIP_SHIFT);
public Interrupt getMachineExternalInterrupt() {
return meip;
private final int sourceWords; // Size of blocks holding flags for sources in words.
private final int[] priorityBySource;
private final int[] thresholdByContext;
private final Interrupt[] interruptByContext = {meip, seip};
private final AtomicInteger[] pending;
private final AtomicInteger[] claimed;
private final int[] enabled; // Contiguous words for all sources and all contexts (c0:s0...c0:sN,...,cM:s0...cM:N)
public R5PlatformLevelInterruptController() {
sourceWords = (PLIC_SOURCE_COUNT + 31) >>> 5;
priorityBySource = new int[PLIC_SOURCE_COUNT];
thresholdByContext = new int[PLIC_CONTEXT_COUNT];
pending = new AtomicInteger[sourceWords];
for (int i = 0; i < sourceWords; i++) {
pending[i] = new AtomicInteger(0);
}
claimed = new AtomicInteger[sourceWords];
for (int i = 0; i < sourceWords; i++) {
claimed[i] = new AtomicInteger(0);
}
enabled = new int[sourceWords * PLIC_CONTEXT_COUNT];
}
public Interrupt getSupervisorExternalInterrupt() {
return seip;
public void setHart(final InterruptController interruptController) {
for (final Interrupt interrupt : interruptByContext) {
interrupt.controller = interruptController;
}
}
@Override
public int getLength() {
return 0x04000000;
return PLIC_LENGTH;
}
@Override
public int load(final int offset, final int sizeLog2) {
assert sizeLog2 == 2;
switch (offset) {
// 0x0: Reserved.
// 0x1 - 0x000FFC: Priorities; hardcoded to zero.
case PLIC_PENDING_BASE: { // Pending bits 0-31.
return pending & ~served;
if (offset >= PLIC_PRIORITY_BASE && offset < PLIC_PRIORITY_BASE + (PLIC_SOURCE_COUNT << 2)) {
// base + 0x000004: Interrupt source 1 priority
// base + 0x000008: Interrupt source 2 priority
// ...
// base + 0x000FFC: Interrupt source 1023 priority
final int source = ((offset - PLIC_PRIORITY_BASE) >> 2) + 1; // Plus one because we skip zero.
return priorityBySource[source];
} else if (offset >= PLIC_PENDING_BASE && offset < PLIC_PENDING_BASE + sourceWords) {
// base + 0x001000: Interrupt Pending bit 0-31
// base + 0x00107C: Interrupt Pending bit 992-1023
// ...
final int word = (offset - PLIC_PENDING_BASE) >> 2;
return pending[word].get();
} else if (offset >= PLIC_ENABLE_BASE && offset < PLIC_ENABLE_BASE + PLIC_CONTEXT_COUNT * PLIC_ENABLE_STRIDE) {
// base + 0x002000: Enable bits for sources 0-31 on context 0
// base + 0x002004: Enable bits for sources 32-63 on context 0
// ...
// base + 0x00207F: Enable bits for sources 992-1023 on context 0
// base + 0x002080: Enable bits for sources 0-31 on context 1
// base + 0x002084: Enable bits for sources 32-63 on context 1
// ...
final int context = (offset - PLIC_ENABLE_BASE) / PLIC_ENABLE_STRIDE;
final int contextOffset = offset & (PLIC_ENABLE_STRIDE - 1);
final int word = contextOffset >>> 2;
if (word < sourceWords) {
return enabled[context * sourceWords + word];
} else {
LOGGER.debug("invalid enabled read [{}]", Integer.toHexString(offset));
}
case PLIC_ENABLE_BASE: { // Enable bits 0-31 on context 0 (hart 0).
return 0xFFFFFFFF; // Hardcoded to enabled.
}
// PLIC_CONTEXT_BASE: Priority threshold on context 0; hardcoded to zero.
case PLIC_CONTEXT_BASE + 4: { // Claim/complete for context 0
return claim();
}
default: {
return 0;
return 0;
} else if (offset >= PLIC_CONTEXT_BASE && offset < PLIC_CONTEXT_BASE + PLIC_CONTEXT_COUNT * PLIC_CONTEXT_STRIDE) {
// base + 0x200000: Priority threshold for context 0
// base + 0x200004: Claim/complete for context 0
// base + 0x200008: Reserved
// ...
final int context = (offset - PLIC_CONTEXT_BASE) / PLIC_CONTEXT_STRIDE;
final int contextOffset = offset & (PLIC_CONTEXT_STRIDE - 1);
if (contextOffset == 0) { // Priority threshold.
return thresholdByContext[context];
} else if (contextOffset == 4) { // Claim.
final int value = claim(context);
updateInterrupts();
return value;
} else {
LOGGER.debug("invalid context [{}]", context);
}
return 0;
}
LOGGER.debug("invalid read offset [{}]", Integer.toHexString(offset));
return 0;
}
@Override
public void store(final int offset, final int value, final int sizeLog2) {
assert sizeLog2 == 2;
switch (offset) {
case PLIC_CONTEXT_BASE + 4: { // Claim/complete for context 0
complete(value);
break;
if (offset >= PLIC_PRIORITY_BASE && offset < PLIC_PRIORITY_BASE + (PLIC_SOURCE_COUNT << 2)) {
// base + 0x000004: Interrupt source 1 priority
// base + 0x000008: Interrupt source 2 priority
// ...
// base + 0x000FFC: Interrupt source 1023 priority
final int source = ((offset - PLIC_PRIORITY_BASE) >> 2) + 1; // Plus one because we skip zero.
priorityBySource[source] = value & PLIC_MAX_PRIORITY;
updateInterrupts();
return;
} else if (offset >= PLIC_PENDING_BASE && offset < PLIC_PENDING_BASE + sourceWords) {
// base + 0x001000: Interrupt Pending bit 0-31
// base + 0x00107C: Interrupt Pending bit 992-1023
// ...
LOGGER.debug("invalid pending write");
return;
} else if (offset >= PLIC_ENABLE_BASE && offset < PLIC_ENABLE_BASE + PLIC_CONTEXT_COUNT * PLIC_ENABLE_STRIDE) {
// base + 0x002000: Enable bits for sources 0-31 on context 0
// base + 0x002004: Enable bits for sources 32-63 on context 0
// ...
// base + 0x00207F: Enable bits for sources 992-1023 on context 0
// base + 0x002080: Enable bits for sources 0-31 on context 1
// base + 0x002084: Enable bits for sources 32-63 on context 1
// ...
final int context = (offset - PLIC_ENABLE_BASE) / PLIC_ENABLE_STRIDE;
final int contextOffset = offset & (PLIC_ENABLE_STRIDE - 1);
final int word = contextOffset >>> 2;
if (word < sourceWords) {
enabled[context * sourceWords + word] = value;
} else {
LOGGER.debug("invalid enabled write [{}]", value);
}
return;
} else if (offset >= PLIC_CONTEXT_BASE && offset < PLIC_CONTEXT_BASE + PLIC_CONTEXT_COUNT * PLIC_CONTEXT_STRIDE) {
// base + 0x200000: Priority threshold for context 0
// base + 0x200004: Claim/complete for context 0
// base + 0x200008: Reserved
// ...
final int context = (offset - PLIC_CONTEXT_BASE) / PLIC_CONTEXT_STRIDE;
final int contextOffset = offset & (PLIC_CONTEXT_STRIDE - 1);
if (contextOffset == 0) { // Priority threshold.
if (Integer.compareUnsigned(value, PLIC_MAX_PRIORITY) <= 0) {
thresholdByContext[context] = value;
updateInterrupts();
} else {
LOGGER.debug("invalid threshold write [{}]", value);
}
return;
} else if (contextOffset == 4) { // Complete.
if (Integer.compareUnsigned(value, PLIC_SOURCE_COUNT) < 0) {
setClaimed(value, false);
updateInterrupts();
} else {
LOGGER.debug("invalid complete write [{}]", value);
}
return;
} else {
LOGGER.debug("invalid context [{}]", context);
return;
}
}
LOGGER.debug("invalid write offset [{}]", Integer.toHexString(offset));
}
@Override
public void raiseInterrupts(final int mask) {
pending |= mask;
propagate();
public void raiseInterrupts(int mask) {
for (int i = 0; mask != 0; i++, mask = mask >>> 1) {
if ((mask & 0b1) != 0) {
setPending(i, true);
}
}
updateInterrupts();
}
@Override
public void lowerInterrupts(final int mask) {
pending &= ~mask;
propagate();
public void lowerInterrupts(int mask) {
for (int i = 0; mask != 0; i++, mask = mask >>> 1) {
if ((mask & 0b1) != 0) {
setPending(i, false);
}
}
updateInterrupts();
}
@Override
public int getRaisedInterrupts() {
return pending[0].get();
}
@Override
public Iterable<Interrupt> getInterrupts() {
return Arrays.asList(meip, seip);
return Arrays.asList(interruptByContext);
}
@Override
@@ -149,42 +260,82 @@ public class R5PlatformLevelInterruptController implements MemoryMappedDevice, I
interrupts.releaseInterrupt(id);
}
private int claim() {
final int unserved = pending & ~served;
if (unserved == 0) {
return 0;
private boolean hasPending(final int context) {
for (int i = 0; i < sourceWords; i++) {
final int unclaimed = (pending[i].get() & ~claimed[i].get()) & enabled[context * sourceWords + i];
if (unclaimed == 0) {
continue;
}
for (int j = 0; j < 32; j++) {
final int source = (i * 32) + j;
final int priority = priorityBySource[source];
final boolean enabled = (unclaimed & (1 << j)) != 0;
if (enabled && priority > thresholdByContext[context]) {
return true;
}
}
}
final int index = Integer.numberOfTrailingZeros(unserved);
served |= 1 << index;
propagate();
return index + 1;
return false;
}
private void complete(final int value) {
if (value <= 0) {
return;
}
final int index = value - 1;
if (index >= 32) {
return;
}
served &= ~(1 << index);
propagate();
}
private void propagate() {
final int mask = pending & ~served;
if (mask != 0) {
meip.raiseInterrupt();
seip.raiseInterrupt();
private void setPending(final int source, final boolean value) {
final int word = source >>> 5;
final int mask = 1 << (source & 31);
if (value) {
pending[word].updateAndGet(operand -> operand |= mask);
} else {
meip.lowerInterrupt();
seip.lowerInterrupt();
pending[word].updateAndGet(operand -> operand &= ~mask);
}
}
private int claim(final int context) {
int maxSource = 0;
int maxPriority = thresholdByContext[context];
for (int i = 0; i < sourceWords; i++) {
final int unclaimed = (pending[i].get() & ~claimed[i].get()) & enabled[context * sourceWords + i];
if (unclaimed == 0) {
continue;
}
for (int j = 0; j < 32; j++) {
final int source = (i * 32) + j;
final int priority = priorityBySource[source];
final boolean enabled = (unclaimed & (1 << j)) != 0;
if (enabled && priority > maxPriority) {
maxSource = source;
maxPriority = priority;
}
}
}
if (maxSource > 0) {
setPending(maxSource, false);
setClaimed(maxSource, true);
}
return maxSource;
}
private void setClaimed(final int source, final boolean value) {
final int word = source >>> 5;
final int mask = 1 << (source & 31);
if (value) {
claimed[word].updateAndGet(operand -> operand |= mask);
} else {
claimed[word].updateAndGet(operand -> operand &= ~mask);
}
}
private void updateInterrupts() {
for (int context = 0; context < PLIC_CONTEXT_COUNT; context++) {
if (hasPending(context)) {
interruptByContext[context].raiseInterrupt();
} else {
interruptByContext[context].lowerInterrupt();
}
}
}
}