Inject an exception into EL1 instead of panicking for unsupported MSRs

When accessing an unsupported, undefined, or deliberately blocked system
register, inject an exception into the EL1 of the VM instead of panicking.

Exception Class (EC) 0x0 is used, because it applies to accessing a system
register that is either not allocated or not permitted. See Arm Architecture
Reference Manual Armv8-A, page D13-2924.

Added a basic unit test for this, which I plan to expand to ensure that other
protected system registers inject exceptions.

Bug: 140916188
Change-Id: Idaf3c420bdd5a4b5e1d77000c6f850476dc3acc6
diff --git a/src/arch/aarch64/cpu.c b/src/arch/aarch64/cpu.c
index ba9d27b..a8ff2bc 100644
--- a/src/arch/aarch64/cpu.c
+++ b/src/arch/aarch64/cpu.c
@@ -97,9 +97,8 @@
 	r->lazy.cnthctl_el2 = cnthctl;
 	r->lazy.vttbr_el2 = pa_addr(table) | ((uint64_t)vm_id << 48);
 	r->lazy.vmpidr_el2 = vcpu_id;
-	/* TODO: Use constant here. */
-	r->spsr = 5 |	 /* M bits, set to EL1h. */
-		  (0xf << 6); /* DAIF bits set; disable interrupts. */
+	/* Mask (disable) interrupts and run in EL1h mode. */
+	r->spsr = PSR_D | PSR_A | PSR_I | PSR_F | PSR_PE_MODE_EL1H;
 
 	r->lazy.mdcr_el2 = get_mdcr_el2_value(vm_id);
 
diff --git a/src/arch/aarch64/hftest/exceptions.S b/src/arch/aarch64/hftest/exceptions.S
index d44b758..b54e4d2 100644
--- a/src/arch/aarch64/hftest/exceptions.S
+++ b/src/arch/aarch64/hftest/exceptions.S
@@ -37,9 +37,7 @@
 
 .balign 0x80
 sync_cur_spx:
-	mrs x0, esr_el1
-	mrs x1, elr_el1
-	b sync_current_exception
+	current_exception_spx el1 sync_exception_current
 
 .balign 0x80
 irq_cur_spx:
@@ -87,8 +85,36 @@
 
 .balign 0x40
 /**
- * Restores volatile registers from stack and returns.
+ * Restores the volatile registers from the stack.
+
+ * Register x0: if false restores elr_el1, if true retains the value of elr_el1.
+ * This enables exception handlers to indicate whether they have changed the
+ * value of elr_el1 (e.g., to skip the faulting instruction).
  */
 restore_from_stack_and_return:
