Override read values from feature ID registers to reflect Hafnium support

Hafnium limits access to certain features to certain or to all VMs. When
querying available features in feature ID registers, the returned result should
reflect the supported features.

Reorganize how features are trapped so that the logic can be controlled in one
place rather than being spread out throughout the code.

Bug: 144415253
Bug: 144898870
Change-Id: Id13fdcd3fd50beb70bbd51a3de15e2c5e1003eb6
diff --git a/Makefile b/Makefile
index 322556c..b2fa20f 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,8 @@
 # Separate the different items in the list with a grep or (\|).
 # debug_el1.c : uses XMACROS, which checkpatch doesn't understand.
 # perfmon.c : uses XMACROS, which checkpatch doesn't understand.
-CHECKPATCH_IGNORE := "src/arch/aarch64/hypervisor/debug_el1.c\|src/arch/aarch64/hypervisor/perfmon.c"
+# feature_id.c : uses XMACROS, which checkpatch doesn't understand.
+CHECKPATCH_IGNORE := "src/arch/aarch64/hypervisor/debug_el1.c\|src/arch/aarch64/hypervisor/perfmon.c\|src/arch/aarch64/hypervisor/feature_id.c"
 
 OUT ?= out/$(PROJECT)
 OUT_DIR = out/$(PROJECT)
diff --git a/inc/hf/arch/vm.h b/inc/hf/arch/vm.h
new file mode 100644
index 0000000..5612388
--- /dev/null
+++ b/inc/hf/arch/vm.h
@@ -0,0 +1,24 @@
+/*
+ * 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/vm.h"
+
+/**
+ * Set architecture-specific features for the specified VM.
+ */
+void arch_vm_features_set(struct vm *vm);
diff --git a/inc/vmapi/hf/call.h b/inc/vmapi/hf/call.h
index e9af0e7..2e4a506 100644
--- a/inc/vmapi/hf/call.h
+++ b/inc/vmapi/hf/call.h
@@ -21,7 +21,7 @@
 #include "hf/types.h"
 
 /**
- * This function must be implemented to trigger the architecture specific
+ * This function must be implemented to trigger the architecture-specific
  * mechanism to call to the hypervisor.
  */
 int64_t hf_call(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3);
diff --git a/src/arch/aarch64/hypervisor/BUILD.gn b/src/arch/aarch64/hypervisor/BUILD.gn
index db2cca5..e72833a 100644
--- a/src/arch/aarch64/hypervisor/BUILD.gn
+++ b/src/arch/aarch64/hypervisor/BUILD.gn
@@ -33,10 +33,12 @@
   sources += [
     "cpu.c",
     "debug_el1.c",
+    "feature_id.c",
     "handler.c",
     "perfmon.c",
     "psci_handler.c",
     "sysregs.c",
+    "vm.c",
   ]
 
   deps = [
diff --git a/src/arch/aarch64/hypervisor/cpu.c b/src/arch/aarch64/hypervisor/cpu.c
index 0e792f5..346c6b6 100644
--- a/src/arch/aarch64/hypervisor/cpu.c
+++ b/src/arch/aarch64/hypervisor/cpu.c
@@ -25,6 +25,7 @@
 #include "hf/std.h"
 #include "hf/vm.h"
 
+#include "feature_id.h"
 #include "msr.h"
 #include "perfmon.h"
 #include "sysregs.h"
@@ -95,7 +96,7 @@
 	/* 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);
+	r->lazy.mdcr_el2 = get_mdcr_el2_value();
 
 	/*
 	 * NOTE: It is important that MDSCR_EL1.MDE (bit 15) is set to 0 for
@@ -110,6 +111,9 @@
 	/* Disable cycle counting on initialization. */
 	r->lazy.pmccfiltr_el0 = perfmon_get_pmccfiltr_el0_init_value(vm_id);
 
+	/* Set feature-specific register values. */
+	feature_set_traps(vcpu->vm, r);
+
 	gic_regs_reset(r, is_primary);
 }
 
