Moved linux hdd to VM to always be present.
More prepwork for low level devices on the bus.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
package li.cil.oc2.api.bus.device.sedna;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package li.cil.oc2.api.bus.device.sedna;
|
||||
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
|
||||
/**
|
||||
* Allows adding {@link MemoryMappedDevice}s directly to the underlying
|
||||
* virtual machine.
|
||||
* <p>
|
||||
* This is a more low-level approach than the {@link RPCDevice}. Devices
|
||||
* implemented through this interface will require explicit driver support
|
||||
* in the guest system.
|
||||
*/
|
||||
public interface SednaDevice extends Device {
|
||||
/**
|
||||
* Called to initialize this device.
|
||||
* <p>
|
||||
* Register {@link MemoryMappedDevice}s and claim interrupts via the
|
||||
* {@link InterruptAllocator} made available through the {@code context}.
|
||||
* <p>
|
||||
* If loading cannot complete, e.g. because resources cannot be allocated,
|
||||
* this should return {@code false}. The virtual machine will periodically
|
||||
* try again to load failed devices. The virtual machine will only start
|
||||
* running after all devices have successfully loaded.
|
||||
*
|
||||
* @param context the virtual machine context.
|
||||
* @return {@code true} if the device was loaded successfully; {@code false} otherwise.
|
||||
*/
|
||||
SednaDeviceLoadResult load(VirtualMachineContext context);
|
||||
|
||||
/**
|
||||
* Called when the device is unloaded.
|
||||
* <p>
|
||||
* This is guaranteed to be called when the device is disposed, be it because
|
||||
* it was removed from the {@link DeviceBus} or because of the virtual machine
|
||||
* being destroyed.
|
||||
* <p>
|
||||
* Release unmanaged resources here.
|
||||
*/
|
||||
void unload();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package li.cil.oc2.api.bus.device.sedna;
|
||||
|
||||
public final class SednaDeviceLoadResult {
|
||||
public static SednaDeviceLoadResult success() {
|
||||
return new SednaDeviceLoadResult(true);
|
||||
}
|
||||
|
||||
public static SednaDeviceLoadResult fail() {
|
||||
return new SednaDeviceLoadResult(false);
|
||||
}
|
||||
|
||||
private final boolean wasSuccessful;
|
||||
|
||||
private SednaDeviceLoadResult(final boolean wasSuccessful) {
|
||||
this.wasSuccessful = wasSuccessful;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return wasSuccessful;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package li.cil.oc2.api.bus.device.sedna;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
/**
|
||||
* Provides access to a virtual machine to low level devices.
|
||||
*
|
||||
* @see SednaDevice
|
||||
*/
|
||||
public interface VirtualMachineContext {
|
||||
/**
|
||||
* The memory of the virtual machine.
|
||||
* <p>
|
||||
* {@link MemoryMappedDevice}s can only be added inside {@link SednaDevice#load(VirtualMachineContext)}.
|
||||
* Trying to add devices after that method has returned will result in an exception.
|
||||
* <p>
|
||||
* Removing {@link MemoryMappedDevice}s is not supported. Added devices will
|
||||
* automatically removed when the {@link SednaDevice} that added it is unloaded,
|
||||
* e.g. because it has been removed from the {@link DeviceBus}.
|
||||
*
|
||||
* @return the memory map of the virtual machine.
|
||||
*/
|
||||
MemoryMap getMemoryMap();
|
||||
|
||||
/**
|
||||
* An object that allows claiming interrupts for use with the {@link InterruptController}.
|
||||
* <p>
|
||||
* Interrupts can only be claimed inside {@link SednaDevice#load(VirtualMachineContext)}.
|
||||
* Trying to claim interrupts after that method has returned will result in an exception.
|
||||
* <p>
|
||||
* Claimed interrupts will automatically be released when the {@link SednaDevice} that
|
||||
* claimed them is unloaded, e.g. because it is removed from the {@link DeviceBus}.
|
||||
*
|
||||
* @return the interrupt allocator.
|
||||
*/
|
||||
InterruptAllocator getInterruptAllocator();
|
||||
|
||||
/**
|
||||
* The interrupt controller of the virtual machine devices should attach to.
|
||||
* <p>
|
||||
* Raising or lowering interrupts that have not been claimed using the {@link InterruptAllocator}
|
||||
* made available through this instance will result in an exception.
|
||||
* <p>
|
||||
* Interrupts raised will automatically be lowered when the {@link SednaDevice} that
|
||||
* raised them is unloaded, e.g. because it is removed from the {@link DeviceBus}.
|
||||
*
|
||||
* @return the interrupt controller of the virtual machine.
|
||||
*/
|
||||
InterruptController getInterruptController();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.bus.device.sedna;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,6 +1,6 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.bus.device;
|
||||
package li.cil.oc2.api.imc;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
public interface MemoryMappedDeviceReference {
|
||||
boolean load(final MemoryMap memoryMap, final InterruptController interruptController);
|
||||
|
||||
void unload();
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
public interface VirtualMachineContext {
|
||||
MemoryMap getMemoryMap();
|
||||
|
||||
InterruptAllocator getInterruptAllocator();
|
||||
|
||||
InterruptController getInterruptController();
|
||||
}
|
||||
@@ -19,9 +19,7 @@ import li.cil.oc2.serialization.NBTSerialization;
|
||||
import li.cil.sedna.api.Sizes;
|
||||
import li.cil.sedna.api.device.PhysicalMemory;
|
||||
import li.cil.sedna.buildroot.Buildroot;
|
||||
import li.cil.sedna.device.block.ByteBufferBlockDevice;
|
||||
import li.cil.sedna.device.memory.Memory;
|
||||
import li.cil.sedna.device.virtio.VirtIOBlockDevice;
|
||||
import li.cil.sedna.device.virtio.VirtIOFileSystemDevice;
|
||||
import li.cil.sedna.fs.HostFileSystem;
|
||||
import li.cil.sedna.memory.PhysicalMemoryInputStream;
|
||||
@@ -41,7 +39,6 @@ import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -54,6 +51,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
private static final String BUS_STATE_NBT_TAG_NAME = "busState";
|
||||
private static final String TERMINAL_NBT_TAG_NAME = "terminal";
|
||||
private static final String VIRTUAL_MACHINE_NBT_TAG_NAME = "virtualMachine";
|
||||
private static final String VFS_NBT_TAG_NAME = "vfs";
|
||||
private static final String RUNNER_NBT_TAG_NAME = "runner";
|
||||
private static final String RUN_STATE_NBT_TAG_NAME = "runState";
|
||||
|
||||
@@ -77,11 +75,11 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
private final TileEntityDeviceBusElement busElement;
|
||||
private final Terminal terminal;
|
||||
private final VirtualMachine virtualMachine;
|
||||
private final VirtIOFileSystemDevice vfs;
|
||||
private ConsoleRunner runner;
|
||||
|
||||
private PhysicalMemory ram;
|
||||
private UUID ramBlobHandle;
|
||||
private VirtIOBlockDevice hdd;
|
||||
|
||||
public ComputerTileEntity() {
|
||||
super(OpenComputers.COMPUTER_TILE_ENTITY.get());
|
||||
@@ -94,6 +92,10 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
terminal = new Terminal();
|
||||
virtualMachine = new VirtualMachine(busController);
|
||||
|
||||
vfs = new VirtIOFileSystemDevice(virtualMachine.board.getMemoryMap(), "scripts", new HostFileSystem());
|
||||
vfs.getInterrupt().set(virtualMachine.vmAdapter.claimInterrupt(), virtualMachine.board.getInterruptController());
|
||||
virtualMachine.board.addDevice(vfs);
|
||||
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement);
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_CONTROLLER_CAPABILITY, busController);
|
||||
}
|
||||
@@ -213,7 +215,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(final BlockState state,final CompoundNBT tag) {
|
||||
public void handleUpdateTag(final BlockState state, final CompoundNBT tag) {
|
||||
super.handleUpdateTag(state, tag);
|
||||
|
||||
NBTSerialization.deserialize(tag.getCompound(TERMINAL_NBT_TAG_NAME), terminal);
|
||||
@@ -234,6 +236,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
|
||||
compound.put(BUS_ELEMENT_NBT_TAG_NAME, NBTSerialization.serialize(busElement));
|
||||
compound.put(VIRTUAL_MACHINE_NBT_TAG_NAME, NBTSerialization.serialize(virtualMachine));
|
||||
compound.put(VFS_NBT_TAG_NAME, NBTSerialization.serialize(vfs));
|
||||
|
||||
if (runner != null) {
|
||||
compound.put(RUNNER_NBT_TAG_NAME, NBTSerialization.serialize(runner));
|
||||
@@ -268,6 +271,10 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
NBTSerialization.deserialize(compound.getCompound(VIRTUAL_MACHINE_NBT_TAG_NAME), virtualMachine);
|
||||
}
|
||||
|
||||
if (compound.contains(VFS_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
NBTSerialization.deserialize(compound.getCompound(VFS_NBT_TAG_NAME), vfs);
|
||||
}
|
||||
|
||||
if (compound.contains(RUNNER_NBT_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
runner = new ConsoleRunner(virtualMachine);
|
||||
NBTSerialization.deserialize(compound.getCompound(RUNNER_NBT_TAG_NAME), runner);
|
||||
@@ -300,14 +307,6 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
} else {
|
||||
list.add(new CompoundNBT());
|
||||
}
|
||||
|
||||
if (hdd != null) {
|
||||
final CompoundNBT hddNbt = new CompoundNBT();
|
||||
hddNbt.put("hdd", NBTSerialization.serialize(hdd));
|
||||
list.add(hddNbt);
|
||||
} else {
|
||||
list.add(new CompoundNBT());
|
||||
}
|
||||
}
|
||||
|
||||
private void readDevices(final ListNBT list) {
|
||||
@@ -317,51 +316,23 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
if (ramNbt.hasUniqueId("ram")) {
|
||||
ramBlobHandle = ramNbt.getUniqueId("ram");
|
||||
}
|
||||
|
||||
final CompoundNBT hddNbt = list.getCompound(1);
|
||||
if (hddNbt.contains("hdd", NBTTagIds.TAG_COMPOUND)) {
|
||||
if (hdd == null) {
|
||||
hdd = new VirtIOBlockDevice(virtualMachine.board.getMemoryMap());
|
||||
hdd.getInterrupt().set(0x3, virtualMachine.board.getInterruptController());
|
||||
virtualMachine.board.addDevice(hdd);
|
||||
}
|
||||
|
||||
NBTSerialization.deserialize(hddNbt.getCompound("hdd"), hdd);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean loadDevices() {
|
||||
// TODO Load devices generated from items.
|
||||
|
||||
try {
|
||||
if (ram == null) {
|
||||
final int RAM_SIZE = 24 * 1024 * 1024;
|
||||
ram = Memory.create(RAM_SIZE);
|
||||
virtualMachine.board.addDevice(0x80000000L, ram);
|
||||
}
|
||||
|
||||
if (ramBlobHandle != null) {
|
||||
ramJobHandle = BlobStorage.JobHandle.combine(ramJobHandle,
|
||||
BlobStorage.submitLoad(ramBlobHandle, new PhysicalMemoryOutputStream(ram)));
|
||||
}
|
||||
|
||||
if (hdd == null) {
|
||||
hdd = new VirtIOBlockDevice(virtualMachine.board.getMemoryMap());
|
||||
hdd.getInterrupt().set(0x3, virtualMachine.board.getInterruptController());
|
||||
virtualMachine.board.addDevice(hdd);
|
||||
}
|
||||
|
||||
final ByteBufferBlockDevice blockDevice = ByteBufferBlockDevice.createFromStream(Buildroot.getRootFilesystem(), true);
|
||||
hdd.setBlockDevice(blockDevice);
|
||||
|
||||
final VirtIOFileSystemDevice vfs = new VirtIOFileSystemDevice(virtualMachine.board.getMemoryMap(), "scripts", new HostFileSystem());
|
||||
vfs.getInterrupt().set(0x4, virtualMachine.board.getInterruptController());
|
||||
virtualMachine.board.addDevice(vfs);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error(e);
|
||||
if (ram == null) {
|
||||
final int RAM_SIZE = 24 * 1024 * 1024;
|
||||
ram = Memory.create(RAM_SIZE);
|
||||
virtualMachine.board.addDevice(0x80000000L, ram);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (ramBlobHandle != null) {
|
||||
ramJobHandle = BlobStorage.JobHandle.combine(ramJobHandle,
|
||||
BlobStorage.submitLoad(ramBlobHandle, new PhysicalMemoryOutputStream(ram)));
|
||||
}
|
||||
|
||||
return virtualMachine.vmAdapter.load();
|
||||
}
|
||||
|
||||
private void unloadDevices() {
|
||||
@@ -373,16 +344,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
BlobStorage.freeHandle(ramBlobHandle);
|
||||
}
|
||||
|
||||
if (hdd != null) {
|
||||
virtualMachine.board.removeDevice(hdd);
|
||||
try {
|
||||
hdd.close();
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
|
||||
hdd = null;
|
||||
}
|
||||
virtualMachine.vmAdapter.unload();
|
||||
}
|
||||
|
||||
private void stopRunnerAndUnloadDevices() {
|
||||
|
||||
@@ -3,31 +3,56 @@ package li.cil.oc2.common.tile.computer;
|
||||
import li.cil.ceres.api.Serialized;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.common.bus.RPCAdapter;
|
||||
import li.cil.oc2.common.vm.VirtualMachineDeviceBusAdapter;
|
||||
import li.cil.sedna.buildroot.Buildroot;
|
||||
import li.cil.sedna.device.block.ByteBufferBlockDevice;
|
||||
import li.cil.sedna.device.rtc.GoldfishRTC;
|
||||
import li.cil.sedna.device.rtc.SystemTimeRealTimeCounter;
|
||||
import li.cil.sedna.device.serial.UART16550A;
|
||||
import li.cil.sedna.device.virtio.VirtIOBlockDevice;
|
||||
import li.cil.sedna.device.virtio.VirtIOConsoleDevice;
|
||||
import li.cil.sedna.riscv.R5Board;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class VirtualMachine {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@Serialized public R5Board board;
|
||||
@Serialized public VirtualMachineDeviceBusAdapter vmAdapter;
|
||||
@Serialized public UART16550A uart;
|
||||
@Serialized public VirtIOBlockDevice hdd;
|
||||
@Serialized public VirtIOConsoleDevice deviceBusSerialDevice;
|
||||
@Serialized public RPCAdapter rpcAdapter;
|
||||
|
||||
public VirtualMachine(final DeviceBusController busController) {
|
||||
board = new R5Board();
|
||||
vmAdapter = new VirtualMachineDeviceBusAdapter(board.getMemoryMap(), board.getInterruptController());
|
||||
|
||||
uart = new UART16550A();
|
||||
uart.getInterrupt().set(0x1, board.getInterruptController());
|
||||
uart.getInterrupt().set(vmAdapter.claimInterrupt(), board.getInterruptController());
|
||||
board.addDevice(uart);
|
||||
|
||||
hdd = new VirtIOBlockDevice(board.getMemoryMap());
|
||||
hdd.getInterrupt().set(vmAdapter.claimInterrupt(), board.getInterruptController());
|
||||
board.addDevice(hdd);
|
||||
|
||||
final ByteBufferBlockDevice blockDevice;
|
||||
try {
|
||||
blockDevice = ByteBufferBlockDevice.createFromStream(Buildroot.getRootFilesystem(), true);
|
||||
hdd.setBlockDevice(blockDevice);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
|
||||
final GoldfishRTC rtc = new GoldfishRTC(SystemTimeRealTimeCounter.get());
|
||||
rtc.getInterrupt().set(0x2, board.getInterruptController());
|
||||
rtc.getInterrupt().set(vmAdapter.claimInterrupt(), board.getInterruptController());
|
||||
board.addDevice(rtc);
|
||||
|
||||
deviceBusSerialDevice = new VirtIOConsoleDevice(board.getMemoryMap());
|
||||
deviceBusSerialDevice.getInterrupt().set(0x3, board.getInterruptController());
|
||||
deviceBusSerialDevice.getInterrupt().set(vmAdapter.claimInterrupt(), board.getInterruptController());
|
||||
board.addDevice(deviceBusSerialDevice);
|
||||
|
||||
rpcAdapter = new RPCAdapter(busController, deviceBusSerialDevice);
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.oc2.api.vm.InterruptAllocator;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public final class InterruptAllocatorImpl implements InterruptAllocator {
|
||||
private final BitSet interrupts;
|
||||
private final int interruptCount;
|
||||
private boolean isValid = true;
|
||||
|
||||
public InterruptAllocatorImpl(final BitSet interrupts, final int interruptCount) {
|
||||
this.interrupts = new BitSet(interruptCount);
|
||||
this.interrupts.or(interrupts);
|
||||
this.interruptCount = interruptCount;
|
||||
}
|
||||
|
||||
public BitSet complete() {
|
||||
isValid = false;
|
||||
return interrupts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt(final int interrupt) {
|
||||
if (!isValid) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
if (interrupts.get(interrupt)) {
|
||||
return claimInterrupt();
|
||||
} else {
|
||||
interrupts.set(interrupt);
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt() {
|
||||
if (!isValid) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
final int interrupt = interrupts.nextClearBit(0);
|
||||
if (interrupt >= interruptCount) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
interrupts.set(interrupt);
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.oc2.api.bus.device.sedna.InterruptAllocator;
|
||||
import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public final class ManagedInterruptAllocator implements InterruptAllocator {
|
||||
private final BitSet interrupts;
|
||||
private final BitSet reservedInterrupts;
|
||||
private final BitSet managedInterrupts;
|
||||
private final int interruptCount;
|
||||
private boolean isFrozen;
|
||||
private int managedMask;
|
||||
|
||||
public ManagedInterruptAllocator(final BitSet interrupts, final BitSet reservedInterrupts, final int interruptCount) {
|
||||
this.interrupts = interrupts;
|
||||
this.reservedInterrupts = reservedInterrupts;
|
||||
this.managedInterrupts = new BitSet();
|
||||
this.interruptCount = interruptCount;
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
isFrozen = true;
|
||||
managedMask = (int) managedInterrupts.toLongArray()[0];
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
interrupts.andNot(managedInterrupts);
|
||||
managedMask = 0;
|
||||
}
|
||||
|
||||
public boolean isMaskValid(final int mask) {
|
||||
return (mask & ~managedMask) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt(final int interrupt) {
|
||||
if (isFrozen) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (interrupt < 1 || interrupt > R5PlatformLevelInterruptController.INTERRUPT_COUNT) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
final int interruptBit = interrupt - 1;
|
||||
if (interrupts.get(interruptBit)) {
|
||||
return claimInterrupt();
|
||||
} else {
|
||||
interrupts.set(interruptBit);
|
||||
reservedInterrupts.set(interruptBit);
|
||||
managedInterrupts.set(interruptBit);
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt() {
|
||||
if (isFrozen) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
final int interruptBit = reservedInterrupts.nextClearBit(0);
|
||||
if (interruptBit >= interruptCount) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
interrupts.set(interruptBit);
|
||||
reservedInterrupts.set(interruptBit);
|
||||
managedInterrupts.set(interruptBit);
|
||||
|
||||
final int interrupt = interruptBit + 1;
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
|
||||
public final class ManagedInterruptController implements InterruptController {
|
||||
private final InterruptController interruptController;
|
||||
private final ManagedInterruptAllocator allocator;
|
||||
private int managedInterrupts = 0;
|
||||
private boolean isValid = true;
|
||||
|
||||
public ManagedInterruptController(final InterruptController interruptController, final ManagedInterruptAllocator allocator) {
|
||||
this.interruptController = interruptController;
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
isValid = false;
|
||||
interruptController.lowerInterrupts(managedInterrupts);
|
||||
managedInterrupts = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raiseInterrupts(final int mask) {
|
||||
if (!isValid) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
if (allocator.isMaskValid(mask)) {
|
||||
interruptController.raiseInterrupts(mask);
|
||||
managedInterrupts |= mask;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Trying to raise interrupt not allocated by this context.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lowerInterrupts(final int mask) {
|
||||
if (!isValid) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
if (allocator.isMaskValid(mask)) {
|
||||
interruptController.lowerInterrupts(mask);
|
||||
managedInterrupts &= ~managedInterrupts;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Trying to lower interrupt not allocated by this context.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRaisedInterrupts() {
|
||||
return interruptController.getRaisedInterrupts();
|
||||
}
|
||||
}
|
||||
81
src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java
Normal file
81
src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.memory.MemoryAccessException;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
import li.cil.sedna.api.memory.MemoryRange;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
final class ManagedMemoryMap implements MemoryMap {
|
||||
private final MemoryMap memoryMap;
|
||||
private final ArrayList<MemoryMappedDevice> managedDevices = new ArrayList<>();
|
||||
private boolean isFrozen;
|
||||
|
||||
ManagedMemoryMap(final MemoryMap memoryMap) {
|
||||
this.memoryMap = memoryMap;
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
isFrozen = true;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
for (final MemoryMappedDevice device : managedDevices) {
|
||||
memoryMap.removeDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalLong findFreeRange(final long start, final long end, final int size) {
|
||||
return memoryMap.findFreeRange(start, end, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDevice(final long address, final MemoryMappedDevice device) {
|
||||
if (isFrozen) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (memoryMap.addDevice(address, device)) {
|
||||
managedDevices.add(device);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDevice(final MemoryMappedDevice device) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MemoryRange> getMemoryRange(final MemoryMappedDevice device) {
|
||||
return memoryMap.getMemoryRange(device);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MemoryRange getMemoryRange(final long address) {
|
||||
return memoryMap.getMemoryRange(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirty(final MemoryRange range, final int offset) {
|
||||
memoryMap.setDirty(range, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long load(final long address, final int sizeLog2) throws MemoryAccessException {
|
||||
return memoryMap.load(address, sizeLog2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final long address, final long value, final int sizeLog2) throws MemoryAccessException {
|
||||
memoryMap.store(address, value, sizeLog2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.oc2.api.bus.device.sedna.InterruptAllocator;
|
||||
import li.cil.oc2.api.bus.device.sedna.VirtualMachineContext;
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public final class ManagedVirtualMachineContext implements VirtualMachineContext {
|
||||
private final ManagedMemoryMap memoryMap;
|
||||
private final ManagedInterruptAllocator interruptAllocator;
|
||||
private final ManagedInterruptController interruptController;
|
||||
|
||||
public ManagedVirtualMachineContext(final MemoryMap memoryMap, final InterruptController interruptController, final BitSet allocatedInterrupts, final BitSet reservedInterrupts) {
|
||||
this.memoryMap = new ManagedMemoryMap(memoryMap);
|
||||
this.interruptAllocator = new ManagedInterruptAllocator(allocatedInterrupts, reservedInterrupts, R5PlatformLevelInterruptController.INTERRUPT_COUNT);
|
||||
this.interruptController = new ManagedInterruptController(interruptController, interruptAllocator);
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
memoryMap.freeze();
|
||||
interruptAllocator.freeze();
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
memoryMap.invalidate();
|
||||
interruptAllocator.invalidate();
|
||||
interruptController.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryMap getMemoryMap() {
|
||||
return memoryMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterruptAllocator getInterruptAllocator() {
|
||||
return interruptAllocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterruptController getInterruptController() {
|
||||
return interruptController;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.ceres.api.Serialized;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.sedna.SednaDevice;
|
||||
import li.cil.oc2.api.bus.device.sedna.SednaDeviceLoadResult;
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class VirtualMachineDeviceBusAdapter {
|
||||
private final MemoryMap memoryMap;
|
||||
private final InterruptController interruptController;
|
||||
|
||||
private final BitSet allocatedInterrupts = new BitSet();
|
||||
private final HashMap<SednaDevice, ManagedVirtualMachineContext> deviceContexts = new HashMap<>();
|
||||
private final ArrayList<SednaDevice> incompleteLoads = new ArrayList<>();
|
||||
|
||||
// This is a superset of allocatedInterrupts. We use this so that after loading we
|
||||
// avoid potentially new devices (due external code changes, etc.) to grab interrupts
|
||||
// previously used by other devices. Only claiming interrupts explicitly will allow
|
||||
// grabbing reserved interrupts.
|
||||
@Serialized @SuppressWarnings("FieldMayBeFinal") private BitSet reservedInterrupts = new BitSet();
|
||||
|
||||
public VirtualMachineDeviceBusAdapter(final MemoryMap memoryMap, final InterruptController interruptController) {
|
||||
this.memoryMap = memoryMap;
|
||||
this.interruptController = interruptController;
|
||||
}
|
||||
|
||||
public int claimInterrupt() {
|
||||
return claimInterrupt(allocatedInterrupts.nextClearBit(0) + 1);
|
||||
}
|
||||
|
||||
public int claimInterrupt(final int interrupt) {
|
||||
if (interrupt < 1 || interrupt > R5PlatformLevelInterruptController.INTERRUPT_COUNT) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
allocatedInterrupts.set(interrupt - 1);
|
||||
return interrupt;
|
||||
}
|
||||
|
||||
public boolean load() {
|
||||
for (int i = incompleteLoads.size() - 1; i >= 0; i--) {
|
||||
final SednaDevice device = incompleteLoads.remove(i);
|
||||
|
||||
final ManagedVirtualMachineContext context = new ManagedVirtualMachineContext(
|
||||
memoryMap, interruptController, allocatedInterrupts, reservedInterrupts);
|
||||
deviceContexts.put(device, context);
|
||||
|
||||
final SednaDeviceLoadResult result = device.load(context);
|
||||
context.freeze();
|
||||
|
||||
if (!result.wasSuccessful()) {
|
||||
context.invalidate();
|
||||
incompleteLoads.add(device);
|
||||
}
|
||||
}
|
||||
|
||||
return incompleteLoads.isEmpty();
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
deviceContexts.forEach((device, context) -> {
|
||||
context.invalidate();
|
||||
device.unload();
|
||||
});
|
||||
|
||||
incompleteLoads.clear();
|
||||
incompleteLoads.addAll(deviceContexts.keySet());
|
||||
}
|
||||
|
||||
public void setDevices(final Collection<Device> devices) {
|
||||
final HashSet<SednaDevice> oldDevices = new HashSet<>(deviceContexts.keySet());
|
||||
final HashSet<SednaDevice> newDevices = new HashSet<>();
|
||||
for (final Device device : devices) {
|
||||
if (device instanceof SednaDevice) {
|
||||
newDevices.add((SednaDevice) device);
|
||||
}
|
||||
}
|
||||
|
||||
final HashSet<SednaDevice> removedDevices = new HashSet<>(oldDevices);
|
||||
removedDevices.removeAll(newDevices);
|
||||
for (final SednaDevice device : removedDevices) {
|
||||
deviceContexts.remove(device).invalidate();
|
||||
incompleteLoads.remove(device);
|
||||
device.unload();
|
||||
}
|
||||
|
||||
final HashSet<SednaDevice> addedDevices = new HashSet<>(newDevices);
|
||||
addedDevices.removeAll(oldDevices);
|
||||
for (final SednaDevice device : addedDevices) {
|
||||
deviceContexts.put(device, null);
|
||||
incompleteLoads.add(device);
|
||||
}
|
||||
|
||||
reservedInterrupts.clear();
|
||||
reservedInterrupts.or(allocatedInterrupts);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user