diff --git a/src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/sedna/InterruptAllocator.java similarity index 78% rename from src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java rename to src/main/java/li/cil/oc2/api/bus/device/sedna/InterruptAllocator.java index e7dfafb7..d29714dd 100644 --- a/src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java +++ b/src/main/java/li/cil/oc2/api/bus/device/sedna/InterruptAllocator.java @@ -1,4 +1,4 @@ -package li.cil.oc2.api.vm; +package li.cil.oc2.api.bus.device.sedna; import java.util.OptionalInt; diff --git a/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDevice.java b/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDevice.java new file mode 100644 index 00000000..618d51f1 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDevice.java @@ -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. + *
+ * 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. + *
+ * Register {@link MemoryMappedDevice}s and claim interrupts via the + * {@link InterruptAllocator} made available through the {@code context}. + *
+ * 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. + *
+ * 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. + *
+ * Release unmanaged resources here. + */ + void unload(); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDeviceLoadResult.java b/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDeviceLoadResult.java new file mode 100644 index 00000000..94e8a590 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDeviceLoadResult.java @@ -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; + } +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/sedna/VirtualMachineContext.java b/src/main/java/li/cil/oc2/api/bus/device/sedna/VirtualMachineContext.java new file mode 100644 index 00000000..7d38ccb3 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/sedna/VirtualMachineContext.java @@ -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. + *
+ * {@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. + *
+ * 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}. + *
+ * Interrupts can only be claimed inside {@link SednaDevice#load(VirtualMachineContext)}. + * Trying to claim interrupts after that method has returned will result in an exception. + *
+ * 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. + *
+ * Raising or lowering interrupts that have not been claimed using the {@link InterruptAllocator} + * made available through this instance will result in an exception. + *
+ * 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();
+}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/sedna/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/sedna/package-info.java
new file mode 100644
index 00000000..a7e4ab27
--- /dev/null
+++ b/src/main/java/li/cil/oc2/api/bus/device/sedna/package-info.java
@@ -0,0 +1,7 @@
+@ParametersAreNonnullByDefault
+@MethodsReturnNonnullByDefault
+package li.cil.oc2.api.bus.device.sedna;
+
+import mcp.MethodsReturnNonnullByDefault;
+
+import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file
diff --git a/src/main/java/li/cil/oc2/api/bus/device/package-info.java b/src/main/java/li/cil/oc2/api/imc/package-info.java
similarity index 82%
rename from src/main/java/li/cil/oc2/api/bus/device/package-info.java
rename to src/main/java/li/cil/oc2/api/imc/package-info.java
index fc476f91..ffe7393c 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/package-info.java
+++ b/src/main/java/li/cil/oc2/api/imc/package-info.java
@@ -1,6 +1,6 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
-package li.cil.oc2.api.bus.device;
+package li.cil.oc2.api.imc;
import mcp.MethodsReturnNonnullByDefault;
diff --git a/src/main/java/li/cil/oc2/api/vm/MemoryMappedDeviceReference.java b/src/main/java/li/cil/oc2/api/vm/MemoryMappedDeviceReference.java
deleted file mode 100644
index 1a523330..00000000
--- a/src/main/java/li/cil/oc2/api/vm/MemoryMappedDeviceReference.java
+++ /dev/null
@@ -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();
-}
diff --git a/src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java b/src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java
deleted file mode 100644
index 5b4722a6..00000000
--- a/src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java
+++ /dev/null
@@ -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();
-}
diff --git a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java
index 4fa256e1..38a9fbb2 100644
--- a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java
+++ b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java
@@ -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() {
diff --git a/src/main/java/li/cil/oc2/common/tile/computer/VirtualMachine.java b/src/main/java/li/cil/oc2/common/tile/computer/VirtualMachine.java
index 0f09a9ec..9c774bbe 100644
--- a/src/main/java/li/cil/oc2/common/tile/computer/VirtualMachine.java
+++ b/src/main/java/li/cil/oc2/common/tile/computer/VirtualMachine.java
@@ -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);
diff --git a/src/main/java/li/cil/oc2/common/vm/InterruptAllocatorImpl.java b/src/main/java/li/cil/oc2/common/vm/InterruptAllocatorImpl.java
deleted file mode 100644
index 79a94e97..00000000
--- a/src/main/java/li/cil/oc2/common/vm/InterruptAllocatorImpl.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java
new file mode 100644
index 00000000..b984d341
--- /dev/null
+++ b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java
@@ -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);
+ }
+}
diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java
new file mode 100644
index 00000000..ff38d2c9
--- /dev/null
+++ b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java
@@ -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();
+ }
+}
diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java
new file mode 100644
index 00000000..b407dd6f
--- /dev/null
+++ b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java
@@ -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