Way too late first proper commit.

This commit is contained in:
Florian Nücke
2020-09-12 05:34:47 +02:00
parent 8f2940ebca
commit d86f9be2e7
61 changed files with 7453 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
package li.cil.circuity;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.vm.device.memory.ByteBufferMemory;
import li.cil.circuity.vm.riscv.R5Board;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public final class Main {
public static void main(final String[] args) throws Exception {
final PhysicalMemory memory = new ByteBufferMemory(64 * 1014 * 1024);
final R5Board board = new R5Board();
board.addDevice(0x80000000, memory);
final String firmware = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\fw_jump.bin";
loadProgramFile(memory, 0, firmware);
final String kernel = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\Image";
loadProgramFile(memory, 0x400000, kernel);
final UARTReader reader = new UARTReader(board);
final Thread thread = new Thread(reader);
thread.start();
// System.out.println("Waiting for VisualVM...");
// Thread.sleep(10 * 1000);
// System.out.println("Starting!");
final long start = System.currentTimeMillis();
final int n = 100000;
for (int i = 0; i < n; i++) {
board.step(100000);
}
final long duration = System.currentTimeMillis() - start;
System.out.printf("Took: %.2fs\n", duration / 1000.0);
reader.stop();
thread.join();
}
private static void loadProgramFile(final PhysicalMemory memory, int address, final String path) throws Exception {
try (final FileInputStream is = new FileInputStream(path)) {
final BufferedInputStream bis = new BufferedInputStream(is);
for (int value = bis.read(); value != -1; value = bis.read()) {
memory.store8(address++, (byte) value);
}
}
}
private static final class UARTReader implements Runnable {
private final R5Board board;
private boolean keepRunning = true;
private UARTReader(final R5Board board) {
this.board = board;
}
public void stop() {
keepRunning = false;
}
@Override
public void run() {
try {
while (keepRunning) {
final int i = board.readValue();
if (i >= 0) {
System.out.print((char) i);
}
Thread.yield();
}
} catch (final Throwable t) {
t.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,27 @@
package li.cil.circuity.api.vm;
import li.cil.circuity.api.vm.device.InterruptController;
public final class Interrupt {
public int id;
public InterruptController controller;
public Interrupt() {
}
public Interrupt(final int id) {
this.id = id;
}
public void raiseInterrupt() {
if (controller != null) {
controller.raiseInterrupts(1 << id);
}
}
public void lowerInterrupt() {
if (controller != null) {
controller.lowerInterrupts(1 << id);
}
}
}

View File

@@ -0,0 +1,45 @@
package li.cil.circuity.api.vm;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import javax.annotation.Nullable;
import java.util.OptionalInt;
/**
* Represents a physical memory mapping of devices.
*/
public interface MemoryMap {
/**
* Tries to find a vacant memory range of the specified size in the specified ranged.
*
* @param start the minimum starting address for the memory range to find (inclusive).
* @param end the maximum starting address for the memory range to find (inclusive).
* @param size the size of the memory range to find.
* @return the address of a free memory range, if one was found.
*/
OptionalInt findFreeRange(final int start, final int end, final int size);
boolean addDevice(final int address, final MemoryMappedDevice device);
void removeDevice(final MemoryMappedDevice device);
int getDeviceAddress(final MemoryMappedDevice device);
@Nullable
MemoryRange getMemoryRange(final int address);
void setDirty(final MemoryRange range, final int offset);
byte load8(final int address) throws MemoryAccessException;
void store8(final int address, final byte value) throws MemoryAccessException;
short load16(final int address) throws MemoryAccessException;
void store16(final int address, final short value) throws MemoryAccessException;
int load32(final int address) throws MemoryAccessException;
void store32(final int address, final int value) throws MemoryAccessException;
}

View File

@@ -0,0 +1,52 @@
package li.cil.circuity.api.vm;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import java.util.Objects;
public final class MemoryRange {
public final MemoryMappedDevice device;
public final int start, end; // both are inclusive
public MemoryRange(final MemoryMappedDevice device, final int start, final int end) {
if (Integer.compareUnsigned(start, end) > 0) {
throw new IllegalArgumentException();
}
this.device = device;
this.start = start;
this.end = end;
}
public MemoryRange(final MemoryMappedDevice device, final int address) {
this(device, address, address + device.getLength() - 1);
}
public boolean contains(final int address) {
return Integer.compareUnsigned(address, start) >= 0 && Integer.compareUnsigned(address, end) <= 0;
}
public boolean intersects(final MemoryRange other) {
return Integer.compareUnsigned(start, other.end) <= 0 && Integer.compareUnsigned(end, other.start) >= 0;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MemoryRange that = (MemoryRange) o;
return start == that.start &&
end == that.end &&
device.equals(that.device);
}
@Override
public int hashCode() {
return Objects.hash(device, start, end);
}
@Override
public String toString() {
return String.format("%s@[%x-%x]", device, start, end);
}
}

View File

@@ -0,0 +1,4 @@
package li.cil.circuity.api.vm.device;
public interface Device {
}

View File

@@ -0,0 +1,26 @@
package li.cil.circuity.api.vm.device;
/**
* Interrupt controllers expose an API to set and unset some interrupts from the outside.
*/
public interface InterruptController extends Device {
/**
* Mark the interrupts represented by the set bits in the specified mask as active.
* <p>
* In the mask each registered interrupt is represented by a single bit, at the location of its id. So if interrupts
* 1 and 4 are to be made active, the mask would be <code>0b00000101</code>.
*
* @param mask the mask of interrupts to set active.
*/
void raiseInterrupts(final int mask);
/**
* Mark the interrupts represented by the set bits in the specified mask as inactive.
* <p>
* In the mask each registered interrupt is represented by a single bit, at the location of its id. So if interrupts
* 1 and 4 are to be made inactive, the mask would be <code>0b00000101</code>.
*
* @param mask the mask of interrupts to set inactive.
*/
void lowerInterrupts(final int mask);
}

View File

@@ -0,0 +1,7 @@
package li.cil.circuity.api.vm.device;
import li.cil.circuity.api.vm.Interrupt;
public interface InterruptSource extends Device {
Iterable<Interrupt> getInterrupts();
}

View File

@@ -0,0 +1,44 @@
package li.cil.circuity.api.vm.device;
/**
* An interrupter is an interrupt controller that will raise some interrupt based on interrupts
* it received.
* <p>
* Implementations track a list of registered interrupts to allow devices being connected to the
* controller without these devices having to know of each other. Additionally, this list
* of registered interrupt ids shall be persisted to allow devices to reclaim their interrupt
* ids after a load. Implementations may decide to only retain ids for reclaiming for one
* load iteration, i.e. any ids that have not been reclaimed after a load when the next save
* occurs may be freed for regular registration again.
*/
public interface Interrupter extends InterruptController {
/**
* Register a new interrupt.
* <p>
* Use this to reserve a new interrupt with an arbitrary id.
*
* @return the id of the registered interrupt; -1 if no more interrupts can be registered.
*/
int registerInterrupt();
/**
* Register an interrupt with a specific id.
* <p>
* Use this to reclaim an interrupt ID after state has been restored for example, i.e. a savegame has been loaded.
*
* @param id the id to reclaim.
* @return <code>true</code> if this id had not yet been claimed; <code>false</code> otherwise.
*/
boolean registerInterrupt(final int id);
/**
* Release an interrupt id.
* <p>
* This can be used to return the lease on an interrupt id to the controller so it can be handed out in future
* {@link #registerInterrupt()} calls. Typically this should be called by devices that have been disconnected
* from the controller for any reason.
*
* @param id the id to return to the pool of available ids.
*/
void releaseInterrupt(final int id);
}

View File

@@ -0,0 +1,5 @@
package li.cil.circuity.api.vm.device;
public interface Resettable {
void reset();
}

View File

@@ -0,0 +1,5 @@
package li.cil.circuity.api.vm.device;
public interface Steppable extends Device {
void step(final int cycles);
}

View File

@@ -0,0 +1,13 @@
package li.cil.circuity.api.vm.device.memory;
public class MemoryAccessException extends Exception {
private final int address;
public MemoryAccessException(final int address) {
this.address = address;
}
public int getAddress() {
return address;
}
}

View File

@@ -0,0 +1,43 @@
package li.cil.circuity.api.vm.device.memory;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* {@link MemoryMappedDevice}s can be registered with a {@link MemoryMap}
* so that they can be accessed via a memory range using the same mechanisms used for accessing RAM.
*/
public interface MemoryMappedDevice extends Device {
Logger LOGGER = LogManager.getLogger();
int getLength();
default byte load8(final int offset) throws MemoryAccessException {
LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset);
return 0;
}
default void store8(final int offset, final byte value) throws MemoryAccessException {
LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset);
}
default short load16(final int offset) throws MemoryAccessException {
LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset);
return 0;
}
default void store16(final int offset, final short value) throws MemoryAccessException {
LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset);
}
default int load32(final int offset) throws MemoryAccessException {
LOGGER.debug("Unsupported device read access in [{}] at [{}].", getClass().getSimpleName(), offset);
return 0;
}
default void store32(final int offset, final int value) throws MemoryAccessException {
LOGGER.debug("Unsupported device write access in [{}] at [{}].", getClass().getSimpleName(), offset);
}
}

View File

@@ -0,0 +1,11 @@
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

@@ -0,0 +1,12 @@
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.
*/
public interface PhysicalMemory extends MemoryMappedDevice {
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.api.vm.device.rtc;
import li.cil.circuity.api.vm.device.Device;
public interface RealTimeCounter extends Device {
long getTime();
int getFrequency();
}

View File

@@ -0,0 +1,27 @@
package li.cil.circuity.api.vm.device.rtc;
public final class SystemTimeRealTimeCounter implements RealTimeCounter {
private static final SystemTimeRealTimeCounter INSTANCE = new SystemTimeRealTimeCounter();
private static final int NANOSECONDS_PER_SECOND = 1_000_000_000;
private static final int FREQUENCY = 1000; // 10_000_000
public static RealTimeCounter create() {
return INSTANCE;
}
@Override
public long getTime() {
return System.currentTimeMillis();
// final long milliseconds = System.currentTimeMillis();
// final long seconds = milliseconds / 1000;
// final long nanoseconds = System.nanoTime() % NANOSECONDS_PER_SECOND;
//
// return seconds * FREQUENCY + (nanoseconds / (NANOSECONDS_PER_SECOND / FREQUENCY));
}
@Override
public int getFrequency() {
return FREQUENCY;
}
}

View File

@@ -0,0 +1,107 @@
package li.cil.circuity.api.vm.devicetree;
import li.cil.circuity.vm.devicetree.DeviceTreeRegistry;
/**
* List of commonly used @{@link DeviceTreeRegistry} node names.
* <p>Sourced Devicetree Specification 0.3, available at https://github.com/devicetree-org/devicetree-specification/releases</p>
*/
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public final class DeviceNames {
public static final String ADC = "adc";
public static final String ACCELEROMETER = "accelerometer";
public static final String ATM = "atm";
public static final String AUDIO_CODEC = "audio-codec";
public static final String AUDIO_CONTROLLER = "audio-controller";
public static final String BACKLIGHT = "backlight";
public static final String BLUETOOTH = "bluetooth";
public static final String BUS = "bus";
public static final String CACHE_CONTROLLER = "cache-controller";
public static final String CAMERA = "camera";
public static final String CAN = "can";
public static final String CHARGER = "charger";
public static final String CLOCK = "clock";
public static final String CLOCK_CONTROLLER = "clock-controller";
public static final String COMPACT_FLASH = "compact-flash";
public static final String CPU = "cpu";
public static final String CPUS = "cpus";
public static final String CRYPTO = "crypto";
public static final String DISK = "disk";
public static final String DISPLAY = "display";
public static final String DMA_CONTROLLER = "dma-controller";
public static final String DSI = "dsi";
public static final String DSP = "dsp";
public static final String EEPROM = "eeprom";
public static final String EFUSE = "efuse";
public static final String ENDPOINT = "endpoint";
public static final String ETHERNET = "ethernet";
public static final String ETHERNET_PHY = "ethernet-phy";
public static final String FDC = "fdc";
public static final String FLASH = "flash";
public static final String GNSS = "gnss";
public static final String GPIO = "gpio";
public static final String GPU = "gpu";
public static final String GYROMETER = "gyrometer";
public static final String HDMI = "hdmi";
public static final String HWLOCK = "hwlock";
public static final String I2C = "i2c";
public static final String I2C_MUX = "i2c-mux";
public static final String IDE = "ide";
public static final String INTERRUPT_CONTROLLER = "interrupt-controller";
public static final String IOMMU = "iommu";
public static final String ISA = "isa";
public static final String KEYBOARD = "keyboard";
public static final String KEY = "key";
public static final String KEYS = "keys";
public static final String LCD_CONTROLLER = "lcd-controller";
public static final String LED = "led";
public static final String LEDS = "leds";
public static final String LED_CONTROLLER = "led-controller";
public static final String LIGHT_SENSOR = "light-sensor";
public static final String MAGNETOMETER = "magnetometer";
public static final String MAILBOX = "mailbox";
public static final String MDIO = "mdio";
public static final String MEMORY = "memory";
public static final String MEMORY_CONTROLLER = "memory-controller";
public static final String MMC = "mmc";
public static final String MMC_SLOT = "mmc-slot";
public static final String MOUSE = "mouse";
public static final String NAND_CONTROLLER = "nand-controller";
public static final String NVRAM = "nvram";
public static final String OSCILLATOR = "oscillator";
public static final String PARALLEL = "parallel";
public static final String PC_CARD = "pc-card";
public static final String PCI = "pci";
public static final String PCIE = "pcie";
public static final String PHY = "phy";
public static final String PINCTRL = "pinctrl";
public static final String PMIC = "pmic";
public static final String PMU = "pmu";
public static final String PORT = "port";
public static final String PORTS = "ports";
public static final String POWER_MONITOR = "power-monitor";
public static final String PWM = "pwm";
public static final String REGULATOR = "regulator";
public static final String RESET_CONTROLLER = "reset-controller";
public static final String RNG = "rng";
public static final String RTC = "rtc";
public static final String SATA = "sata";
public static final String SCSI = "scsi";
public static final String SERIAL = "serial";
public static final String SOUND = "sound";
public static final String SPI = "spi";
public static final String SRAM_CONTROLLER = "sram-controller";
public static final String SSI_CONTROLLER = "ssi-controller";
public static final String SYSCON = "syscon";
public static final String TEMPERATURE_SENSOR = "temperature-sensor";
public static final String TIMER = "timer";
public static final String TOUCHSCREEN = "touchscreen";
public static final String TPM = "tpm";
public static final String USB = "usb";
public static final String USB_HUB = "usb-hub";
public static final String USB_PHY = "usb-phy";
public static final String VIDEO_CODEC = "video-codec";
public static final String VME = "vme";
public static final String WATCHDOG = "watchdog";
public static final String WIFI = "wifi";
}

View File

@@ -0,0 +1,42 @@
package li.cil.circuity.api.vm.devicetree;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.vm.devicetree.FlattenedDeviceTree;
import javax.annotation.Nullable;
import java.util.function.Consumer;
public interface DeviceTree {
FlattenedDeviceTree flatten();
int createPHandle(final Device device);
int getPHandle(final Device device);
DeviceTree find(final String path);
DeviceTree addProp(final String name, final Object... values);
DeviceTree getChild(final String name, @Nullable final String address);
default DeviceTree getChild(final String name, final int address) {
return getChild(name, String.format("%x", address));
}
default DeviceTree getChild(final String name) {
return getChild(name, null);
}
default DeviceTree putChild(final String name, @Nullable final String address, final Consumer<DeviceTree> builder) {
builder.accept(getChild(name, address));
return this;
}
default DeviceTree putChild(final String name, final int address, final Consumer<DeviceTree> builder) {
return putChild(name, String.format("%x", address), builder);
}
default DeviceTree putChild(final String name, final Consumer<DeviceTree> builder) {
return putChild(name, null, builder);
}
}

View File

@@ -0,0 +1,31 @@
package li.cil.circuity.api.vm.devicetree;
import li.cil.circuity.vm.devicetree.DeviceTreeRegistry;
/**
* <p>List of well-known device tree node properties.</p>
* <p>Sourced Devicetree Specification 0.3, available at https://github.com/devicetree-org/devicetree-specification/releases</p>
*
* @see DeviceTreeRegistry
*/
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public final class DeviceTreePropertyNames {
public static final String COMPATIBLE = "compatible";
public static final String MODEL = "model";
public static final String PHANDLE = "phandle";
public static final String STATUS = "status";
public static final String NUM_ADDRESS_CELLS = "#address-cells";
public static final String NUM_SIZE_CELLS = "#size-cells";
public static final String REG = "reg";
public static final String VIRTUAL_REG = "virtual-reg";
public static final String RANGES = "ranges";
public static final String DMA_RANGES = "dma-ranges";
public static final String DEVICE_TYPE = "device_type";
public static final String INTERRUPTS = "interrupts";
public static final String INTERRUPT_PARENT = "interrupt-parent";
public static final String INTERRUPTS_EXTENDED = "interrupts-extended";
public static final String NUM_INTERRUPT_CELLS = "#interrupt-cells";
public static final String INTERRUPT_CONTROLLER = "interrupt-controller";
public static final String INTERRUPT_MAP = "interrupt-map";
public static final String INTERRUPT_MAP_MASK = "interrupt-map-mask";
}

View File

@@ -0,0 +1,18 @@
package li.cil.circuity.api.vm.devicetree;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import java.util.Optional;
public interface DeviceTreeProvider {
default Optional<String> getName(final Device device) {
return Optional.empty();
}
default Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
return Optional.empty();
}
void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device);
}

