From 32169e05bb42e55c313f43d8738a085b1a7f91b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 8 Dec 2020 19:43:46 +0100 Subject: [PATCH] Moved linux hdd to VM to always be present. More prepwork for low level devices on the bus. --- .../device/sedna}/InterruptAllocator.java | 2 +- .../oc2/api/bus/device/sedna/SednaDevice.java | 43 ++++++++ .../device/sedna/SednaDeviceLoadResult.java | 21 ++++ .../device/sedna/VirtualMachineContext.java | 53 +++++++++ .../api/bus/device/sedna/package-info.java | 7 ++ .../api/{bus/device => imc}/package-info.java | 2 +- .../api/vm/MemoryMappedDeviceReference.java | 10 -- .../cil/oc2/api/vm/VirtualMachineContext.java | 12 --- .../oc2/common/tile/ComputerTileEntity.java | 84 ++++----------- .../common/tile/computer/VirtualMachine.java | 31 +++++- .../oc2/common/vm/InterruptAllocatorImpl.java | 52 --------- .../common/vm/ManagedInterruptAllocator.java | 77 +++++++++++++ .../common/vm/ManagedInterruptController.java | 54 ++++++++++ .../cil/oc2/common/vm/ManagedMemoryMap.java | 81 ++++++++++++++ .../vm/ManagedVirtualMachineContext.java | 47 ++++++++ .../vm/VirtualMachineDeviceBusAdapter.java | 102 ++++++++++++++++++ 16 files changed, 538 insertions(+), 140 deletions(-) rename src/main/java/li/cil/oc2/api/{vm => bus/device/sedna}/InterruptAllocator.java (78%) create mode 100644 src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDevice.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/sedna/SednaDeviceLoadResult.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/sedna/VirtualMachineContext.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/sedna/package-info.java rename src/main/java/li/cil/oc2/api/{bus/device => imc}/package-info.java (82%) delete mode 100644 src/main/java/li/cil/oc2/api/vm/MemoryMappedDeviceReference.java delete mode 100644 src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java delete mode 100644 src/main/java/li/cil/oc2/common/vm/InterruptAllocatorImpl.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedVirtualMachineContext.java create mode 100644 src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java 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 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 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); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVirtualMachineContext.java b/src/main/java/li/cil/oc2/common/vm/ManagedVirtualMachineContext.java new file mode 100644 index 00000000..3e8181bc --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/ManagedVirtualMachineContext.java @@ -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; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java new file mode 100644 index 00000000..4e9010a0 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java @@ -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 deviceContexts = new HashMap<>(); + private final ArrayList 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 devices) { + final HashSet oldDevices = new HashSet<>(deviceContexts.keySet()); + final HashSet newDevices = new HashSet<>(); + for (final Device device : devices) { + if (device instanceof SednaDevice) { + newDevices.add((SednaDevice) device); + } + } + + final HashSet removedDevices = new HashSet<>(oldDevices); + removedDevices.removeAll(newDevices); + for (final SednaDevice device : removedDevices) { + deviceContexts.remove(device).invalidate(); + incompleteLoads.remove(device); + device.unload(); + } + + final HashSet addedDevices = new HashSet<>(newDevices); + addedDevices.removeAll(oldDevices); + for (final SednaDevice device : addedDevices) { + deviceContexts.put(device, null); + incompleteLoads.add(device); + } + + reservedInterrupts.clear(); + reservedInterrupts.or(allocatedInterrupts); + } +}