Handle spurious page faults due to break-before-make.

Also adding test that triggers spurious faults and fails without this
fix, but passes with it.

Change-Id: I30c591d87c5278a8bb4ed4ec992544f751204d90
diff --git a/src/cpu.c b/src/cpu.c
index 03e0074..5db709c 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -160,3 +160,74 @@
 {
 	return vcpu - vcpu->vm->vcpus;
 }
+
+/**
+ * Handles a page fault. It does so by determining if it's a legitimate or
+ * spurious fault, and recovering from the latter.
+ *
+ * Returns true if the caller should resume the current vcpu, or false if its VM
+ * should be aborted.
+ */
+bool vcpu_handle_page_fault(const struct vcpu *current,
+			    struct vcpu_fault_info *f)
+{
+	struct vm *vm = current->vm;
+	ipaddr_t second_addr;
+	bool second;
+	int mode;
+	int mask = f->mode | MM_MODE_INVALID;
+	bool ret = false;
+
+	/* We can't recover if we don't know the size. */
+	if (f->size == 0) {
+		goto exit;
+	}
+
+	sl_lock(&vm->lock);
+
+	/*
+	 * Check if this is a legitimate fault, i.e., if the page table doesn't
+	 * allow the access attemped by the VM.
+	 */
+	if (!mm_vm_get_mode(&vm->ptable, f->ipaddr, ipa_add(f->ipaddr, 1),
+			    &mode) ||
+	    (mode & mask) != f->mode) {
+		goto exit_unlock;
+	}
+
+	/*
+	 * Do the same mode check on the second page, if the fault straddles two
+	 * pages.
+	 */
+	second_addr = ipa_add(f->ipaddr, f->size - 1);
+	second = (ipa_addr(f->ipaddr) >> PAGE_BITS) !=
+		 (ipa_addr(second_addr) >> PAGE_BITS);
+	if (second) {
+		if (!mm_vm_get_mode(&vm->ptable, second_addr,
+				    ipa_add(second_addr, 1), &mode) ||
+		    (mode & mask) != f->mode) {
+			goto exit_unlock;
+		}
+	}
+
+	/*
+	 * This is a spurious fault, likely because another CPU is updating the
+	 * page table. It is responsible for issuing global tlb invalidations
+	 * while holding the VM lock, so we don't need to do anything else to
+	 * recover from it. (Acquiring/releasing the lock ensured that the
+	 * invalidations have completed.)
+	 */
+
+	ret = true;
+
+exit_unlock:
+	sl_unlock(&vm->lock);
+exit:
+	if (!ret) {
+		dlog("Stage-2 page fault: pc=0x%x, vmid=%u, vcpu=%u, "
+		     "vaddr=0x%x, ipaddr=0x%x, mode=0x%x, size=%u\n",
+		     f->pc, vm->id, vcpu_index(current), f->vaddr, f->ipaddr,
+		     f->mode, f->size);
+	}
+	return ret;
+}