View File

@@ -0,0 +1,150 @@
package li.cil.circuity.vm;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.MemoryRange;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalInt;
public final class SimpleMemoryMap implements MemoryMap {
private final Map<MemoryMappedDevice, MemoryRange> devices = new HashMap<>();
// For device IO we often get sequential access to the same range/device, so we remember the last one as a cache.
private MemoryRange cache;
@Override
public OptionalInt findFreeRange(final int start, final int end, final int size) {
if (size == 0) {
return OptionalInt.empty();
}
if (Integer.compareUnsigned(end, start) < 0) {
return OptionalInt.empty();
}
if (Integer.compareUnsigned(end - start, size - 1) < 0) {
return OptionalInt.empty();
}
if (Integer.toUnsignedLong(start) + Integer.toUnsignedLong(size) >= 0xFFFFFFFFL) {
return OptionalInt.empty();
}
final MemoryRange candidateRange = new MemoryRange(null, start, start + size);
for (final MemoryRange existingRange : devices.values()) {
if (existingRange.intersects(candidateRange)) {
if (existingRange.end != 0xFFFFFFFF) { // Avoid overflow.
return findFreeRange(existingRange.end + 1, end, size);
} else {
return OptionalInt.empty();
}
}
}
return OptionalInt.of(start);
}
@Override
public boolean addDevice(final int address, final MemoryMappedDevice device) {
final MemoryRange deviceRange = new MemoryRange(device, address);
if (devices.values().stream().anyMatch(range -> range.intersects(deviceRange))) {
return false;
}
devices.put(device, deviceRange);
return true;
}
@Override
public void removeDevice(final MemoryMappedDevice device) {
devices.remove(device);
cache = null;
}
@Override
public int getDeviceAddress(final MemoryMappedDevice device) {
final MemoryRange range = devices.get(device);
if (range != null) {
return range.start;
} else {
return -1;
}
}
@Nullable
@Override
public MemoryRange getMemoryRange(final int address) {
// TODO some proper index structure to speed this up?
if (cache != null && cache.contains(address)) {
return cache;
}
for (final MemoryRange range : devices.values()) {
if (range.contains(address)) {
cache = range;
return range;
}
}
return null;
}
@Override
public void setDirty(final MemoryRange range, final int offset) {
// todo implement tracking dirty bits; really need this if we want to add a frame buffer.
}
@Override
public byte load8(final int address) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
return range.device.load8(address - range.start);
}
return 0;
}
@Override
public void store8(final int address, final byte value) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
range.device.store8(address - range.start, value);
}
}
@Override
public short load16(final int address) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
return range.device.load16(address - range.start);
}
return 0;
}
@Override
public void store16(final int address, final short value) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
range.device.store16(address - range.start, value);
}
}
@Override
public int load32(final int address) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
return range.device.load32(address - range.start);
}
return 0;
}
@Override
public void store32(final int address, final int value) throws MemoryAccessException {
final MemoryRange range = getMemoryRange(address);
if (range != null) {
range.device.store32(address - range.start, value);
}
}
}

View File

@@ -0,0 +1,14 @@
package li.cil.circuity.vm.components;
public final class InterruptSourceRegistry {
public int registerInterrupt() {
return -1;
}
public boolean registerInterrupt(final int id) {
return false;
}
public void releaseInterrupt(final int id) {
}
}

View File

