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/Makefile b/Makefile
index c000c72..5ef5252 100644
--- a/Makefile
+++ b/Makefile
@@ -45,7 +45,8 @@
 # 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"
+# perfmon.c : uses XMACROS, which checkpatch doesn't understand.
+CHECKPATCH_IGNORE := "src/arch/aarch64/hypervisor/debug_el1.c\|src/arch/aarch64/hypervisor/perfmon.c"
 
 # Select the project to build.
 PROJECT ?= reference
diff --git a/driver/linux b/driver/linux
index a989330..d426b6c 160000
--- a/driver/linux
+++ b/driver/linux
@@ -1 +1 @@
-Subproject commit a9893306ccd532c8ac0f568b60cc55489f493b2c
+Subproject commit d426b6cb6d2bdde5b7d6c140141f739378d2ca95
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. */
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index b8bdfb2..7302db0 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -32,6 +32,7 @@
     "mailbox.c",
     "memory_sharing.c",
     "no_services.c",
+    "perfmon.c",
     "run_race.c",
     "smp.c",
     "spci.c",
diff --git a/test/vmapi/primary_with_secondaries/debug_el1.c b/test/vmapi/primary_with_secondaries/debug_el1.c
index 991caa2..74d2647 100644
--- a/test/vmapi/primary_with_secondaries/debug_el1.c
+++ b/test/vmapi/primary_with_secondaries/debug_el1.c
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include "debug_el1.h"
-
 #include "primary_with_secondary.h"