diff --git a/src/arch/aarch64/hypervisor/feature_id.c b/src/arch/aarch64/hypervisor/feature_id.c
new file mode 100644
index 0000000..20e6f55
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/feature_id.c
@@ -0,0 +1,303 @@
+/*
+ * 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 "feature_id.h"
+
+#include "hf/check.h"
+#include "hf/dlog.h"
+#include "hf/types.h"
+#include "hf/vm.h"
+
+#include "msr.h"
+#include "sysregs.h"
+
+/* clang-format off */
+
+/**
+ * Definitions of read-only feature ID (group 3) registers' encodings.
+ * See Arm Architecture Reference Manual Armv8-A, Table D1-52 and D12-2.
+ * NAME, op0, op1, crn, crm, op2
+ */
+#define FEATURE_ID_REGISTERS_READ              \
+	X(ID_PFR0_EL1       , 3, 0,  0,  1, 0) \
+	X(ID_PFR1_EL1       , 3, 0,  0,  1, 1) \
+	X(ID_DFR0_EL1       , 3, 0,  0,  1, 2) \
+	X(ID_AFR0_EL1       , 3, 0,  0,  1, 3) \
+	X(ID_MMFR0_EL1      , 3, 0,  0,  1, 4) \
+	X(ID_MMFR1_EL1      , 3, 0,  0,  1, 5) \
+	X(ID_MMFR2_EL1      , 3, 0,  0,  1, 6) \
+	X(ID_MMFR3_EL1      , 3, 0,  0,  1, 7) \
+	X(ID_ISAR0_EL1      , 3, 0,  0,  2, 0) \
+	X(ID_ISAR1_EL1      , 3, 0,  0,  2, 1) \
+	X(ID_ISAR2_EL1      , 3, 0,  0,  2, 2) \
+	X(ID_ISAR3_EL1      , 3, 0,  0,  2, 3) \
+	X(ID_ISAR4_EL1      , 3, 0,  0,  2, 4) \
+	X(ID_ISAR5_EL1      , 3, 0,  0,  2, 5) \
+	X(ID_MMFR4_EL1      , 3, 0,  0,  2, 6) \
+	\
+	X(MVFR0_EL1         , 3, 0,  0,  3, 0) \
+	X(MVFR1_EL1         , 3, 0,  0,  3, 1) \
+	X(MVFR2_EL1         , 3, 0,  0,  3, 2) \
+	\
+	X(ID_AA64PFR0_EL1   , 3, 0,  0,  4, 0) \
+	X(ID_AA64PFR1_EL1   , 3, 0,  0,  4, 1) \
+	\
+	X(ID_AA64DFR0_EL1   , 3, 0,  0,  5, 0) \
+	X(ID_AA64DFR1_EL1   , 3, 0,  0,  5, 1) \
+	\
+	X(ID_AA64AFR0_EL1   , 3, 0,  0,  5, 4) \
+	X(ID_AA64AFR1_EL1   , 3, 0,  0,  5, 5) \
+	\
+	X(ID_AA64ISAR0_EL1  , 3, 0,  0,  6, 0) \
+	X(ID_AA64ISAR1_EL1  , 3, 0,  0,  6, 1) \
+	\
+	X(ID_AA64MMFR0_EL1  , 3, 0,  0,  7, 0) \
+	X(ID_AA64MMFR1_EL1  , 3, 0,  0,  7, 1) \
+	X(ID_AA64MMFR2_EL1  , 3, 0,  0,  7, 2)
+
+/* clang-format on */
+
+enum {
+#define X(reg_name, op0, op1, crn, crm, op2) \
+	reg_name##_ENC = GET_ISS_ENCODING(op0, op1, crn, crm, op2),
+	FEATURE_ID_REGISTERS_READ
+#undef X
+};
+
+/**
+ * Returns true if the ESR register shows an access to a feature ID group 3
+ * register.
+ */
+bool feature_id_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. */
+	return op0 == 3 && op1 == 0 && crn == 0 && crm >= 1 && crm <= 7;
+}
+
+/**
+ * RAS-related. RES0 when RAS is not implemented.
+ */
+#define ID_AA64MMFR1_EL1_SPEC_SEI (UINT64_C(0xf) << 24)
+
+/**
+ * Indicates support for LORegions.
+ */
+#define ID_AA64MMFR1_EL1_LO (UINT64_C(0xf) << 24)
+
+/**
+ * RAS Extension version.
+ */
+#define ID_AA64PFR0_EL1_RAS (UINT64_C(0xf) << 28)
+
+/**
+ * Self-hosted Trace Extension Version
+ */
+#define ID_AA64DFR0_EL1_TRACE_FILT (UINT64_C(0xf) << 40)
+
+/**
+ * OS Double Lock implemented.
+ */
+#define ID_AA64DFR0_EL1_DOUBLE_LOCK (UINT64_C(0xf) << 36)
+
+/**
+ * Statistical Profiling Extension version.
+ */
+#define ID_AA64DFR0_EL1_PMS_VER (UINT64_C(0xf) << 32)
+
+/**
+ * Performance Monitors Extension version.
+ */
+#define ID_AA64DFR0_EL1_PMU_VER (UINT64_C(0xf) << 8)
+
+/**
+ * Indicates whether System register interface to trace unit is implemented.
+ */
+#define ID_AA64DFR0_EL1_TRACE_VER (UINT64_C(0xf) << 4)
+
+/**
+ * Debug architecture version.
+ */
+#define ID_AA64DFR0_EL1_DEBUG_VER (UINT64_C(0xf))
+
+/**
+ * PAuth: whether an implementation defined algorithm for generic code
+ * authentication is implemented.
+ */
+#define ID_AA64ISAR1_EL1_GPI (UINT64_C(0xf) << 28)
+
+/**
+ * PAuth: whether QARMA or Architected algorithm for generic code authentication
+ * is implemented.
+ */
+#define ID_AA64ISAR1_EL1_GPA (UINT64_C(0xf) << 24)
+
+/**
+ * PAuth: whether an implementation defined algorithm for address authentication
+ * is implemented.
+ */
+#define ID_AA64ISAR1_EL1_API (UINT64_C(0xf) << 8)
+
+/**
+ * PAuth: whether QARMA or Architected algorithm for address authentication is
+ * implemented.
+ */
+#define ID_AA64ISAR1_EL1_APA (UINT64_C(0xf) << 24)
+
+void feature_set_traps(struct vm *vm, struct arch_regs *regs)
+{
+	arch_features_t features = vm->arch.trapped_features;
+
+	if (features & ~HF_FEATURE_ALL) {
+		panic("features has undefined bits 0x%x", features);
+	}
+
+	/* By default do not mask out any features. */
+	vm->arch.tid3_masks.id_aa64mmfr1_el1 = ~0ULL;
+	vm->arch.tid3_masks.id_aa64pfr0_el1 = ~0ULL;
+	vm->arch.tid3_masks.id_aa64dfr0_el1 = ~0ULL;
+	vm->arch.tid3_masks.id_aa64isar1_el1 = ~0ULL;
+
+	if (features & HF_FEATURE_RAS) {
+		regs->lazy.hcr_el2 |= HCR_EL2_TERR;
+		vm->arch.tid3_masks.id_aa64mmfr1_el1 &=
+			~ID_AA64MMFR1_EL1_SPEC_SEI;
+		vm->arch.tid3_masks.id_aa64pfr0_el1 &= ~ID_AA64PFR0_EL1_RAS;
+	}
+
+	if (features & HF_FEATURE_SPE) {
+		/*
+		 * Trap VM accesses to Statistical Profiling Extension (SPE)
+		 * registers.
+		 */
+		regs->lazy.mdcr_el2 |= MDCR_EL2_TPMS;
+
+		/*
+		 * Set E2PB to 0b00. This ensures that accesses to Profiling
+		 * Buffer controls at EL1 are trapped to EL2.
+		 */
+		regs->lazy.mdcr_el2 &= ~MDCR_EL2_E2PB;
+
+		vm->arch.tid3_masks.id_aa64dfr0_el1 &= ~ID_AA64DFR0_EL1_PMS_VER;
+	}
+
+	if (features & HF_FEATURE_DEBUG) {
+		regs->lazy.mdcr_el2 |=
+			MDCR_EL2_TDRA | MDCR_EL2_TDOSA | MDCR_EL2_TDA;
+
+		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
+			~ID_AA64DFR0_EL1_DOUBLE_LOCK;
+	}
+
+	if (features & HF_FEATURE_TRACE) {
+		regs->lazy.mdcr_el2 |= MDCR_EL2_TTRF;
+
+		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
+			~ID_AA64DFR0_EL1_TRACE_FILT;
+		vm->arch.tid3_masks.id_aa64dfr0_el1 &=
+			~ID_AA64DFR0_EL1_TRACE_VER;
+	}
+
+	if (features & HF_FEATURE_PERFMON) {
+		regs->lazy.mdcr_el2 |= MDCR_EL2_TPM | MDCR_EL2_TPMCR;
+
+		vm->arch.tid3_masks.id_aa64dfr0_el1 &= ~ID_AA64DFR0_EL1_PMU_VER;
+	}
+
+	if (features & HF_FEATURE_LOR) {
+		regs->lazy.hcr_el2 |= HCR_EL2_TLOR;
+
+		vm->arch.tid3_masks.id_aa64mmfr1_el1 &= ~ID_AA64MMFR1_EL1_LO;
+	}
+
+	if (features & HF_FEATURE_PAUTH) {
+		/* APK and API bits *enable* trapping when cleared.  */
+		regs->lazy.hcr_el2 &= ~(HCR_EL2_APK | HCR_EL2_API);
+
+		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_GPI;
+		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_GPA;
+		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_API;
+		vm->arch.tid3_masks.id_aa64isar1_el1 &= ~ID_AA64ISAR1_EL1_APA;
+	}
+}
+
+/**
+ * Processes an access (mrs) to a feature ID register.
+ * Returns true if the access was allowed and performed, false otherwise.
+ */
+bool feature_id_process_access(struct vcpu *vcpu, uintreg_t esr)
+{
+	const struct vm *vm = vcpu->vm;
+	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)) {
+		dlog("Unsupported feature ID 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));
+		return true;
+	}
+
+	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;
+		FEATURE_ID_REGISTERS_READ
+#undef X
+	default:
+		/* Reserved registers should be read as zero (raz). */
+		value = 0;
+		dlog("Unsupported feature ID 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;
+	}
+
+	/* Mask values for features Hafnium might restrict. */
+	switch (sys_register) {
+	case ID_AA64MMFR1_EL1_ENC:
+		value &= vm->arch.tid3_masks.id_aa64mmfr1_el1;
+		break;
+	case ID_AA64PFR0_EL1_ENC:
+		value &= vm->arch.tid3_masks.id_aa64pfr0_el1;
+		break;
+	case ID_AA64DFR0_EL1_ENC:
+		value &= vm->arch.tid3_masks.id_aa64dfr0_el1;
+		break;
+	case ID_AA64ISAR1_EL1_ENC:
+		value &= vm->arch.tid3_masks.id_aa64isar1_el1;
+		break;
+	default:
+		break;
+	}
+
+	if (rt_register != RT_REG_XZR) {
+		vcpu->regs.r[rt_register] = value;
+	}
+
+	return true;
+}
diff --git a/src/arch/aarch64/hypervisor/feature_id.h b/src/arch/aarch64/hypervisor/feature_id.h
new file mode 100644
index 0000000..86c7c01
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/feature_id.h
@@ -0,0 +1,58 @@
+/*
+ * 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"
+
+#define HF_FEATURE_NONE UINT64_C(0)
+
+/*  Reliability, Availability, and Serviceability (RAS) Extension Features */
+#define HF_FEATURE_RAS UINT64_C(1)
+
+/* Limited Ordering Regions */
+#define HF_FEATURE_LOR (UINT64_C(1) << 1)
+
+/* Performance Monitor */
+#define HF_FEATURE_PERFMON (UINT64_C(1) << 2)
+
+/* Debug Registers */
+#define HF_FEATURE_DEBUG (UINT64_C(1) << 3)
+
+/* Statistical Profiling Extension (SPE) */
+#define HF_FEATURE_SPE (UINT64_C(1) << 4)
+
+/* Self-hosted Trace */
+#define HF_FEATURE_TRACE (UINT64_C(1) << 5)
+
+/* Pointer Authentication (PAuth) */
+#define HF_FEATURE_PAUTH (UINT64_C(1) << 6)
+
+/*
+ * NOTE: This should be based on the last (highest value) defined feature.
+ * Adjust if adding more features.
+ */
+#define HF_FEATURE_ALL ((HF_FEATURE_PAUTH << 1) - 1)
+
+bool feature_id_is_register_access(uintreg_t esr_el2);
+
+bool feature_id_process_access(struct vcpu *vcpu, uintreg_t esr_el2);
+
+void feature_set_traps(struct vm *vm, struct arch_regs *regs);
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 0e75b97..a2c4bf4 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -31,6 +31,7 @@
 #include "vmapi/hf/call.h"
 
 #include "debug_el1.h"