-	restore_volatile_from_stack el1
+	/* Restore registers x2-x18, x29 & x30. */
+	ldp x2, x3, [sp, #8 * 2]
+	ldp x4, x5, [sp, #8 * 4]
+	ldp x6, x7, [sp, #8 * 6]
+	ldp x8, x9, [sp, #8 * 8]
+	ldp x10, x11, [sp, #8 * 10]
+	ldp x12, x13, [sp, #8 * 12]
+	ldp x14, x15, [sp, #8 * 14]
+	ldp x16, x17, [sp, #8 * 16]
+	ldr x18, [sp, #8 * 18]
+	ldp x29, x30, [sp, #8 * 20]
+
+	cbnz x0, skip_elr
+
+	/* Restore register elr_el1 using x1 as scratch. */
+	ldr x1, [sp, #8 * 22]
+	msr elr_el1, x1
+
+skip_elr:
+	/* Restore register spsr_el1 using x1 as scratch. */
+	ldr x1, [sp, #8 * 23]
+        msr spsr_el1, x1
+
+	/* Restore x0 & x1, and release stack space. */
+	ldp x0, x1, [sp], #8 * 24
 	eret
diff --git a/src/arch/aarch64/hftest/interrupts.c b/src/arch/aarch64/hftest/interrupts.c
index f3fa2e5..447d1d2 100644
--- a/src/arch/aarch64/hftest/interrupts.c
+++ b/src/arch/aarch64/hftest/interrupts.c
@@ -25,6 +25,7 @@
 
 extern uint8_t vector_table_el1;
 static void (*irq_callback)(void);
+static bool (*exception_callback)(void);
 
 void irq_current(void)
 {
@@ -35,16 +36,11 @@
 	}
 }
 
-void exception_setup(void (*irq)(void))
+noreturn static bool default_sync_current_exception(void)
 {
-	irq_callback = irq;
+	uintreg_t esr = read_msr(esr_el1);
+	uintreg_t elr = read_msr(elr_el1);
 
-	/* Set exception vector table. */
-	write_msr(VBAR_EL1, &vector_table_el1);
-}
-
-void sync_current_exception(uintreg_t esr, uintreg_t elr)
-{
 	switch (esr >> 26) {
 	case 0x25: /* EC = 100101, Data abort. */
 		dlog("Data abort: pc=%#x, esr=%#x, ec=%#x", elr, esr,
@@ -69,6 +65,23 @@
 	}
 }
 
+bool sync_exception_current(void)
+{
+	if (exception_callback != NULL) {
+		return exception_callback();
+	}
+	return default_sync_current_exception();
+}
+
+void exception_setup(void (*irq)(void), bool (*exception)(void))
+{
+	irq_callback = irq;
+	exception_callback = exception;
+
+	/* Set exception vector table. */
+	write_msr(VBAR_EL1, &vector_table_el1);
+}
+
 void interrupt_wait(void)
 {
 	__asm__ volatile("wfi");
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 737ed83..6eaaac5 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -41,13 +41,18 @@
 /**
  * Gets the Exception Class from the ESR.
  */
-#define GET_EC(esr) ((esr) >> 26)
+#define GET_ESR_EC(esr) ((esr) >> 26)
+
+/**
+ * Gets the Instruction Length bit for the synchronous exception
+ */
+#define GET_ESR_IL(esr) ((esr) & (1 << 25))
 
 /**
  * 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)
+#define GET_NEXT_PC_INC(esr) (GET_ESR_IL(esr) ? 4 : 2)
 
 /**
  * The Client ID field within X7 for an SMC64 call.
@@ -200,7 +205,7 @@
 noreturn void sync_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	uintreg_t esr = read_msr(esr_el2);
-	uintreg_t ec = GET_EC(esr);
+	uintreg_t ec = GET_ESR_EC(esr);
 
 	(void)spsr;
 
@@ -556,7 +561,7 @@
 	struct vcpu *vcpu = current();
 	struct vcpu_fault_info info;
 	struct vcpu *new_vcpu;
-	uintreg_t ec = GET_EC(esr);
+	uintreg_t ec = GET_ESR_EC(esr);
 
 	switch (ec) {
 	case 0x01: /* EC = 000001, WFI or WFE. */
@@ -638,48 +643,122 @@
 	return api_abort(vcpu);
 }
 
+/*
+ * Exception vector offsets.
+ * See Arm Architecture Reference Manual Armv8-A, D1.10.2.
+ */
+
+/**
+ * Offset for synchronous exceptions at current EL with SPx.
+ */
+#define OFFSET_CURRENT_SPX UINT64_C(0x200)
+
+/**
+ * Offset for synchronous exceptions at lower EL using AArch64.
+ */
+#define OFFSET_LOWER_EL_64 UINT64_C(0x400)
+
+/**
+ * Offset for synchronous exceptions at lower EL using AArch32.
+ */
+#define OFFSET_LOWER_EL_32 UINT64_C(0x600)
+
+/**
+ * Returns the address for the exception handler at EL1.
+ */
+static uintreg_t get_el1_exception_handler_addr(const struct vcpu *vcpu)
+{
+	uintreg_t base_addr = read_msr(vbar_el1);
+	uintreg_t pe_mode = vcpu->regs.spsr & PSR_PE_MODE_MASK;
+	bool is_arch32 = vcpu->regs.spsr & PSR_ARCH_MODE_32;
+
+	if (pe_mode == PSR_PE_MODE_EL0T) {
+		if (is_arch32) {
+			base_addr += OFFSET_LOWER_EL_32;
+		} else {
+			base_addr += OFFSET_LOWER_EL_64;
+		}
+	} else {
+		CHECK(!is_arch32);
+		base_addr += OFFSET_CURRENT_SPX;
+	}
+
+	return base_addr;
+}
+
+/**
+ * Injects an exception with an unknown reason (EC=0x0) to the EL1.
+ * See Arm Architecture Reference Manual Armv8-A, page D13-2924.
+ *
+ * NOTE: This function assumes that the lazy registers haven't been saved, and
+ * writes to the lazy registers of the CPU directly instead of the vCPU.
+ */
+static struct vcpu *inject_el1_unknown_exception(struct vcpu *vcpu,
+						 uintreg_t esr_el2)
+{
+	uintreg_t esr_el1_value = GET_ESR_IL(esr_el2);
+	uintreg_t handler_address = get_el1_exception_handler_addr(vcpu);
+	char *direction_str;
+
+	/* Update the CPU state to inject the exception. */
+	write_msr(esr_el1, esr_el1_value);
+	write_msr(elr_el1, vcpu->regs.pc);
+	write_msr(spsr_el1, vcpu->regs.spsr);
+
+	/*
+	 * Mask (disable) interrupts and run in EL1h mode.
+	 * EL1h mode is used because by default, taking an exception selects the
+	 * stack pointer for the target Exception level. The software can change
+	 * that later in the handler if needed.
+	 * See Arm Architecture Reference Manual Armv8-A, page D13-2924
+	 */
+	vcpu->regs.spsr = PSR_D | PSR_A | PSR_I | PSR_F | PSR_PE_MODE_EL1H;
+
+	/* Transfer control to the exception hander. */
+	vcpu->regs.pc = handler_address;
+
+	direction_str = ISS_IS_READ(esr_el2) ? "read" : "write";
+	dlog("Trapped access to system register %s: op0=%d, op1=%d, crn=%d, "
+	     "crm=%d, op2=%d, rt=%d.\n",
+	     direction_str, GET_ISS_OP0(esr_el2), GET_ISS_OP1(esr_el2),
+	     GET_ISS_CRN(esr_el2), GET_ISS_CRM(esr_el2), GET_ISS_OP2(esr_el2),
+	     GET_ISS_RT(esr_el2));
+
+	dlog("Injecting Unknown Reason exception into VM%d.\n", vcpu->vm->id);
+	dlog("Exception handler address 0x%x\n", handler_address);
+
+	/* Schedule the same VM to continue running. */
+	return NULL;
+}
+
 /**
  * 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 *handle_system_register_access(uintreg_t esr_el2)
 {
 	struct vcpu *vcpu = current();
 	spci_vm_id_t vm_id = vcpu->vm->id;
-	uintreg_t ec = GET_EC(esr);
-	char *direction_str;
+	uintreg_t ec = GET_ESR_EC(esr_el2);
 
 	CHECK(ec == 0x18);
-
 	/*
 	 * Handle accesses to debug and performance monitor registers.
-	 * Abort when encountering unhandled register accesses.
+	 * Inject an exception for unhandled/unsupported registers.
 	 */
-	if (debug_el1_is_register_access(esr)) {
-		if (!debug_el1_process_access(vcpu, vm_id, esr)) {
-			goto fail;
+	if (debug_el1_is_register_access(esr_el2)) {
+		if (!debug_el1_process_access(vcpu, vm_id, esr_el2)) {
+			return inject_el1_unknown_exception(vcpu, esr_el2);
 		}
-	} else if (perfmon_is_register_access(esr)) {
-		if (!perfmon_process_access(vcpu, vm_id, esr)) {
-			goto fail;
+	} else if (perfmon_is_register_access(esr_el2)) {
+		if (!perfmon_process_access(vcpu, vm_id, esr_el2)) {
+			return inject_el1_unknown_exception(vcpu, esr_el2);
 		}
 	} else {
-		goto fail;
+		return inject_el1_unknown_exception(vcpu, esr_el2);
 	}
 
 	/* Instruction was fulfilled. Skip it and run the next one. */
-	vcpu->regs.pc += GET_NEXT_PC_INC(esr);
+	vcpu->regs.pc += GET_NEXT_PC_INC(esr_el2);
 	return NULL;
-
-fail:
-	direction_str = ISS_IS_READ(esr) ? "read" : "write";
-
-	dlog("Unhandled system register %s: op0=%d, op1=%d, crn=%d, "
-	     "crm=%d, op2=%d, rt=%d.\n",
-	     direction_str, GET_ISS_OP0(esr), GET_ISS_OP1(esr),
-	     GET_ISS_CRN(esr), GET_ISS_CRM(esr), GET_ISS_OP2(esr),
-	     GET_ISS_RT(esr));
-
-	/* Abort if unable to fulfill the register access. */
-	return api_abort(vcpu);
 }
diff --git a/src/arch/aarch64/hypervisor/sysregs.h b/src/arch/aarch64/hypervisor/sysregs.h
index fa95fee..252d7c1 100644
--- a/src/arch/aarch64/hypervisor/sysregs.h
+++ b/src/arch/aarch64/hypervisor/sysregs.h
@@ -396,6 +396,53 @@
  */
 #define CPTR_EL2_TTA (UINT64_C(0x1) << 28)
 
+/*
+ * Process State Bit definitions.
+ *
+ * These apply to the PSTATE, as well as registers that contain PSTATE fields,
+ * e.g., SPSR_EL1.
+ */
+
+/**
+ * Debug exception mask bit.
+ */
+#define PSR_D (UINT64_C(1) << 9)
+
+/**
+ * Asynchronos SError interrupt mask bit.
+ */
+#define PSR_A (UINT64_C(1) << 8)
+
+/**
+ * Asynchronos IRQ interrupt mask bit.
+ */
+#define PSR_I (UINT64_C(1) << 7)
+
+/**
+ * Asynchronos FIQ interrupt mask bit.
+ */
+#define PSR_F (UINT64_C(1) << 6)
+
+/**
+ * AArch32 State bit.
+ */
+#define PSR_ARCH_MODE_32 (UINT64_C(1) << 4)
+
+/**
+ * PE Mode bit mask.
+ */
+#define PSR_PE_MODE_MASK UINT64_C(0xf)
+
+/**
+ * PE Mode: EL0t.
+ */
+#define PSR_PE_MODE_EL0T UINT64_C(0x0)
+
+/**
+ * PE Mode: EL1h.
+ */
+#define PSR_PE_MODE_EL1H UINT64_C(0x5)
+
 uintreg_t get_hcr_el2_value(spci_vm_id_t vm_id);
 
 uintreg_t get_mdcr_el2_value(spci_vm_id_t vm_id);
diff --git a/src/arch/aarch64/inc/hf/arch/vm/interrupts.h b/src/arch/aarch64/inc/hf/arch/vm/interrupts.h
index fa0ba2a..33087b6 100644
--- a/src/arch/aarch64/inc/hf/arch/vm/interrupts.h
+++ b/src/arch/aarch64/inc/hf/arch/vm/interrupts.h
@@ -16,5 +16,7 @@
 
 #pragma once
 
-void exception_setup(void (*irq)(void));
+#include <stdbool.h>
+
+void exception_setup(void (*irq)(void), bool (*exception)(void));
 void interrupt_wait(void);
diff --git a/test/hftest/standalone_main.c b/test/hftest/standalone_main.c
index f42c71b..193623c 100644
--- a/test/hftest/standalone_main.c
+++ b/test/hftest/standalone_main.c
@@ -51,7 +51,7 @@
 	 * Install the exception handler with no IRQ callback for now, so that
 	 * exceptions are logged.
 	 */
-	exception_setup(NULL);
+	exception_setup(NULL, NULL);
 
 	hftest_use_list(hftest_begin, hftest_end - hftest_begin);
 
diff --git a/test/vmapi/arch/aarch64/gicv3/gicv3.c b/test/vmapi/arch/aarch64/gicv3/gicv3.c
index f0f22f1..c3f2734 100644
--- a/test/vmapi/arch/aarch64/gicv3/gicv3.c
+++ b/test/vmapi/arch/aarch64/gicv3/gicv3.c
@@ -56,7 +56,7 @@
 	hftest_mm_identity_map((void *)GICR_BASE, PAGE_SIZE, mode);
 	hftest_mm_identity_map((void *)SGI_BASE, PAGE_SIZE, mode);
 
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	interrupt_gic_setup();
 }
 
diff --git a/test/vmapi/arch/aarch64/gicv3/services/timer.c b/test/vmapi/arch/aarch64/gicv3/services/timer.c
index 1aee695..2dd7e76 100644
--- a/test/vmapi/arch/aarch64/gicv3/services/timer.c
+++ b/test/vmapi/arch/aarch64/gicv3/services/timer.c
@@ -55,7 +55,7 @@
 
 TEST_SERVICE(timer)
 {
-	exception_setup(irq_current);
+	exception_setup(irq_current, NULL);
 	hf_interrupt_enable(HF_VIRTUAL_TIMER_INTID, true);
 	arch_irq_enable();
 
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 54501f7..3a38f26 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -21,7 +21,10 @@
 # Tests with secondary VMs.
 vm_kernel("primary_with_secondaries_test_vm") {
   testonly = true
-  public_configs = [ ":config" ]
+  public_configs = [
+    ":config",
+    "//src/arch/aarch64:config",
+  ]
 
   sources = [
     "abort.c",
@@ -36,6 +39,7 @@
     "run_race.c",
     "smp.c",
     "spci.c",
+    "sysregs.c",
   ]
 
   deps = [
diff --git a/test/vmapi/primary_with_secondaries/services/echo_with_notification.c b/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
index 2b6b7d1..be8f1d0 100644
--- a/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
+++ b/test/vmapi/primary_with_secondaries/services/echo_with_notification.c
@@ -48,7 +48,7 @@
 
 TEST_SERVICE(echo_with_notification)
 {
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	hf_interrupt_enable(HF_MAILBOX_WRITABLE_INTID, true);
 
 	/* Loop, echo messages back to the sender. */
diff --git a/test/vmapi/primary_with_secondaries/services/interruptible.c b/test/vmapi/primary_with_secondaries/services/interruptible.c
index aa2de0d..0e1df08 100644
--- a/test/vmapi/primary_with_secondaries/services/interruptible.c
+++ b/test/vmapi/primary_with_secondaries/services/interruptible.c
@@ -65,7 +65,7 @@
 	spci_vm_id_t this_vm_id = hf_vm_get_id();
 	void *recv_buf = SERVICE_RECV_BUFFER();
 
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	hf_interrupt_enable(SELF_INTERRUPT_ID, true);
 	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true);
 	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_B, true);
diff --git a/test/vmapi/primary_with_secondaries/services/interruptible_echo.c b/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
index d7b5506..8300665 100644
--- a/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
+++ b/test/vmapi/primary_with_secondaries/services/interruptible_echo.c
@@ -33,7 +33,7 @@
 
 TEST_SERVICE(interruptible_echo)
 {
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true);
 	arch_irq_enable();
 
diff --git a/test/vmapi/primary_with_secondaries/services/receive_block.c b/test/vmapi/primary_with_secondaries/services/receive_block.c
index 29ec406..50543be 100644
--- a/test/vmapi/primary_with_secondaries/services/receive_block.c
+++ b/test/vmapi/primary_with_secondaries/services/receive_block.c
@@ -42,7 +42,7 @@
 	int32_t i;
 	const char message[] = "Done waiting";
 
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	arch_irq_disable();
 	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true);
 
diff --git a/test/vmapi/primary_with_secondaries/services/wfi.c b/test/vmapi/primary_with_secondaries/services/wfi.c
index e45f511..59baa51 100644
--- a/test/vmapi/primary_with_secondaries/services/wfi.c
+++ b/test/vmapi/primary_with_secondaries/services/wfi.c
@@ -40,7 +40,7 @@
 	int32_t i;
 	const char message[] = "Done waiting";
 
-	exception_setup(irq);
+	exception_setup(irq, NULL);
 	arch_irq_disable();
 	hf_interrupt_enable(EXTERNAL_INTERRUPT_ID_A, true);
 
diff --git a/test/vmapi/primary_with_secondaries/sysregs.c b/test/vmapi/primary_with_secondaries/sysregs.c
new file mode 100644
index 0000000..3b31d71
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/sysregs.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sysregs.h"
+
+#include "hf/arch/vm/interrupts.h"
+
+#include "primary_with_secondary.h"
+#include "util.h"
+
+/**
+ * Tracks the number of times the exception handler has been invoked.
+ */
+static int num_exceptions = 0;
+
+static bool handle_exception(void)
+{
+	dlog("%s function is triggered!\n", __func__);
+	++num_exceptions;
+
+	/* Skip instruction that triggered the exception. */
+	uint64_t next_pc = read_msr(elr_el1);
+	next_pc += 4UL;
+	write_msr(elr_el1, next_pc);
+
+	/* Indicate that elr_el1 should not be restored. */
+	return true;
+}
+
+SET_UP(sysregs)
+{
+	exception_setup(NULL, handle_exception);
+}
+
+/**
+ * Test that accessing LOR registers would inject an exception.
+ */
+TEST(sysregs, lor_exception)
+{
+	num_exceptions = 0;
+
+	EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+	TRY_READ(MSR_LORC_EL1);
+
+	EXPECT_EQ(num_exceptions, 1);
+}