Little bit of documentation and dead code removal.

This commit is contained in:
Florian Nücke
2020-09-26 01:31:06 +02:00
parent 9722ae24d1
commit 78b853308a
12 changed files with 221 additions and 44 deletions

View File

@@ -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<MemoryRange> getMemoryRange(final MemoryMappedDevice device);
/**
* Returns the memory range that the specified address fall into, if any.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}

View File

@@ -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.
* <p>
* 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;
}

View File

@@ -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<Interrupt> getInterrupts();
}

View File

@@ -1,5 +1,21 @@
package li.cil.circuity.api.vm.device;
/**
* Steppable devices can be advanced by some number of <em>cycles</em>.
* <p>
* 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.
* <p>
* 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);
}

View File

@@ -1,5 +1,12 @@
package li.cil.circuity.api.vm.device.memory;
/**
* Base class for all memory related exceptions.
* <p>
* 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;

View File

@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}

View File

@@ -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;
}

View File

@@ -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.
* <p>
* {@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.
* <p>
* 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 {
}

View File

@@ -1,5 +1,10 @@
package li.cil.circuity.api.vm.device.memory;
/**
* Constants for different value sizes.
* <p>
* 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;

View File

@@ -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());

View File

@@ -11,6 +11,14 @@ import li.cil.circuity.api.vm.device.memory.Sizes;
import java.util.Collections;
/**
* Implements a 16550A UART.
* <p>
* 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.
* <p>
* 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)

View File

@@ -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();
}
}
}