+#include "feature_id.h"
 #include "msr.h"
 #include "perfmon.h"
 #include "psci.h"
@@ -144,8 +145,8 @@
 
 	/*
 	 * Ensure that no data reads or writes for the VM happen until after the
-	 * TLB invalidation has taken effect. Non-sharable is enough because the
-	 * TLB is local to the CPU.
+	 * TLB invalidation has taken effect. Non-shareable is enough because
+	 * the TLB is local to the CPU.
 	 */
 	dsb(nsh);
 }
@@ -753,6 +754,10 @@
 		if (!perfmon_process_access(vcpu, vm_id, esr_el2)) {
 			return inject_el1_unknown_exception(vcpu, esr_el2);
 		}
+	} else if (feature_id_is_register_access(esr_el2)) {
+		if (!feature_id_process_access(vcpu, esr_el2)) {
+			return inject_el1_unknown_exception(vcpu, esr_el2);
+		}
 	} else {
 		return inject_el1_unknown_exception(vcpu, esr_el2);
 	}
diff --git a/src/arch/aarch64/hypervisor/sysregs.c b/src/arch/aarch64/hypervisor/sysregs.c
index 3ae24f1..c2c51be 100644
--- a/src/arch/aarch64/hypervisor/sysregs.c
+++ b/src/arch/aarch64/hypervisor/sysregs.c
@@ -17,7 +17,6 @@
 #include "sysregs.h"
 
 #include "hf/check.h"
