Support for accessing performance monitoring registers

For now, the primary VM can access all performance monitoring registers, whereas
secondary VMs cannot.  Registers that enable counting or performance-related
interrupts are saved and switched, therefore, the primary VM cannot count events
or set interrupts for secondary VMs.

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

Bug: 132394973
Change-Id: I89c9f6658d30b8bca3c13ae99c3354f957fd0bb3
diff --git a/src/arch/aarch64/cpu.c b/src/arch/aarch64/cpu.c
index 2ce71d0..50b295e 100644
--- a/src/arch/aarch64/cpu.c
+++ b/src/arch/aarch64/cpu.c
@@ -23,6 +23,7 @@
 #include "hf/addr.h"
 #include "hf/std.h"
 
+#include "hypervisor/perfmon.h"
 #include "hypervisor/sysregs.h"
 
 void arch_irq_disable(void)
@@ -116,6 +117,9 @@
 	 */
 	r->lazy.mdscr_el1 = 0x0u & ~(0x1u << 15);
 
+	/* Disable cycle counting on initialization. */
+	r->lazy.pmccfiltr_el0 = perfmon_get_pmccfiltr_el0_init_value(vm_id);
+
 	gic_regs_reset(r, is_primary);
 }
 
diff --git a/src/arch/aarch64/hypervisor/BUILD.gn b/src/arch/aarch64/hypervisor/BUILD.gn
index 32cb667..c1ac799 100644
--- a/src/arch/aarch64/hypervisor/BUILD.gn
+++ b/src/arch/aarch64/hypervisor/BUILD.gn
@@ -25,6 +25,7 @@
     "debug_el1.c",
     "handler.c",
     "offsets.c",
+    "perfmon.c",
     "psci_handler.c",
     "sysregs.c",
   ]
diff --git a/src/arch/aarch64/hypervisor/debug_el1.c b/src/arch/aarch64/hypervisor/debug_el1.c
index 15ca849..da8ea43 100644
--- a/src/arch/aarch64/hypervisor/debug_el1.c
+++ b/src/arch/aarch64/hypervisor/debug_el1.c
@@ -129,7 +129,7 @@
 /**
  * Returns true if the ESR register shows an access to an EL1 debug register.
  */
