Moved linux hdd to VM to always be present.

More prepwork for low level devices on the bus.
This commit is contained in:
Florian Nücke
2020-12-08 19:43:46 +01:00
parent 86f6e34d27
commit 32169e05bb
16 changed files with 538 additions and 140 deletions

View File

@@ -1,4 +1,4 @@
package li.cil.oc2.api.vm;
package li.cil.oc2.api.bus.device.sedna;
import java.util.OptionalInt;

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.api.bus.device.sedna;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,6 +1,6 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.api.bus.device;
package li.cil.oc2.api.imc;
import mcp.MethodsReturnNonnullByDefault;

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}