Stop secondary VMs from accessing GIC system registers.

Bug: 132960440
Change-Id: I2505afe9e885406a5b91811ff1a3451fa3a34393
diff --git a/src/arch/aarch64/cpu.c b/src/arch/aarch64/cpu.c
index b7b5b99..bfe9c42 100644
--- a/src/arch/aarch64/cpu.c
+++ b/src/arch/aarch64/cpu.c
@@ -33,6 +33,20 @@
 	__asm__ volatile("msr DAIFClr, #0xf");
 }
 
+static void gic_regs_reset(struct arch_regs *r, bool is_primary)
+{
+#if GIC_VERSION == 3 || GIC_VERSION == 4
+	uint32_t ich_hcr = 0;
+
+	if (!is_primary) {
+		/* Trap EL1 access to GICv3 system registers. */
+		ich_hcr =
+			(0x1fu << 10); /* TDIR, TSEI, TALL1, TALL0, TC bits. */
+	}
+	r->gic.ich_hcr_el2 = ich_hcr;
+#endif
+}
+
 void arch_regs_reset(struct arch_regs *r, bool is_primary, uint64_t vm_id,
 		     uint64_t vcpu_id, paddr_t table)
 {
@@ -81,6 +95,8 @@
 	/* TODO: Use constant here. */
 	r->spsr = 5 |	 /* M bits, set to EL1h. */
 		  (0xf << 6); /* DAIF bits set; disable interrupts. */
+
+	gic_regs_reset(r, is_primary);
 }
 
 void arch_regs_set_pc_arg(struct arch_regs *r, ipaddr_t pc, uintreg_t arg)
diff --git a/src/arch/aarch64/hftest/interrupts_gicv3.c b/src/arch/aarch64/hftest/interrupts_gicv3.c
index 4c036d4..a3fd489 100644
--- a/src/arch/aarch64/hftest/interrupts_gicv3.c
+++ b/src/arch/aarch64/hftest/interrupts_gicv3.c
@@ -41,12 +41,12 @@
 
 	/* Set exception vector table. */
 	write_msr(VBAR_EL1, &vector_table_el1);
