diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventListener.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventListener.java new file mode 100644 index 00000000..1a5f2293 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventListener.java @@ -0,0 +1,5 @@ +package li.cil.oc2.api.bus.device.vm; + +public interface VMLifecycleEventListener extends VMDevice { + void handleLifecycleEvent(VMLifecycleEventType event); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventType.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventType.java new file mode 100644 index 00000000..fc39dae7 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMLifecycleEventType.java @@ -0,0 +1,26 @@ +package li.cil.oc2.api.bus.device.vm; + +public enum VMLifecycleEventType { + /** + * Reported when the VM resumes running. + *

+ * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. + *

+ * Fired on initial boot-up as well as when the VM resumes after being restored + * from a saved state. It is intended for awaiting asynchronous load operations. + */ + RESUME_RUNNING, + + /** + * Reported exactly once, when the VM first starts running. + *

+ * Fired after all devices reported success from {@link VMDevice#load(VMContext)}. + *

+ * If a running VM is restored from a saved state, this event will not be fired. It is + * intended for initializing the VM state on boot, e.g. by loading initial executable + * code into memory. + *

+ * This is invoked from the worker thread running the VM. + */ + INITIALIZE, +} 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 e7c8550a..1754d4ab 100644 --- a/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java +++ b/src/main/java/li/cil/oc2/common/vm/VirtualMachineDeviceBusAdapter.java @@ -4,13 +4,12 @@ import li.cil.ceres.api.Serialized; import li.cil.oc2.api.bus.device.Device; import li.cil.oc2.api.bus.device.vm.VMDevice; import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.api.bus.device.vm.VMLifecycleEventListener; +import li.cil.oc2.api.bus.device.vm.VMLifecycleEventType; import li.cil.sedna.api.Board; import li.cil.sedna.riscv.device.R5PlatformLevelInterruptController; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.Set; +import java.util.*; public final class VirtualMachineDeviceBusAdapter { private final Board board; @@ -19,6 +18,8 @@ public final class VirtualMachineDeviceBusAdapter { private final HashMap deviceContexts = new HashMap<>(); private final ArrayList incompleteLoads = new ArrayList<>(); + private final HashSet lifecycleEventListeners = new HashSet<>(); + /////////////////////////////////////////////////////////////////// // This is a superset of allocatedInterrupts. We use this so that after loading we @@ -86,6 +87,12 @@ public final class VirtualMachineDeviceBusAdapter { incompleteLoads.addAll(deviceContexts.keySet()); } + public void fireLifecycleEvent(final VMLifecycleEventType event) { + for (final VMLifecycleEventListener tickListener : lifecycleEventListeners) { + tickListener.handleLifecycleEvent(event); + } + } + public void addDevices(final Set devices) { for (final Device device : devices) { if (device instanceof VMDevice) { @@ -97,6 +104,10 @@ public final class VirtualMachineDeviceBusAdapter { } incompleteLoads.add(vmDevice); + + if (vmDevice instanceof VMLifecycleEventListener) { + lifecycleEventListeners.add((VMLifecycleEventListener) vmDevice); + } } } } @@ -113,6 +124,10 @@ public final class VirtualMachineDeviceBusAdapter { incompleteLoads.remove(vmDevice); + if (vmDevice instanceof VMLifecycleEventListener) { + lifecycleEventListeners.remove(vmDevice); + } + vmDevice.unload(); } }