From b53f85cca2a2ee2739bbc37996b0247056245224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 12 Dec 2020 13:35:58 +0100 Subject: [PATCH] Analogous to interrupt allocator added memory range allocator for adding devices. --- .../bus/device/vm/MemoryRangeAllocator.java | 11 +++ .../cil/oc2/api/bus/device/vm/VMContext.java | 50 ++++++---- .../common/vm/ManagedInterruptAllocator.java | 22 ++--- .../common/vm/ManagedInterruptController.java | 16 ++-- .../cil/oc2/common/vm/ManagedMemoryMap.java | 24 +---- .../vm/ManagedMemoryRangeAllocator.java | 62 ++++++++++++ .../cil/oc2/common/vm/ManagedVMContext.java | 34 ++++--- .../li/cil/oc2/common/vm/VirtualMachine.java | 2 +- .../vm/VirtualMachineDeviceBusAdapter.java | 23 ++--- .../li/cil/oc2/common/bus/VMDeviceTests.java | 96 +++++++++++++++++-- 10 files changed, 244 insertions(+), 96 deletions(-) create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java new file mode 100644 index 00000000..6cfe8382 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryRangeAllocator.java @@ -0,0 +1,11 @@ +package li.cil.oc2.api.bus.device.vm; + +import li.cil.sedna.api.device.MemoryMappedDevice; + +import java.util.OptionalLong; + +public interface MemoryRangeAllocator { + OptionalLong claimMemoryRange(long address, MemoryMappedDevice device); + + OptionalLong claimMemoryRange(MemoryMappedDevice device); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java index 339dc6fb..d990041a 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java @@ -14,32 +14,16 @@ public interface VMContext { /** * The memory of the virtual machine. *

- * {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#load(VMContext)}. - * 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 VMDevice} that added it is unloaded, - * e.g. because it has been removed from the {@link DeviceBus}. + * Adding or removing {@link MemoryMappedDevice}s directly is not supported. + * Use the {@link MemoryRangeAllocator} provided by {@link #getMemoryRangeAllocator()} + * to add devices. * * @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 VMDevice#load(VMContext)}. - * Trying to claim interrupts after that method has returned will result in an exception. - *

- * Claimed interrupts will automatically be released when the {@link VMDevice} 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. + * The interrupt controller of the virtual machine. *

* Raising or lowering interrupts that have not been claimed using the {@link InterruptAllocator} * made available through this instance will result in an exception. @@ -50,4 +34,30 @@ public interface VMContext { * @return the interrupt controller of the virtual machine. */ InterruptController getInterruptController(); + + /** + * Allows adding {@link MemoryMappedDevice}s to the VM's {@link MemoryMap}. + *

+ * {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#load(VMContext)}. + * Trying to add devices after that method has returned will result in an exception. + *

+ * Added devices will be automatically removed when the {@link VMDevice} that added it + * is unloaded, e.g. because it has been removed from the {@link DeviceBus}. + * + * @return the memory range allocator. + */ + MemoryRangeAllocator getMemoryRangeAllocator(); + + /** + * Allows claiming interrupts for use with the VM's {@link InterruptController}. + *

+ * Interrupts can only be claimed inside {@link VMDevice#load(VMContext)}. + * Trying to claim interrupts after that method has returned will result in an exception. + *

+ * Claimed interrupts will automatically be released when the {@link VMDevice} that + * claimed them is unloaded, e.g. because it is removed from the {@link DeviceBus}. + * + * @return the interrupt allocator. + */ + InterruptAllocator getInterruptAllocator(); } diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java index cccc3a8a..b2de8723 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptAllocator.java @@ -7,7 +7,7 @@ import java.util.BitSet; import java.util.OptionalInt; public final class ManagedInterruptAllocator implements InterruptAllocator { - private final BitSet interrupts; + private final BitSet claimedInterrupts; private final BitSet reservedInterrupts; private final BitSet managedInterrupts; private final int interruptCount; @@ -16,8 +16,8 @@ public final class ManagedInterruptAllocator implements InterruptAllocator { /////////////////////////////////////////////////////////////////// - public ManagedInterruptAllocator(final BitSet interrupts, final BitSet reservedInterrupts, final int interruptCount) { - this.interrupts = interrupts; + public ManagedInterruptAllocator(final BitSet claimedInterrupts, final BitSet reservedInterrupts, final int interruptCount) { + this.claimedInterrupts = claimedInterrupts; this.reservedInterrupts = reservedInterrupts; this.managedInterrupts = new BitSet(interruptCount); this.interruptCount = interruptCount; @@ -30,7 +30,7 @@ public final class ManagedInterruptAllocator implements InterruptAllocator { } public void invalidate() { - interrupts.andNot(managedInterrupts); + claimedInterrupts.andNot(managedInterrupts); managedMask = 0; } @@ -48,10 +48,10 @@ public final class ManagedInterruptAllocator implements InterruptAllocator { throw new IllegalArgumentException(); } - if (interrupts.get(interrupt)) { + if (claimedInterrupts.get(interrupt)) { return claimInterrupt(); } else { - interrupts.set(interrupt); + claimedInterrupts.set(interrupt); reservedInterrupts.set(interrupt); managedInterrupts.set(interrupt); return OptionalInt.of(interrupt); @@ -64,16 +64,16 @@ public final class ManagedInterruptAllocator implements InterruptAllocator { throw new IllegalStateException(); } - final BitSet claimedInterrupts = new BitSet(); - claimedInterrupts.or(interrupts); - claimedInterrupts.or(reservedInterrupts); + final BitSet allClaimedInterrupts = new BitSet(); + allClaimedInterrupts.or(claimedInterrupts); + allClaimedInterrupts.or(reservedInterrupts); - final int interrupt = claimedInterrupts.nextClearBit(0); + final int interrupt = allClaimedInterrupts.nextClearBit(0); if (interrupt >= interruptCount) { return OptionalInt.empty(); } - interrupts.set(interrupt); + claimedInterrupts.set(interrupt); reservedInterrupts.set(interrupt); managedInterrupts.set(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 index 2e0330b1..34eca29f 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedInterruptController.java @@ -5,7 +5,7 @@ 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 int raisedInterrupts = 0; private boolean isValid = true; /////////////////////////////////////////////////////////////////// @@ -17,19 +17,19 @@ public final class ManagedInterruptController implements InterruptController { public void invalidate() { isValid = false; - interruptController.lowerInterrupts(managedInterrupts); - managedInterrupts = 0; + interruptController.lowerInterrupts(raisedInterrupts); + raisedInterrupts = 0; } @Override public void raiseInterrupts(final int mask) { if (!isValid) { - throw new IllegalArgumentException(); + throw new IllegalStateException(); } if (allocator.isMaskValid(mask)) { interruptController.raiseInterrupts(mask); - managedInterrupts |= mask; + raisedInterrupts |= mask; } else { throw new IllegalArgumentException("Trying to raise interrupt not allocated by this context."); } @@ -38,12 +38,12 @@ public final class ManagedInterruptController implements InterruptController { @Override public void lowerInterrupts(final int mask) { if (!isValid) { - throw new IllegalArgumentException(); + throw new IllegalStateException(); } if (allocator.isMaskValid(mask)) { interruptController.lowerInterrupts(mask); - managedInterrupts &= ~managedInterrupts; + raisedInterrupts &= ~mask; } else { throw new IllegalArgumentException("Trying to lower interrupt not allocated by this context."); } @@ -51,6 +51,6 @@ public final class ManagedInterruptController implements InterruptController { @Override public int getRaisedInterrupts() { - return interruptController.getRaisedInterrupts(); + return raisedInterrupts; } } diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java index 87810d36..b9a5e453 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryMap.java @@ -6,14 +6,11 @@ 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; /////////////////////////////////////////////////////////////////// @@ -21,16 +18,6 @@ final class ManagedMemoryMap implements 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); @@ -38,16 +25,7 @@ final class ManagedMemoryMap implements MemoryMap { @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; - } + throw new UnsupportedOperationException(); } @Override diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java new file mode 100644 index 00000000..65df3d00 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryRangeAllocator.java @@ -0,0 +1,62 @@ +package li.cil.oc2.common.vm; + +import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; +import li.cil.sedna.api.Board; +import li.cil.sedna.api.device.MemoryMappedDevice; +import li.cil.sedna.api.memory.MemoryRange; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.OptionalLong; + +public final class ManagedMemoryRangeAllocator implements MemoryRangeAllocator { + private final Board board; + private final ArrayList managedDevices = new ArrayList<>(); + private boolean isFrozen; + + public ManagedMemoryRangeAllocator(final Board board) { + this.board = board; + } + + public void freeze() { + isFrozen = true; + } + + public void invalidate() { + for (final MemoryMappedDevice device : managedDevices) { + board.removeDevice(device); + } + managedDevices.clear(); + } + + @Override + public OptionalLong claimMemoryRange(final long address, final MemoryMappedDevice device) { + if (isFrozen) { + throw new IllegalStateException(); + } + + if (board.addDevice(address, device)) { + managedDevices.add(device); + return OptionalLong.of(address); + } + + return claimMemoryRange(device); + } + + @Override + public OptionalLong claimMemoryRange(final MemoryMappedDevice device) { + if (isFrozen) { + throw new IllegalStateException(); + } + + if (!board.addDevice(device)) { + return OptionalLong.empty(); + } + + final Optional range = board.getMemoryMap().getMemoryRange(device); + assert range.isPresent(); + + managedDevices.add(device); + return OptionalLong.of(range.get().address()); + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java index c833321f..07cbb145 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java @@ -1,33 +1,36 @@ package li.cil.oc2.common.vm; import li.cil.oc2.api.bus.device.vm.InterruptAllocator; +import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator; import li.cil.oc2.api.bus.device.vm.VMContext; +import li.cil.sedna.api.Board; 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 ManagedVMContext implements VMContext { private final ManagedMemoryMap memoryMap; - private final ManagedInterruptAllocator interruptAllocator; private final ManagedInterruptController interruptController; + private final ManagedMemoryRangeAllocator memoryRangeAllocator; + private final ManagedInterruptAllocator interruptAllocator; /////////////////////////////////////////////////////////////////// - public ManagedVMContext(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 ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts) { + this.memoryRangeAllocator = new ManagedMemoryRangeAllocator(board); + this.interruptAllocator = new ManagedInterruptAllocator(claimedInterrupts, reservedInterrupts, board.getInterruptCount()); + this.memoryMap = new ManagedMemoryMap(board.getMemoryMap()); + this.interruptController = new ManagedInterruptController(board.getInterruptController(), interruptAllocator); } public void freeze() { - memoryMap.freeze(); + memoryRangeAllocator.freeze(); interruptAllocator.freeze(); } public void invalidate() { - memoryMap.invalidate(); + memoryRangeAllocator.invalidate(); interruptAllocator.invalidate(); interruptController.invalidate(); } @@ -37,13 +40,18 @@ public final class ManagedVMContext implements VMContext { return memoryMap; } - @Override - public InterruptAllocator getInterruptAllocator() { - return interruptAllocator; - } - @Override public InterruptController getInterruptController() { return interruptController; } + + @Override + public MemoryRangeAllocator getMemoryRangeAllocator() { + return memoryRangeAllocator; + } + + @Override + public InterruptAllocator getInterruptAllocator() { + return interruptAllocator; + } } diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java index 97bd96a3..d2346186 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachine.java @@ -45,7 +45,7 @@ public final class VirtualMachine { board.getCpu().setFrequency(REPORTED_CPU_FREQUENCY); - vmAdapter = new VirtualMachineDeviceBusAdapter(board.getMemoryMap(), board.getInterruptController()); + vmAdapter = new VirtualMachineDeviceBusAdapter(board); uart = new UART16550A(); uart.getInterrupt().set(vmAdapter.claimInterrupt(), board.getInterruptController()); diff --git a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java index 845e9247..8934312a 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java @@ -4,8 +4,7 @@ import li.cil.ceres.api.Serialized; import li.cil.oc2.api.bus.Device; import li.cil.oc2.api.bus.device.vm.VMDevice; import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; -import li.cil.sedna.api.device.InterruptController; -import li.cil.sedna.api.memory.MemoryMap; +import li.cil.sedna.api.Board; import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController; import java.util.ArrayList; @@ -14,10 +13,9 @@ import java.util.HashMap; import java.util.Set; public final class VirtualMachineDeviceBusAdapter { - private final MemoryMap memoryMap; - private final InterruptController interruptController; + private final Board board; - private final BitSet allocatedInterrupts = new BitSet(); + private final BitSet claimedInterrupts = new BitSet(); private final HashMap deviceContexts = new HashMap<>(); private final ArrayList incompleteLoads = new ArrayList<>(); @@ -31,21 +29,20 @@ public final class VirtualMachineDeviceBusAdapter { /////////////////////////////////////////////////////////////////// - public VirtualMachineDeviceBusAdapter(final MemoryMap memoryMap, final InterruptController interruptController) { - this.memoryMap = memoryMap; - this.interruptController = interruptController; + public VirtualMachineDeviceBusAdapter(final Board board) { + this.board = board; } public int claimInterrupt() { - return claimInterrupt(allocatedInterrupts.nextClearBit(0) + 1); + return claimInterrupt(claimedInterrupts.nextClearBit(0)); } public int claimInterrupt(final int interrupt) { - if (interrupt < 1 || interrupt > R5PlatformLevelInterruptController.INTERRUPT_COUNT) { + if (interrupt < 0 || interrupt >= R5PlatformLevelInterruptController.INTERRUPT_COUNT) { throw new IllegalArgumentException(); } - allocatedInterrupts.set(interrupt - 1); + claimedInterrupts.set(interrupt); return interrupt; } @@ -54,7 +51,7 @@ public final class VirtualMachineDeviceBusAdapter { final VMDevice device = incompleteLoads.remove(i); final ManagedVMContext context = new ManagedVMContext( - memoryMap, interruptController, allocatedInterrupts, reservedInterrupts); + board, claimedInterrupts, reservedInterrupts); deviceContexts.put(device, context); final VMDeviceLoadResult result = device.load(context); @@ -71,7 +68,7 @@ public final class VirtualMachineDeviceBusAdapter { } reservedInterrupts.clear(); - reservedInterrupts.or(allocatedInterrupts); + reservedInterrupts.or(claimedInterrupts); return true; } diff --git a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java index 28ed1ec5..d26091ef 100644 --- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java +++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java @@ -4,7 +4,9 @@ import li.cil.oc2.api.bus.device.vm.VMContext; import li.cil.oc2.api.bus.device.vm.VMDevice; import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; import li.cil.oc2.common.vm.VirtualMachineDeviceBusAdapter; +import li.cil.sedna.api.Board; import li.cil.sedna.api.device.InterruptController; +import li.cil.sedna.api.device.MemoryMappedDevice; import li.cil.sedna.api.memory.MemoryMap; import li.cil.sedna.memory.SimpleMemoryMap; import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController; @@ -13,6 +15,7 @@ import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.OptionalInt; +import java.util.OptionalLong; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -27,7 +30,27 @@ public final class VMDeviceTests { public void setupEach() { memoryMap = new SimpleMemoryMap(); interruptController = new R5PlatformLevelInterruptController(); - adapter = new VirtualMachineDeviceBusAdapter(memoryMap, interruptController); + + final Board board = mock(Board.class); + when(board.getMemoryMap()).thenReturn(memoryMap); + when(board.getInterruptController()).thenReturn(interruptController); + when(board.getInterruptCount()).thenReturn(16); + when(board.addDevice(any())).then(invocation -> { + final MemoryMappedDevice device = invocation.getArgument(0); + final OptionalLong address = memoryMap.findFreeRange(0, 0xFFFFFFFF, device.getLength()); + if (address.isPresent()) { + memoryMap.addDevice(address.getAsLong(), device); + return true; + } else { + return false; + } + }); + doAnswer(invocation -> { + memoryMap.removeDevice(invocation.getArgument(0)); + return null; + }).when(board).removeDevice(any()); + + adapter = new VirtualMachineDeviceBusAdapter(board); } @Test @@ -141,8 +164,6 @@ public final class VMDeviceTests { adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load()); - verify(device).load(any()); - final int claimedInterruptMask = 1 << deviceData.interrupt; deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask); @@ -161,8 +182,6 @@ public final class VMDeviceTests { adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load()); - verify(device).load(any()); - final int someInterruptMask = 0x1; assertThrows(IllegalArgumentException.class, () -> deviceData.context.getInterruptController().raiseInterrupts(someInterruptMask)); @@ -186,8 +205,6 @@ public final class VMDeviceTests { adapter.addDevices(Collections.singleton(device)); assertTrue(adapter.load()); - verify(device).load(any()); - final int claimedInterruptMask = 1 << deviceData.interrupt; deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask); @@ -198,8 +215,73 @@ public final class VMDeviceTests { assertFalse((interruptController.getRaisedInterrupts() & claimedInterruptMask) != 0); } + @Test + public void devicesCannotAddToMemoryMapDirectly() { + final VMDevice device = mock(VMDevice.class); + when(device.load(any())).thenAnswer(invocation -> { + final VMContext context = invocation.getArgument(0); + + assertThrows(UnsupportedOperationException.class, () -> + context.getMemoryMap().addDevice(0, mock(MemoryMappedDevice.class))); + + return VMDeviceLoadResult.success(); + }); + + adapter.addDevices(Collections.singleton(device)); + adapter.load(); + } + + @Test + public void devicesCanAddMemoryMappedDevices() { + final DeviceData deviceData = new DeviceData(); + final VMDevice device = mock(VMDevice.class); + when(device.load(any())).thenAnswer(invocation -> { + final VMContext context = invocation.getArgument(0); + + deviceData.context = context; + deviceData.device = mock(MemoryMappedDevice.class); + when(deviceData.device.getLength()).thenReturn(0x1000); + + assertTrue(context.getMemoryRangeAllocator().claimMemoryRange(deviceData.device).isPresent()); + + return VMDeviceLoadResult.success(); + }); + + adapter.addDevices(Collections.singleton(device)); + assertTrue(adapter.load()); + + assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent()); + } + + @Test + public void addedDevicesGetRemovedOnUnload() { + final DeviceData deviceData = new DeviceData(); + final VMDevice device = mock(VMDevice.class); + when(device.load(any())).thenAnswer(invocation -> { + final VMContext context = invocation.getArgument(0); + + deviceData.context = context; + deviceData.device = mock(MemoryMappedDevice.class); + when(deviceData.device.getLength()).thenReturn(0x1000); + + assertTrue(context.getMemoryRangeAllocator().claimMemoryRange(deviceData.device).isPresent()); + + return VMDeviceLoadResult.success(); + }); + + adapter.addDevices(Collections.singleton(device)); + assertTrue(adapter.load()); + + assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent()); + + adapter.unload(); + + assertFalse(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent()); + } + private static final class DeviceData { public VMContext context; public int interrupt; + public MemoryMappedDevice device; } }