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(&current_vm->boot_list_node, &vm->boot_list_node)
 		: list_append(&current_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;
+}