@@ -0,0 +1,463 @@
package li.cil.circuity.vm.device;
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
import it.unimi.dsi.fastutil.bytes.BytePriorityQueue;
import li.cil.circuity.api.vm.Interrupt;
import li.cil.circuity.api.vm.device.InterruptSource;
import li.cil.circuity.api.vm.device.Resettable;
import li.cil.circuity.api.vm.device.Steppable;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import java.util.Collections;
// TODO Break implementation
@SuppressWarnings("PointlessBitwiseExpression")
public final class UART16550A implements Resettable, Steppable, MemoryMappedDevice, InterruptSource {
private static final int UART_RBR_OFFSET = 0; // Receive buffer register (Read-only)
private static final int UART_THR_OFFSET = 0; // Transmitter holding register (Write-only)
private static final int UART_IER_OFFSET = 1; // Interrupt enable register (Read-write)
private static final int UART_FCR_OFFSET = 2; // FIFO control register (Write-only)
private static final int UART_IIR_OFFSET = 2; // Interrupt identification register (Read-write)
private static final int UART_LCR_OFFSET = 3; // Line control register (Read-write)
private static final int UART_MCR_OFFSET = 4; // Modem control register (Read-write)
private static final int UART_LSR_OFFSET = 5; // Line status register (Read-only)
private static final int UART_MSR_OFFSET = 6; // Modem status register (Read-only)
private static final int UART_SCR_OFFSET = 7; // Scratch register (Read-only)
private static final int UART_DLL_OFFSET = 0; // Divisor latch register (least significant byte) (Read-write)
private static final int UART_DLM_OFFSET = 1; // Divisor latch register (most significant byte) (Read-write)
private static final int UART_IER_RDI = 0b0001; // Received data available
private static final int UART_IER_THRI = 0b0010; // Transmitter holding register empty
private static final int UART_IER_RLSI = 0b0100; // Receiver line status register change
private static final int UART_IER_MSI = 0b1000; // Modem status register change
private static final int UART_IIR_NO_INTERRUPT = 0b00000001; // Any interrupt pending bit
private static final int UART_IIR_ID_MASK = 0b00001110; // Last interrupt type mask
private static final int UART_IIR_MSI = 0x0000; // Modem status change
private static final int UART_IIR_THRI = 0b0010; // Transmitter holding register empty
private static final int UART_IIR_RDI = 0b0100; // Received data available
private static final int UART_IIR_RLSI = 0b0110; // Line status change
private static final int UART_IIR_CTI = 0b1100; // Character timeout
private static final int UART_IIR_NO_FIFO = 0b00000000; // No FIFO
private static final int UART_IIR_UNUSABLE_FIFO = 0b10000000; // Unusable FIFO
private static final int UART_IIR_FIFO_ENABLED = 0b11000000; // FIFO enabled
private static final int UART_LCR_WORD_LENGTH_8 = 0b00000011; // Data word length = 8
private static final int UART_LCR_STOP_BITS = 0b00000100; // 0 = 1 stop bit, 1 = 1.5 stop bits (5 bits word) / 2 stop bits (7, 7 or 8 bits word)
private static final int UART_LCR_ODD_PARITY = 0b00001000; // Odd parity
private static final int UART_LCR_EVEN_PARITY = 0b00011000; // Even parity
private static final int UART_LCR_HIGH_PARITY = 0b00101000; // High parity (stick)
private static final int UART_LCR_LOW_PARITY = 0b00111000; // Low parity (stick)
private static final int UART_LCR_BREAK_SIGNAL = 0b01000000; // Break signal enabled
private static final int UART_LCR_DLAB = 0b10000000; // Divisor latch access bit
private static final int UART_LCR_MUTABLE_BITS_MASK = UART_LCR_DLAB; // We only support a few settings...
private static final int UART_MCR_DTR = 1 << 0; // Data terminal ready
private static final int UART_MCR_RTS = 1 << 1; // Request to send
private static final int UART_MCR_AO1 = 1 << 2; // Auxiliary output 1
private static final int UART_MCR_AO2 = 1 << 3; // Auxiliary output 2
private static final int UART_MCR_LBM = 1 << 4; // Loopback mode
private static final int UART_LSR_DR = 1 << 0; // Receiver data ready
private static final int UART_LSR_OE = 1 << 1; // Overrun error indicator
private static final int UART_LSR_PE = 1 << 2; // Parity error indicator
private static final int UART_LSR_FE = 1 << 3; // Frame error indicator
private static final int UART_LSR_BI = 1 << 4; // Break interrupt indicator
private static final int UART_LSR_THRE = 1 << 5; // Transmit-hold-register empty
private static final int UART_LSR_TEMT = 1 << 6; // Transmitter empty
private static final int UART_LSR_FIFOE = 1 << 7; // Fifo error
private static final int UART_LSR_IRQ_MASK = UART_LSR_BI | UART_LSR_FE | UART_LSR_PE | UART_LSR_OE;
private static final int UART_MSR_DCTS = 1 << 0; // Change in Clear to send
private static final int UART_MSR_DDSR = 1 << 1; // Change in Data set ready
private static final int UART_MSR_TERI = 1 << 2; // Trailing edge Ring indicator
private static final int UART_MSR_DDCD = 1 << 3; // Change in Carrier detect
private static final int UART_MSR_CTS = 1 << 4; // Clear to send
private static final int UART_MSR_DSR = 1 << 5; // Data set ready
private static final int UART_MSR_RI = 1 << 6; // Ring indicator
private static final int UART_MSR_DCD = 1 << 7; // Carrier detect
private static final int UART_MSR_DIRTY = UART_MSR_DCTS | UART_MSR_DDSR | UART_MSR_TERI | UART_MSR_DDCD;
private static final int UART_FCR_FE = 1 << 0; // Enable/disable FIFOs
private static final int UART_FCR_RFR = 1 << 1; // Clear receive FIFO
private static final int UART_FCR_XFR = 1 << 2; // Clear transmit FIFO
private static final int UART_FCR_DMS = 1 << 3; // Select DMA mode
private static final int UART_FCR_ITL_MASK = 0b11000000; // Receive FIFO interrupt trigger level mask
private static final int UART_FCR_ITL1 = 0b00000000; // Receive FIFO interrupt trigger at 1 byte
private static final int UART_FCR_ITL2 = 0b01000000; // Receive FIFO interrupt trigger at 4 bytes
private static final int UART_FCR_ITL3 = 0b10000000; // Receive FIFO interrupt trigger at 8 bytes
private static final int UART_FCR_ITL4 = 0b11000000; // Receive FIFO interrupt trigger at 14 bytes
private static final int UART_DL_2304 = 0x0900; // 50 Baud
private static final int UART_DL_384 = 0x0180; // 300 Baud
private static final int UART_DL_96 = 0x0060; // 1200 Baud
private static final int UART_DL_48 = 0x0030; // 2400 Baud
private static final int UART_DL_24 = 0x0018; // 4800 Baud
private static final int UART_DL_12 = 0x000C; // 9600 Baud
private static final int UART_DL_6 = 0x0006; // 19200 Baud
private static final int UART_DL_3 = 0x0003; // 38400 Baud
private static final int UART_DL_2 = 0x0002; // 57600 Baud
private static final int UART_DL_1 = 0x0001; // 115200 Baud
private static final int FIFO_QUEUE_CAPACITY = 16;
private byte rbr;
private byte thr;
private byte ier;
private byte iir;
private byte fcr;
private byte lcr;
private byte mcr;
private byte lsr;
private byte msr;
private byte scr;
private short dl;
private int triggerLevel;
private final BytePriorityQueue receiveFifo = new ByteArrayFIFOQueue(FIFO_QUEUE_CAPACITY);
private final BytePriorityQueue transmitFifo = new ByteArrayFIFOQueue(FIFO_QUEUE_CAPACITY);
private boolean interruptUpdatePending;
private boolean transmitInterruptPending;
private boolean timeoutInterruptPending;
private final Interrupt interrupt = new Interrupt();
private final Object lock = new Object();
public UART16550A() {
reset();
}
public Interrupt getInterrupt() {
return interrupt;
}
public void putByte(final byte value) {
synchronized (lock) {
if ((fcr & UART_FCR_FE) != 0) {
if (receiveFifo.size() < FIFO_QUEUE_CAPACITY) {
receiveFifo.enqueue(value);
} else {
lsr |= UART_LSR_OE;
}
lsr |= UART_LSR_DR;
} else {
if ((lsr & UART_LSR_DR) != 0) {
lsr |= UART_LSR_OE;
}
rbr = value;
lsr |= UART_LSR_DR;
}
interruptUpdatePending = true;
}
}
public int getByte() {
synchronized (lock) {
if ((lsr & UART_LSR_TEMT) != 0) {
return -1;
}
final byte value;
if ((fcr & UART_FCR_FE) != 0) {
value = transmitFifo.dequeueByte();
if (transmitFifo.isEmpty()) {
lsr |= UART_LSR_THRE | UART_LSR_TEMT;
}
} else {
value = thr;
lsr |= UART_LSR_THRE | UART_LSR_TEMT;
}
if ((lsr & UART_LSR_THRE) != 0 && !transmitInterruptPending) {
transmitInterruptPending = true;
interruptUpdatePending = true;
}
return value;
}
}
@Override
public void reset() {
rbr = 0;
thr = 0;
ier = 0;
iir = (byte) UART_IIR_NO_INTERRUPT;
fcr = 0;
lcr = 0;
mcr = (byte) UART_MCR_AO2;
lsr = (byte) (UART_LSR_THRE | UART_LSR_TEMT);
msr = (byte) (UART_MSR_CTS | UART_MSR_DCD | UART_MSR_DSR);
scr = 0;
dl = UART_DL_12;
triggerLevel = 1;
receiveFifo.clear();
transmitFifo.clear();
interruptUpdatePending = false;
transmitInterruptPending = false;
timeoutInterruptPending = false;
interrupt.lowerInterrupt();
}
@Override
public void step(final int cycles) {
if (interruptUpdatePending) {
updateInterrupts();
}
// TODO set timeout interrupt after 4 char transmit times
}
@Override
public int getLength() {
return 0x100;
}
@Override
public byte load8(final int offset) {
switch (offset) {
// case UART_DLL_OFFSET:
case UART_RBR_OFFSET: {
if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLL
return (byte) dl;
} else { // UART_RBR
final byte result;
synchronized (lock) {
if ((fcr & UART_FCR_FE) != 0) { // FIFO enabled
result = receiveFifo.isEmpty() ? 0 : receiveFifo.dequeueByte();
if (receiveFifo.isEmpty()) {
lsr &= ~(UART_LSR_DR | UART_LSR_BI);
}
timeoutInterruptPending = false;
} else { // No FIFO
result = rbr;
lsr &= ~(UART_LSR_DR | UART_LSR_BI);
}
updateInterrupts();
}
if ((mcr & UART_MCR_LBM) == 0) {
// TODO Fire event that input was accepted?
}
return result;
}
}
// case UART_DLM_OFFSET:
case UART_IER_OFFSET: {
if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLM
return (byte) (dl >>> 8);
} else { // UART_IER
return ier;
}
}
case UART_IIR_OFFSET: {
synchronized (lock) {
final byte result = iir;
if ((iir & UART_IIR_ID_MASK) == UART_IIR_THRI) {
transmitInterruptPending = false;
updateInterrupts();
}
return result;
}
}
case UART_LCR_OFFSET: {
return lcr;
}
case UART_MCR_OFFSET: {
return mcr;
}
case UART_LSR_OFFSET: {
synchronized (lock) {
final byte result = lsr;
if ((lsr & (UART_LSR_BI | UART_LSR_OE)) != 0) {
lsr &= ~(UART_LSR_BI | UART_LSR_OE);
updateInterrupts();
}
return result;
}
}
case UART_MSR_OFFSET: {
if ((mcr & UART_MCR_LBM) != 0) {
// Loopback mode -> output = input
return (byte) ((mcr & 0b1100) << 4 | // OUT [3:2] -> [7:6]
(mcr & 0b0010) << 3 | // RTS [1] -> [4]
(mcr & 0b0001) << 5); // DTR [0] -> [5]
} else {
synchronized (lock) {
final byte result = msr;
if ((msr & UART_MSR_DIRTY) != 0) {
msr &= ~UART_MSR_DIRTY;
updateInterrupts();
}
return result;
}
}
}
case UART_SCR_OFFSET: {
return scr;
}
}
return 0;
}
@Override
public void store8(final int offset, final byte value) {
switch (offset) {
// case UART_DLL_OFFSET:
case UART_THR_OFFSET: {
if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLL
dl = (short) ((dl & 0xFF00) | (value & 0x00FF));
} else { // UART_RBR
synchronized (lock) {
thr = value;
if ((fcr & UART_FCR_FE) != 0) {
if (transmitFifo.size() >= FIFO_QUEUE_CAPACITY) {
transmitFifo.dequeueByte();
}
transmitFifo.enqueue(thr);
}
transmitInterruptPending = false;
lsr &= ~(UART_LSR_THRE | UART_LSR_TEMT);
updateInterrupts();
}
}
break;
}
// case UART_DLM_OFFSET:
case UART_IER_OFFSET: {
if ((lcr & UART_LCR_DLAB) != 0) { // UART_DLM
dl = (short) ((value << 8) | (dl & 0x00FF));
} else { // UART_IER
synchronized (lock) {
final int changes = ier ^ value;
ier = (byte) (value & 0b1111);
if ((changes & UART_IER_THRI) != 0) {
transmitInterruptPending = (ier & UART_IER_THRI) != 0 && (lsr & UART_LSR_THRE) != 0;
}
if (changes != 0) {
updateInterrupts();
}
}
}
break;
}
case UART_FCR_OFFSET: {
synchronized (lock) {
final int changes = fcr ^ value;
final boolean forceClear = (changes & UART_FCR_FE) != 0;
if (forceClear || ((int) value & UART_FCR_RFR) != 0) {
synchronized (receiveFifo) {
lsr &= ~(UART_LSR_DR | UART_LSR_BI);
timeoutInterruptPending = false;
receiveFifo.clear();
}
}
if (forceClear || ((int) value & UART_FCR_XFR) != 0) {
synchronized (transmitFifo) {
lsr |= UART_LSR_THRE;
transmitInterruptPending = true;
transmitFifo.clear();
}
}
fcr = (byte) ((int) value & 0b11001001);
if (((int) value & UART_FCR_FE) != 0) {
iir |= UART_IIR_FIFO_ENABLED;
switch ((int) value & UART_FCR_ITL_MASK) {
case UART_FCR_ITL1: {
triggerLevel = 1;
break;
}
case UART_FCR_ITL2: {
triggerLevel = 4;
break;
}
case UART_FCR_ITL3: {
triggerLevel = 8;
break;
}
case UART_FCR_ITL4: {
triggerLevel = 14;
break;
}
}
} else {
iir &= ~UART_IIR_FIFO_ENABLED;
}
updateInterrupts();
}
break;
}
case UART_LCR_OFFSET: {
lcr = value;
break;
}
case UART_MCR_OFFSET: {
mcr = (byte) (value & 0b11111);
break;
}
case UART_SCR_OFFSET: {
scr = value;
break;
}
}
}
@Override
public Iterable<Interrupt> getInterrupts() {
return Collections.singleton(interrupt);
}
private void updateInterrupts() {
final int niir;
if ((ier & UART_IER_RLSI) != 0 && (lsr & UART_LSR_IRQ_MASK) != 0) {
niir = UART_IIR_RLSI;
} else if ((ier & UART_IER_RDI) != 0 && timeoutInterruptPending) {
niir = UART_IIR_CTI;
} else if ((ier & UART_IER_RDI) != 0 && (lsr & UART_LSR_DR) != 0 &&
((fcr & UART_FCR_FE) == 0 || receiveFifo.size() > triggerLevel)) {
niir = UART_IIR_RDI;
} else if ((ier & UART_IER_THRI) != 0 && transmitInterruptPending) {
niir = UART_IIR_THRI;
} else if ((ier & UART_IER_MSI) != 0 && (msr & UART_MSR_DIRTY) != 0) {
niir = UART_IIR_MSI;
} else {
niir = UART_IIR_NO_INTERRUPT;
}
iir = (byte) (niir | (iir & ~(UART_IIR_ID_MASK | UART_IIR_NO_INTERRUPT)));
if ((iir & UART_IIR_NO_INTERRUPT) != 0) {
interrupt.lowerInterrupt();
} else {
interrupt.raiseInterrupt();
}
}
}

View File

