Way too late first proper commit.
This commit is contained in:
81
src/main/java/li/cil/circuity/Main.java
Normal file
81
src/main/java/li/cil/circuity/Main.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/main/java/li/cil/circuity/api/vm/Interrupt.java
Normal file
27
src/main/java/li/cil/circuity/api/vm/Interrupt.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/main/java/li/cil/circuity/api/vm/MemoryMap.java
Normal file
45
src/main/java/li/cil/circuity/api/vm/MemoryMap.java
Normal 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;
|
||||
}
|
||||
52
src/main/java/li/cil/circuity/api/vm/MemoryRange.java
Normal file
52
src/main/java/li/cil/circuity/api/vm/MemoryRange.java
Normal 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);
|
||||
}
|
||||
}
|
||||
4
src/main/java/li/cil/circuity/api/vm/device/Device.java
Normal file
4
src/main/java/li/cil/circuity/api/vm/device/Device.java
Normal file
@@ -0,0 +1,4 @@
|
||||
package li.cil.circuity.api.vm.device;
|
||||
|
||||
public interface Device {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
44
src/main/java/li/cil/circuity/api/vm/device/Interrupter.java
Normal file
44
src/main/java/li/cil/circuity/api/vm/device/Interrupter.java
Normal 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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package li.cil.circuity.api.vm.device;
|
||||
|
||||
public interface Resettable {
|
||||
void reset();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package li.cil.circuity.api.vm.device;
|
||||
|
||||
public interface Steppable extends Device {
|
||||
void step(final int cycles);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
107
src/main/java/li/cil/circuity/api/vm/devicetree/DeviceNames.java
Normal file
107
src/main/java/li/cil/circuity/api/vm/devicetree/DeviceNames.java
Normal 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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
150
src/main/java/li/cil/circuity/vm/SimpleMemoryMap.java
Normal file
150
src/main/java/li/cil/circuity/vm/SimpleMemoryMap.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
463
src/main/java/li/cil/circuity/vm/device/UART16550A.java
Normal file
463
src/main/java/li/cil/circuity/vm/device/UART16550A.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
193
src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeImpl.java
Normal file
193
src/main/java/li/cil/circuity/vm/devicetree/DeviceTreeImpl.java
Normal 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 == '-';
|
||||
}
|
||||
}
|
||||
@@ -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 == '#';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
176
src/main/java/li/cil/circuity/vm/riscv/R5.java
Normal file
176
src/main/java/li/cil/circuity/vm/riscv/R5.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
617
src/main/java/li/cil/circuity/vm/riscv/R5Assembler.java
Normal file
617
src/main/java/li/cil/circuity/vm/riscv/R5Assembler.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
238
src/main/java/li/cil/circuity/vm/riscv/R5Board.java
Normal file
238
src/main/java/li/cil/circuity/vm/riscv/R5Board.java
Normal 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;
|
||||
}
|
||||
}
|
||||
2311
src/main/java/li/cil/circuity/vm/riscv/R5CPU.java
Normal file
2311
src/main/java/li/cil/circuity/vm/riscv/R5CPU.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
1092
src/main/java/li/cil/circuity/vm/riscv/R5Disassembler.java
Normal file
1092
src/main/java/li/cil/circuity/vm/riscv/R5Disassembler.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
150
src/test/java/li/cil/circuity/vm/riscv/InstructionTests.java
Normal file
150
src/test/java/li/cil/circuity/vm/riscv/InstructionTests.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user