-#include "hf/dlog.h"
 #include "hf/panic.h"
 #include "hf/types.h"
 
@@ -32,82 +31,73 @@
 {
 	uintreg_t hcr_el2_value = 0;
 
-	/*
-	 * TODO(b/132395845):  Access to RAS registers is not trapped at the
-	 * moment for the primary VM, only for the secondaries (TERR). RAS
-	 * register isn't needed now, but it might be required for debugging.
-	 * When Hafnium introduces debug vs release builds, trap accesses for
-	 * primary VMs in release builds, but do not trap them in debug builds.
-	 */
-	hcr_el2_value = HCR_EL2_RW | HCR_EL2_TACR | HCR_EL2_TIDCP |
-			HCR_EL2_TSC | HCR_EL2_PTW | HCR_EL2_VM | HCR_EL2_TSW |
-			HCR_EL2_TLOR;
+	/* Baseline values for all VMs. */
 
+	/*
+	 * Trap access to registers in ID group 3. These registers report on
+	 * the underlying support for CPU features. Because Hafnium restricts
+	 * certain features, e.g., RAS, it should emulate access to these
+	 * registers to report the correct set of features supported.
+	 */
+	hcr_el2_value |= HCR_EL2_TID3;
+
+	/* Execution state for EL1 is AArch64. */
+	hcr_el2_value |= HCR_EL2_RW;
+
+	/* Trap implementation registers and functionality. */
+	hcr_el2_value |= HCR_EL2_TACR | HCR_EL2_TIDCP;
+
+	/* Trap SMC instructions. */
+	hcr_el2_value |= HCR_EL2_TSC;
+
+	/*
+	 * Translation table access made as part of a stage 1 translation
+	 * table walk is subject to a stage 2 translation;
+	 */
+	hcr_el2_value |= HCR_EL2_PTW;
+
+	/* Enable stage 2 address translation;*/
+	hcr_el2_value |= HCR_EL2_VM;
+
+	/* Trap cache maintenance instructions that operate by Set/Way. */
+	hcr_el2_value |= HCR_EL2_TSW;
+
+	/* Do *not* trap PAuth. APK and API bits *disable* trapping when set. */
+	hcr_el2_value |= HCR_EL2_APK | HCR_EL2_API;
+
+	/* Baseline values for all secondary VMs. */
 	if (vm_id != HF_PRIMARY_VM_ID) {
-		hcr_el2_value |= HCR_EL2_TWE | HCR_EL2_TWI |
-				 HCR_EL2_BSU_INNER_SHAREABLE | HCR_EL2_FB |
-				 HCR_EL2_AMO | HCR_EL2_IMO | HCR_EL2_FMO |
-				 HCR_EL2_TERR;
-	} else {
-		hcr_el2_value |= HCR_EL2_APK | HCR_EL2_API;
+		/*
+		 * Set the minimum shareability domain to barrier instructions
+		 * as inner shareable.
+		 */
+		hcr_el2_value |= HCR_EL2_BSU_INNER_SHAREABLE;
+
+		/*
+		 * Broadcast instructions related to invalidating the TLB within
+		 * the Inner Shareable domain.
+		 */
+		hcr_el2_value |= HCR_EL2_FB;
+
+		/* Route physical SError/IRQ/FIQ interrupts to EL2. */
+		hcr_el2_value |= HCR_EL2_AMO | HCR_EL2_IMO | HCR_EL2_FMO;
+
+		/* Trap wait for event/interrupt instructions. */
+		hcr_el2_value |= HCR_EL2_TWE | HCR_EL2_TWI;
 	}
 
 	return hcr_el2_value;
 }
 
 /**
- * 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.
+ * Returns the default value for MDCR_EL2.
  */
