diff --git a/src/main/java/li/cil/circuity/api/vm/MemoryMap.java b/src/main/java/li/cil/circuity/api/vm/MemoryMap.java index d4c98c2d..c11a10ed 100644 --- a/src/main/java/li/cil/circuity/api/vm/MemoryMap.java +++ b/src/main/java/li/cil/circuity/api/vm/MemoryMap.java @@ -44,14 +44,64 @@ public interface MemoryMap { */ void removeDevice(final MemoryMappedDevice device); + /** + * Returns the memory range the specified device currently occupies in this mapping, if any. + * + * @param device the device to get the memory range for. + * @return the range the device occupies, if it is in this mapping. + */ Optional getMemoryRange(final MemoryMappedDevice device); + /** + * Returns the memory range that the specified address fall into, if any. + *

+ * This is useful for getting a direct reference to a {@link MemoryMappedDevice} at + * a specific memory location, usually to perform multiple read or write operations + * on it without having to go through the slower {@link #load(int, int)} and + * {@link #store(int, int, int)} calls. + * + * @param address the address to get a memory range for. + * @return the memory range the address falls into, if any. + */ @Nullable MemoryRange getMemoryRange(final int address); + /** + * Marks a location in memory dirty. + *

+ * This may be called by systems in parallel to performing actual store operations + * directly on {@link MemoryMappedDevice}s. + * + * @param range the memory range in which data has changed. + * @param offset the offset inside that memory range at which data has changed. + */ void setDirty(final MemoryRange range, final int offset); + /** + * Reads a value from the specified physical address. + *

+ * When performing many operations on an address range that is known to be occupied by + * a single device, it is more efficient to obtain a reference to that device via + * {@link #getMemoryRange(int)} instead and operate on that device directly. + * + * @param address the physical address to read a value from. + * @param sizeLog2 the size of the value to read. See {@link li.cil.circuity.api.vm.device.memory.Sizes}. + * @return the value read from the specified location. + * @throws MemoryAccessException if an error occurred accessing the memory a the specified location. + */ int load(final int address, final int sizeLog2) throws MemoryAccessException; + /** + * Writes a value to the specified physical address. + *

+ * When performing many operations on an address range that is known to be occupied by + * a single device, it is more efficient to obtain a reference to that device via + * {@link #getMemoryRange(int)} instead and operate on that device directly. + * + * @param address the physical address to write a value to. + * @param value the value to write to the specified location. + * @param sizeLog2 the size of the value to write. See {@link li.cil.circuity.api.vm.device.memory.Sizes}. + * @throws MemoryAccessException if an error occurred accessing the memory a the specified location. + */ void store(final int address, final int value, final int sizeLog2) throws MemoryAccessException; } diff --git a/src/main/java/li/cil/circuity/api/vm/MemoryRange.java b/src/main/java/li/cil/circuity/api/vm/MemoryRange.java index 9882174c..3f3f9780 100644 --- a/src/main/java/li/cil/circuity/api/vm/MemoryRange.java +++ b/src/main/java/li/cil/circuity/api/vm/MemoryRange.java @@ -4,9 +4,24 @@ import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice; import java.util.Objects; +/** + * Represents a segment of memory mapped by a {@link MemoryMap}. + */ public final class MemoryRange { + /** + * The device assigned to this memory range. + */ public final MemoryMappedDevice device; - public final int start, end; // both are inclusive + + /** + * The first byte-aligned address inside this memory range (inclusive). + */ + public final int start; + + /** + * The last byte-aligned address inside this memory range (inclusive). + */ + public final int end; public MemoryRange(final MemoryMappedDevice device, final int start, final int end) { if (Integer.compareUnsigned(start, end) > 0) { @@ -22,18 +37,42 @@ public final class MemoryRange { this(device, address, address + device.getLength() - 1); } + /** + * The address of this memory range. + *

+ * This is the same as {@link #start}. + * + * @return the address of this memory range. + */ public int address() { return start; } + /** + * The size of this memory range, in bytes. + * + * @return the size of this memory range. + */ public final int size() { return end - start + 1; } + /** + * Checks if the specified address is contained within this memory range. + * + * @param address the address to check for. + * @return {@code true} if the address falls into this memory range; {@code false} otherwise. + */ public boolean contains(final int address) { return Integer.compareUnsigned(address, start) >= 0 && Integer.compareUnsigned(address, end) <= 0; } + /** + * Checks if the specified memory range intersects with this memory range. + * + * @param other the memory range to check for. + * @return {@code true} if the memory range intersects this memory range; {@code false} otherwise. + */ public boolean intersects(final MemoryRange other) { return Integer.compareUnsigned(start, other.end) <= 0 && Integer.compareUnsigned(end, other.start) >= 0; } diff --git a/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java b/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java index 282c394d..73c76dfd 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java +++ b/src/main/java/li/cil/circuity/api/vm/device/InterruptSource.java @@ -2,6 +2,14 @@ package li.cil.circuity.api.vm.device; import li.cil.circuity.api.vm.Interrupt; +/** + * An interrupt source is a device that can raise and lower interrupts in an {@link InterruptController}. + */ public interface InterruptSource extends Device { + /** + * Returns all interrupts used by this device. + * + * @return the interrupts used by this device. + */ Iterable getInterrupts(); } diff --git a/src/main/java/li/cil/circuity/api/vm/device/Steppable.java b/src/main/java/li/cil/circuity/api/vm/device/Steppable.java index e8dfdd8d..8996fa11 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/Steppable.java +++ b/src/main/java/li/cil/circuity/api/vm/device/Steppable.java @@ -1,5 +1,21 @@ package li.cil.circuity.api.vm.device; +/** + * Steppable devices can be advanced by some number of cycles. + *

+ * Cycles in this context are an abstract concept and interpretation is up to the device + * implementing this interface. This can be actual emulated clock cycles, number of + * instructions processed or simply a generic value representing time. + *

+ * Devices implementing this must not make any assumptions on the number of cycles + * they are allowed at a single time. This number may change wildly between calls. + * However, while they should not, devices may run for more cycles than specified. + */ public interface Steppable extends Device { + /** + * Advance this device by the given number of cycles. + * + * @param cycles the number of cycles to advance the device by. + */ void step(final int cycles); } diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java index a81b4a89..3112f855 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryAccessException.java @@ -1,5 +1,12 @@ package li.cil.circuity.api.vm.device.memory; +/** + * Base class for all memory related exceptions. + *

+ * This exception may be thrown whenever memory mapped in a {@link li.cil.circuity.api.vm.MemoryMap} + * is accessed, specifically any {@link MemoryMappedDevice} may throw these exceptions to signal an + * invalid access. + */ public class MemoryAccessException extends Exception { private final int address; diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java index 3ba93ddd..e1096352 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryMappedDevice.java @@ -8,15 +8,56 @@ import li.cil.circuity.api.vm.device.Device; * so that they can be accessed via a memory range using the same mechanisms used for accessing RAM. */ public interface MemoryMappedDevice extends Device { + /** + * The number of bytes this device occupies in memory. + *

+ * This is used by a {@link MemoryMap} to compute the {@link li.cil.circuity.api.vm.MemoryRange} the + * device will occupy. + * + * @return the size of the device in bytes. + */ int getLength(); + /** + * Returns a bitmask indicating the value sizes supported by this device. + *

+ * Code accessing a memory mapped device, e.g. {@link MemoryMap}s, may use this to verify + * load and store requests before calling {@link #load(int, int)} and {@link #store(int, int, int)} + * on a device. + * + * @return a bit mask indicating the supported value sizes. + */ default int getSupportedSizes() { return (1 << Sizes.SIZE_8_LOG2) | (1 << Sizes.SIZE_16_LOG2) | (1 << Sizes.SIZE_32_LOG2); } + /** + * Reads a value from this device. + *

+ * Most devices that are not {@link PhysicalMemory} will cause side-effects from + * having certain areas of their memory written to. + * + * @param offset the offset local to the device to read from. + * @param sizeLog2 the size of the value to read, log2. See {@link Sizes}. + * @return the value read from the device. + * @throws MemoryAccessException if there was an error accessing the data in this device. + * @see Sizes + */ int load(final int offset, final int sizeLog2) throws MemoryAccessException; + /** + * Writes a value to this device. + *

+ * Most devices that are not {@link PhysicalMemory} may cause side-effects from + * having certain areas of their memory written to. + * + * @param offset the offset local to the device to write to. + * @param value the value to write to the device. + * @param sizeLog2 the size of the value to write, log2. See {@link Sizes}. + * @throws MemoryAccessException if there was an error accessing the data in this device. + * @see Sizes + */ void store(final int offset, final int value, final int sizeLog2) throws MemoryAccessException; } diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java b/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java deleted file mode 100644 index eaaff865..00000000 --- a/src/main/java/li/cil/circuity/api/vm/device/memory/MemoryStream.java +++ /dev/null @@ -1,11 +0,0 @@ -package li.cil.circuity.api.vm.device.memory; - -public interface MemoryStream { - boolean canLoad16(); - - short load16() throws MemoryAccessException; - - boolean canLoad32(); - - int load32() throws MemoryAccessException; -} diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java b/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java index b543f4bb..642e60d5 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/PhysicalMemory.java @@ -1,12 +1,15 @@ package li.cil.circuity.api.vm.device.memory; -import li.cil.circuity.api.vm.MemoryMap; - /** * Instances marked with this interface can be treated as random-access memory. *

- * {@link MemoryMap}s may use this to decide whether a memory - * region can be stored in a translation lookaside buffer. + * For example, CPUs may use this to decide whether a memory region can be stored + * in a translation look-aside buffer. + *

+ * In particular, implementing this interface communicates that values written to + * this device can be read back from the same address, and that the {@link li.cil.circuity.api.vm.MemoryRange} + * they occupy can be used as a continuous whole, without any inaccessible areas in + * it. */ public interface PhysicalMemory extends MemoryMappedDevice { } diff --git a/src/main/java/li/cil/circuity/api/vm/device/memory/Sizes.java b/src/main/java/li/cil/circuity/api/vm/device/memory/Sizes.java index eda4b395..8220034a 100644 --- a/src/main/java/li/cil/circuity/api/vm/device/memory/Sizes.java +++ b/src/main/java/li/cil/circuity/api/vm/device/memory/Sizes.java @@ -1,5 +1,10 @@ package li.cil.circuity.api.vm.device.memory; +/** + * Constants for different value sizes. + *

+ * These constants are named by their bit width. + */ public final class Sizes { public static final int SIZE_8 = 8; public static final int SIZE_16 = 16; diff --git a/src/main/java/li/cil/circuity/client/gui/RISCVTestScreen.java b/src/main/java/li/cil/circuity/client/gui/RISCVTestScreen.java index 9ced5924..d76bfc3e 100644 --- a/src/main/java/li/cil/circuity/client/gui/RISCVTestScreen.java +++ b/src/main/java/li/cil/circuity/client/gui/RISCVTestScreen.java @@ -23,6 +23,7 @@ import org.lwjgl.glfw.GLFW; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.util.Objects; public final class RISCVTestScreen extends Screen { @@ -34,6 +35,7 @@ public final class RISCVTestScreen extends Screen { private final Terminal terminal = new Terminal(); private VirtualMachineRunner runner; private UART16550A uart; + private VirtIOBlockDevice hdd; private VirtIOConsoleDevice console; private VirtIOKeyboardDevice keyboard; @@ -70,6 +72,14 @@ public final class RISCVTestScreen extends Screen { public void removed() { super.removed(); + if (hdd != null) { + try { + hdd.close(); + } catch (final IOException e) { + LOGGER.error(e); + } + } + if (minecraft != null) { minecraft.keyboardListener.enableRepeatEvents(false); } @@ -160,7 +170,7 @@ public final class RISCVTestScreen extends Screen { final R5Board board = new R5Board(); final PhysicalMemory rom = Memory.create(128 * 1024); final PhysicalMemory memory = Memory.create(32 * 1014 * 1024); - final VirtIOBlockDevice hdd = new VirtIOBlockDevice(board.getMemoryMap(), ByteBufferBlockDevice.createFromFile(rootfsFile, true)); + hdd = new VirtIOBlockDevice(board.getMemoryMap(), ByteBufferBlockDevice.createFromFile(rootfsFile, true)); uart = new UART16550A(); console = new VirtIOConsoleDevice(board.getMemoryMap()); keyboard = new VirtIOKeyboardDevice(board.getMemoryMap()); diff --git a/src/main/java/li/cil/circuity/vm/device/UART16550A.java b/src/main/java/li/cil/circuity/vm/device/UART16550A.java index 1bb884c5..5a057b62 100644 --- a/src/main/java/li/cil/circuity/vm/device/UART16550A.java +++ b/src/main/java/li/cil/circuity/vm/device/UART16550A.java @@ -11,6 +11,14 @@ import li.cil.circuity.api.vm.device.memory.Sizes; import java.util.Collections; +/** + * Implements a 16550A UART. + *

+ * This is not a cycle-correct implementation. It does not care about baudrates and + * timeout delays. But it's good enough to pump data into and out of a virtual machine. + *

+ * See: https://web.archive.org/web/20200207194832/https://www.lammertbies.nl/comm/info/serial-uart + */ @SuppressWarnings("PointlessBitwiseExpression") public final class UART16550A implements Resettable, Steppable, MemoryMappedDevice, InterruptSource { private static final int UART_RBR_OFFSET = 0; // Receive buffer register (Read-only) diff --git a/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java b/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java index 04a255df..ff1f672b 100644 --- a/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java +++ b/src/main/java/li/cil/circuity/vm/device/memory/ByteBufferMemory.java @@ -9,6 +9,9 @@ import li.cil.circuity.vm.device.memory.exception.StoreFaultException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +/** + * Simple physical memory implementation backed by a {@link ByteBuffer}. + */ public class ByteBufferMemory implements PhysicalMemory { private final ByteBuffer data; @@ -26,40 +29,38 @@ public class ByteBufferMemory implements PhysicalMemory { @Override public int load(final int offset, final int sizeLog2) throws MemoryAccessException { - try { - switch (sizeLog2) { - case Sizes.SIZE_8_LOG2: - return data.get(offset); - case Sizes.SIZE_16_LOG2: - return data.getShort(offset); - case Sizes.SIZE_32_LOG2: - return data.getInt(offset); - default: - throw new IllegalArgumentException(); - } - } catch (final IndexOutOfBoundsException e) { + if (offset < 0 || offset >= data.limit()) { throw new LoadFaultException(offset); } + switch (sizeLog2) { + case Sizes.SIZE_8_LOG2: + return data.get(offset); + case Sizes.SIZE_16_LOG2: + return data.getShort(offset); + case Sizes.SIZE_32_LOG2: + return data.getInt(offset); + default: + throw new IllegalArgumentException(); + } } @Override public void store(final int offset, final int value, final int sizeLog2) throws MemoryAccessException { - try { - switch (sizeLog2) { - case Sizes.SIZE_8_LOG2: - data.put(offset, (byte) value); - break; - case Sizes.SIZE_16_LOG2: - data.putShort(offset, (short) value); - break; - case Sizes.SIZE_32_LOG2: - data.putInt(offset, value); - break; - default: - throw new IllegalArgumentException(); - } - } catch (final IndexOutOfBoundsException e) { + if (offset < 0 || offset >= data.limit()) { throw new StoreFaultException(offset); } + switch (sizeLog2) { + case Sizes.SIZE_8_LOG2: + data.put(offset, (byte) value); + break; + case Sizes.SIZE_16_LOG2: + data.putShort(offset, (short) value); + break; + case Sizes.SIZE_32_LOG2: + data.putInt(offset, value); + break; + default: + throw new IllegalArgumentException(); + } } }