@@ -0,0 +1,79 @@
package li.cil.circuity.vm.device.memory;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.vm.device.memory.exception.LoadFaultException;
import li.cil.circuity.vm.device.memory.exception.StoreFaultException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ByteBufferMemory implements PhysicalMemory {
private final ByteBuffer data;
public ByteBufferMemory(final int size) {
if ((size & 0b11) != 0)
throw new IllegalArgumentException("size must be a multiple of four");
this.data = ByteBuffer.allocate(size);
data.order(ByteOrder.LITTLE_ENDIAN);
}
@Override
public int getLength() {
return data.capacity();
}
@Override
public byte load8(final int offset) throws MemoryAccessException {
try {
return data.get(offset);
} catch (final IndexOutOfBoundsException e) {
throw new LoadFaultException(offset);
}
}
@Override
public void store8(final int offset, final byte value) throws MemoryAccessException {
try {
data.put(offset, value);
} catch (final IndexOutOfBoundsException e) {
throw new StoreFaultException(offset);
}
}
@Override
public short load16(final int offset) throws MemoryAccessException {
try {
return data.getShort(offset);
} catch (final IndexOutOfBoundsException e) {
throw new LoadFaultException(offset);
}
}
@Override
public void store16(final int offset, final short value) throws MemoryAccessException {
try {
data.putShort(offset, value);
} catch (final IndexOutOfBoundsException e) {
throw new StoreFaultException(offset);
}
}
@Override
public int load32(final int offset) throws MemoryAccessException {
try {
return data.getInt(offset);
} catch (final IndexOutOfBoundsException e) {
throw new LoadFaultException(offset);
}
}
@Override
public void store32(final int offset, final int value) throws MemoryAccessException {
try {
data.putInt(offset, value);
} catch (final IndexOutOfBoundsException e) {
throw new StoreFaultException(offset);
}
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class FetchFaultException extends MemoryAccessException {
public FetchFaultException(final int address) {
super(address);
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class FetchPageFaultException extends MemoryAccessException {
public FetchPageFaultException(final int address) {
super(address);
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class LoadFaultException extends MemoryAccessException {
public LoadFaultException(final int address) {
super(address);
}
}

View File

@@ -0,0 +1,16 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class LoadPageFaultException extends MemoryAccessException {
private final int address;
public LoadPageFaultException(final int address) {
super(address);
this.address = address;
}
public int getAddress() {
return address;
}
}

View File

@@ -0,0 +1,16 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class MisalignedFetchException extends MemoryAccessException {
private final int address;
public MisalignedFetchException(final int address) {
super(address);
this.address = address;
}
public int getAddress() {
return address;
}
}

View File

@@ -0,0 +1,16 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class MisalignedLoadException extends MemoryAccessException {
private final int address;
public MisalignedLoadException(final int address) {
super(address);
this.address = address;
}
public int getAddress() {
return address;
}
}

View File

@@ -0,0 +1,16 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class MisalignedStoreException extends MemoryAccessException {
private final int address;
public MisalignedStoreException(final int address) {
super(address);
this.address = address;
}
public int getAddress() {
return address;
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class StoreFaultException extends MemoryAccessException {
public StoreFaultException(final int address) {
super(address);
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.device.memory.exception;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
public final class StorePageFaultException extends MemoryAccessException {
public StorePageFaultException(final int address) {
super(address);
}
}

View File

@@ -0,0 +1,193 @@
package li.cil.circuity.vm.devicetree;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
final class DeviceTreeImpl implements DeviceTree {
private final DeviceTreeImpl root;
private final Object2IntMap<Device> phandles;
private final IntSet createdPhandles;
private final MemoryMap mmu;
public final String name; // node-name
public final String address; // unit-address
private final List<DeviceTreeProperty> properties = new ArrayList<>();
public final List<DeviceTreeImpl> children = new ArrayList<>();
public DeviceTreeImpl(@Nullable final DeviceTreeImpl parent, final MemoryMap mmu, final String name, final String address) {
this.root = parent != null ? parent.root : this;
this.phandles = parent != null ? parent.phandles : new Object2IntArrayMap<>();
this.createdPhandles = parent != null ? parent.createdPhandles : new IntArraySet();
this.mmu = mmu;
this.name = name == null ? "" : validateName(name);
this.address = address;
}
public DeviceTreeImpl(final MemoryMap mmu) {
this(null, mmu, null, null);
}
@Override
public FlattenedDeviceTree flatten() {
final FlattenedDeviceTree fdt = new FlattenedDeviceTree();
flatten(fdt);
return fdt;
}
@Override
public int createPHandle(final Device device) {
final int phandle = getPHandle(device);
createdPhandles.add(phandle);
return phandle;
}
@Override
public int getPHandle(final Device device) {
return phandles.computeIntIfAbsent(device, d -> phandles.size() + 1);
}
@Override
public DeviceTree find(final String path) {
if (path.isEmpty()) {
return this;
}
final int splitIndex = path.indexOf('/');
if (splitIndex == 0) {
return root.find(path.substring(1));
} else {
if (splitIndex > 0) {
final String childName = path.substring(0, splitIndex);
final String pathTail = path.substring(splitIndex + 1);
return getChild(childName).find(pathTail);
} else {
return getChild(path);
}
}
}
@Override
public DeviceTree addProp(final String name, final Object... values) {
properties.add(new DeviceTreeProperty(name, values));
return this;
}
@Override
public DeviceTree putChild(final String name, final String address, final Consumer<DeviceTree> builder) {
builder.accept(getChild(name, address));
return this;
}
@Override
public DeviceTree getChild(final String name, @Nullable final String address) {
final String fullName = fullName(name, address);
for (final DeviceTreeImpl child : children) {
if (fullName.equals(child.fullName())) {
return child;
}
}
final DeviceTreeImpl child = new DeviceTreeImpl(this, mmu, name, address);
children.add(child);
return child;
}
private void flatten(final FlattenedDeviceTree fdt) {
if (address == null) {
fdt.beginNode(name);
} else {
fdt.beginNode(name + "@" + address);
}
for (final DeviceTreeProperty property : properties) {
property.flatten(fdt);
}
for (final DeviceTree child : children) {
if (child instanceof DeviceTreeImpl) {
final DeviceTreeImpl childBuilder = (DeviceTreeImpl) child;
childBuilder.flatten(fdt);
}
}
fdt.endNode();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
toString(sb, 0);
return sb.toString();
}
private void toString(final StringBuilder sb, final int indent) {
appendIndent(sb, indent).append(name);
if (address != null) {
sb.append('@').append(address);
}
sb.append(" {\n");
for (final DeviceTreeProperty property : properties) {
appendIndent(sb, indent + 1).append(property.toString()).append('\n');
}
for (final DeviceTree child : children) {
if (child instanceof DeviceTreeImpl) {
final DeviceTreeImpl childBuilder = (DeviceTreeImpl) child;
childBuilder.toString(sb, indent + 1);
} else {
appendIndent(sb, indent + 1).append(child.toString()).append('\n');
}
}
appendIndent(sb, indent).append("}\n");
}
private static StringBuilder appendIndent(final StringBuilder sb, final int indent) {
for (int i = 0; i < indent * 4; i++) {
sb.append(' ');
}
return sb;
}
private String fullName() {
return fullName(name, address);
}
private static String fullName(final String name, @Nullable final String address) {
return address != null ? String.format("%s@%s", name, address) : name;
}
private static String validateName(final String value) {
if (value.length() < 1)
throw new IllegalArgumentException("name too short (<1)");
if (value.length() > 31)
throw new IllegalArgumentException("name too long (>31)");
for (int i = 0; i < value.length(); i++) {
final char ch = value.charAt(i);
if (!isValidCharacterForNodeName(ch)) {
throw new IllegalArgumentException("invalid character [" + ch + "] in name [" + value + "]");
}
}
return value;
}
private static boolean isValidCharacterForNodeName(final int ch) {
return (ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
ch == ',' || ch == '.' ||
ch == '_' || ch == '+' ||
ch == '-';
}
}

View File

@@ -0,0 +1,96 @@
package li.cil.circuity.vm.devicetree;
public final class DeviceTreeProperty {
private final String name;
private final Object[] values;
public DeviceTreeProperty(final String name, final Object... values) {
this.name = validateName(name);
this.values = values;
for (final Object value : values) {
if (!(value instanceof String ||
value instanceof Integer ||
value instanceof Long)) {
throw new IllegalArgumentException();
}
}
}
public void flatten(final FlattenedDeviceTree fdt) {
fdt.property(name, values);
}
@Override
public String toString() {
if (values == null || values.length == 0) {
return name + ";";
} else {
final StringBuilder sb = new StringBuilder();
sb.append(name).append(" = ");
boolean isFirst = true;
boolean previousWasNumber = false;
for (final Object current : values) {
final boolean currentIsNumber = current instanceof Number;
if (previousWasNumber && !currentIsNumber) {
sb.append('>');
}
if (!isFirst)
if (!previousWasNumber || !currentIsNumber) {
sb.append(", ");
} else {
sb.append(" ");
}
if (currentIsNumber && !previousWasNumber) {
sb.append('<');
}
isFirst = false;
if (current instanceof String) {
sb.append('"').append((String) current).append('"');
} else if (current instanceof Integer) {
sb.append("0x").append(Integer.toUnsignedString((int) current, 16));
} else if (current instanceof Long) {
sb.append("0x").append(Integer.toUnsignedString((int) (((long) current) >>> 32), 16));
sb.append("0x").append(Integer.toUnsignedString((int) ((long) current), 16));
} else {
throw new AssertionError();
}
previousWasNumber = currentIsNumber;
}
if (previousWasNumber) {
sb.append('>');
}
sb.append(";");
return sb.toString();
}
}
private static String validateName(final String value) {
if (value.length() < 1)
throw new IllegalArgumentException("name too short (<1)");
if (value.length() > 31)
throw new IllegalArgumentException("name too long (>31)");
for (int i = 0; i < value.length(); i++) {
final char ch = value.charAt(i);
if (!isValidCharacterForPropertyName(ch)) {
throw new IllegalArgumentException("invalid character [" + ch + "] in name [" + value + "]");
}
}
return value;
}
private static boolean isValidCharacterForPropertyName(final int ch) {
return (ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
ch == ',' || ch == '.' ||
ch == '_' || ch == '+' ||
ch == '-' || ch == '?' ||
ch == '#';
}
}

View File

@@ -0,0 +1,144 @@
package li.cil.circuity.vm.devicetree;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.device.InterruptSource;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import li.cil.circuity.vm.device.UART16550A;
import li.cil.circuity.vm.devicetree.provider.*;
import li.cil.circuity.vm.riscv.device.R5CoreLocalInterrupter;
import li.cil.circuity.vm.riscv.device.R5HostTargetInterface;
import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Consumer;
public final class DeviceTreeRegistry {
private static final Logger LOGGER = LogManager.getLogger();
private static final Map<Class<? extends Device>, DeviceTreeProvider> providers = new HashMap<>();
private static final Map<Class<? extends Device>, DeviceTreeProvider> providerCache = new HashMap<>();
static {
addProvider(MemoryMappedDevice.class, MemoryMappedDeviceProvider.INSTANCE);
addProvider(InterruptSource.class, InterruptSourceProvider.INSTANCE);
addProvider(PhysicalMemory.class, PhysicalMemoryProvider.INSTANCE);
addProvider(R5HostTargetInterface.class, HostTargetInterfaceProvider.INSTANCE);
addProvider(R5PlatformLevelInterruptController.class, PlatformLevelInterruptControllerProvider.INSTANCE);
addProvider(R5CoreLocalInterrupter.class, CoreLocalInterrupterProvider.INSTANCE);
addProvider(UART16550A.class, UART16550AProvider.INSTANCE);
// VirtIOProvider.INSTANCE
}
public static void addProvider(final Class<? extends Device> clazz, final DeviceTreeProvider provider) {
providers.put(clazz, provider);
providerCache.clear();
}
private static void visitBaseTypes(@Nullable final Class<?> clazz, final Consumer<Class<?>> visitor) {
if (clazz == null) {
return;
}
visitor.accept(clazz);
visitBaseTypes(clazz.getSuperclass(), visitor);
final Class<?>[] interfaces = clazz.getInterfaces();
for (final Class<?> iface : interfaces) {
visitor.accept(iface);
visitBaseTypes(iface, visitor);
}
}
@Nullable
public static DeviceTreeProvider getProvider(final Device device) {
final Class<? extends Device> deviceClass = device.getClass();
if (providerCache.containsKey(deviceClass)) {
return providerCache.get(deviceClass);
}
final List<DeviceTreeProvider> relevant = new ArrayList<>();
final Set<Class<?>> seen = new HashSet<>();
visitBaseTypes(deviceClass, c -> {
if (seen.add(c) && providers.containsKey(c)) {
relevant.add(providers.get(c));
}
});
if (relevant.size() == 0) {
return null;
}
if (relevant.size() == 1) {
return relevant.get(0);
}
// Flip things around so when iterating in visit() we go from least to most specific provider.
Collections.reverse(relevant);
return new DeviceTreeProvider() {
@Override
public Optional<String> getName(final Device device) {
for (int i = relevant.size() - 1; i >= 0; i--) {
final Optional<String> name = relevant.get(i).getName(device);
if (name.isPresent()) {
return name;
}
}
return Optional.empty();
}
@Override
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
for (int i = relevant.size() - 1; i >= 0; i--) {
final Optional<DeviceTree> node = relevant.get(i).createNode(root, memoryMap, device, deviceName);
if (node.isPresent()) {
return node;
}
}
return Optional.empty();
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
for (final DeviceTreeProvider provider : relevant) {
provider.visit(node, memoryMap, device);
}
}
};
}
public static void visit(final DeviceTree root, final MemoryMap memoryMap, final Device device) {
final DeviceTreeProvider provider = getProvider(device);
if (provider == null) {
LOGGER.warn("No provider for device [{}].", device);
return;
}
final Optional<String> name = provider.getName(device);
if (!name.isPresent()) {
LOGGER.warn("Failed obtaining name for device [{}].", device);
return;
}
final Optional<DeviceTree> node = provider.createNode(root, memoryMap, device, name.get());
if (!node.isPresent()) {
LOGGER.warn("Failed obtaining node for device [{}].", device);
return;
}
provider.visit(node.get(), memoryMap, device);
}
public static DeviceTree create(final MemoryMap mmu) {
return new DeviceTreeImpl(mmu);
}
}

View File

@@ -0,0 +1,224 @@
package li.cil.circuity.vm.devicetree;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public final class FlattenedDeviceTree {
private static final int FDT_MAGIC = 0xd00dfeed;
private static final int FDT_VERSION = 0x11;
private static final int FDT_LAST_COMP_VERSION = 0x10;
private static final int FDT_BEGIN_NODE = 0x00000001;
private static final int FDT_END_NODE = 0x00000002;
private static final int FDT_PROP = 0x00000003;
private static final int FDT_NOP = 0x00000004;
private static final int FDT_END = 0x00000009;
private static final int FDT_HEADER_SIZE = 10 * 4; // 10 * sizeof(int)
private List<String> names = new ArrayList<>();
private IntList nameOffsets = new IntArrayList();
private final ByteArrayOutputStream structureBlock = new ByteArrayOutputStream();
private final DataOutputStream structureBlockWriter = new DataOutputStream(structureBlock);
private int openStructureNodes = 0;
public void beginNode(final String name) {
try {
structureBlockWriter.writeInt(FDT_BEGIN_NODE);
structureBlockWriter.writeBytes(name);
structureBlockWriter.writeByte(0);
pad(structureBlockWriter, 4);
openStructureNodes++;
} catch (final IOException e) {
throw new AssertionError(e);
}
}
public void endNode() {
try {
structureBlockWriter.writeInt(FDT_END_NODE);
openStructureNodes--;
} catch (final IOException e) {
throw new AssertionError(e);
}
}
public void property(final String name, final Object... values) {
try {
int valuesByteLength = 0;
for (final Object value : values) {
if (value instanceof String) {
valuesByteLength += ((String) value).length() + 1;
} else if (value instanceof Integer) {
valuesByteLength += 4;
} else if (value instanceof Long) {
valuesByteLength += 8;
} else {
throw new IllegalArgumentException();
}
}
structureBlockWriter.writeInt(FDT_PROP);
structureBlockWriter.writeInt(valuesByteLength);
structureBlockWriter.writeInt(getPropertyNameOffset(name));
for (final Object value : values) {
if (value instanceof String) {
structureBlockWriter.writeBytes((String) value);
structureBlockWriter.writeByte(0);
} else if (value instanceof Integer) {
structureBlockWriter.writeInt((int) value);
} else if (value instanceof Long) {
structureBlockWriter.writeInt((int) (((long) value) >>> 32));
structureBlockWriter.writeInt((int) ((long) value));
} else {
throw new AssertionError();
}
}
pad(structureBlockWriter, 4);
} catch (final IOException e) {
throw new AssertionError(e);
}
}
public byte[] toDTB() {
if (openStructureNodes != 0) {
throw new IllegalStateException("Unbalanced nodes.");
}
try {
final ByteArrayOutputStream fdt = new ByteArrayOutputStream();
final DataOutputStream fdtWriter = new DataOutputStream(fdt);
// We write blocks in this order:
// - Memory reservation block
// - Structure block
// - Strings block
// We need to compute the offsets and sizes of these blocks in advance for the header.
int pos = FDT_HEADER_SIZE;
final int headerPadding = paddingTo(FDT_HEADER_SIZE, 8);
pos += headerPadding;
final int memoryReservationBlockOffset = pos;
final int memoryReservationBlockLength = 8 + 8; // single 0L, 0L reservation struct.
pos += memoryReservationBlockLength;
final int structureBlockOffset = pos;
final int structureBlockLength = structureBlockWriter.size() + 4; // + 4 for FDT_END
pos += structureBlockLength;
final int stringsBlockOffset = pos;
final int stringsBlockLength = stringsBlockLength();
pos += stringsBlockLength;
final fdt_header header = new fdt_header();
header.boot_cpuid_phys = 0;
header.totalsize = pos;
header.size_dt_strings = stringsBlockLength;
header.size_dt_struct = structureBlockLength;
header.off_dt_struct = structureBlockOffset;
header.off_dt_strings = stringsBlockOffset;
header.off_mem_rsvmap = memoryReservationBlockOffset;
header.write(fdtWriter);
pad(fdtWriter, 8);
fdtWriter.writeLong(0); // fdt_reserve_entry.address
fdtWriter.writeLong(0); // fdt_reserve_entry.size
fdtWriter.write(structureBlock.toByteArray());
fdtWriter.writeInt(FDT_END);
for (final String name : names) {
fdtWriter.writeBytes(name);
fdtWriter.writeByte(0);
}
return fdt.toByteArray();
} catch (final IOException e) {
throw new AssertionError(e);
}
}
private void pad(final DataOutputStream output, final int alignment) throws IOException {
final int padding = paddingTo(output.size(), alignment);
for (int i = 0; i < padding; i++) {
output.writeByte(0);
}
}
private int paddingTo(final int size, final int alignment) {
final int mask = alignment - 1;
if ((size & mask) == 0) {
return 0;
} else {
return ((size & ~mask) + alignment) - size;
}
}
private int getPropertyNameOffset(final String name) {
final int index = names.indexOf(name);
if (index >= 0) {
return nameOffsets.getInt(index);
} else if (names.size() > 0) {
final int nameOffset = stringsBlockLength();
names.add(name);
nameOffsets.add(nameOffset);
return nameOffset;
} else {
names.add(name);
nameOffsets.add(0);
return 0;
}
}
private int stringsBlockLength() {
if (nameOffsets.size() == 0) {
return 0;
}
final int lastNameIndex = names.size() - 1;
final int lastNameLength = names.get(lastNameIndex).length();
final int lastNameOffset = nameOffsets.getInt(lastNameIndex);
return lastNameOffset + lastNameLength + 1; // +1 for \0 separator
}
private static final class fdt_header {
public final int magic = FDT_MAGIC;
public int totalsize;
public int off_dt_struct;
public int off_dt_strings;
public int off_mem_rsvmap;
public final int version = FDT_VERSION;
public final int last_comp_version = FDT_LAST_COMP_VERSION;
public int boot_cpuid_phys;
public int size_dt_strings;
public int size_dt_struct;
public void write(final DataOutput output) throws IOException {
output.writeInt(magic);
output.writeInt(totalsize);
output.writeInt(off_dt_struct);
output.writeInt(off_dt_strings);
output.writeInt(off_mem_rsvmap);
output.writeInt(version);
output.writeInt(last_comp_version);
output.writeInt(boot_cpuid_phys);
output.writeInt(size_dt_strings);
output.writeInt(size_dt_struct);
}
}
}

View File

@@ -0,0 +1,29 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import java.util.Optional;
public final class CoreLocalInterrupterProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new CoreLocalInterrupterProvider();
@Override
public Optional<String> getName(final Device device) {
return Optional.of("clint");
}
@Override
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
return Optional.of(root.find("/soc").getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device)));
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
node.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,clint0");
}
}

View File

@@ -0,0 +1,23 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import java.util.Optional;
public class HostTargetInterfaceProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new HostTargetInterfaceProvider();
@Override
public Optional<String> getName(final Device device) {
return Optional.of("htif");
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
node.addProp(DeviceTreePropertyNames.COMPATIBLE, "ucb,htif0");
}
}

View File

@@ -0,0 +1,29 @@
package li.cil.circuity.vm.devicetree.provider;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import li.cil.circuity.api.vm.Interrupt;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.device.InterruptController;
import li.cil.circuity.api.vm.device.InterruptSource;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
public final class InterruptSourceProvider implements DeviceTreeProvider {
public static final InterruptSourceProvider INSTANCE = new InterruptSourceProvider();
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
final InterruptSource interruptSource = (InterruptSource) device;
final IntList interrupts = new IntArrayList();
for (final Interrupt interrupt : interruptSource.getInterrupts()) {
final InterruptController controller = interrupt.controller;
if (controller != null) {
interrupts.add(node.getPHandle(controller));
interrupts.add(interrupt.id);
}
}
node.addProp("interrupts-extended", interrupts.toArray());
}
}

View File

@@ -0,0 +1,28 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import java.util.Optional;
public class MemoryMappedDeviceProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new MemoryMappedDeviceProvider();
@Override
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
return Optional.of(root.getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device)));
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
final MemoryMappedDevice mappedDevice = (MemoryMappedDevice) device;
// TODO in the future when we may want to change bus widths check parent for cell and size cell num.
node.addProp(DeviceTreePropertyNames.REG,
((long) memoryMap.getDeviceAddress(mappedDevice)) & 0xFFFFFFFFL,
((long) mappedDevice.getLength()) & 0xFFFFFFFFL);
}
}

View File

@@ -0,0 +1,24 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.devicetree.DeviceNames;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import java.util.Optional;
public class PhysicalMemoryProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new PhysicalMemoryProvider();
@Override
public Optional<String> getName(final Device device) {
return Optional.of(DeviceNames.MEMORY);
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
node.addProp(DeviceTreePropertyNames.DEVICE_TYPE, "memory");
}
}