-uintreg_t get_mdcr_el2_value(spci_vm_id_t vm_id)
+uintreg_t get_mdcr_el2_value(void)
 {
 	uintreg_t mdcr_el2_value = read_msr(MDCR_EL2);
 	uintreg_t pmcr_el0 = read_msr(PMCR_EL0);
 
-	/*
-	 * TODO: Investigate gating settings these values depending on which
-	 * features are supported by the current CPU.
-	 */
-
-	/*
-	 * Trap all VM accesses to Statistical Profiling Extention (SPE)
-	 * registers.
-	 */
-	mdcr_el2_value |= MDCR_EL2_TPMS;
-
-	/*
-	 * Set E2PB to 0b00. This ensures that accesses to Profiling Buffer
-	 * controls at EL1 are trapped to EL2.
-	 */
-	mdcr_el2_value &= ~MDCR_EL2_E2PB;
-
-	/*
-	 * Trap all VM accesses to debug registers for fine-grained control.
-	 * Do not trap the Primary VM's debug events, e.g., watchpoint or
-	 * breakpoint events (!MDCR_EL2_TDE).
-	 */
-	mdcr_el2_value |=
-		MDCR_EL2_TTRF | MDCR_EL2_TDRA | MDCR_EL2_TDOSA | MDCR_EL2_TDA;
-
-	if (vm_id != HF_PRIMARY_VM_ID) {
-		/*
-		 * Debug event exceptions should be disabled in secondary VMs
-		 * 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;
-	}
+	/* Baseline values for all VMs. */
 
 	/* Disable cycle and event counting at EL2. */
 	mdcr_el2_value |= MDCR_EL2_HCCD | MDCR_EL2_HPMD;
@@ -119,7 +109,7 @@
 }
 
 /**
- * Returns the value for MDCR_EL2 for the CPU.
+ * Returns the value for CPTR_EL2 for the CPU.
  */
 uintreg_t get_cptr_el2_value(void)
 {
diff --git a/src/arch/aarch64/hypervisor/sysregs.h b/src/arch/aarch64/hypervisor/sysregs.h
index 60e7f31..250c9bf 100644
--- a/src/arch/aarch64/hypervisor/sysregs.h
+++ b/src/arch/aarch64/hypervisor/sysregs.h
@@ -34,7 +34,7 @@
 #define MDCR_EL2_HCCD (UINT64_C(0x1) << 23)
 
 /**
- * Controls traps for Trace Filter.
+ * Controls traps for Trace Filter when Self-hosted Trace is implemented.
  */
 #define MDCR_EL2_TTRF (UINT64_C(0x1) << 19)
 
@@ -45,8 +45,8 @@
 
 /**
  * Trap Performance Monitor Sampling.
- * Controls access to Statistical Profiling control registers from EL1. Depends
- * on whether the Statistical Profiling Extention (SPE) is implemented.
+ * Traps access to Statistical Profiling control registers from EL1 when
+ * the Statistical Profiling Extension (SPE) is implemented.
  */
 #define MDCR_EL2_TPMS (UINT64_C(0x1) << 14)
 
@@ -62,7 +62,8 @@
 #define MDCR_EL2_TDRA (UINT64_C(0x1) << 11)
 
 /**
- * Controls traps for OS-Related Register Access.
+ * Controls traps for debug OS-Related Register accesses when DoubleLock is
+ * implemented.
  */
 #define MDCR_EL2_TDOSA (UINT64_C(0x1) << 10)
 
@@ -210,24 +211,24 @@
 #define HCR_EL2_TID4 (UINT64_C(0x1) << 49)
 
 /**
- * When set disables traps on Pointer Authentication related instruction
+ * When set *disables* traps on Pointer Authentication related instruction
  * execution.
  */
 #define HCR_EL2_API (UINT64_C(0x1) << 41)
 
 /**
- * When set disables traps on access to Pointer Authentication's "key"
+ * When set *disables* traps on access to Pointer Authentication's "key"
  * registers.
  */
 #define HCR_EL2_APK (UINT64_C(0x1) << 40)
 
 /**
- * Trap Error record accesses.
+ * Trap Error record accesses when RAS is implemented.
  */
 #define HCR_EL2_TERR (UINT64_C(0x1) << 36)
 
 /**
- * Trap LOR registers.
+ * Trap LOR register accesses when LORegions is implemented.
  */
 #define HCR_EL2_TLOR (UINT64_C(0x1) << 35)
 
@@ -326,7 +327,7 @@
 
 /**
  * Trap WFI instructions.
- * When set, traps EL0 and EL1 execution of WFI intstructions to EL2.
+ * When set, traps EL0 and EL1 execution of WFI instructions to EL2.
  */
 #define HCR_EL2_TWI (UINT64_C(0x1) << 13)
 
@@ -351,7 +352,7 @@
 #define HCR_EL2_VI (UINT64_C(0x1) << 7)
 
 /**
- * Physical Serror Routing.
+ * Physical SError Routing.
  * When set, physical SError interrupts are taken to EL2, unless routed to EL3.
  */
 #define HCR_EL2_AMO (UINT64_C(0x1) << 5)
@@ -384,7 +385,7 @@
 #define HCR_EL2_SWIO (UINT64_C(0x1) << 1)
 
 /**
- * Virtulization enable.
+ * Virtualization enable.
  * When set EL1 and EL0 stage 2 address translation is enabled.
  */
 #define HCR_EL2_VM (UINT64_C(0x1) << 0)
@@ -409,17 +410,17 @@
 #define PSR_D (UINT64_C(1) << 9)
 
 /**
- * Asynchronos SError interrupt mask bit.
+ * Asynchronous SError interrupt mask bit.
  */
 #define PSR_A (UINT64_C(1) << 8)
 
 /**
- * Asynchronos IRQ interrupt mask bit.
+ * Asynchronous IRQ interrupt mask bit.
  */
 #define PSR_I (UINT64_C(1) << 7)
 
 /**
- * Asynchronos FIQ interrupt mask bit.
+ * Asynchronous FIQ interrupt mask bit.
  */
 #define PSR_F (UINT64_C(1) << 6)
 
@@ -445,6 +446,6 @@
 
 uintreg_t get_hcr_el2_value(spci_vm_id_t vm_id);
 
-uintreg_t get_mdcr_el2_value(spci_vm_id_t vm_id);
+uintreg_t get_mdcr_el2_value(void);
 
 uintreg_t get_cptr_el2_value(void);
diff --git a/src/arch/aarch64/hypervisor/vm.c b/src/arch/aarch64/hypervisor/vm.c
new file mode 100644
index 0000000..97c8746
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/vm.c
@@ -0,0 +1,60 @@
+/*
+ * 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/arch/vm.h"
+
+#include "hypervisor/feature_id.h"
+
+void arch_vm_features_set(struct vm *vm)
+{
+	/* Features to trap for all VMs. */
+
+	/*
+	 * It is not safe to enable this yet, in part, because the feature's
+	 * registers are not context switched in Hafnium.
+	 */
+	vm->arch.trapped_features |= HF_FEATURE_LOR;
+
+	vm->arch.trapped_features |= HF_FEATURE_SPE;
+
+	vm->arch.trapped_features |= HF_FEATURE_TRACE;
+
+	vm->arch.trapped_features |= HF_FEATURE_DEBUG;
+
+	if (vm->id != HF_PRIMARY_VM_ID) {
+		/* Features to trap only for the secondary VMs. */
+
+		vm->arch.trapped_features |= HF_FEATURE_PERFMON;
+
+		/*
+		 * TODO(b/132395845):  Access to RAS registers is not trapped at
+		 * the moment for the primary VM, only for the secondaries. RAS
+		 * register access isn't needed now, but it might be
+		 * required for debugging. When Hafnium introduces debug vs
+		 * release builds, trap accesses for primary VMs in release
+		 * builds, but do not trap them in debug builds.
+		 */
+		vm->arch.trapped_features |= HF_FEATURE_RAS;
+
+		/*
+		 * The PAuth mechanism holds state in the key registers. Only
+		 * the primary VM is allowed to use the PAuth functionality for
+		 * now. This prevents Hafnium from having to save/restore the
+		 * key register on a VM switch.
+		 */
+		vm->arch.trapped_features |= HF_FEATURE_PAUTH;
+	}
+}
diff --git a/src/arch/aarch64/inc/hf/arch/types.h b/src/arch/aarch64/inc/hf/arch/types.h
index 987fd7b..667fc20 100644
--- a/src/arch/aarch64/inc/hf/arch/types.h
+++ b/src/arch/aarch64/inc/hf/arch/types.h
@@ -43,6 +43,9 @@
 /** The ID of a physical or virtual CPU. */
 typedef uint64_t cpu_id_t;
 
+/** A bitset for AArch64 CPU features. */
+typedef uint64_t arch_features_t;
+
 /**
  * The struct for storing a floating point register.
  *
@@ -56,7 +59,7 @@
 static_assert(sizeof(struct float_reg) == FLOAT_REG_BYTES,
 	      "Ensure float register type is 128 bits.");
 
-/** Arch-specifc information about a VM. */
+/** Arch-specific information about a VM. */
 struct arch_vm {
 	/**
 	 * The index of the last vCPU of this VM which ran on each pCPU. Each
@@ -65,6 +68,17 @@
 	 * access this field.
 	 */
 	spci_vcpu_index_t last_vcpu_on_cpu[MAX_CPUS];
+	arch_features_t trapped_features;
+
+	/*
+	 * Masks for feature registers trappable by HCR_EL2.TID3.
+	 */
+	struct {
+		uintreg_t id_aa64mmfr1_el1;
+		uintreg_t id_aa64pfr0_el1;
+		uintreg_t id_aa64dfr0_el1;
+		uintreg_t id_aa64isar1_el1;
+	} tid3_masks;
 };
 
 /** Type to represent the register state of a vCPU.  */
diff --git a/src/arch/aarch64/mm.c b/src/arch/aarch64/mm.c
index d6b00f2..f856ac4 100644
--- a/src/arch/aarch64/mm.c
+++ b/src/arch/aarch64/mm.c
@@ -264,7 +264,7 @@
 }
 
 /**
- * Extracts the architecture specific attributes applies to the given page table
+ * Extracts the architecture-specific attributes applies to the given page table
  * entry.
  */
 uint64_t arch_mm_pte_attrs(pte_t pte, uint8_t level)
diff --git a/src/load.c b/src/load.c
index bd4879d..e62476f 100644
--- a/src/load.c
+++ b/src/load.c
@@ -18,6 +18,8 @@
 
 #include <stdbool.h>
 
+#include "hf/arch/vm.h"
+
 #include "hf/api.h"
 #include "hf/boot_params.h"
 #include "hf/check.h"
@@ -99,6 +101,9 @@
 {
 	vm->smc_whitelist = manifest_vm->smc_whitelist;
 
+	/* Initialize architecture-specific features.  */
+	arch_vm_features_set(vm);
+
 	return true;
 }