-
-	write_msr(ICC_CTLR_EL1, 0);
 }
 
 void interrupt_gic_setup(void)
 {
+	write_msr(ICC_CTLR_EL1, 0);
+
 	GICD_CTLR = 1u << 4    /* Enable affinity routing. */
 		    | 1u << 1; /* Enable group 1 non-secure interrupts. */
 
diff --git a/src/arch/aarch64/hypervisor/exceptions.S b/src/arch/aarch64/hypervisor/exceptions.S
index ea17ffa..9acd240 100644
--- a/src/arch/aarch64/hypervisor/exceptions.S
+++ b/src/arch/aarch64/hypervisor/exceptions.S
@@ -312,6 +312,15 @@
 	mrs x28, vttbr_el2
 	str x28, [x1, #VCPU_LAZY + 16 * 14]
 
+	/* Save GIC registers. */
+#if GIC_VERSION == 3 || GIC_VERSION == 4
+	/* Offset is too large, so start from a new base. */
+	add x2, x1, #VCPU_GIC
+
+	mrs x3, ich_hcr_el2
+	str x3, [x2, #16 * 0]
+#endif
+
 	/*
 	 * Save floating point registers.
 	 *
@@ -382,7 +391,7 @@
 	ldp q24, q25, [x2, #32 * 12]
 	ldp q26, q27, [x2, #32 * 13]
 	ldp q28, q29, [x2, #32 * 14]
-	/* Offest becomes too large, so move the base. */
+	/* Offset becomes too large, so move the base. */
 	ldp q30, q31, [x2, #32 * 15]!
 	ldp x3, x4, [x2, #32 * 1]
 	msr fpsr, x3
@@ -458,6 +467,15 @@
 	ldr x28, [x0, #VCPU_LAZY + 16 * 14]
 	msr vttbr_el2, x28
 
+	/* Restore GIC registers. */
+#if GIC_VERSION == 3 || GIC_VERSION == 4
+	/* Offset is too large, so start from a new base. */
+	add x2, x0, #VCPU_GIC
+
+	ldr x3, [x2, #16 * 0]
+	msr ich_hcr_el2, x3
+#endif
+
 	/* Restore non-volatile registers. */
 	ldp x19, x20, [x0, #VCPU_REGS + 8 * 19]
 	ldp x21, x22, [x0, #VCPU_REGS + 8 * 21]
diff --git a/src/arch/aarch64/hypervisor/offsets.c b/src/arch/aarch64/hypervisor/offsets.c
index f85cf05..366a595 100644
--- a/src/arch/aarch64/hypervisor/offsets.c
+++ b/src/arch/aarch64/hypervisor/offsets.c
@@ -31,3 +31,7 @@
 CHECK_OFFSET(VCPU_REGS, struct vcpu, regs);
 CHECK_OFFSET(VCPU_LAZY, struct vcpu, regs.lazy);
 CHECK_OFFSET(VCPU_FREGS, struct vcpu, regs.fp);
+
+#ifdef VCPU_GIC
+CHECK_OFFSET(VCPU_GIC, struct vcpu, regs.gic);
+#endif
diff --git a/src/arch/aarch64/hypervisor/offsets.h b/src/arch/aarch64/hypervisor/offsets.h
index d872e64..51724e0 100644
--- a/src/arch/aarch64/hypervisor/offsets.h
+++ b/src/arch/aarch64/hypervisor/offsets.h
@@ -21,4 +21,8 @@
 #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 + 232)
+
+#if GIC_VERSION == 3 || GIC_VERSION == 4
+#define VCPU_GIC (VCPU_FREGS + 528)
+#endif
diff --git a/src/arch/aarch64/inc/hf/arch/types.h b/src/arch/aarch64/inc/hf/arch/types.h
index e6f1b9d..f768309 100644
--- a/src/arch/aarch64/inc/hf/arch/types.h
+++ b/src/arch/aarch64/inc/hf/arch/types.h
@@ -56,6 +56,7 @@
 	uintreg_t r[31];
 	uintreg_t pc;
 	uintreg_t spsr;
+
 	/* System registers. */
 	struct {
 		uintreg_t vmpidr_el2;
@@ -88,6 +89,18 @@
 		uintreg_t cnthctl_el2;
 		uintreg_t vttbr_el2;
 	} lazy;
+
+	/* Floating point registers. */
+	struct float_reg fp[32];
+	uintreg_t fpsr;
+	uintreg_t fpcr;
+
+#if GIC_VERSION == 3 || GIC_VERSION == 4
+	struct {
+		uintreg_t ich_hcr_el2;
+	} gic;
+#endif
+
 	/*
 	 * Peripheral registers, handled separately from other system registers.
 	 */
@@ -95,8 +108,4 @@
 		uintreg_t cntv_cval_el0;
 		uintreg_t cntv_ctl_el0;
 	} peripherals;
-	/* Floating point registers. */
-	struct float_reg fp[32];
-	uintreg_t fpsr;
-	uintreg_t fpcr;
 };
diff --git a/test/vmapi/gicv3/gicv3.c b/test/vmapi/gicv3/gicv3.c
index 7e90790..e1e1056 100644
--- a/test/vmapi/gicv3/gicv3.c
+++ b/test/vmapi/gicv3/gicv3.c
@@ -74,3 +74,33 @@
 	EXPECT_EQ(GICD_CTLR & 0x13, 0x12);
 	EXPECT_EQ(read_msr(ICC_CTLR_EL1) & 0xff, 0);
 }
+
+/*
+ * Check that an attempt by a secondary VM to read a GICv3 system register is
+ * trapped.
+ */
+TEST(system, icc_ctlr_read_trapped_secondary)
+{
+	struct hf_vcpu_run_return run_res;
+
+	EXPECT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
+	SERVICE_SELECT(SERVICE_VM0, "read_systemreg_ctlr", send_buffer);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
+
+/*
+ * Check that an attempt by a secondary VM to write a GICv3 system register is
+ * trapped.
+ */
+TEST(system, icc_ctlr_write_trapped_secondary)
+{
+	struct hf_vcpu_run_return run_res;
+
+	EXPECT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
+	SERVICE_SELECT(SERVICE_VM0, "write_systemreg_ctlr", send_buffer);
+
+	run_res = hf_vcpu_run(SERVICE_VM0, 0);
+	EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
+}
diff --git a/test/vmapi/gicv3/services/BUILD.gn b/test/vmapi/gicv3/services/BUILD.gn
index c76c4da..89786b1 100644
--- a/test/vmapi/gicv3/services/BUILD.gn
+++ b/test/vmapi/gicv3/services/BUILD.gn
@@ -53,6 +53,22 @@
   ]
 }
 
+# Service which tries to access GICv3 system registers.
+source_set("systemreg") {
+  testonly = true
+  public_configs = [ "//test/hftest:hftest_config" ]
+
+  sources = [
+    "systemreg.c",
+  ]
+
+  deps = [
+    ":common",
+    "//src/arch/aarch64:arch",
+    "//src/arch/aarch64/hftest:interrupts_gicv3",
+  ]
+}
+
 # Group services together into VMs.
 
 vm_kernel("gicv3_service_vm0") {
@@ -60,6 +76,7 @@
 
   deps = [
     ":busy",
+    ":systemreg",
     ":timer",
     "//test/hftest:hftest_secondary_vm",
   ]
diff --git a/test/vmapi/gicv3/services/systemreg.c b/test/vmapi/gicv3/services/systemreg.c
new file mode 100644
index 0000000..ebf85de
--- /dev/null
+++ b/test/vmapi/gicv3/services/systemreg.c
@@ -0,0 +1,46 @@
+/*
+ * 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/cpu.h"
+#include "hf/arch/vm/events.h"
+#include "hf/arch/vm/interrupts_gicv3.h"
+#include "hf/arch/vm/timer.h"
+
+#include "hf/dlog.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "common.h"
+#include "hftest.h"
+
+/*
+ * Secondary VM that tries to access GICv3 system registers.
+ */
+
+TEST_SERVICE(read_systemreg_ctlr)
+{
+	/* Reading ICC_CTLR_EL1 should trap and abort the VM. */
+	dlog("ICC_CTLR_EL1=0x%x\n", read_msr(ICC_CTLR_EL1));
+	FAIL("Reading ICC_CTLR_EL1 didn't trap.");
+}
+
+TEST_SERVICE(write_systemreg_ctlr)
+{
+	/* Writing ICC_CTLR_EL1 should trap and abort the VM. */
+	write_msr(ICC_CTLR_EL1, 0);
+	FAIL("Writing ICC_CTLR_EL1 didn't trap.");
+}