View File

@@ -0,0 +1,38 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.devicetree.DeviceNames;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController;
import java.util.Optional;
public final class PlatformLevelInterruptControllerProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new PlatformLevelInterruptControllerProvider();
@Override
public Optional<String> getName(final Device device) {
return Optional.of("plic");
}
@Override
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
return Optional.of(root.find("/soc").getChild(deviceName, memoryMap.getDeviceAddress((MemoryMappedDevice) device)));
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
final R5PlatformLevelInterruptController plic = (R5PlatformLevelInterruptController) device;
node
.addProp("#address-cells", 0)
.addProp("#interrupt-cells", 1)
.addProp(DeviceNames.INTERRUPT_CONTROLLER)
.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,plic0")
.addProp("riscv,ndev", 31)
.addProp(DeviceTreePropertyNames.PHANDLE, node.createPHandle(plic));
}
}

View File

@@ -0,0 +1,25 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
import java.util.Optional;
public final class UART16550AProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new UART16550AProvider();
@Override
public Optional<String> getName(final Device device) {
return Optional.of("uart");
}
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
node
.addProp(DeviceTreePropertyNames.COMPATIBLE, "ns16550a")
.addProp("clock-frequency", 3686400);
}
}

View File

@@ -0,0 +1,16 @@
package li.cil.circuity.vm.devicetree.provider;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Device;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.api.vm.devicetree.DeviceTreeProvider;
public final class VirtIOProvider implements DeviceTreeProvider {
public static final DeviceTreeProvider INSTANCE = new VirtIOProvider();
@Override
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
node.addProp(DeviceTreePropertyNames.COMPATIBLE, "virtio,mmio");
}
}

View File

@@ -0,0 +1,176 @@
package li.cil.circuity.vm.riscv;
@SuppressWarnings({"unused", "RedundantSuppression", "PointlessBitwiseExpression"})
public final class R5 {
// Privilege levels.
public static final int PRIVILEGE_U = 0; // User
public static final int PRIVILEGE_S = 1; // Supervisor
public static final int PRIVILEGE_H = 2; // Hypervisor
public static final int PRIVILEGE_M = 3; // Machine
// Software interrupts.
public static final int USIP_SHIFT = 0; // User
public static final int SSIP_SHIFT = 1; // Supervisor
public static final int HSIP_SHIFT = 2; // Hypervisor
public static final int MSIP_SHIFT = 3; // Machine
// Timer interrupts.
public static final int UTIP_SHIFT = 4; // User
public static final int STIP_SHIFT = 5; // Supervisor
public static final int HTIP_SHIFT = 6; // Hypervisor
public static final int MTIP_SHIFT = 7; // Machine
// External interrupts.
public static final int UEIP_SHIFT = 8; // User
public static final int SEIP_SHIFT = 9; // Supervisor
public static final int HEIP_SHIFT = 10; // Hypervisor
public static final int MEIP_SHIFT = 11; // Machine
// Interrupt masks for mip/mideleg CSRs.
public static final int USIP_MASK = 0b1 << USIP_SHIFT;
public static final int SSIP_MASK = 0b1 << SSIP_SHIFT;
public static final int HSIP_MASK = 0b1 << HSIP_SHIFT;
public static final int MSIP_MASK = 0b1 << MSIP_SHIFT;
public static final int UTIP_MASK = 0b1 << UTIP_SHIFT;
public static final int STIP_MASK = 0b1 << STIP_SHIFT;
public static final int HTIP_MASK = 0b1 << HTIP_SHIFT;
public static final int MTIP_MASK = 0b1 << MTIP_SHIFT;
public static final int UEIP_MASK = 0b1 << UEIP_SHIFT;
public static final int SEIP_MASK = 0b1 << SEIP_SHIFT;
public static final int HEIP_MASK = 0b1 << HEIP_SHIFT;
public static final int MEIP_MASK = 0b1 << MEIP_SHIFT;
// Machine status (mstatus[h]) CSR masks and offsets.
public static final int STATUS_UIE_SHIFT = 0; // U-mode interrupt-enable bit
public static final int STATUS_SIE_SHIFT = 1; // S-mode interrupt-enable bit
public static final int STATUS_MIE_SHIFT = 3; // M-mode interrupt-enable bit
public static final int STATUS_UPIE_SHIFT = 4; // Prior U-mode interrupt-enabled bit.
public static final int STATUS_SPIE_SHIFT = 5; // Prior S-mode interrupt-enabled bit.
public static final int STATUS_UBE_SHIFT = 6; // U-mode fetch/store endianness (0 = little, 1 = big).
public static final int STATUS_MPIE_SHIFT = 7; // Prior M-mode interrupt-enabled bit.
public static final int STATUS_SPP_SHIFT = 8; // Prior S-mode privilege mode.
public static final int STATUS_MPP_SHIFT = 11; // Prior M-mode privilege mode.
public static final int STATUS_FS_SHIFT = 13;
public static final int STATUS_XS_SHIFT = 15;
public static final int STATUS_MPRV_SHIFT = 17; // Modify PRiVilege.
public static final int STATUS_SUM_SHIFT = 18; // Permit Supervisor User Memory access.
public static final int STATUS_MXR_SHIFT = 19; // Make eXecutable Readable.
public static final int STATUS_TVM_SHIFT = 20; // Trap Virtual Memory
public static final int STATUS_TW_SHIFT = 21; // Timeout Wait
public static final int STATUS_TSR_SHIFT = 22; // Trap SRET
public static final int STATUS_SD_SHIFT = 31; // State Dirty
public static final int STATUS_SBE_SHIFT = 34; // S-mode fetch/store endianness (0 = little, 1 = big).
public static final int STATUS_MBE_SHIFT = 35; // M-mode fetch/store endianness (0 = little, 1 = big).
public static final int STATUS_UIE_MASK = 1 << STATUS_UIE_SHIFT;
public static final int STATUS_SIE_MASK = 1 << STATUS_SIE_SHIFT;
public static final int STATUS_MIE_MASK = 1 << STATUS_MIE_SHIFT;
public static final int STATUS_UPIE_MASK = 1 << STATUS_UPIE_SHIFT;
public static final int STATUS_SPIE_MASK = 1 << STATUS_SPIE_SHIFT;
public static final int STATUS_UBE_MASK = 1 << STATUS_UBE_SHIFT;
public static final int STATUS_MPIE_MASK = 1 << STATUS_MPIE_SHIFT;
public static final int STATUS_SPP_MASK = 1 << STATUS_SPP_SHIFT;
public static final int STATUS_MPP_MASK = 0b11 << STATUS_MPP_SHIFT;
public static final int STATUS_FS_MASK = 0b11 << STATUS_FS_SHIFT;
public static final int STATUS_XS_MASK = 0b11 << STATUS_XS_SHIFT;
public static final int STATUS_MPRV_MASK = 1 << STATUS_MPRV_SHIFT;
public static final int STATUS_SUM_MASK = 1 << STATUS_SUM_SHIFT;
public static final int STATUS_MXR_MASK = 1 << STATUS_MXR_SHIFT;
public static final int STATUS_TVM_MASK = 1 << STATUS_TVM_SHIFT;
public static final int STATUS_TW_MASK = 1 << STATUS_TW_SHIFT;
public static final int STATUS_TSR_MASK = 1 << STATUS_TSR_SHIFT;
public static final int STATUS_SD_MASK = 1 << STATUS_SD_SHIFT;
public static final long STATUS_SBE_MASK = 1L << STATUS_SBE_SHIFT;
public static final long STATUS_MBE_MASK = 1L << STATUS_MBE_SHIFT;
// Exception codes used mep/medeleg CSRs.
public static final int EXCEPTION_MISALIGNED_FETCH = 0;
public static final int EXCEPTION_FAULT_FETCH = 1;
public static final int EXCEPTION_ILLEGAL_INSTRUCTION = 2;
public static final int EXCEPTION_BREAKPOINT = 3;
public static final int EXCEPTION_MISALIGNED_LOAD = 4;
public static final int EXCEPTION_FAULT_LOAD = 5;
public static final int EXCEPTION_MISALIGNED_STORE = 6;
public static final int EXCEPTION_FAULT_STORE = 7;
public static final int EXCEPTION_USER_ECALL = 8;
public static final int EXCEPTION_SUPERVISOR_ECALL = 9;
public static final int EXCEPTION_HYPERVISOR_ECALL = 10;
public static final int EXCEPTION_MACHINE_ECALL = 11;
public static final int EXCEPTION_FETCH_PAGE_FAULT = 12;
public static final int EXCEPTION_LOAD_PAGE_FAULT = 13;
public static final int EXCEPTION_STORE_PAGE_FAULT = 15;
// Highest bit means it's an interrupt/asynchronous exception, otherwise a regular exception.
public static final int INTERRUPT = 1 << 31;
// Supported counters in [m|s]counteren CSRs.
public static final int MCOUNTERN_CY = 1 << 0;
public static final int MCOUNTERN_TM = 1 << 1;
public static final int MCOUNTERN_IR = 1 << 2;
public static final int MCOUNTERN_HPM3 = 1 << 3; // Contiguous HPM counters up to HPM31 after this.
// SATP CSR masks.
public static final int SATP_PPN_MASK = 0b0000_0000_0011_1111_1111_1111_1111_1111;
public static final int SATP_ASID_MASK = 0b0111_1111_1100_0000_0000_0000_0000_0000;
public static final int SATP_MODE_MASK = 0b1000_0000_0000_0000_0000_0000_0000_0000;
// Page sizes are 4KiB (V2p73).
public static final int PAGE_ADDRESS_SHIFT = 12; // 1<<12 == 4096; SATP << 12 == root PTE address
public static final int PAGE_ADDRESS_MASK = (1 << PAGE_ADDRESS_SHIFT) - 1;
// Page table entry masks. See V2p73.
public static final int PTE_DATA_BITS = 10; // Number of PTE data bits.
public static final int PTE_V_MASK = 0b1 << 0; // Valid flag.
public static final int PTE_R_MASK = 0b1 << 1; // Allow read access.
public static final int PTE_W_MASK = 0b1 << 2; // Allow write access.
public static final int PTE_X_MASK = 0b1 << 3; // Allow code execution (instruction fetch).
public static final int PTE_U_MASK = 0b1 << 4; // Allow access to user mode only.
public static final int PTE_G_MASK = 0b1 << 5; // Global mapping.
public static final int PTE_A_MASK = 0b1 << 6; // Accessed flag (read, written or fetched).
public static final int PTE_D_MASK = 0b1 << 7; // Dirty flag (written).
public static final int PTE_RSW_MASK = 0b11 << 8; // Reserved for supervisor software.
// Config for SV32 configuration.
public static final int SV32_LEVELS = 2;
public static final int SV32_PTE_SIZE_LOG2 = 2; // => * size == << log2(size)
public static final int SV32_XPN_SIZE = 10; // page number size per level in bits
public static final int SV32_XPN_MASK = (1 << SV32_XPN_SIZE) - 1;
/**
* Computes flags for the machine ISA CSR given a list of extension letters.
*
* @param extensions the list of extensions to build a mask from.
* @return the mask representing the list of extensions.
*/
public static int isa(final char... extensions) {
int result = 0;
for (final char ch : extensions) {
final char extension = Character.toUpperCase(ch);
if (extension < 'A' || extension > 'Z') {
throw new IllegalArgumentException("Not a valid extension letter: " + extension);
}
result |= 1 << (extension - 'A');
}
return result;
}
/**
* Gets the value for the MXL field in the misa CSR for a given XLEN.
*
* @param xlen the XLEN to get the MXL value for. Must be 32, 64 or 128.
* @return the MXL for the specified XLEN.
*/
public static int mxl(final int xlen) {
switch (xlen) {
case 32:
return 0b01;
case 64:
return 0b10;
case 128:
return 0b11;
default:
throw new IllegalArgumentException();
}
}
}

