feat: add `vm_lock_both_in_order`

Adds helper function for acquiring a second lock ordered with respect to
a first lock.

Change-Id: I8f4d9d2e20f202d893c12755cf44d9c03d5b0122
Signed-off-by: Karl Meakin <karl.meakin@arm.com>
diff --git a/src/vm.c b/src/vm.c
index 266c3bc..577aeea 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -8,6 +8,7 @@
 
 #include "hf/vm.h"
 
+#include "hf/arch/spinlock.h"
 #include "hf/arch/vm.h"
 
 #include "hf/api.h"
@@ -210,6 +211,34 @@
 }
 
 /**
+ * Locks two VMs ensuring that the locking order is according to the locks'
+ * addresses, given `vm1` is already locked.
+ */
+struct two_vm_locked vm_lock_both_in_order(struct vm_locked vm1, struct vm *vm2)
+{
+	struct spinlock *sl1 = &vm1.vm->lock;
+	struct spinlock *sl2 = &vm2->lock;
+
+	/*
+	 * Use `sl_lock`/`sl_unlock` directly rather than
+	 * `vm_lock`/`vm_unlock` because `vm_unlock` sets the vm field
+	 * to NULL.
+	 */
+	if (sl1 < sl2) {
+		sl_lock(sl2);
+	} else {
+		sl_unlock(sl1);
+		sl_lock(sl2);
+		sl_lock(sl1);
+	}
+
+	return (struct two_vm_locked){
+		.vm1 = vm1,
+		.vm2 = (struct vm_locked){.vm = vm2},
+	};
+}
+
+/**
  * Unlocks a VM previously locked with vm_lock, and updates `locked` to reflect
  * the fact that the VM is no longer locked.
  */