feat: implement state machine for vm state transitions
With the new guidance added in FF-A v1.3 ALP2 specification around
partition lifecycle management, it is a good idea to build a robust
state machine to allow vm state transitions.
The states introduced are purely implementation defined but have good
correlation with the underlying vCPU(s) state.
Change-Id: Ie0291365fd233c00ede48606e8987aa85422a0fb
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/inc/hf/vm.h b/inc/hf/vm.h
index 311c703..6221e50 100644
--- a/inc/hf/vm.h
+++ b/inc/hf/vm.h
@@ -166,11 +166,33 @@
bool permissive;
};
+/*
+ * Implementation defined states for a VM (or SP). Refer to `vm_set_state`
+ * helper for documentation of legal transitions.
+ */
+enum vm_state {
+ /* VM has not yet been created. This is the default value. */
+ VM_STATE_NULL,
+
+ /* VM has been created and initialized by partition manager. */
+ VM_STATE_CREATED,
+
+ /*
+ * At least one execution context of the VM has been given CPU cycles to
+ * initialize itself.
+ */
+ VM_STATE_RUNNING,
+
+ /* The VM has been aborted due to a fatal error. */
+ VM_STATE_ABORTING,
+};
+
/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */
struct vm {
ffa_id_t id;
struct ffa_uuid uuids[PARTITION_MAX_UUIDS];
enum ffa_version ffa_version;
+ enum vm_state state;
/*
* Whether this FF-A instance has negotiated an FF-A version through a
@@ -431,3 +453,5 @@
struct vm *vm_get_boot_vm_secondary_core(void);
struct vm *vm_get_next_boot(struct vm *vm);
struct vm *vm_get_next_boot_secondary_core(struct vm *vm);
+enum vm_state vm_read_state(struct vm *vm);
+bool vm_set_state(struct vm_locked vm_locked, enum vm_state to_state);
diff --git a/src/vm.c b/src/vm.c
index bca85bd..e3039d7 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -1261,3 +1261,100 @@
? list_prepend(¤t_vm->boot_list_node, &vm->boot_list_node)
: list_append(¤t_vm->boot_list_node, &vm->boot_list_node);
}
+
+/**
+ * Light weight read operation. Its safe to access the state without a lock.
+ * The atomic primitive ensures any update by another CPU to this field is
+ * visible.
+ */
+enum vm_state vm_read_state(struct vm *vm)
+{
+ return __atomic_load_n(&vm->state, __ATOMIC_ACQUIRE);
+}
+
+/* Internal helper. Not safe to write to the state field without a lock. */
+static void vm_write_state(struct vm *vm, enum vm_state new_state)
+{
+ __atomic_store_n(&vm->state, new_state, __ATOMIC_RELEASE);
+}
+
+static inline const char *vm_state_print_name(enum vm_state state)
+{
+ switch (state) {
+ case VM_STATE_NULL:
+ return "VM_STATE_NULL";
+ case VM_STATE_CREATED:
+ return "VM_STATE_CREATED";
+ case VM_STATE_RUNNING:
+ return "VM_STATE_RUNNING";
+ case VM_STATE_ABORTING:
+ return "VM_STATE_ABORTING";
+ }
+}
+
+/**
+ * Perform legal transitions between various states of a VM. The caller is
+ * expected to hold the VM's lock.
+ *
+ * The following state transitions are valid:
+ * NULL -> CREATED : Hafnium successfully initialized the VM.
+ * CREATED -> RUNNING : The first execution context has been allocated CPU
+ * cycles.
+ * RUNNING -> ABORTING : An execution context of VM encountered fatal error.
+ * ABORTING -> NULL : Hafnium destroyed the VM.
+ * ABORTING -> CREATED : Hafnium has reinitialized the VM with the aim of
+ * restarting it.
+ *
+ * Return true if the transition is valid and the state was updated, false
+ * otherwise.
+ */
+bool vm_set_state(struct vm_locked vm_locked, enum vm_state to_state)
+{
+ struct vm *vm = vm_locked.vm;
+ enum vm_state from_state;
+ bool ret = false;
+
+ assert(vm != NULL);
+ from_state = vm_read_state(vm);
+
+ if (to_state == from_state) {
+ return true;
+ }
+
+ switch (from_state) {
+ case VM_STATE_NULL:
+ if (to_state == VM_STATE_CREATED) {
+ ret = true;
+ }
+ break;
+ case VM_STATE_CREATED:
+ if (to_state == VM_STATE_RUNNING ||
+ to_state == VM_STATE_ABORTING) {
+ ret = true;
+ }
+ break;
+ case VM_STATE_RUNNING:
+ if (to_state == VM_STATE_ABORTING) {
+ ret = true;
+ }
+ break;
+ case VM_STATE_ABORTING:
+ if (to_state == VM_STATE_NULL || to_state == VM_STATE_CREATED) {
+ ret = true;
+ }
+ break;
+ default:
+ ret = false;
+ break;
+ }
+
+ if (ret) {
+ vm_write_state(vm, to_state);
+ } else {
+ dlog_error("Partition %#x transition from %s to %s failed.\n",
+ vm->id, vm_state_print_name(vm->state),
+ vm_state_print_name(to_state));
+ }
+
+ return ret;
+}