-bool is_debug_el1_register_access(uintreg_t esr)
+bool debug_el1_is_register_access(uintreg_t esr)
 {
 	/*
 	 * Architecture Reference Manual D12.2: op0 == 2 is for debug and trace
diff --git a/src/arch/aarch64/hypervisor/debug_el1.h b/src/arch/aarch64/hypervisor/debug_el1.h
index eb6399d..9dc1ef6 100644
--- a/src/arch/aarch64/hypervisor/debug_el1.h
+++ b/src/arch/aarch64/hypervisor/debug_el1.h
@@ -22,7 +22,7 @@
 
 #include "vmapi/hf/spci.h"
 
-bool is_debug_el1_register_access(uintreg_t esr_el2);
+bool debug_el1_is_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 8cca831..e8e391b 100644
--- a/src/arch/aarch64/hypervisor/exceptions.S
+++ b/src/arch/aarch64/hypervisor/exceptions.S
@@ -280,7 +280,15 @@
 	stp x4, x5, [x28], #16
 
 	mrs x6, mdscr_el1
-	str x6, [x28], #16
+	mrs x7, pmccfiltr_el0
+	stp x6, x7, [x28], #16
+
+	mrs x8, pmcr_el0
+	mrs x9, pmcntenset_el0
+	stp x8, x9, [x28], #16
+
+	mrs x10, pmintenset_el1
+	str x10, [x28], #16
 
 	/* Save GIC registers. */
 #if GIC_VERSION == 3 || GIC_VERSION == 4
@@ -438,8 +446,27 @@
 	msr vttbr_el2, x4
 	msr mdcr_el2, x5
 
-	ldr x6, [x28], #16
+	ldp x6, x7, [x28], #16
 	msr mdscr_el1, x6
+	msr pmccfiltr_el0, x7
+
+	ldp x8, x9, [x28], #16
+	msr pmcr_el0, x8
+	/*
+	 * NOTE: Writing 0s to pmcntenset_el0's bits do not alter their values.
+	 * To reset them, clear the register by writing to pmcntenclr_el0.
+	 */
+	mov x27, #0xffffffff
+	msr pmcntenclr_el0, x27
+	msr pmcntenset_el0, x9
+
+	ldr x10, [x28], #16
+	/*
+	 * NOTE: Writing 0s to pmintenset_el1's bits do not alter their values.
+	 * To reset them, clear the register by writing to pmintenclr_el1.
+	 */
+	msr pmintenclr_el1, x27
+	msr pmintenset_el1, x10
 
 	/* Restore GIC registers. */
 #if GIC_VERSION == 3 || GIC_VERSION == 4
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 39bf58c..e72d69e 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -32,6 +32,7 @@
 
 #include "debug_el1.h"
 #include "msr.h"
+#include "perfmon.h"
 #include "psci.h"
 #include "psci_handler.h"
 #include "smc.h"
@@ -631,16 +632,26 @@
 	CHECK(ec == 0x18);
 
 	/*
-	 * Handle accesses to debug registers.
+	 * Handle accesses to debug and performance monitor registers.
 	 * Abort when encountering unhandled register accesses.
 	 */
-	if (is_debug_el1_register_access(esr) &&
-	    debug_el1_process_access(vcpu, vm_id, esr)) {
-		/* Instruction was fulfilled. Skip it and run the next one. */
-		vcpu->regs.pc += GET_NEXT_PC_INC(esr);
-		return NULL;
+	if (debug_el1_is_register_access(esr)) {
+		if (!debug_el1_process_access(vcpu, vm_id, esr)) {
+			goto fail;
+		}
+	} else if (perfmon_is_register_access(esr)) {
+		if (!perfmon_process_access(vcpu, vm_id, esr)) {
+			goto fail;
+		}
+	} else {
+		goto fail;
 	}
 
+	/* Instruction was fulfilled. Skip it and run the next one. */
+	vcpu->regs.pc += GET_NEXT_PC_INC(esr);
+	return NULL;
+
+fail:
 	direction_str = ISS_IS_READ(esr) ? "read" : "write";
 
 	dlog("Unhandled system register %s: op0=%d, op1=%d, crn=%d, "
diff --git a/src/arch/aarch64/hypervisor/offsets.h b/src/arch/aarch64/hypervisor/offsets.h
index 9f43fb8..cc2621b 100644
--- a/src/arch/aarch64/hypervisor/offsets.h
+++ b/src/arch/aarch64/hypervisor/offsets.h
@@ -21,7 +21,7 @@
 #define CPU_STACK_BOTTOM 8
 #define VCPU_REGS 32
 #define VCPU_LAZY (VCPU_REGS + 264)
-#define VCPU_FREGS (VCPU_LAZY + 248)
+#define VCPU_FREGS (VCPU_LAZY + 280)
 
 #if GIC_VERSION == 3 || GIC_VERSION == 4
 #define VCPU_GIC (VCPU_FREGS + 528)
diff --git a/src/arch/aarch64/hypervisor/perfmon.c b/src/arch/aarch64/hypervisor/perfmon.c
new file mode 100644
index 0000000..48dfdf3
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/perfmon.c
@@ -0,0 +1,238 @@
+/*
+ * 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 "perfmon.h"
+
+#include "hf/check.h"
+#include "hf/dlog.h"
+#include "hf/panic.h"
+#include "hf/types.h"
+
+#include "msr.h"
+#include "sysregs.h"
+
+/* clang-format off */
+
+/**
+ * Definitions of read-only performance monitor registers' encodings.
+ * See Arm Architecture Reference Manual Armv8-A, D12.3.1.
+ * NAME, op0, op1, crn, crm, op2
+ */
+#define PERFMON_REGISTERS_READ                 \
+	X(PMCEID0_EL0       , 3, 3,  9, 12, 6) \
+	X(PMCEID1_EL0       , 3, 3,  9, 12, 7) \
+	X(PMEVCNTR0_EL0     , 3, 3, 14,  8, 0) \
+	X(PMEVCNTR1_EL0     , 3, 3, 14,  8, 1) \
+	X(PMEVCNTR2_EL0     , 3, 3, 14,  8, 2) \
+	X(PMEVCNTR3_EL0     , 3, 3, 14,  8, 3) \
+	X(PMEVCNTR4_EL0     , 3, 3, 14,  8, 4) \
+	X(PMEVCNTR5_EL0     , 3, 3, 14,  8, 5) \
+	X(PMEVCNTR6_EL0     , 3, 3, 14,  8, 6) \
+	X(PMEVCNTR7_EL0     , 3, 3, 14,  8, 7) \
+	X(PMEVCNTR8_EL0     , 3, 3, 14,  9, 0) \
+	X(PMEVCNTR9_EL0     , 3, 3, 14,  9, 1) \
+	X(PMEVCNTR10_EL0    , 3, 3, 14,  9, 2) \
+	X(PMEVCNTR11_EL0    , 3, 3, 14,  9, 3) \
+	X(PMEVCNTR12_EL0    , 3, 3, 14,  9, 4) \
+	X(PMEVCNTR13_EL0    , 3, 3, 14,  9, 5) \
+	X(PMEVCNTR14_EL0    , 3, 3, 14,  9, 6) \
+	X(PMEVCNTR15_EL0    , 3, 3, 14,  9, 7) \
+	X(PMEVCNTR16_EL0    , 3, 3, 14, 10, 0) \
+	X(PMEVCNTR17_EL0    , 3, 3, 14, 10, 1) \
+	X(PMEVCNTR18_EL0    , 3, 3, 14, 10, 2) \
+	X(PMEVCNTR19_EL0    , 3, 3, 14, 10, 3) \
+	X(PMEVCNTR20_EL0    , 3, 3, 14, 10, 4) \
+	X(PMEVCNTR21_EL0    , 3, 3, 14, 10, 5) \
+	X(PMEVCNTR22_EL0    , 3, 3, 14, 10, 6) \
+	X(PMEVCNTR23_EL0    , 3, 3, 14, 10, 7) \
+	X(PMEVCNTR24_EL0    , 3, 3, 14, 11, 0) \
+	X(PMEVCNTR25_EL0    , 3, 3, 14, 11, 1) \
+	X(PMEVCNTR26_EL0    , 3, 3, 14, 11, 2) \
+	X(PMEVCNTR27_EL0    , 3, 3, 14, 11, 3) \
+	X(PMEVCNTR28_EL0    , 3, 3, 14, 11, 4) \
+	X(PMEVCNTR29_EL0    , 3, 3, 14, 11, 5) \
+	X(PMEVCNTR30_EL0    , 3, 3, 14, 11, 6) \
+
+/**
+ * Definitions of write-only performance monitor registers' encodings.
+ * See Arm Architecture Reference Manual Armv8-A, D12.3.1.
+ * NAME, op0, op1, crn, crm, op2
+ */
+#define PERFMON_REGISTERS_WRITE                \
+	X(PMSWINC_EL0       , 3, 3,  9, 12, 4) \
+
+/**
+ * Definitions of readable and writeable performance monitor registers' encodings.
+ * See Arm Architecture Reference Manual Armv8-A, D12.3.1.
+ * NAME, op0, op1, crn, crm, op2
+ */
+#define PERFMON_REGISTERS_READ_WRITE           \
+	X(PMINTENSET_EL1    , 3, 0,  9, 14, 1) \
+	X(PMINTENCLR_EL1    , 3, 0,  9, 14, 2) \
+	X(PMCR_EL0          , 3, 3,  9, 12, 0) \
+	X(PMCNTENSET_EL0    , 3, 3,  9, 12, 1) \
+	X(PMCNTENCLR_EL0    , 3, 3,  9, 12, 2) \
+	X(PMOVSCLR_EL0      , 3, 3,  9, 12, 3) \
+	X(PMSELR_EL0        , 3, 3,  9, 12, 5) \
+	X(PMCCNTR_EL0       , 3, 3,  9, 13, 0) \
+	X(PMXEVTYPER_EL0    , 3, 3,  9, 13, 1) \
+	X(PMXEVCNTR_EL0     , 3, 3,  9, 13, 2) \
+	X(PMUSERENR_EL0     , 3, 3,  9, 14, 0) \
+	X(PMOVSSET_EL0      , 3, 3,  9, 14, 3) \
+	X(PMEVTYPER0_EL0    , 3, 3, 14, 12, 0) \
+	X(PMEVTYPER1_EL0    , 3, 3, 14, 12, 1) \
+	X(PMEVTYPER2_EL0    , 3, 3, 14, 12, 2) \
+	X(PMEVTYPER3_EL0    , 3, 3, 14, 12, 3) \
+	X(PMEVTYPER4_EL0    , 3, 3, 14, 12, 4) \
+	X(PMEVTYPER5_EL0    , 3, 3, 14, 12, 5) \
+	X(PMEVTYPER6_EL0    , 3, 3, 14, 12, 6) \
+	X(PMEVTYPER7_EL0    , 3, 3, 14, 12, 7) \
+	X(PMEVTYPER8_EL0    , 3, 3, 14, 13, 0) \
+	X(PMEVTYPER9_EL0    , 3, 3, 14, 13, 1) \
+	X(PMEVTYPER10_EL0   , 3, 3, 14, 13, 2) \
+	X(PMEVTYPER11_EL0   , 3, 3, 14, 13, 3) \
+	X(PMEVTYPER12_EL0   , 3, 3, 14, 13, 4) \
+	X(PMEVTYPER13_EL0   , 3, 3, 14, 13, 5) \
+	X(PMEVTYPER14_EL0   , 3, 3, 14, 13, 6) \
+	X(PMEVTYPER15_EL0   , 3, 3, 14, 13, 7) \
+	X(PMEVTYPER16_EL0   , 3, 3, 14, 14, 0) \
+	X(PMEVTYPER17_EL0   , 3, 3, 14, 14, 1) \
+	X(PMEVTYPER18_EL0   , 3, 3, 14, 14, 2) \
+	X(PMEVTYPER19_EL0   , 3, 3, 14, 14, 3) \
+	X(PMEVTYPER20_EL0   , 3, 3, 14, 14, 4) \
+	X(PMEVTYPER21_EL0   , 3, 3, 14, 14, 5) \
+	X(PMEVTYPER22_EL0   , 3, 3, 14, 14, 6) \
+	X(PMEVTYPER23_EL0   , 3, 3, 14, 14, 7) \
+	X(PMEVTYPER24_EL0   , 3, 3, 14, 15, 0) \
+	X(PMEVTYPER25_EL0   , 3, 3, 14, 15, 1) \
+	X(PMEVTYPER26_EL0   , 3, 3, 14, 15, 2) \
+	X(PMEVTYPER27_EL0   , 3, 3, 14, 15, 3) \
+	X(PMEVTYPER28_EL0   , 3, 3, 14, 15, 4) \
+	X(PMEVTYPER29_EL0   , 3, 3, 14, 15, 5) \
+	X(PMEVTYPER30_EL0   , 3, 3, 14, 15, 6) \
+	X(PMCCFILTR_EL0     , 3, 3, 14, 15, 7)
+
+/* clang-format on */
+
+/**
+ * Returns true if the ESR register shows an access to a performance monitor
+ * register.
+ */
+bool perfmon_is_register_access(uintreg_t esr)
+{
+	uintreg_t op0 = GET_ISS_OP0(esr);
+	uintreg_t op1 = GET_ISS_OP1(esr);
+	uintreg_t crn = GET_ISS_CRN(esr);
+	uintreg_t crm = GET_ISS_CRM(esr);
+
+	/* From the Arm Architecture Reference Manual Table D12-2. */
+
+	/* For PMINTENCLR_EL1 and PMINTENSET_EL1*/
+	if (op0 == 3 && op1 == 0 && crn == 9 && crm == 14) {
+		return true;
+	}
+
+	/* For PMEVCNTRn_EL0, PMEVTYPERn_EL0, and PMCCFILTR_EL0. */
+	if (op0 == 3 && op1 == 3 && crn == 14 && crm >= 8 && crm <= 15) {
+		return true;
+	}
+
+	/* For all remaining performance monitor registers. */
+	return op0 == 3 && op1 == 3 && crn == 9 && crm >= 12 && crm <= 14;
+}
+
+/**
+ * Processes an access (msr, mrs) to a performance monitor register.
+ * Returns true if the access was allowed and performed, false otherwise.
+ */
+bool perfmon_process_access(struct vcpu *vcpu, spci_vm_id_t vm_id,
+			    uintreg_t esr)
+{
+	/*
+	 * For now, performance monitor 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);
+	uintreg_t rt_register = GET_ISS_RT(esr);
+	uintreg_t value;
+
+	/* +1 because Rt can access register XZR */
+	CHECK(rt_register < NUM_GP_REGS + 1);
+
+	if (ISS_IS_READ(esr)) {
+		switch (sys_register) {
+#define X(reg_name, op0, op1, crn, crm, op2)              \
+	case (GET_ISS_ENCODING(op0, op1, crn, crm, op2)): \
+		value = read_msr(reg_name);               \
+		break;
+			PERFMON_REGISTERS_READ
+			PERFMON_REGISTERS_READ_WRITE
+#undef X
+		default:
+			value = vcpu->regs.r[rt_register];
+			dlog("Unsupported performance monitor register read: "
+			     "op0=%d, op1=%d, crn=%d, crm=%d, op2=%d, rt=%d.\n",
+			     GET_ISS_OP0(esr), GET_ISS_OP1(esr),
+			     GET_ISS_CRN(esr), GET_ISS_CRM(esr),
+			     GET_ISS_OP2(esr), GET_ISS_RT(esr));
+			break;
+		}
+		if (rt_register != RT_REG_XZR) {
+			vcpu->regs.r[rt_register] = value;
+		}
+	} else {
+		if (rt_register != RT_REG_XZR) {
+			value = vcpu->regs.r[rt_register];
+		} else {
+			value = 0;
+		}
+		switch (sys_register) {
+#define X(reg_name, op0, op1, crn, crm, op2)              \
+	case (GET_ISS_ENCODING(op0, op1, crn, crm, op2)): \
+		write_msr(reg_name, value);               \
+		break;
+			PERFMON_REGISTERS_WRITE
+			PERFMON_REGISTERS_READ_WRITE
+#undef X
+		default:
+			dlog("Unsupported performance monitor register write: "
+			     "op0=%d, op1=%d, crn=%d, crm=%d, op2=%d, rt=%d.\n",
+			     GET_ISS_OP0(esr), GET_ISS_OP1(esr),
+			     GET_ISS_CRN(esr), GET_ISS_CRM(esr),
+			     GET_ISS_OP2(esr), GET_ISS_RT(esr));
+			break;
+		}
+	}
+
+	return true;
+}
+
+/**
+ * Returns the value register PMCCFILTR_EL0 should have at initialization.
+ */
+uintreg_t perfmon_get_pmccfiltr_el0_init_value(spci_vm_id_t vm_id)
+{
+	if (vm_id != HF_PRIMARY_VM_ID) {
+		/* Disable cycle counting for secondary VMs.  */
+		return PMCCFILTR_EL0_P | PMCCFILTR_EL0_U;
+	}
+
+	return 0;
+}
diff --git a/src/arch/aarch64/hypervisor/perfmon.h b/src/arch/aarch64/hypervisor/perfmon.h
new file mode 100644
index 0000000..ff83f79
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/perfmon.h
@@ -0,0 +1,87 @@
+/*
+ * 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"
+
+/**
+ * PMCR_EL0.N: Indicates the number of event counters implemented.
+ */
+#define PMCR_EL0_N_MASK 0xf800
+#define PMCR_EL0_N_SHIFT 11
+#define GET_PMCR_EL0_N(pmcr) ((PMCR_EL0_N_MASK & (pmcr)) >> PMCR_EL0_N_SHIFT)
+
+/**
+ * Set to disable cycle counting when event counting is prohibited.
+ */
+#define PMCR_EL0_DP 0x10
+
+/**
+ * Set to enable export of events where not prohibited.
+ */
+#define PMCR_EL0_X 0x8
+
+/**
+ * Set to enable event counting.
+ */
+#define PMCR_EL0_E 0x1
+
+/**
+ * Set to disable cycle counting in EL1.
+ */
+#define PMCCFILTR_EL0_P 0x80000000
+
+/**
+ * Set to disable cycle counting in EL0.
+ */
+#define PMCCFILTR_EL0_U 0x40000000
+
+/**
+ * Cycle counting in non-secure EL1 is enabled if NSK == P.
+ */
+#define PMCCFILTR_EL0_NSK 0x20000000
+
+/**
+ * Cycle counting in non-secure EL0 is enabled if NSU == U.
+ */
+#define PMCCFILTR_EL0_NSU 0x10000000
+
+/**
+ * Set to enable cycle counting in EL2.
+ */
+#define PMCCFILTR_EL0_NSH 0x8000000
+
+/**
+ * Cycle counting in EL3 is enabled if M == P.
+ */
+#define PMCCFILTR_EL0_M 0x4000000
+
+/**
+ * Cycle counting in Secutre EL2 is enabled if SH != NSH.
+ */
+#define PMCCFILTR_EL0_SH 0x1000000
+
+bool perfmon_is_register_access(uintreg_t esr_el2);
+
+bool perfmon_process_access(struct vcpu *vcpu, spci_vm_id_t vm_id,
+			    uintreg_t esr_el2);
+
+uintreg_t perfmon_get_pmccfiltr_el0_init_value(spci_vm_id_t vm_id);
diff --git a/src/arch/aarch64/hypervisor/sysregs.c b/src/arch/aarch64/hypervisor/sysregs.c
index 8d6336d..a3121a7 100644
--- a/src/arch/aarch64/hypervisor/sysregs.c
+++ b/src/arch/aarch64/hypervisor/sysregs.c
@@ -22,6 +22,7 @@
 #include "hf/types.h"
 
 #include "msr.h"
+#include "perfmon.h"
 
 /**
  * Returns the value for MDCR_EL2 for the particular VM.
@@ -33,6 +34,11 @@
 	uintreg_t pmcr_el0 = read_msr(PMCR_EL0);
 
 	/*
+	 * TODO: Investigate gating settings these values depending on which
+	 * features are supported by the current CPU.
+	 */
+
+	/*
 	 * Preserve E2PB for now, which depends on the SPE implementation.
 	 * TODO: Investigate how to detect whether SPE is implemented, and which
 	 * stage's translation regime is applicable, i.e., EL2 or EL1.
@@ -53,8 +59,23 @@
 		 * but trap them for additional security.
 		 */
 		mdcr_el2_value |= MDCR_EL2_TDE;
+
+		/*
+		 * Trap secondary VM accesses to performance monitor registers
+		 * for fine-grained control.
+		 *
+		 * Do *not* trap primary VM accesses to performance monitor
+		 * registers. Sensitive registers are context switched, and
+		 * access to performance monitor registers is more common than
+		 * access to debug registers, therefore, trapping them all could
+		 * impose a non-trivial overhead.
+		 */
+		mdcr_el2_value |= MDCR_EL2_TPM | MDCR_EL2_TPMCR;
 	}
 
+	/* Disable cycle and event counting at EL2. */
+	mdcr_el2_value |= MDCR_EL2_HCCD | MDCR_EL2_HPMD;
+
 	/* All available event counters accessible from all exception levels. */
 	mdcr_el2_value |= GET_PMCR_EL0_N(pmcr_el0) & MDCR_EL2_HPMN;
 
diff --git a/src/arch/aarch64/hypervisor/sysregs.h b/src/arch/aarch64/hypervisor/sysregs.h
index 435cb18..7fc74b6 100644
--- a/src/arch/aarch64/hypervisor/sysregs.h
+++ b/src/arch/aarch64/hypervisor/sysregs.h
@@ -70,24 +70,26 @@
 #define MDCR_EL2_TDE (0x1u << 8)
 
 /**
- * Controls traps for all PMU register accesses other than PMCR_EL0.
+ * Controls traps for all performance monitor register accesses other than
+ * PMCR_EL0.
  */
 #define MDCR_EL2_TPM (0x1u << 6)
 
 /**
- * Controls traps for PMU register PMCR_EL0.
+ * Controls traps for performance monitor register PMCR_EL0.
  */
 #define MDCR_EL2_TPMCR (0x1u << 5)
 
 /**
  * Defines the number of event counters that are accessible from various
- * exception levels, if permitted.  Dependant on whether PMUv3 is implemented.
+ * exception levels, if permitted. Dependant on whether PMUv3
+ * is implemented.
  */
 #define MDCR_EL2_HPMN (0x1fu << 0)
 
 /**
  * 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).
+ * includes also rt and direction. Exclude them, @see D13.2.37 (D13-2977).
  */
 #define ISS_SYSREG_MASK                                \
 	(((1u << 22) - 1u) & /* Select the ISS bits */ \
diff --git a/src/arch/aarch64/inc/hf/arch/types.h b/src/arch/aarch64/inc/hf/arch/types.h
index 34beb15..b750631 100644
--- a/src/arch/aarch64/inc/hf/arch/types.h
+++ b/src/arch/aarch64/inc/hf/arch/types.h
@@ -111,6 +111,10 @@
 		uintreg_t vttbr_el2;
 		uintreg_t mdcr_el2;
 		uintreg_t mdscr_el1;
+		uintreg_t pmccfiltr_el0;
+		uintreg_t pmcr_el0;
+		uintreg_t pmcntenset_el0;
+		uintreg_t pmintenset_el1;
 	} lazy;
 
 	/* Floating point registers. */