View File

@@ -0,0 +1,617 @@
package li.cil.circuity.vm.riscv;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import java.util.*;
import java.util.function.Function;
public final class R5Assembler {
private static final Map<String, InstructionSpec> INSTRUCTION_SPEC_MAP = new HashMap<>();
private static final Map<String, String> TOKEN_ALIASES = new HashMap<>();
private static final List<String> FIELD_ARGUMENT_ORDER = new ArrayList<>();
static {
new InstructionSpec("LUI", "immU;rd;opcode")
.bind("opcode", 0b0110111)
.postprocess("imm", i -> i >> 12)
.register();
new InstructionSpec("AUIPC", "immU;rd;opcode")
.bind("opcode", 0b0010111)
.register();
new InstructionSpec("JAL", "immJ;rd;opcode")
.bind("opcode", 0b1101111)
.postprocess("imm", i -> i >> 1)
.register();
new InstructionSpec("JALR", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b1100111)
.bind("funct3", 0b000)
.register();
new InstructionSpec("BEQ", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b000)
.register();
new InstructionSpec("BNE", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b001)
.register();
new InstructionSpec("BLT", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b100)
.register();
new InstructionSpec("BGE", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b101)
.register();
new InstructionSpec("BLTU", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b110)
.register();
new InstructionSpec("BGEU", "immB;rs2;rs1;funct3;opcode")
.bind("opcode", 0b1100011)
.bind("funct3", 0b111)
.register();
new InstructionSpec("LB", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0000011)
.bind("funct3", 0b000)
.register();
new InstructionSpec("LH", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0000011)
.bind("funct3", 0b001)
.register();
new InstructionSpec("LW", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0000011)
.bind("funct3", 0b010)
.register();
new InstructionSpec("LBU", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0000011)
.bind("funct3", 0b100)
.register();
new InstructionSpec("LHU", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0000011)
.bind("funct3", 0b101)
.register();
new InstructionSpec("SB", "immS;rs2;rs1;funct3;opcode")
.bind("opcode", 0b0100011)
.bind("funct3", 0b000)
.register();
new InstructionSpec("SH", "immS;rs2;rs1;funct3;opcode")
.bind("opcode", 0b0100011)
.bind("funct3", 0b001)
.register();
new InstructionSpec("SW", "immS;rs2;rs1;funct3;opcode")
.bind("opcode", 0b0100011)
.bind("funct3", 0b010)
.register();
new InstructionSpec("ADDI", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b000)
.register();
new InstructionSpec("SLTI", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b010)
.register();
new InstructionSpec("SLTIU", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b011)
.register();
new InstructionSpec("XORI", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b100)
.register();
new InstructionSpec("ORI", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b110)
.register();
new InstructionSpec("ANDI", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b111)
.register();
new InstructionSpec("SLLI", "funct7;shamt;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b001)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SRLI", "funct7;shamt;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b101)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SRAI", "funct7;shamt;rs1;funct3;rd;opcode")
.bind("opcode", 0b0010011)
.bind("funct3", 0b101)
.bind("funct7", 0b0100000)
.register();
new InstructionSpec("ADD", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b000)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SUB", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b000)
.bind("funct7", 0b0100000)
.register();
new InstructionSpec("SLL", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b001)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SLT", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b010)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SLTU", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b011)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("XOR", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b100)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SRL", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b101)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("SRA", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b101)
.bind("funct7", 0b0100000)
.register();
new InstructionSpec("OR", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b110)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("AND", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b111)
.bind("funct7", 0b0000000)
.register();
new InstructionSpec("FENCE", "fm[31:27];pred[26:24];succ[23:20];rs1;funct3;rd;opcode")
.bind("opcode", 0b0001111)
.bind("funct3", 0b000)
.bind("fm", 0b00000)
.bind("pred", 0b000)
.bind("succ", 0b0000)
.register();
new InstructionSpec("FENCE.I", "immI;rs1;funct3;rd;opcode")
.bind("opcode", 0b0001111)
.bind("funct3", 0b001)
.register();
new InstructionSpec("ECALL", "funct12;zero[19:7];opcode")
.bind("opcode", 0b1110011)
.bind("zero", 0)
.bind("funct12", 0b000000000000)
.register();
new InstructionSpec("EBREAK", "funct12;zero[19:7];opcode")
.bind("opcode", 0b1110011)
.bind("zero", 0)
.bind("funct12", 0b000000000001)
.register();
new InstructionSpec("CSRRW", "csr;rs1;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b001)
.register();
new InstructionSpec("CSRRS", "csr;rs1;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b010)
.register();
new InstructionSpec("CSRRC", "csr;rs1;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b011)
.register();
new InstructionSpec("CSRRWI", "csr;uimm;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b101)
.register();
new InstructionSpec("CSRRSI", "csr;uimm;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b110)
.register();
new InstructionSpec("CSRRCI", "csr;uimm;funct3;rd;opcode")
.bind("opcode", 0b1110011)
.bind("funct3", 0b111)
.register();
new InstructionSpec("MUL", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b000)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("MULH", "funct12;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b001)
.bind("funct12", 0b0000001)
.register();
new InstructionSpec("MULHSU", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b010)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("MULHU", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b011)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("DIV", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b100)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("DIVU", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b101)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("REM", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b110)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("REMU", "funct7;rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0110011)
.bind("funct3", 0b111)
.bind("funct7", 0b0000001)
.register();
new InstructionSpec("LW.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("rs2", 0b00000)
.bind("funct5", 0b00010)
.register();
new InstructionSpec("SC.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b00011)
.register();
new InstructionSpec("AMOSWAP.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b00001)
.register();
new InstructionSpec("AMOADD.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b00000)
.register();
new InstructionSpec("AMOXOR.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b00100)
.register();
new InstructionSpec("AMOAND.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b01100)
.register();
new InstructionSpec("AMOOR.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b01000)
.register();
new InstructionSpec("AMOMIN.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b10000)
.register();
new InstructionSpec("AMOMAX.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b10100)
.register();
new InstructionSpec("AMOMINU.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b11000)
.register();
new InstructionSpec("AMOMAXU.W", "funct5;aq[26];rl[25];rs2;rs1;funct3;rd;opcode")
.bind("opcode", 0b0101111)
.bind("funct3", 0b010)
.bind("funct5", 0b11100)
.register();
TOKEN_ALIASES.put("zero", "x0");
TOKEN_ALIASES.put("ra", "x1");
TOKEN_ALIASES.put("sp", "x2");
TOKEN_ALIASES.put("gp", "x3");
TOKEN_ALIASES.put("tp", "x4");
TOKEN_ALIASES.put("t0", "x5");
TOKEN_ALIASES.put("t1", "x6");
TOKEN_ALIASES.put("t2", "x7");
TOKEN_ALIASES.put("s0", "x8");
TOKEN_ALIASES.put("fp", "x8");
TOKEN_ALIASES.put("s1", "x9");
TOKEN_ALIASES.put("a0", "x10");
TOKEN_ALIASES.put("a1", "x11");
TOKEN_ALIASES.put("a2", "x12");
TOKEN_ALIASES.put("a3", "x13");
TOKEN_ALIASES.put("a4", "x14");
TOKEN_ALIASES.put("a5", "x15");
TOKEN_ALIASES.put("a6", "x16");
TOKEN_ALIASES.put("a7", "x17");
TOKEN_ALIASES.put("s2", "x18");
TOKEN_ALIASES.put("s3", "x19");
TOKEN_ALIASES.put("s4", "x20");
TOKEN_ALIASES.put("s5", "x21");
TOKEN_ALIASES.put("s6", "x22");
TOKEN_ALIASES.put("s7", "x23");
TOKEN_ALIASES.put("s8", "x24");
TOKEN_ALIASES.put("s9", "x25");
TOKEN_ALIASES.put("s10", "x26");
TOKEN_ALIASES.put("s11", "x27");
TOKEN_ALIASES.put("t3", "x28");
TOKEN_ALIASES.put("t4", "x29");
TOKEN_ALIASES.put("t5", "x30");
TOKEN_ALIASES.put("t6", "x31");
FIELD_ARGUMENT_ORDER.add("rd");
FIELD_ARGUMENT_ORDER.add("rs1");
FIELD_ARGUMENT_ORDER.add("rs2");
FIELD_ARGUMENT_ORDER.add("imm");
}
public static void assemble(final String code, final PhysicalMemory memory, final int offset) throws MemoryAccessException {
assemble(code.split("\n"), memory, offset);
}
public static void assemble(final String[] code, final PhysicalMemory memory, final int offset) throws MemoryAccessException {
int writeIndex = offset;
for (final String line : code) {
String processedLine;
final int commentStart = line.indexOf('#');
if (commentStart >= 0) {
processedLine = line.substring(0, commentStart).trim();
} else {
processedLine = line.trim();
}
if (processedLine.isEmpty()) {
continue;
}
final String[] instAndArgs = line.trim().split(" ", 2);
final String instName = instAndArgs[0];
final InstructionSpec inst = INSTRUCTION_SPEC_MAP.get(instName.toLowerCase());
if (inst == null) {
throw new IllegalArgumentException("illegal instruction name '" + instName + "'");
}
final int instAssembled;
if (instAndArgs.length > 1) {
final String[] args = instAndArgs[1].split(",");
for (int i = 0; i < args.length; i++) {
args[i] = args[i].trim();
}
instAssembled = inst.assemble(resolveArgs(inst.fields.keySet(), args));
} else {
instAssembled = inst.assemble(Collections.emptyMap());
}
memory.store32(writeIndex, instAssembled);
writeIndex += 4;
}
}
private static Map<String, Integer> resolveArgs(final Set<String> fieldNames, final String[] args) {
final Map<String, Integer> result = new HashMap<>();
int argIndex = 0;
for (final String fieldName : FIELD_ARGUMENT_ORDER) {
if (!fieldNames.contains(fieldName)) {
continue;
}
String arg = args[argIndex];
arg = TOKEN_ALIASES.getOrDefault(arg, arg);
final int argValue;
if (arg.charAt(0) == 'x') {
argValue = Integer.decode(arg.substring(1));
} else {
argValue = Integer.decode(arg);
}
result.put(fieldName, argValue);
argIndex++;
if (argIndex >= args.length) {
break;
}
}
if (argIndex < args.length) {
throw new IllegalArgumentException();
}
return result;
}
public static final class InstructionSpec {
private static final char FIELD_SPEC_SEPARATOR = ';';
private static final char BIT_SPEC_START = '[';
private static final char BIT_SPEC_END = ']';
private static final char BIT_SPEC_SEPARATOR = ',';
public static final char BIT_SPEC_RANGE_SEPARATOR = ':';
private static final Map<String, String> DEFAULT_BIT_RANGES = new HashMap<>();
static {
DEFAULT_BIT_RANGES.put("opcode", "opcode[6:0]");
DEFAULT_BIT_RANGES.put("rd", "rd[11:7]");
DEFAULT_BIT_RANGES.put("funct3", "funct3[14:12]");
DEFAULT_BIT_RANGES.put("rs1", "rs1[19:15]");
DEFAULT_BIT_RANGES.put("uimm", "rs1[19:15]");
DEFAULT_BIT_RANGES.put("rs2", "rs2[24:20]");
DEFAULT_BIT_RANGES.put("shamt", "rs2[24:20]");
DEFAULT_BIT_RANGES.put("funct7", "funct7[31:25]");
DEFAULT_BIT_RANGES.put("immI", "imm[31:20]");
DEFAULT_BIT_RANGES.put("csr", "imm[31:20]");
DEFAULT_BIT_RANGES.put("immS", "imm[31:25,11:7]");
DEFAULT_BIT_RANGES.put("immB", "imm[31,7,30:25,11:6]");
DEFAULT_BIT_RANGES.put("immU", "imm[31:12]");
DEFAULT_BIT_RANGES.put("immJ", "imm[31,19:12,20,30:21]");
DEFAULT_BIT_RANGES.put("funct5", "funct5[31:27]");
DEFAULT_BIT_RANGES.put("funct12", "funct12[31:20]");
}
private final String name;
private final Map<String, List<BitRange>> fields;
private final Map<String, Integer> values;
private Map<String, Function<Integer, Integer>> postprocessors;
public InstructionSpec(final String name, final String spec) {
// Spec grammar:
// SPEC := FIELD_SPEC_LIST
// FIELD_SPEC_LIST := FIELD_SPEC [ ";" FIELD_SPEC_LIST ]
// FIELD_SPEC := FIELD_NAME "[" BIT_SPEC_LIST "]"
// BIT_SPEC_LIST := BIT_SPEC [ "," BIT_SPEC_LIST ]
// BIT_SPEC := 0-9 | # Single bit indexing into opcode bits; equivalent to N:N
// 0-9 ":" 0-9 # Bit range indexing into opcode bits
this.name = name;
fields = new HashMap<>();
values = new HashMap<>();
postprocessors = new HashMap<>();
parseFieldSpecList(spec, fields);
}
public InstructionSpec bind(final String field, final int value) {
values.put(field, value);
return this;
}
public InstructionSpec postprocess(final String field, final Function<Integer, Integer> callback) {
postprocessors.put(field, callback);
return this;
}
public void register() {
INSTRUCTION_SPEC_MAP.put(name.toLowerCase(), this);
}
public int assemble(final Map<String, Integer> values) {
final Function<String, Integer> getValue = s -> {
final int value = values.computeIfAbsent(s, key -> this.values.computeIfAbsent(key, s1 -> {
throw new IllegalArgumentException("missing value '" + s1 + "'");
}));
final Function<Integer, Integer> postprocessor = postprocessors.get(s);
if (postprocessor != null) {
return postprocessor.apply(value);
} else {
return value;
}
};
int result = 0;
for (final String fieldName : fields.keySet()) {
final List<BitRange> fieldMapping = fields.get(fieldName);
final int fieldValue = getValue.apply(fieldName);
result |= map(fieldValue, fieldMapping);
}
return result;
}
private int map(final int value, final List<BitRange> mapping) {
int mask = 0b1;
int result = 0;
for (final BitRange range : mapping) {
final int high = range.high;
final int low = range.low;
for (int i = low; i <= high; i++, mask <<= 1) {
final int bitValue = (value & mask) != 0 ? 1 : 0;
result |= bitValue << i;
}
}
return result;
}
private static void parseFieldSpecList(final String fieldSpecList, final Map<String, List<BitRange>> fields) {
int fieldSpecStart = 0;
int nextFieldSpecSplit = fieldSpecList.indexOf(FIELD_SPEC_SEPARATOR);
do {
if (nextFieldSpecSplit < 0)
nextFieldSpecSplit = fieldSpecList.length(); // No more split, use remainder.
String fieldSpec = fieldSpecList.substring(fieldSpecStart, nextFieldSpecSplit);
fieldSpec = DEFAULT_BIT_RANGES.getOrDefault(fieldSpec, fieldSpec);
if (fieldSpec.length() == 0) {
continue;
}
final int bitSpecListStart = fieldSpec.indexOf(BIT_SPEC_START);
if (bitSpecListStart < 0 || fieldSpec.charAt(fieldSpec.length() - 1) != BIT_SPEC_END) {
throw new IllegalArgumentException("no bit spec list for field '" + fieldSpec + "'");
}
final String fieldName = fieldSpec.substring(0, bitSpecListStart);
final String bitSpecList = fieldSpec.substring(bitSpecListStart + 1, fieldSpec.length() - 1);
int bitSpecStart = 0;
if (bitSpecList.length() == 0) {
throw new IllegalArgumentException("empty bit spec list for field '" + fieldName + "'");
}
final List<BitRange> bitRanges = new ArrayList<>();
int nextBitSpecSplit = bitSpecList.indexOf(BIT_SPEC_SEPARATOR);
do {
if (nextBitSpecSplit < 0)
nextBitSpecSplit = bitSpecList.length(); // No more split, use remainder.
final String bitSpec = bitSpecList.substring(bitSpecStart, nextBitSpecSplit);
if (bitSpec.length() == 0) {
continue;
}
final int separatorIndex = bitSpec.indexOf(BIT_SPEC_RANGE_SEPARATOR);
if (separatorIndex < 0) {
final int bitIndex = Integer.decode(bitSpec);
bitRanges.add(new BitRange(bitIndex, bitIndex));
} else {
final int bitIndexHigh = Integer.decode(bitSpec.substring(0, separatorIndex));
final int bitIndexLow = Integer.decode(bitSpec.substring(separatorIndex + 1));
bitRanges.add(new BitRange(bitIndexHigh, bitIndexLow));
}
bitSpecStart = nextBitSpecSplit + 1;
nextBitSpecSplit = bitSpecList.indexOf(BIT_SPEC_SEPARATOR, bitSpecStart);
} while (bitSpecStart < bitSpecList.length());
if (bitRanges.size() == 0) {
throw new IllegalArgumentException("empty bit spec list for field '" + fieldName + "'");
}
Collections.reverse(bitRanges);
fields.put(fieldName, bitRanges);
fieldSpecStart = nextFieldSpecSplit + 1;
nextFieldSpecSplit = fieldSpecList.indexOf(FIELD_SPEC_SEPARATOR, fieldSpecStart);
} while (fieldSpecStart < fieldSpecList.length());
}
private static final class BitRange {
public final int high, low;
public BitRange(final int high, final int low) {
this.high = Math.max(high, low);
this.low = Math.min(high, low);
}
}
}
}

View File

@@ -0,0 +1,238 @@
package li.cil.circuity.vm.riscv;
import li.cil.circuity.api.vm.MemoryMap;
import li.cil.circuity.api.vm.device.Steppable;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.api.vm.device.rtc.RealTimeCounter;
import li.cil.circuity.api.vm.device.rtc.SystemTimeRealTimeCounter;
import li.cil.circuity.api.vm.devicetree.DeviceNames;
import li.cil.circuity.api.vm.devicetree.DeviceTree;
import li.cil.circuity.api.vm.devicetree.DeviceTreePropertyNames;
import li.cil.circuity.vm.SimpleMemoryMap;
import li.cil.circuity.vm.device.UART16550A;
import li.cil.circuity.vm.device.memory.ByteBufferMemory;
import li.cil.circuity.vm.devicetree.DeviceTreeRegistry;
import li.cil.circuity.vm.devicetree.FlattenedDeviceTree;
import li.cil.circuity.vm.riscv.device.R5CoreLocalInterrupter;
import li.cil.circuity.vm.riscv.device.R5HostTargetInterface;
import li.cil.circuity.vm.riscv.device.R5PlatformLevelInterruptController;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
public final class R5Board {
private static final Logger LOGGER = LogManager.getLogger();
private static final int PHYSICAL_MEMORY_FIRST = 0x80000000;
private static final int PHYSICAL_MEMORY_LAST = 0xFFFFFFFF;
private static final int DEVICE_MEMORY_FIRST = 0x40010000;
private static final int DEVICE_MEMORY_LAST = 0x400FFFFF;
private static final int CLINT_ADDRESS = 0x02000000;
private static final int PLIC_ADDRESS = 0x0C000000;
private static final int HTIF_ADDRESS = 0x40008000;
private static final int UART_ADDRESS = 0x10000000;
private static final int BIOS_ADDRESS = 0x1000;
private static final int LOW_MEMORY_SIZE = 0x2000; // Just needs to fit "jump to firmware".
private static final int FIRMWARE_ADDRESS = PHYSICAL_MEMORY_FIRST;
private static final int FDT_ADDRESS = FIRMWARE_ADDRESS + 0x02200000;
private final RealTimeCounter rtc;
private final MemoryMap memoryMap;
private final R5CPU cpu;
private final UART16550A uart;
private final List<MemoryMappedDevice> devices = new ArrayList<>();
private final List<Steppable> steppableDevices = new ArrayList<>();
public R5Board() {
rtc = SystemTimeRealTimeCounter.create();
memoryMap = new SimpleMemoryMap();
cpu = new R5CPU(rtc, memoryMap);
uart = new UART16550A();
final PhysicalMemory flash = new ByteBufferMemory(LOW_MEMORY_SIZE);
final R5HostTargetInterface htif = new R5HostTargetInterface();
final R5CoreLocalInterrupter clint = new R5CoreLocalInterrupter(rtc);
final R5PlatformLevelInterruptController plic = new R5PlatformLevelInterruptController();
steppableDevices.add(cpu);
// Wire up interrupts.
clint.getMachineSoftwareInterrupt().controller = cpu;
clint.getMachineTimerInterrupt().controller = cpu;
plic.getMachineExternalInterrupt().controller = cpu;
plic.getSupervisorExternalInterrupt().controller = cpu;
uart.getInterrupt().id = 0xA;
uart.getInterrupt().controller = plic;
// Map devices to memory.
addDevice(CLINT_ADDRESS, clint);
addDevice(PLIC_ADDRESS, plic);
addDevice(HTIF_ADDRESS, htif);
addDevice(UART_ADDRESS, uart);
memoryMap.addDevice(BIOS_ADDRESS, flash);
}
public void step(final int cycles) {
for (final Steppable device : steppableDevices) {
device.step(cycles);
}
}
public R5CPU getCpu() {
return cpu;
}
public boolean addDevice(final int address, final MemoryMappedDevice device) {
if (device.getLength() == 0) {
return false;
}
if (devices.contains(device)) {
// This prevents adding the same device at different addresses. However, that
// could be circumvented by using a wrapper device, so we save ourselves the
// additional bookkeeping needed for this here.
return false;
}
if (!memoryMap.addDevice(address, device)) {
return false;
}
devices.add(device);
if (device instanceof Steppable) {
steppableDevices.add((Steppable) device);
}
reset();
return true;
}
public boolean addDevice(final MemoryMappedDevice device) {
if (device.getLength() == 0) {
return false;
}
final int startMin, startMax;
if (device instanceof PhysicalMemory) {
startMin = PHYSICAL_MEMORY_FIRST;
startMax = PHYSICAL_MEMORY_LAST - device.getLength() + 1;
} else {
startMin = DEVICE_MEMORY_FIRST;
startMax = DEVICE_MEMORY_LAST - device.getLength() + 1;
}
final OptionalInt address = memoryMap.findFreeRange(startMin, startMax, device.getLength());
return address.isPresent() && addDevice(address.getAsInt(), device);
}
public void removeDevice(final MemoryMappedDevice device) {
memoryMap.removeDevice(device);
devices.remove(device);
if (device instanceof Steppable) {
steppableDevices.remove(device);
}
reset();
}
public void reset() {
cpu.reset();
try {
final FlattenedDeviceTree fdt = buildDeviceTree().flatten();
final byte[] dtb = fdt.toDTB();
for (int i = 0; i < dtb.length; i++) {
memoryMap.store8(FDT_ADDRESS + i, dtb[i]);
}
} catch (final MemoryAccessException e) {
throw new AssertionError();
}
try {
final int lui = 0b0110111;
final int jalr = 0b1100111;
final int rd_x5 = 5 << 7;
final int rd_x11 = 11 << 7;
final int rs1_x5 = 5 << 15;
int pc = 0x1000; // R5CPU starts executing at 0x1000.
// lui a1, FDT_ADDRESS -> store FDT address in a1 for firmware
memoryMap.store32(pc, lui | rd_x11 + FDT_ADDRESS);
pc += 4;
// lui t0, PHYSICAL_MEMORY_FIRST -> load address of firmware
memoryMap.store32(pc, lui | rd_x5 + PHYSICAL_MEMORY_FIRST);
pc += 4;
// jalr zero, t0, 0 -> jump to firmware
memoryMap.store32(pc, jalr | rs1_x5);
} catch (final MemoryAccessException e) {
LOGGER.error(e);
}
}
public int readValue() {
return uart.getByte();
}
private DeviceTree buildDeviceTree() {
final DeviceTree root = DeviceTreeRegistry.create(memoryMap);
root
.addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 2)
.addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 2)
.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv-virtio")
.addProp(DeviceTreePropertyNames.MODEL, "riscv-virtio,qemu");
// .addProp(DeviceTreePropertyNames.COMPATIBLE, "ucbbar,riscvemu-bar_dev")
// .addProp(DeviceTreePropertyNames.MODEL, "ucbbar,riscvemu-bar");
root.putChild(DeviceNames.CPUS, cpus -> cpus
.addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 1)
.addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 0)
.addProp("timebase-frequency", rtc.getFrequency())
.putChild(DeviceNames.CPU, 0, cpuNode -> cpuNode
.addProp(DeviceTreePropertyNames.DEVICE_TYPE, DeviceNames.CPU)
.addProp(DeviceTreePropertyNames.REG, 0)
.addProp(DeviceTreePropertyNames.STATUS, "okay")
.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv")
.addProp("riscv,isa", "rv32imacsu")
.addProp("mmu-type", "riscv,sv32")
.addProp("clock-frequency", 1_000_000)
.putChild(DeviceNames.INTERRUPT_CONTROLLER, ic -> ic
.addProp("#interrupt-cells", 1)
.addProp(DeviceTreePropertyNames.INTERRUPT_CONTROLLER)
.addProp(DeviceTreePropertyNames.COMPATIBLE, "riscv,cpu-intc")
.addProp(DeviceTreePropertyNames.PHANDLE, ic.createPHandle(cpu)))));
root.putChild("soc", soc -> soc
.addProp(DeviceTreePropertyNames.NUM_ADDRESS_CELLS, 2)
.addProp(DeviceTreePropertyNames.NUM_SIZE_CELLS, 2)
.addProp(DeviceTreePropertyNames.COMPATIBLE, "simple-bus")
.addProp(DeviceTreePropertyNames.RANGES));
for (final MemoryMappedDevice device : devices) {
DeviceTreeRegistry.visit(root, memoryMap, device);
}
root.putChild("chosen", chosen -> chosen
.addProp("bootargs", "console=ttyS0")
.addProp("stdout-path", String.format("uart@%x", UART_ADDRESS)));
// .addProp("riscv,kernel-start", 0x400000)
// .addProp("riscv,kernel-end", 0x400000 + 14676260 /*8032596*/));
return root;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
package li.cil.circuity.vm.riscv;
@SuppressWarnings("SpellCheckingInspection")
public final class R5CPUStateSnapshot {
public int pc;
public final int[] x = new int[32];
// public final float[] f = new float[32];
// public byte fflags;
// public byte frm;
public int reservation_set = -1;
public long mcycle;
public int mstatus;
public int mtvec;
public int medeleg, mideleg;
public int mip, mie;
public int mcounteren;
public int mscratch;
public int mepc;
public int mcause;
public int mtval;
public byte fs;
public int stvec;
public int scounteren;
public int sscratch;
public int sepc;
public int scause;
public int stval;
public int satp;
public int priv;
public boolean waitingForInterrupt;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
package li.cil.circuity.vm.riscv.device;
import li.cil.circuity.api.vm.Interrupt;
import li.cil.circuity.api.vm.device.InterruptSource;
import li.cil.circuity.api.vm.device.Steppable;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
import li.cil.circuity.api.vm.device.rtc.RealTimeCounter;
import li.cil.circuity.vm.riscv.R5;
import java.util.Arrays;
/**
* See: https://github.com/riscv/riscv-isa-sim/blob/master/riscv/clint.cc
* <pre>
* // 0000 msip hart 0
* // 0004 msip hart 1
* 4000 mtimecmp hart 0 lo
* 4004 mtimecmp hart 0 hi
* bff8 mtime lo
* bffc mtime hi
* </pre>
*/
public final class R5CoreLocalInterrupter implements Steppable, InterruptSource, MemoryMappedDevice {
private final RealTimeCounter rtc;
private long mtimecmp = -1;
private final Interrupt msip = new Interrupt(R5.MSIP_SHIFT);
private final Interrupt mtip = new Interrupt(R5.MTIP_SHIFT);
public R5CoreLocalInterrupter(final RealTimeCounter rtc) {
this.rtc = rtc;
}
public Interrupt getMachineSoftwareInterrupt() {
return msip;
}
public Interrupt getMachineTimerInterrupt() {
return mtip;
}
@Override
public void step(final int cycles) {
checkInterrupt();
}
@Override
public Iterable<Interrupt> getInterrupts() {
return Arrays.asList(msip, mtip);
}
@Override
public int getLength() {
return 0x000C0000;
}
@Override
public int load32(final int offset) {
switch (offset) {
case 0x4000: {
return (int) mtimecmp;
}
case 0x4004: {
return (int) (mtimecmp >>> 32);
}
case 0xBFF8: {
return (int) rtc.getTime();
}
case 0xBFFC: {
return (int) (rtc.getTime() >> 32);
}
default: {
return 0;
}
}
}
@Override
public void store32(final int offset, final int value) {
switch (offset) {
case 0x4000: {
mtimecmp = (mtimecmp & ~0xFFFFFFFFL) | (value & 0xFFFFFFFFL);
if (!checkInterrupt()) {
mtip.lowerInterrupt();
}
break;
}
case 0x4004: {
mtimecmp = (mtimecmp & 0xFFFFFFFFL) | ((long) value << 32);
if (!checkInterrupt()) {
mtip.lowerInterrupt();
}
break;
}
}
}
private boolean checkInterrupt() {
if (Long.compareUnsigned(mtimecmp, rtc.getTime()) <= 0) {
mtip.raiseInterrupt();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,78 @@
package li.cil.circuity.vm.riscv.device;
import li.cil.circuity.api.vm.device.memory.MemoryMappedDevice;
public final class R5HostTargetInterface implements MemoryMappedDevice {
public static final int COMMAND_POWER_OFF = 1;
private long toHost, fromHost;
@Override
public int getLength() {
return 16;
}
@Override
public int load32(final int offset) {
switch (offset) {
case 0: {
return (int) toHost;
}
case 4: {
return (int) (toHost >> 32);
}
case 8: {
return (int) fromHost;
}
case 12: {
return (int) (fromHost >> 32);
}
default: {
return 0;
}
}
}
@Override
public void store32(final int offset, final int value) {
switch (offset) {
case 0: {
toHost = (toHost & ~0xFFFFFFFFL) | value;
break;
}
case 4: {
toHost = (toHost & 0xFFFFFFFFL) | ((long) value << 32);
handleCommand();
break;
}
case 8: {
fromHost = (fromHost & ~0xFFFFFFFFL) | value;
break;
}
case 12: {
fromHost = (fromHost & 0xFFFFFFFFL) | ((long) value << 32);
break;
}
}
}
private void handleCommand() {
final int device = (int) (toHost >>> 56);
final int cmd = (int) (toHost >>> 48) & 0xff;
if (toHost == COMMAND_POWER_OFF) { // request power off
System.out.println("power off");
// todo stop vm
} else if (device == 1 && cmd == 1) { // console output
// console.write_data(toHost & 0xff);
System.out.print((char) (toHost & 0xFF));
toHost = 0;
fromHost = ((long) device << 56) | ((long) cmd << 48);
} else if (device == 1 && cmd == 0) { // request keyboard interrupt
toHost = 0;
} else {
System.out.printf("HTIF: unsupported tohost=0x%016x\n", toHost);
}
}
}

View File

@@ -0,0 +1,188 @@
package li.cil.circuity.vm.riscv.device;
import li.cil.circuity.api.vm.Interrupt;
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 java.util.Arrays;
/**
* Implementation of a PLIC with 32 sources.
* <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 int PLIC_ENABLE_STRIDE = 0x80;
private static final int PLIC_CONTEXT_BASE = 0x200000;
private static final int PLIC_CONTEXT_STRIDE = 0x1000;
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;
}
public Interrupt getSupervisorExternalInterrupt() {
return seip;
}
@Override
public int getLength() {
return 0x04000000;
}
@Override
public int load32(final int offset) {
switch (offset) {
// 0x0: Reserved.
// 0x1 - 0x000FFC: Priorities; hardcoded to zero.
case PLIC_PENDING_BASE: { // Pending bits 0-31.
return pending & ~served;
}
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;
}
}
}
@Override
public void store32(final int offset, final int value) {
switch (offset) {
case PLIC_CONTEXT_BASE + 4: { // Claim/complete for context 0
complete(value);
break;
}
}
}
@Override
public void raiseInterrupts(final int mask) {
pending |= mask;
propagate();
}
@Override
public void lowerInterrupts(final int mask) {
pending &= ~mask;
propagate();
}
@Override
public Iterable<Interrupt> getInterrupts() {
return Arrays.asList(meip, seip);
}
@Override
public int registerInterrupt() {
return interrupts.registerInterrupt();
}
@Override
public boolean registerInterrupt(final int id) {
return interrupts.registerInterrupt(id);
}
@Override
public void releaseInterrupt(final int id) {
interrupts.releaseInterrupt(id);
}
private int claim() {
final int unserved = pending & ~served;
if (unserved == 0) {
return 0;
}
final int index = Integer.numberOfTrailingZeros(unserved);
served |= 1 << index;
propagate();
return index + 1;
}
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();
} else {
meip.lowerInterrupt();
seip.lowerInterrupt();
}
}
}

View File

@@ -0,0 +1,9 @@
package li.cil.circuity.vm.riscv.exception;
import li.cil.circuity.vm.riscv.R5;
public final class R5BreakpointException extends R5Exception {
public R5BreakpointException() {
super(R5.EXCEPTION_BREAKPOINT);
}
}

View File

@@ -0,0 +1,31 @@
package li.cil.circuity.vm.riscv.exception;
import li.cil.circuity.vm.riscv.R5;
public final class R5ECallException extends R5Exception {
public R5ECallException(final int privilege) {
super(exceptionForPrivilege(privilege));
}
private static int exceptionForPrivilege(final int privilege) {
/*
switch (privilege) {
case R5.PRIVILEGE_U: {
return R5.EXCEPTION_USER_ECALL;
}
case R5.PRIVILEGE_S: {
return R5.EXCEPTION_SUPERVISOR_ECALL;
}
case R5.PRIVILEGE_H: {
return R5.EXCEPTION_HYPERVISOR_ECALL;
}
case R5.PRIVILEGE_M: {
return R5.EXCEPTION_MACHINE_ECALL;
}
}
Optimized:
*/
return R5.EXCEPTION_USER_ECALL + privilege;
}
}

View File

@@ -0,0 +1,17 @@
package li.cil.circuity.vm.riscv.exception;
public class R5Exception extends Exception {
private final int exceptionCause;
public R5Exception(final int exceptionCause) {
this.exceptionCause = exceptionCause;
}
public int getExceptionCause() {
return exceptionCause;
}
public int getExceptionValue() {
return 0;
}
}

View File

@@ -0,0 +1,17 @@
package li.cil.circuity.vm.riscv.exception;
import li.cil.circuity.vm.riscv.R5;
public final class R5IllegalInstructionException extends R5Exception {
private final int instruction;
public R5IllegalInstructionException(final int instruction) {
super(R5.EXCEPTION_ILLEGAL_INSTRUCTION);
this.instruction = instruction;
}
@Override
public int getExceptionValue() {
return instruction;
}
}

View File

@@ -0,0 +1,80 @@
package li.cil.circuity.vm.riscv;
import li.cil.circuity.vm.device.memory.ByteBufferMemory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ByteBufferMemoryTests {
private ByteBufferMemory memory;
@BeforeEach
public void initialize() throws Exception {
memory = new ByteBufferMemory(4 * 1024);
memory.store32(0x00, 0x11223344);
memory.store32(0x10, 0x55667788);
memory.store32(0x20, 0x99AABBCC);
}
@Test
public void testLoad8() throws Exception {
Assertions.assertEquals((byte) 0x44, memory.load8(0x00 + 0));
Assertions.assertEquals((byte) 0x33, memory.load8(0x00 + 1));
Assertions.assertEquals((byte) 0x22, memory.load8(0x00 + 2));
Assertions.assertEquals((byte) 0x11, memory.load8(0x00 + 3));
Assertions.assertEquals((byte) 0x88, memory.load8(0x10 + 0));
Assertions.assertEquals((byte) 0x77, memory.load8(0x10 + 1));
Assertions.assertEquals((byte) 0x66, memory.load8(0x10 + 2));
Assertions.assertEquals((byte) 0x55, memory.load8(0x10 + 3));
Assertions.assertEquals((byte) 0xCC, memory.load8(0x20 + 0));
Assertions.assertEquals((byte) 0xBB, memory.load8(0x20 + 1));
Assertions.assertEquals((byte) 0xAA, memory.load8(0x20 + 2));
Assertions.assertEquals((byte) 0x99, memory.load8(0x20 + 3));
}
@Test
public void testStore8() throws Exception {
memory.store8(0x00 + 0, (byte) 0x11);
memory.store8(0x00 + 1, (byte) 0x22);
memory.store8(0x00 + 2, (byte) 0x33);
memory.store8(0x00 + 3, (byte) 0x44);
Assertions.assertEquals(0x44332211, memory.load32(0x00 + 0));
}
@Test
public void testLoad16() throws Exception {
Assertions.assertEquals((short) 0x3344, memory.load16(0x00 + 0));
Assertions.assertEquals((short) 0x1122, memory.load16(0x00 + 2));
Assertions.assertEquals((short) 0x7788, memory.load16(0x10 + 0));
Assertions.assertEquals((short) 0x5566, memory.load16(0x10 + 2));
Assertions.assertEquals((short) 0x99AA, memory.load16(0x20 + 2));
Assertions.assertEquals((short) 0xBBCC, memory.load16(0x20 + 0));
}
@Test
public void testStore16() throws Exception {
memory.store16(0x00 + 0, (short) 0x2211);
memory.store16(0x00 + 2, (short) 0x4433);
Assertions.assertEquals(0x44332211, memory.load32(0x00 + 0));
}
@Test
public void testLoad32() throws Exception {
Assertions.assertEquals(0x11223344, memory.load32(0x00));
Assertions.assertEquals(0x55667788, memory.load32(0x10));
Assertions.assertEquals(0x99AABBCC, memory.load32(0x20));
}
@Test
public void testStore32() throws Exception {
memory.store32(0, 0x44332211);
Assertions.assertEquals(0x44332211, memory.load32(0x00));
}
}

View File

@@ -0,0 +1,150 @@
package li.cil.circuity.vm.riscv;
import li.cil.circuity.api.vm.device.memory.MemoryAccessException;
import li.cil.circuity.api.vm.device.memory.PhysicalMemory;
import li.cil.circuity.vm.device.memory.ByteBufferMemory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class InstructionTests {
static final Logger LOGGER = LogManager.getLogger();
PhysicalMemory memory;
R5Board board;
@BeforeEach
public void initialize() {
board = new R5Board();
memory = new ByteBufferMemory(64 * 1014 * 1024);
board.addDevice(0x80000000, memory);
}
@Test
public void testLUI() throws MemoryAccessException {
loadProgram("lui a0, 0xf000");
board.step(2);
final R5CPUStateSnapshot state = board.getCpu().getState();
Assertions.assertEquals(0xf000, state.x[10]);
}
@Test
public void testAUIPC() throws MemoryAccessException {
loadProgram("auipc a0, 0");
board.step(2);
final R5CPUStateSnapshot state = board.getCpu().getState();
Assertions.assertEquals(state.pc - 4, state.x[10]);
}
@Test
public void testJAL() throws MemoryAccessException {
loadProgram("jal a0, 0xf0");
final R5CPUStateSnapshot state1 = board.getCpu().getState();
board.step(2);
final R5CPUStateSnapshot state2 = board.getCpu().getState();
Assertions.assertEquals(state2.pc, state1.pc + 0xf0);
Assertions.assertEquals(state1.pc + 4, state2.x[10]);
board.getCpu().setState(state1);
loadProgram("jal a0, 0xf1");
board.step(2);
final R5CPUStateSnapshot state3 = board.getCpu().getState();
Assertions.assertEquals(state3.pc, state1.pc + 0xf0);
Assertions.assertEquals(state1.pc + 4, state3.x[10]);
}
@Test
public void testJALR() throws MemoryAccessException {
loadProgram("addi a0, a0, 0x100",
"jalr a0, a0, 0x200");
final R5CPUStateSnapshot state1 = board.getCpu().getState();
board.step(3);
final R5CPUStateSnapshot state2 = board.getCpu().getState();
Assertions.assertEquals(state2.pc, 0x300);
Assertions.assertEquals(state1.pc + 8, state2.x[10]);
}
@Test
public void testADDI() throws MemoryAccessException {
loadProgram("addi a0, a0, 42");
board.step(2);
final R5CPUStateSnapshot state = board.getCpu().getState();
Assertions.assertEquals(42, state.x[10]);
}
@Test
public void testBios() throws Exception {
// final String firmware = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\fw_jump.bin";
// loadProgramFile(firmware, 0);
//
// final String kernel = "C:\\Users\\fnuecke\\Documents\\Repositories\\Circuity-1.15\\buildroot\\Image";
// loadProgramFile(kernel, 0x400000);
//
// final UARTReader reader = new UARTReader(board);
// final Thread thread = new Thread(reader);
// thread.start();
//
// final int n = 10000000;
// for (int i = 0; i < n; i++) {
// board.step(100000);
// }
//
// reader.stop();
// thread.join();
}
private void loadProgram(final String... value) throws MemoryAccessException {
R5Assembler.assemble(value, memory, 0);
}
private void loadProgramFile(final String path, int address) throws Exception {
try (final FileInputStream is = new FileInputStream(path)) {
final BufferedInputStream bis = new BufferedInputStream(is);
for (int value = bis.read(); value != -1; value = bis.read()) {
memory.store8(address++, (byte) value);
}
}
}
private static final class UARTReader implements Runnable {
private final R5Board board;
private boolean keepRunning = true;
private UARTReader(final R5Board board) {
this.board = board;
}
public void stop() {
keepRunning = false;
}
@Override
public void run() {
try {
while (keepRunning) {
final int i = board.readValue();
if (i >= 0) {
System.out.print((char) i);
}
Thread.yield();
}
} catch (final Throwable t) {
LOGGER.error(t);
}
}
}
}

View File

@@ -0,0 +1,13 @@
package li.cil.circuity.vm.riscv;
import li.cil.circuity.vm.device.memory.ByteBufferMemory;
import org.junit.jupiter.api.Test;
public class VirtualMachineTests {
@Test
public void testVM() {
final R5Board virtualMachine = new R5Board();
virtualMachine.addDevice(new ByteBufferMemory(4 * 1024 * 1024));
virtualMachine.reset();
}
}