Analogous to interrupt allocator added memory range allocator for adding devices.

This commit is contained in:
Florian Nücke
2020-12-12 13:35:58 +01:00
parent d4fffc40b9
commit b53f85cca2
10 changed files with 244 additions and 96 deletions

View File

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

View File

@@ -14,32 +14,16 @@ public interface VMContext {
/**
* The memory of the virtual machine.
* <p>
* {@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.
* <p>
* 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}.
* <p>
* Interrupts can only be claimed inside {@link VMDevice#load(VMContext)}.
* Trying to claim interrupts after that method has returned will result in an exception.
* <p>
* 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.
* <p>
* 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}.
* <p>
* {@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.
* <p>
* 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}.
* <p>
* Interrupts can only be claimed inside {@link VMDevice#load(VMContext)}.
* Trying to claim interrupts after that method has returned will result in an exception.
* <p>
* 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();
}

View File

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

View File

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

View File

@@ -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<MemoryMappedDevice> 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

View File

@@ -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<MemoryMappedDevice> 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<MemoryRange> range = board.getMemoryMap().getMemoryRange(device);
assert range.isPresent();
managedDevices.add(device);
return OptionalLong.of(range.get().address());
}
}

View File

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

View File

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

View File

@@ -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<VMDevice, ManagedVMContext> deviceContexts = new HashMap<>();
private final ArrayList<VMDevice> 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;
}

View File

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