From 8b44de7d882d49c8f90467eddabe2ca42617e28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 14 Sep 2020 17:46:30 +0200 Subject: [PATCH] Made PLIC more feature-complete. Still hardcoded to 32 sources and one hart for now. --- .../li/cil/circuity/vm/riscv/R5Board.java | 7 +- .../java/li/cil/circuity/vm/riscv/R5CPU.java | 4 +- .../riscv/device/R5CoreLocalInterrupter.java | 20 +- .../R5PlatformLevelInterruptController.java | 367 ++++++++++++------ 4 files changed, 281 insertions(+), 117 deletions(-) diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5Board.java b/src/main/java/li/cil/circuity/vm/riscv/R5Board.java index 36e40f52..b9de949b 100644 --- a/src/main/java/li/cil/circuity/vm/riscv/R5Board.java +++ b/src/main/java/li/cil/circuity/vm/riscv/R5Board.java @@ -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 diff --git a/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java b/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java index 66bcbb95..621a50f8 100644 --- a/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java +++ b/src/main/java/li/cil/circuity/vm/riscv/R5CPU.java @@ -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); diff --git a/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java b/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java index f7df9366..690965b6 100644 --- a/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java +++ b/src/main/java/li/cil/circuity/vm/riscv/device/R5CoreLocalInterrupter.java @@ -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() { diff --git a/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java b/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java index 41b6d0f5..7e49ebbd 100644 --- a/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java +++ b/src/main/java/li/cil/circuity/vm/riscv/device/R5PlatformLevelInterruptController.java @@ -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. *

* 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 - *

- * 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
- * 
*/ 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 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(); + } } } }