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/Makefile b/Makefile
index 2d3a885..3473b2e 100644
--- a/Makefile
+++ b/Makefile
@@ -38,9 +38,15 @@
NINJA ?= $(PREBUILTS)/ninja/ninja
export PATH := $(PREBUILTS)/clang/bin:$(PATH)
+
CHECKPATCH := $(PWD)/third_party/linux/scripts/checkpatch.pl \
--ignore BRACES,SPDX_LICENSE_TAG,VOLATILE,SPLIT_STRING,AVOID_EXTERNS,USE_SPINLOCK_T,NEW_TYPEDEFS,INITIALISED_STATIC,FILE_PATH_CHANGES,EMBEDDED_FUNCTION_NAME,SINGLE_STATEMENT_DO_WHILE_MACRO,MACRO_WITH_FLOW_CONTROL --quiet
+# Specifies the grep pattern for ignoring specific files in checkpatch.
+# Separate the different items in the list with a grep or (\|).
+# debug_el1.c : uses XMACROS, which checkpatch doesn't understand.
+CHECKPATCH_IGNORE := "src/arch/aarch64/hypervisor/debug_el1.c"
+
# Select the project to build.
PROJECT ?= reference
@@ -74,10 +80,10 @@
.PHONY: checkpatch
checkpatch:
- @find src/ -name \*.c -o -name \*.h | xargs $(CHECKPATCH) -f
- @find inc/ -name \*.c -o -name \*.h | xargs $(CHECKPATCH) -f
+ @find src/ -name \*.c -o -name \*.h | grep -v $(CHECKPATCH_IGNORE) | xargs $(CHECKPATCH) -f
+ @find inc/ -name \*.c -o -name \*.h | grep -v $(CHECKPATCH_IGNORE) | xargs $(CHECKPATCH) -f
# TODO: enable for test/
- @find project/ -name \*.c -o -name \*.h | xargs $(CHECKPATCH) -f
+ @find project/ -name \*.c -o -name \*.h | grep -v $(CHECKPATCH_IGNORE) | xargs $(CHECKPATCH) -f
# see .clang-tidy.
.PHONY: tidy
diff --git a/src/arch/aarch64/cpu.c b/src/arch/aarch64/cpu.c
index 121f355..5881dd4 100644
--- a/src/arch/aarch64/cpu.c
+++ b/src/arch/aarch64/cpu.c
@@ -23,6 +23,8 @@
#include "hf/addr.h"
#include "hf/std.h"
+#include "hypervisor/debug_el1.h"
+
void arch_irq_disable(void)
{
__asm__ volatile("msr DAIFSet, #0xf");
@@ -102,6 +104,18 @@
r->spsr = 5 | /* M bits, set to EL1h. */
(0xf << 6); /* DAIF bits set; disable interrupts. */
+ r->lazy.mdcr_el2 = get_mdcr_el2_value(vm_id);
+
+ /*
+ * NOTE: It is important that MDSCR_EL1.MDE (bit 15) is set to 0 for
+ * secondary VMs as long as Hafnium does not support debug register
+ * access for secondary VMs. If adding Hafnium support for secondary VM
+ * debug register accesses, then on context switches Hafnium needs to
+ * save/restore EL1 debug register state that either might change, or
+ * that needs to be protected.
+ */
+ r->lazy.mdscr_el1 = 0x0u & ~(0x1u << 15);
+
gic_regs_reset(r, is_primary);
}
diff --git a/src/arch/aarch64/hypervisor/BUILD.gn b/src/arch/aarch64/hypervisor/BUILD.gn
index 984adbf..36c6baa 100644
--- a/src/arch/aarch64/hypervisor/BUILD.gn
+++ b/src/arch/aarch64/hypervisor/BUILD.gn
@@ -23,6 +23,7 @@
]
sources += [
+ "debug_el1.c",
"handler.c",
"offsets.c",
"psci_handler.c",
diff --git a/src/arch/aarch64/hypervisor/debug_el1.c b/src/arch/aarch64/hypervisor/debug_el1.c
new file mode 100644
index 0000000..d278349
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/debug_el1.c
@@ -0,0 +1,289 @@
+/*
+ * 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 "debug_el1.h"
+
+#include "hf/check.h"
+#include "hf/dlog.h"
+#include "hf/panic.h"
+#include "hf/types.h"
+
+#include "msr.h"
+
+/**
+ * Controls traps for Trace Filter.
+ */
+#define MDCR_EL2_TTRF (1u << 19)
+
+/**
+ * Controls traps for Debug ROM.
+ */
+#define MDCR_EL2_TDRA (1u << 11)
+
+/**
+ * Controls traps for OS-Related Register Access.
+ */
+#define MDCR_EL2_TDOSA (1u << 10)
+
+/**
+ * Controls traps for remaining Debug Registers not trapped by TDRA and TDOSA.
+ */
+#define MDCR_EL2_TDA (1u << 9)
+
+/**
+ * Controls traps for all debug exceptions (e.g., breakpoints).
+ */
+#define MDCR_EL2_TDE (1u << 8)
+
+/**
+ * Controls traps for debug events, i.e., breakpoints, watchpoints, and vector.
+ * catch exceptions.
+ */
+#define MDSCR_EL1_MDE (1u << 15)
+
+/**
+ * System register are identified by op0, op2, op1, crn, crm. The ISS encoding
+ * includes also rt and direction. Exclude them, @see D13.2.37 (D13-2977).
+ */
+#define ISS_SYSREG_MASK \
+ (((1u << 22) - 1u) & /* Select the ISS bits*/ \
+ ~(0x1fu << 5) & /* exclude rt */ \
+ ~1u /* exclude direction */)
+
+#define GET_ISS_SYSREG(esr) (ISS_SYSREG_MASK & (esr))
+
+/**
+ * Op0 from the ISS encoding in the ESR.
+ */
+#define ISS_OP0_MASK 0x300000
+#define ISS_OP0_SHIFT 20
+#define GET_ISS_OP0(esr) ((ISS_OP0_MASK & (esr)) >> ISS_OP0_SHIFT)
+
+/**
+ * Op1 from the ISS encoding in the ESR.
+ */
+#define ISS_OP1_MASK 0x1c000
+#define ISS_OP1_SHIFT 14
+#define GET_ISS_OP1(esr) ((ISS_OP1_MASK & (esr)) >> ISS_OP1_SHIFT)
+
+/**
+ * Direction (i.e., read (1) or write (0), is the first bit in the ISS/ESR.
+ */
+#define ISS_DIRECTION_MASK 1u
+
+/**
+ * Gets the direction of the system register access, read (1) or write (0).
+ */
+#define GET_ISS_DIRECTION(esr) (ISS_DIRECTION_MASK & (esr))
+
+/**
+ * True if the ISS encoded in the esr indicates a read of the system register.
+ */
+#define ISS_IS_READ(esr) (ISS_DIRECTION_MASK & (esr))
+
+/**
+ * Rt, which identifies the general purpose register used for the operation.
+ */
+#define ISS_RT_MASK 0x3e0
+#define ISS_RT_SHIFT 5
+#define GET_ISS_RT(esr) ((ISS_RT_MASK & (esr)) >> ISS_RT_SHIFT)
+
+/**
+ * Definitions of read-only debug registers' ISS signatures.
+ */
+#define EL1_DEBUG_REGISTERS_READ \
+ X(MDRAR_EL1, 0x200400) \
+ X(DBGAUTHSTATUS_EL1, 0x2c1c1c) \
+ X(OSLSR_EL1, 0x280402)
+
+/**
+ * Definitions of readable and writeable debug registers' ISS signatures.
+ */
+#define EL1_DEBUG_REGISTERS_READ_WRITE \
+ X(DBGCLAIMCLR_EL1, 0x2c1c12) \
+ X(DBGCLAIMSET_EL1, 0x2c1c10) \
+ X(DBGPRCR_EL1, 0x280408) \
+ X(MDCCINT_EL1, 0x200004) \
+ X(MDSCR_EL1, 0x240004) \
+ X(OSDLR_EL1, 0x280406) \
+ X(OSDTRRX_EL1, 0x240000) \
+ X(OSDTRTX_EL1, 0x240006) \
+ X(OSECCR_EL1, 0x24000c) \
+ X(DBGBCR0_EL1, 0x2a0000) \
+ X(DBGBCR1_EL1, 0x2a0002) \
+ X(DBGBCR2_EL1, 0x2a0004) \
+ X(DBGBCR3_EL1, 0x2a0006) \
+ X(DBGBCR4_EL1, 0x2a0008) \
+ X(DBGBCR5_EL1, 0x2a000a) \
+ X(DBGBCR6_EL1, 0x2a000c) \
+ X(DBGBCR7_EL1, 0x2a000e) \
+ X(DBGBCR8_EL1, 0x2a0010) \
+ X(DBGBCR9_EL1, 0x2a0012) \
+ X(DBGBCR10_EL1, 0x2a0014) \
+ X(DBGBCR11_EL1, 0x2a0016) \
+ X(DBGBCR12_EL1, 0x2a0018) \
+ X(DBGBCR13_EL1, 0x2a001a) \
+ X(DBGBCR14_EL1, 0x2a001c) \
+ X(DBGBCR15_EL1, 0x2a001e) \
+ X(DBGBVR0_EL1, 0x280000) \
+ X(DBGBVR1_EL1, 0x280002) \
+ X(DBGBVR2_EL1, 0x280004) \
+ X(DBGBVR3_EL1, 0x280006) \
+ X(DBGBVR4_EL1, 0x280008) \
+ X(DBGBVR5_EL1, 0x28000a) \
+ X(DBGBVR6_EL1, 0x28000c) \
+ X(DBGBVR7_EL1, 0x28000e) \
+ X(DBGBVR8_EL1, 0x280010) \
+ X(DBGBVR9_EL1, 0x280012) \
+ X(DBGBVR10_EL1, 0x280014) \
+ X(DBGBVR11_EL1, 0x280016) \
+ X(DBGBVR12_EL1, 0x280018) \
+ X(DBGBVR13_EL1, 0x28001a) \
+ X(DBGBVR14_EL1, 0x28001c) \
+ X(DBGBVR15_EL1, 0x28001e) \
+ X(DBGWCR0_EL1, 0x2e0000) \
+ X(DBGWCR1_EL1, 0x2e0002) \
+ X(DBGWCR2_EL1, 0x2e0004) \
+ X(DBGWCR3_EL1, 0x2e0006) \
+ X(DBGWCR4_EL1, 0x2e0008) \
+ X(DBGWCR5_EL1, 0x2e000a) \
+ X(DBGWCR6_EL1, 0x2e000c) \
+ X(DBGWCR7_EL1, 0x2e000e) \
+ X(DBGWCR8_EL1, 0x2e0010) \
+ X(DBGWCR9_EL1, 0x2e0012) \
+ X(DBGWCR10_EL1, 0x2e0014) \
+ X(DBGWCR11_EL1, 0x2e0016) \
+ X(DBGWCR12_EL1, 0x2e0018) \
+ X(DBGWCR13_EL1, 0x2e001a) \
+ X(DBGWCR14_EL1, 0x2e001c) \
+ X(DBGWCR15_EL1, 0x2e001e) \
+ X(DBGWVR0_EL1, 0x2c0000) \
+ X(DBGWVR1_EL1, 0x2c0002) \
+ X(DBGWVR2_EL1, 0x2c0004) \
+ X(DBGWVR3_EL1, 0x2c0006) \
+ X(DBGWVR4_EL1, 0x2c0008) \
+ X(DBGWVR5_EL1, 0x2c000a) \
+ X(DBGWVR6_EL1, 0x2c000c) \
+ X(DBGWVR7_EL1, 0x2c000e) \
+ X(DBGWVR8_EL1, 0x2c0010) \
+ X(DBGWVR9_EL1, 0x2c0012) \
+ X(DBGWVR10_EL1, 0x2c0014) \
+ X(DBGWVR11_EL1, 0x2c0016) \
+ X(DBGWVR12_EL1, 0x2c0018) \
+ X(DBGWVR13_EL1, 0x2c001a) \
+ X(DBGWVR14_EL1, 0x2c001c) \
+ X(DBGWVR15_EL1, 0x2c001e)
+
+/**
+ * Definitions of all debug registers' ISS signatures.
+ */
+#define EL1_DEBUG_REGISTERS \
+ EL1_DEBUG_REGISTERS_READ \
+ EL1_DEBUG_REGISTERS_READ_WRITE
+
+/**
+ * Returns the value for mdcr_el2 for the particular VM.
+ * For now, the primary VM has one value and all secondary VMs share a value.
+ */
+uintreg_t get_mdcr_el2_value(spci_vm_id_t vm_id)
+{
+ if (vm_id == HF_PRIMARY_VM_ID) {
+ /*
+ * Trap primary VM accesses to debug registers to have fine
+ * grained control over system register accesses.
+ * Do not trap the Primary VM's debug events (!MDCR_EL2_TDE).
+ */
+ return MDCR_EL2_TTRF | MDCR_EL2_TDRA | MDCR_EL2_TDOSA |
+ MDCR_EL2_TDA;
+ }
+
+ /*
+ * Trap all secondary VM debug register accesses as well as debug
+ * event exceptions.
+ * Debug event exceptions should be disabled in secondary VMs, but trap
+ * them for additional security (MDCR_EL2_TDE).
+ */
+ return MDCR_EL2_TTRF | MDCR_EL2_TDRA | MDCR_EL2_TDOSA | MDCR_EL2_TDA |
+ MDCR_EL2_TDE;
+}
+
+/**
+ * Returns true if the ESR register shows an access to an EL1 debug register.
+ */
+bool is_debug_el1_register_access(uintreg_t esr_el2)
+{
+ /*
+ * Architecture Reference Manual D12.2: op0 == 0b10 is for debug and
+ * trace system registers. op1 = 0x1 for trace, remaining are debug.
+ */
+ return GET_ISS_OP0(esr_el2) == 0x2 && GET_ISS_OP1(esr_el2) != 0x1;
+}
+
+/**
+ * Processes an access (msr, mrs) to an EL1 debug register.
+ * Returns true if the access was allowed and performed, false otherwise.
+ */
+bool debug_el1_process_access(struct vcpu *vcpu, spci_vm_id_t vm_id,
+ uintreg_t esr_el2)
+{
+ /*
+ * For now, debug registers are not supported by secondary VMs.
+ * Disallow accesses to them.
+ */
+ if (vm_id != HF_PRIMARY_VM_ID) {
+ return false;
+ }
+
+ uintreg_t sys_register = GET_ISS_SYSREG(esr_el2);
+ uintreg_t rt_register = GET_ISS_RT(esr_el2);
+ uintreg_t value;
+
+ CHECK(rt_register < NUM_GP_REGS);
+
+ if (ISS_IS_READ(esr_el2)) {
+ switch (sys_register) {
+#define X(reg_name, reg_sig) \
+ case reg_sig: \
+ value = read_msr(reg_name); \
+ break;
+ EL1_DEBUG_REGISTERS
+#undef X
+ default:
+ value = vcpu->regs.r[rt_register];
+ dlog("Unsupported system register read 0x%x\n",
+ sys_register);
+ break;
+ }
+ vcpu->regs.r[rt_register] = value;
+
+ } else {
+ value = vcpu->regs.r[rt_register];
+ switch (sys_register) {
+#define X(reg_name, reg_sig) \
+ case reg_sig: \
+ write_msr(reg_name, value); \
+ break;
+ EL1_DEBUG_REGISTERS_READ_WRITE
+#undef X
+ default:
+ dlog("Unsupported system register write 0x%x\n",
+ sys_register);
+ break;
+ }
+ }
+
+ return true;
+}
diff --git a/src/arch/aarch64/hypervisor/debug_el1.h b/src/arch/aarch64/hypervisor/debug_el1.h
new file mode 100644
index 0000000..28becb5
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/debug_el1.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "hf/arch/types.h"
+
+#include "hf/cpu.h"
+
+#include "vmapi/hf/spci.h"
+
+uintreg_t get_mdcr_el2_value(spci_vm_id_t vm_id);
+
+bool is_debug_el1_register_access(uintreg_t esr_el2);
+
+bool debug_el1_process_access(struct vcpu *vcpu, spci_vm_id_t vm_id,
+ uintreg_t esr_el2);
diff --git a/src/arch/aarch64/hypervisor/exceptions.S b/src/arch/aarch64/hypervisor/exceptions.S
index 6a8309f..1c4d642 100644
--- a/src/arch/aarch64/hypervisor/exceptions.S
+++ b/src/arch/aarch64/hypervisor/exceptions.S
@@ -55,6 +55,54 @@
.endm
/**
+ * Save all general purpose registers into register buffer of current vcpu.
+ */
+.macro save_registers_to_vcpu
+ save_volatile_to_vcpu also_save_x18
+ stp x19, x20, [x18, #VCPU_REGS + 8 * 19]
+ stp x21, x22, [x18, #VCPU_REGS + 8 * 21]
+ stp x23, x24, [x18, #VCPU_REGS + 8 * 23]
+ stp x25, x26, [x18, #VCPU_REGS + 8 * 25]
+ stp x27, x28, [x18, #VCPU_REGS + 8 * 27]
+.endm
+
+/**
+ * Restore the volatile registers from the register buffer of the current vcpu.
+ */
+.macro restore_volatile_from_vcpu vcpu_ptr:req
+ ldp x4, x5, [\vcpu_ptr, #VCPU_REGS + 8 * 4]
+ ldp x6, x7, [\vcpu_ptr, #VCPU_REGS + 8 * 6]
+ ldp x8, x9, [\vcpu_ptr, #VCPU_REGS + 8 * 8]
+ ldp x10, x11, [\vcpu_ptr, #VCPU_REGS + 8 * 10]
+ ldp x12, x13, [\vcpu_ptr, #VCPU_REGS + 8 * 12]
+ ldp x14, x15, [\vcpu_ptr, #VCPU_REGS + 8 * 14]
+ ldp x16, x17, [\vcpu_ptr, #VCPU_REGS + 8 * 16]
+ ldr x18, [\vcpu_ptr, #VCPU_REGS + 8 * 18]
+ ldp x29, x30, [\vcpu_ptr, #VCPU_REGS + 8 * 29]
+
+ /* Restore return address & mode. */
+ ldp x1, x2, [\vcpu_ptr, #VCPU_REGS + 8 * 31]
+ msr elr_el2, x1
+ msr spsr_el2, x2
+
+ /* Restore x0..x3, which we have used as scratch before. */
+ ldp x2, x3, [\vcpu_ptr, #VCPU_REGS + 8 * 2]
+ ldp x0, x1, [\vcpu_ptr, #VCPU_REGS + 8 * 0]
+.endm
+
+/**
+ * Restore all general purpose registers from register buffer of current vcpu.
+ */
+.macro restore_registers_from_vcpu vcpu_ptr:req
+ ldp x19, x20, [\vcpu_ptr, #VCPU_REGS + 8 * 19]
+ ldp x21, x22, [\vcpu_ptr, #VCPU_REGS + 8 * 21]
+ ldp x23, x24, [\vcpu_ptr, #VCPU_REGS + 8 * 23]
+ ldp x25, x26, [\vcpu_ptr, #VCPU_REGS + 8 * 25]
+ ldp x27, x28, [\vcpu_ptr, #VCPU_REGS + 8 * 27]
+ restore_volatile_from_vcpu \vcpu_ptr
+.endm
+
+/**
* This is a generic handler for exceptions taken at a lower EL. It saves the
* volatile registers to the current vcpu and calls the C handler, which can
* select one of two paths: (a) restore volatile registers and return, or
@@ -91,8 +139,8 @@
lsr x18, x18, #26
/* Take the slow path if exception is not due to an HVC instruction. */
- sub x18, x18, #0x16
- cbnz x18, slow_sync_lower
+ cmp x18, #0x16
+ b.ne slow_sync_lower
/*
* Save x29 and x30, which are not saved by the callee, then jump to
@@ -193,6 +241,10 @@
.balign 0x40
slow_sync_lower:
+ /* Take the system register path for EC 0x18 */
+ cmp x18, #0x18
+ b.eq handle_system_register_access_s
+
/* The caller must have saved x18, so we don't save it here. */
save_volatile_to_vcpu
@@ -316,6 +368,9 @@
mrs x5, mdcr_el2
stp x4, x5, [x28], #16
+ mrs x6, mdscr_el1
+ str x6, [x28], #16
+
/* Save GIC registers. */
#if GIC_VERSION == 3 || GIC_VERSION == 4
/* Offset is too large, so start from a new base. */
@@ -472,6 +527,9 @@
msr vttbr_el2, x4
msr mdcr_el2, x5
+ ldr x6, [x28], #16
+ msr mdscr_el1, x6
+
/* Restore GIC registers. */
#if GIC_VERSION == 3 || GIC_VERSION == 4
/* Offset is too large, so start from a new base. */
@@ -506,30 +564,35 @@
* x0 is a pointer to the target vcpu.
*/
vcpu_restore_volatile_and_run:
- ldp x4, x5, [x0, #VCPU_REGS + 8 * 4]
- ldp x6, x7, [x0, #VCPU_REGS + 8 * 6]
- ldp x8, x9, [x0, #VCPU_REGS + 8 * 8]
- ldp x10, x11, [x0, #VCPU_REGS + 8 * 10]
- ldp x12, x13, [x0, #VCPU_REGS + 8 * 12]
- ldp x14, x15, [x0, #VCPU_REGS + 8 * 14]
- ldp x16, x17, [x0, #VCPU_REGS + 8 * 16]
- ldr x18, [x0, #VCPU_REGS + 8 * 18]
- ldp x29, x30, [x0, #VCPU_REGS + 8 * 29]
-
- /* Restore return address & mode. */
- ldp x1, x2, [x0, #VCPU_REGS + 8 * 31]
- msr elr_el2, x1
- msr spsr_el2, x2
-
- /* Restore x0..x3, which we have used as scratch before. */
- ldp x2, x3, [x0, #VCPU_REGS + 8 * 2]
- ldp x0, x1, [x0, #VCPU_REGS + 8 * 0]
+ restore_volatile_from_vcpu x0
eret
.balign 0x40
/**
- * Restores volatile registers from stack and returns.
+ * Restore volatile registers from stack and return to original caller.
*/
restore_from_stack_and_return:
restore_volatile_from_stack el2
eret
+
+.balign 0x40
+/**
+ * Handle accesses to system registers (EC=0x18) and return to original caller.
+ */
+handle_system_register_access_s:
+ /*
+ * All registers are (conservatively) saved because the handler can
+ * clobber non-volatile registers that are used by the msr/mrs, which
+ * results in the wrong value being read or written.
+ */
+ save_registers_to_vcpu
+
+ /* Read syndrome register and call C handler. */
+ mrs x0, esr_el2
+ bl handle_system_register_access
+ cbnz x0, vcpu_switch
+
+ /* vcpu is not changing. */
+ mrs x0, tpidr_el2
+ restore_registers_from_vcpu x0
+ eret
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;
+}
diff --git a/src/arch/aarch64/inc/hf/arch/types.h b/src/arch/aarch64/inc/hf/arch/types.h
index e4b3c3c..25d3e0c 100644
--- a/src/arch/aarch64/inc/hf/arch/types.h
+++ b/src/arch/aarch64/inc/hf/arch/types.h
@@ -25,6 +25,7 @@
#define PAGE_BITS 12
#define PAGE_LEVEL_BITS 9
#define FLOAT_REG_BYTES 16
+#define NUM_GP_REGS 31
/** The type of a page table entry (PTE). */
typedef uint64_t pte_t;
@@ -68,11 +69,15 @@
/** Type to represent the register state of a vCPU. */
struct arch_regs {
/* General purpose registers. */
- uintreg_t r[31];
+ uintreg_t r[NUM_GP_REGS];
uintreg_t pc;
uintreg_t spsr;
- /* System registers. */
+ /*
+ * System registers.
+ * NOTE: Ordering is important. If adding to or reordering registers
+ * below, make sure to update src/arch/aarch64/hypervisor/exceptions.S.
+ */
struct {
uintreg_t vmpidr_el2;
uintreg_t csselr_el1;
@@ -104,6 +109,7 @@
uintreg_t cnthctl_el2;
uintreg_t vttbr_el2;
uintreg_t mdcr_el2;
+ uintreg_t mdscr_el1;
} lazy;
/* Floating point registers. */
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 60b78a0..3c00989 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -26,6 +26,7 @@
sources = [
"abort.c",
"boot.c",
+ "debug_el1.c",
"floating_point.c",
"interrupts.c",
"mailbox.c",
diff --git a/test/vmapi/primary_with_secondaries/debug_el1.c b/test/vmapi/primary_with_secondaries/debug_el1.c
new file mode 100644
index 0000000..991caa2
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/debug_el1.c
@@ -0,0 +1,148 @@
+/*
+ * 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 "debug_el1.h"
+
+#include "primary_with_secondary.h"
+#include "util.h"
+
+/**
+ * QEMU does not properly handle the trapping of certain system register
+ * accesses. This was fixed in a custom local build that we could use. If not
+ * using that build, limit testing to the subset QEMU handles correctly.
+ */
+#define CUSTOM_QEMU_BUILD() 0
+
+/*
+ * TODO(b/132422368): Devise a way to test exhaustively read/write behavior to
+ * all debug registers that does not involve a separate service per register.
+ * This needs proper trap support as a starting point.
+ */
+
+TEST(debug_el1, secondary_mdccint_el1)
+{
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "debug_el1_secondary_mdccint_el1", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(debug_el1, secondary_dbgbcr0_el1)
+{
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "debug_el1_secondary_dbgbcr0_el1", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(debug_el1, secondary_dbgbvr0_el1)
+{
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "debug_el1_secondary_dbgbvr0_el1", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(debug_el1, secondary_dbgwcr0_el1)
+{
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "debug_el1_secondary_dbgwcr0_el1", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(debug_el1, secondary_dbgwvr0_el1)
+{
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM0, "debug_el1_secondary_dbgwvr0_el1", mb.send);
+
+ run_res = hf_vcpu_run(SERVICE_VM0, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+/**
+ * Attempts to access many debug registers for read, without validating their
+ * value.
+ */
+TEST(debug_el1, primary_basic)
+{
+ EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+
+ if (CUSTOM_QEMU_BUILD()) {
+ TRY_READ(DBGAUTHSTATUS_EL1);
+ TRY_READ(DBGCLAIMCLR_EL1);
+ TRY_READ(DBGCLAIMSET_EL1);
+ TRY_READ(DBGPRCR_EL1);
+ TRY_READ(OSDTRRX_EL1);
+ TRY_READ(OSDTRTX_EL1);
+ TRY_READ(OSECCR_EL1);
+
+ TRY_READ(DBGBCR2_EL1);
+ TRY_READ(DBGBCR3_EL1);
+ TRY_READ(DBGBCR4_EL1);
+ TRY_READ(DBGBCR5_EL1);
+ TRY_READ(DBGBVR2_EL1);
+ TRY_READ(DBGBVR3_EL1);
+ TRY_READ(DBGBVR4_EL1);
+ TRY_READ(DBGBVR5_EL1);
+ TRY_READ(DBGWCR2_EL1);
+ TRY_READ(DBGWCR3_EL1);
+ TRY_READ(DBGWVR2_EL1);
+ TRY_READ(DBGWVR3_EL1);
+ }
+
+ /* The following is the subset currently supported by QEMU. */
+ TRY_READ(MDCCINT_EL1);
+ TRY_READ(MDRAR_EL1);
+ TRY_READ(MDSCR_EL1);
+ TRY_READ(OSDLR_EL1);
+ TRY_READ(OSLSR_EL1);
+
+ TRY_READ(DBGBCR0_EL1);
+ TRY_READ(DBGBCR1_EL1);
+ TRY_READ(DBGBVR0_EL1);
+ TRY_READ(DBGBVR1_EL1);
+ TRY_READ(DBGWCR0_EL1);
+ TRY_READ(DBGWCR1_EL1);
+ TRY_READ(DBGWVR0_EL1);
+ TRY_READ(DBGWVR1_EL1);
+}
+
+/**
+ * Tests a few debug registers for read and write, and checks that the expected
+ * value is written/read.
+ */
+TEST(debug_el1, primary_read_write)
+{
+ EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+
+ TRY_WRITE_READ(DBGBCR0_EL1, 0x2);
+ TRY_WRITE_READ(DBGBVR0_EL1, 0xf0);
+}
diff --git a/test/vmapi/primary_with_secondaries/debug_el1.h b/test/vmapi/primary_with_secondaries/debug_el1.h
new file mode 100644
index 0000000..d5d6d77
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/debug_el1.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include "vmapi/hf/call.h"
+
+#include "../msr.h"
+#include "hftest.h"
+
+#define TRY_READ(REG) dlog(#REG "=%#x\n", read_msr(REG));
+
+#define TRY_WRITE_READ(REG, VALUE) \
+ do { \
+ uintreg_t x; \
+ write_msr(REG, VALUE); \
+ x = read_msr(REG); \
+ EXPECT_EQ(x, VALUE); \
+ } while (0);
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index 7dc1964..29da308 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -28,6 +28,16 @@
]
}
+# Service to try to access EL1 debug registers.
+source_set("debug_el1") {
+ testonly = true
+ public_configs = [ "//test/hftest:hftest_config" ]
+
+ sources = [
+ "debug_el1.c",
+ ]
+}
+
# Service to listen for messages and echo them back to the sender.
source_set("echo") {
testonly = true
@@ -189,6 +199,7 @@
":abort",
":boot",
":check_state",
+ ":debug_el1",
":echo",
":echo_with_notification",
":floating_point",
diff --git a/test/vmapi/primary_with_secondaries/services/debug_el1.c b/test/vmapi/primary_with_secondaries/services/debug_el1.c
new file mode 100644
index 0000000..58ab389
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/services/debug_el1.c
@@ -0,0 +1,54 @@
+/*
+ * 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 "../debug_el1.h"
+
+#include "hf/dlog.h"
+
+TEST_SERVICE(debug_el1_secondary_mdccint_el1)
+{
+ EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+ TRY_READ(MDCCINT_EL1);
+ FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(debug_el1_secondary_dbgbcr0_el1)
+{
+ EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+ TRY_READ(DBGBCR0_EL1);
+ FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(debug_el1_secondary_dbgbvr0_el1)
+{
+ EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+ TRY_READ(DBGBVR0_EL1);
+ FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(debug_el1_secondary_dbgwcr0_el1)
+{
+ EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+ TRY_READ(DBGWCR0_EL1);
+ FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(debug_el1_secondary_dbgwvr0_el1)
+{
+ EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+ TRY_READ(DBGWVR0_EL1);
+ FAIL("Reading debug EL1 register in secondary VM didn't trap.");
+}