Add support for accessing EL1 debug registers

For now, the primary vm can access all debug registers, whereas secondary vms
cannot.  Debug event exceptions are disabled for secondary vms, so a malicious
primary cannot have active breakpoints or watchpoints for secondary vms.

This code allows us in the future to add debug support to secondary vms, and to
have fine-grained control over which registers are allowed, either by the
primary or secondary, as well as change the behavior for such accesses.

Bug: 132422368
Change-Id: I616454cc12bea6b8dfebbbdb566ac64c0a6625c2
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 4f4ec44..b0f2259 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -21,6 +21,7 @@
 #include "hf/arch/mm.h"
 
 #include "hf/api.h"
+#include "hf/check.h"
 #include "hf/cpu.h"
 #include "hf/dlog.h"
 #include "hf/panic.h"
@@ -29,6 +30,7 @@
 
 #include "vmapi/hf/call.h"
 
+#include "debug_el1.h"
 #include "msr.h"
 #include "psci.h"
 #include "psci_handler.h"
@@ -36,12 +38,25 @@
 
 #define HCR_EL2_VI (1u << 7)
 
+/**
+ * Gets the Exception Class from the ESR.
+ */
+#define GET_EC(esr) ((esr) >> 26)
+
+/**
+ * Gets the value to increment for the next PC.
+ * The ESR encodes whether the instruction is 2 bytes or 4 bytes long.
+ */
+#define GET_NEXT_PC_INC(esr) (((esr) & (1u << 25)) ? 4 : 2)
+
 struct hvc_handler_return {
 	uintreg_t user_ret;
 	struct vcpu *new;
 };
 
-/* Gets a reference to the currently executing vCPU. */
+/**
+ * Returns a reference to the currently executing vCPU.
+ */
 static struct vcpu *current(void)
 {
 	return (struct vcpu *)read_msr(tpidr_el2);
@@ -185,13 +200,13 @@
 noreturn void sync_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	uintreg_t esr = read_msr(esr_el2);
+	uintreg_t ec = GET_EC(esr);
 
 	(void)spsr;
 
-	switch (esr >> 26) {
+	switch (ec) {
 	case 0x25: /* EC = 100101, Data abort. */
-		dlog("Data abort: pc=%#x, esr=%#x, ec=%#x", elr, esr,
-		     esr >> 26);
+		dlog("Data abort: pc=%#x, esr=%#x, ec=%#x", elr, esr, ec);
 		if (!(esr & (1u << 10))) { /* Check FnV bit. */
 			dlog(", far=%#x", read_msr(far_el2));
 		} else {
@@ -204,7 +219,7 @@
 	default:
 		dlog("Unknown current sync exception pc=%#x, esr=%#x, "
 		     "ec=%#x\n",
-		     elr, esr, esr >> 26);
+		     elr, esr, ec);
 		break;
 	}
 
@@ -473,11 +488,12 @@
 	struct vcpu *vcpu = current();
 	struct vcpu_fault_info info;
 	struct vcpu *new_vcpu;
+	uintreg_t ec = GET_EC(esr);
 
-	switch (esr >> 26) {
+	switch (ec) {
 	case 0x01: /* EC = 000001, WFI or WFE. */
 		/* Skip the instruction. */
-		vcpu->regs.pc += (esr & (1u << 25)) ? 4 : 2;
+		vcpu->regs.pc += GET_NEXT_PC_INC(esr);
 		/* Check TI bit of ISS, 0 = WFI, 1 = WFE. */
 		if (esr & 1) {
 			/* WFE */
@@ -518,7 +534,7 @@
 		}
 
 		/* Skip the SMC instruction. */
-		vcpu->regs.pc = smc_pc + (esr & (1u << 25) ? 4 : 2);
+		vcpu->regs.pc = smc_pc + GET_NEXT_PC_INC(esr);
 		vcpu->regs.r[0] = ret.res0;
 		vcpu->regs.r[1] = ret.res1;
 		vcpu->regs.r[2] = ret.res2;
@@ -526,13 +542,54 @@
 		return next;
 	}
 
+	/*
+	 * EC = 011000, MSR, MRS or System instruction execution that is not
+	 * reported using EC 000000, 000001 or 000111.
+	 */
+	case 0x18:
+		/*
+		 * NOTE: This should never be reached because it goes through a
+		 * separate path handled by handle_system_register_access().
+		 */
+		panic("Handled by handle_system_register_access().");
+
 	default:
 		dlog("Unknown lower sync exception pc=%#x, esr=%#x, "
 		     "ec=%#x\n",
-		     vcpu->regs.pc, esr, esr >> 26);
+		     vcpu->regs.pc, esr, ec);
 		break;
 	}
 
 	/* The exception wasn't handled so abort the VM. */
 	return api_abort(vcpu);
 }
+
+/**
+ * Handles EC = 011000, msr, mrs instruction traps.
+ * Returns non-null ONLY if the access failed and the vcpu is changing.
+ */
+struct vcpu *handle_system_register_access(uintreg_t esr)
+{
+	struct vcpu *vcpu = current();
+	spci_vm_id_t vm_id = vcpu->vm->id;
+	uintreg_t ec = GET_EC(esr);
+
+	CHECK(ec == 0x18);
+
+	/*
+	 * Handle accesses to other registers that trap with the same EC.
+	 * Abort when encountering unhandled register accesses.
+	 */
+	if (!is_debug_el1_register_access(esr)) {
+		return api_abort(vcpu);
+	}
+
+	/* Abort if unable to fulfill the debug register access. */
+	if (!debug_el1_process_access(vcpu, vm_id, esr)) {
+		return api_abort(vcpu);
+	}
+
+	/* Instruction was fulfilled above. Skip it and run the next one. */
+	vcpu->regs.pc += GET_NEXT_PC_INC(esr);
+	return NULL;
+}