+#include "sysregs.h"
 #include "util.h"
 
 /**
@@ -88,8 +87,7 @@
 }
 
 /**
- * Attempts to access many debug registers for read, without validating their
- * value.
+ * Attempts to access debug registers for read, without validating their value.
  */
 TEST(debug_el1, primary_basic)
 {
diff --git a/test/vmapi/primary_with_secondaries/perfmon.c b/test/vmapi/primary_with_secondaries/perfmon.c
new file mode 100644
index 0000000..4741043
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/perfmon.c
@@ -0,0 +1,244 @@
+/*
+ * 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 "../../src/arch/aarch64/hypervisor/perfmon.h"
+
+#include "primary_with_secondary.h"
+#include "sysregs.h"
+#include "util.h"
+
+/*
+ * TODO(b/132394973): Devise a way to test exhaustively read/write behavior to
+ * all debug registers that does not involve a separate service per register,
+ * because creating a new test/VM for every instance becomes too slow.
+ * This needs proper trap support as a starting point.
+ */
+
+TEST(perfmon, secondary_pmccfiltr_el0)
+{
+	struct hf_vcpu_run_return run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "perfmon_secondary_pmccfiltr_el0", mb.send);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(perfmon, secondary_pmcr_el0)
+{
+	struct hf_vcpu_run_return run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "perfmon_secondary_pmcr_el0", mb.send);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+TEST(perfmon, secondary_pmintenset_el1)
+{
+	struct hf_vcpu_run_return run_res;
+	struct mailbox_buffers mb = set_up_mailbox();
+
+	SERVICE_SELECT(SERVICE_VM0, "perfmon_secondary_pmintenset_el1",
+		       mb.send);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+/**
+ * Attempts to access performance monitor registers for read, without validating
+ * their value.
+ */
+TEST(perfmon, primary_basic)
+{
+	EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+
+	TRY_READ(PMCEID0_EL0);
+	TRY_READ(PMCEID1_EL0);
+	TRY_READ(PMCCFILTR_EL0);
+	TRY_READ(PMCR_EL0);
+}
+
+/**
+ * Tests a few performance counter registers for read and write, and checks that
+ * the expected value is written/read.
+ */
+TEST(perfmon, primary_read_write)
+{
+	EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+
+	TRY_WRITE_READ(PMCCNTR_EL0, 0xaaaa);
+
+	write_msr(PMINTENCLR_EL1, 0xffff);
+	CHECK_READ(PMINTENSET_EL1, 0);
+
+	/* Bits set in PMINTENSET_EL1 can be read in PMINTENCLR_EL1. */
+	write_msr(PMINTENSET_EL1, 0xf);
+	CHECK_READ(PMINTENCLR_EL1, 0xf);
+
+	/* Writes to PMINTENSET_EL1 do not clear already set bits. */
+	write_msr(PMINTENSET_EL1, 0xf0);
+	CHECK_READ(PMINTENCLR_EL1, 0xff);
+}
+
+/**
+ * Attempts to read all performance counters supported by the current CPU
+ * configuration.
+ */
+/* NOLINTNEXTLINE(readability-function-size) */
+TEST(perfmon, primary_counters)
+{
+	uintreg_t pmcr_el0 = read_msr(PMCR_EL0);
+	uintreg_t perf_mon_count = GET_PMCR_EL0_N(pmcr_el0);
+
+	EXPECT_EQ(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+
+	if (perf_mon_count == 0) {
+		return;
+	}
+
+	switch (perf_mon_count - 1) {
+	default:
+		FAIL("More performance monitor registers than supported.");
+	case 30:
+		TRY_READ(PMEVCNTR30_EL0);
+		TRY_WRITE_READ(PMEVTYPER30_EL0, 0x1);
+		/* fallthrough */
+	case 29:
+		TRY_READ(PMEVCNTR29_EL0);
+		TRY_WRITE_READ(PMEVTYPER29_EL0, 0x1);
+		/* fallthrough */
+	case 28:
+		TRY_READ(PMEVCNTR28_EL0);
+		TRY_WRITE_READ(PMEVTYPER28_EL0, 0x1);
+		/* fallthrough */
+	case 27:
+		TRY_READ(PMEVCNTR27_EL0);
+		TRY_WRITE_READ(PMEVTYPER27_EL0, 0x1);
+		/* fallthrough */
+	case 26:
+		TRY_READ(PMEVCNTR26_EL0);
+		TRY_WRITE_READ(PMEVTYPER26_EL0, 0x1);
+		/* fallthrough */
+	case 25:
+		TRY_READ(PMEVCNTR25_EL0);
+		TRY_WRITE_READ(PMEVTYPER25_EL0, 0x1);
+		/* fallthrough */
+	case 24:
+		TRY_READ(PMEVCNTR24_EL0);
+		TRY_WRITE_READ(PMEVTYPER24_EL0, 0x1);
+		/* fallthrough */
+	case 23:
+		TRY_READ(PMEVCNTR23_EL0);
+		TRY_WRITE_READ(PMEVTYPER23_EL0, 0x1);
+		/* fallthrough */
+	case 22:
+		TRY_READ(PMEVCNTR22_EL0);
+		TRY_WRITE_READ(PMEVTYPER22_EL0, 0x1);
+		/* fallthrough */
+	case 21:
+		TRY_READ(PMEVCNTR21_EL0);
+		TRY_WRITE_READ(PMEVTYPER21_EL0, 0x1);
+		/* fallthrough */
+	case 20:
+		TRY_READ(PMEVCNTR20_EL0);
+		TRY_WRITE_READ(PMEVTYPER20_EL0, 0x1);
+		/* fallthrough */
+	case 19:
+		TRY_READ(PMEVCNTR19_EL0);
+		TRY_WRITE_READ(PMEVTYPER19_EL0, 0x1);
+		/* fallthrough */
+	case 18:
+		TRY_READ(PMEVCNTR18_EL0);
+		TRY_WRITE_READ(PMEVTYPER18_EL0, 0x1);
+		/* fallthrough */
+	case 17:
+		TRY_READ(PMEVCNTR17_EL0);
+		TRY_WRITE_READ(PMEVTYPER17_EL0, 0x1);
+		/* fallthrough */
+	case 16:
+		TRY_READ(PMEVCNTR16_EL0);
+		TRY_WRITE_READ(PMEVTYPER16_EL0, 0x1);
+		/* fallthrough */
+	case 15:
+		TRY_READ(PMEVCNTR15_EL0);
+		TRY_WRITE_READ(PMEVTYPER15_EL0, 0x1);
+		/* fallthrough */
+	case 14:
+		TRY_READ(PMEVCNTR14_EL0);
+		TRY_WRITE_READ(PMEVTYPER14_EL0, 0x1);
+		/* fallthrough */
+	case 13:
+		TRY_READ(PMEVCNTR13_EL0);
+		TRY_WRITE_READ(PMEVTYPER13_EL0, 0x1);
+		/* fallthrough */
+	case 12:
+		TRY_READ(PMEVCNTR12_EL0);
+		TRY_WRITE_READ(PMEVTYPER12_EL0, 0x1);
+		/* fallthrough */
+	case 11:
+		TRY_READ(PMEVCNTR11_EL0);
+		TRY_WRITE_READ(PMEVTYPER11_EL0, 0x1);
+		/* fallthrough */
+	case 10:
+		TRY_READ(PMEVCNTR10_EL0);
+		TRY_WRITE_READ(PMEVTYPER10_EL0, 0x1);
+		/* fallthrough */
+	case 9:
+		TRY_READ(PMEVCNTR9_EL0);
+		TRY_WRITE_READ(PMEVTYPER9_EL0, 0x1);
+		/* fallthrough */
+	case 8:
+		TRY_READ(PMEVCNTR8_EL0);
+		TRY_WRITE_READ(PMEVTYPER8_EL0, 0x1);
+		/* fallthrough */
+	case 7:
+		TRY_READ(PMEVCNTR7_EL0);
+		TRY_WRITE_READ(PMEVTYPER7_EL0, 0x1);
+		/* fallthrough */
+	case 6:
+		TRY_READ(PMEVCNTR6_EL0);
+		TRY_WRITE_READ(PMEVTYPER6_EL0, 0x1);
+		/* fallthrough */
+	case 5:
+		TRY_READ(PMEVCNTR5_EL0);
+		TRY_WRITE_READ(PMEVTYPER5_EL0, 0x1);
+		/* fallthrough */
+	case 4:
+		TRY_READ(PMEVCNTR4_EL0);
+		TRY_WRITE_READ(PMEVTYPER4_EL0, 0x1);
+		/* fallthrough */
+	case 3:
+		TRY_READ(PMEVCNTR3_EL0);
+		TRY_WRITE_READ(PMEVTYPER3_EL0, 0x1);
+		/* fallthrough */
+	case 2:
+		TRY_READ(PMEVCNTR2_EL0);
+		TRY_WRITE_READ(PMEVTYPER2_EL0, 0x1);
+		/* fallthrough */
+	case 1:
+		TRY_READ(PMEVCNTR1_EL0);
+		TRY_WRITE_READ(PMEVTYPER1_EL0, 0x1);
+		/* fallthrough */
+	case 0:
+		TRY_READ(PMEVCNTR0_EL0);
+		TRY_WRITE_READ(PMEVTYPER0_EL0, 0x1);
+		break;
+	}
+}
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index 29da308..6694a93 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -38,6 +38,16 @@
   ]
 }
 
+# Service to try to access performance monitor registers.
+source_set("perfmon") {
+  testonly = true
+  public_configs = [ "//test/hftest:hftest_config" ]
+
+  sources = [
+    "perfmon.c",
+  ]
+}
+
 # Service to listen for messages and echo them back to the sender.
 source_set("echo") {
   testonly = true
@@ -205,6 +215,7 @@
     ":floating_point",
     ":interruptible",
     ":memory",
+    ":perfmon",
     ":receive_block",
     ":relay",
     ":spci_check",
diff --git a/test/vmapi/primary_with_secondaries/services/debug_el1.c b/test/vmapi/primary_with_secondaries/services/debug_el1.c
index 58ab389..c53e6bd 100644
--- a/test/vmapi/primary_with_secondaries/services/debug_el1.c
+++ b/test/vmapi/primary_with_secondaries/services/debug_el1.c
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "../debug_el1.h"
-
 #include "hf/dlog.h"
 
+#include "../sysregs.h"
+
 TEST_SERVICE(debug_el1_secondary_mdccint_el1)
 {
 	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
diff --git a/test/vmapi/primary_with_secondaries/services/perfmon.c b/test/vmapi/primary_with_secondaries/services/perfmon.c
new file mode 100644
index 0000000..413391a
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/services/perfmon.c
@@ -0,0 +1,40 @@
+/*
+ * 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 "hf/dlog.h"
+
+#include "../sysregs.h"
+
+TEST_SERVICE(perfmon_secondary_pmccfiltr_el0)
+{
+	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+	TRY_READ(PMCCFILTR_EL0);
+	FAIL("Accessing perfmon register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(perfmon_secondary_pmcr_el0)
+{
+	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+	TRY_READ(PMCR_EL0);
+	FAIL("Accessing perfmon register in secondary VM didn't trap.");
+}
+
+TEST_SERVICE(perfmon_secondary_pmintenset_el1)
+{
+	EXPECT_GT(hf_vm_get_id(), HF_PRIMARY_VM_ID);
+	write_msr(PMINTENSET_EL1, 0xf);
+	FAIL("Accessing perfmon register in secondary VM didn't trap.");
+}
diff --git a/test/vmapi/primary_with_secondaries/debug_el1.h b/test/vmapi/primary_with_secondaries/sysregs.h
similarity index 81%
rename from test/vmapi/primary_with_secondaries/debug_el1.h
rename to test/vmapi/primary_with_secondaries/sysregs.h
index 8f0b8e4..e4025d2 100644
--- a/test/vmapi/primary_with_secondaries/debug_el1.h
+++ b/test/vmapi/primary_with_secondaries/sysregs.h
@@ -23,9 +23,18 @@
 
 #define TRY_READ(REG) dlog(#REG "=%#x\n", read_msr(REG))
 
+#define CHECK_READ(REG, VALUE)       \
+	do {                         \
+		uintreg_t x;         \
+		x = read_msr(REG);   \
+		EXPECT_EQ(x, VALUE); \
+	} while (0)
+
 #define TRY_WRITE_READ(REG, VALUE)     \
 	do {                           \
 		uintreg_t x;           \
+		x = read_msr(REG);     \
+		EXPECT_NE(x, VALUE);   \
 		write_msr(REG, VALUE); \
 		x = read_msr(REG);     \
 		EXPECT_EQ(x, VALUE);   \