ANDROID: KVM: arm64: selftests: Test vSError delivery to a protected guest

Cover the vSError path for a protected guest: pend one from the host after
an MMIO exit, check the guest sees it pending in ISR_EL1.A while its
PSTATE.A masks it, unmask so it is taken with ESR_ELx_EC_SERROR, and check
KVM_GET_VCPU_EVENTS reports it no longer pending.

Each step catches a different part of the path: an emulated exception entry
instead of HCR_EL2.VSE, VSE not flushed to the hyp vCPU, and VSE not
reflected back on exit.

Bug: 458241298
Change-Id: Ia9ad3815a1ac98676432d5f5cbbf78ef9ee27098
Signed-off-by: Fuad Tabba <tabba@google.com>
diff --git a/tools/testing/selftests/kvm/arm64/pkvm.c b/tools/testing/selftests/kvm/arm64/pkvm.c
index 6f3356d..09f8ed5 100644
--- a/tools/testing/selftests/kvm/arm64/pkvm.c
+++ b/tools/testing/selftests/kvm/arm64/pkvm.c
@@ -26,6 +26,8 @@ enum guest_commands {
 	CMD_HEARTBEAT = 1,	/* Lets host know that guest is alive. */
 	CMD_INC,		/* Ask host to increment shared/relinquished page. */
 	CMD_INC_PRIVATE,	/* Ask host to increment private page. */
+	CMD_PEND_SERROR,	/* Ask host to inject a virtual SError. */
+	CMD_CHECK_SERROR,	/* Ask host to check the vSError was taken. */
 };
 
 #define  PC(v)  ((uint64_t)&(v))
@@ -70,6 +72,7 @@ static volatile uint64_t guest_ex_pc;
 static volatile uint64_t guest_ex_addr;
 static volatile uint64_t ss_addr[4];
 static volatile char write_data;
+static volatile bool serror_taken;
 static volatile int ss_idx;
 
 /*
@@ -626,6 +629,32 @@ static void test_fpmr(void)
 	write_sysreg_s(saved, SYS_FPMR);
 }
 
+static void serror_handler(struct ex_regs *regs)
+{
+	GUEST_ASSERT_EQ(ESR_ELx_EC(read_sysreg(esr_el1)), ESR_ELx_EC_SERROR);
+	serror_taken = true;
+}
+
+/*
+ * A host-injected SError must reach a protected guest as a virtual SError
+ * (HCR_EL2.VSE), the only form the guest can gate: the host cannot see this
+ * guest's PSTATE.A, so it must not decide masking on the guest's behalf.
+ */
+static void test_serror(void)
+{
+	serror_taken = false;
+
+	GUEST_SYNC(CMD_PEND_SERROR);
+	GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A);
+
+	local_serror_enable();
+	isb();
+	local_serror_disable();
+
+	GUEST_ASSERT(serror_taken);
+	GUEST_SYNC(CMD_CHECK_SERROR);
+}
+
 /*
  * Main code to run by the guest vm.
  */
@@ -668,6 +697,8 @@ static void guest_code(vm_paddr_t ucall_pool_phys, size_t ucall_pool_size,
 
 	test_fpmr();
 
+	test_serror();
+
 	/* Populate the donated memslot to facilitate testing poisoning after destruction. */
 	guest_dirty_memslot();
 
@@ -822,10 +853,32 @@ static void set_guest_mem(struct kvm_vm *vm, uint64_t npages)
 	memset(region->host_mem, 0xaa, PAGE_SIZE * npages);
 }
 
+static void cmd_pend_serror(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu_events events = {};
+
+	events.exception.serror_pending = true;
+	vcpu_events_set(vcpu, &events);
+
+	memset(&events, 0, sizeof(events));
+	vcpu_events_get(vcpu, &events);
+	TEST_ASSERT(events.exception.serror_pending,
+		    "Assert: vSError pending after injection.");
+}
+
+static void cmd_check_serror(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu_events events = {};
+
+	vcpu_events_get(vcpu, &events);
+	TEST_ASSERT(!events.exception.serror_pending,
+		    "Assert: vSError no longer pending once taken.");
+}
+
 /*
  * Processes commands issued by the guest to the host via the ucall interface.
  */
-static void handle_cmd(struct kvm_vm *vm, int cmd)
+static void handle_cmd(struct kvm_vm *vm, struct kvm_vcpu *vcpu, int cmd)
 {
 	switch (cmd) {
 	case CMD_HEARTBEAT:
@@ -837,6 +890,12 @@ static void handle_cmd(struct kvm_vm *vm, int cmd)
 	case CMD_INC_PRIVATE:
 		cmd_inc_private(vm);
 		break;
+	case CMD_PEND_SERROR:
+		cmd_pend_serror(vcpu);
+		break;
+	case CMD_CHECK_SERROR:
+		cmd_check_serror(vcpu);
+		break;
 	default:
 		TEST_FAIL("Unexpected guest command: %d\n", cmd);
 		break;
@@ -884,6 +943,7 @@ static void test_run(enum vm_mem_backing_src_type src_type)
 				ESR_ELx_EC_WATCHPT_CUR, wp_handler);
 	vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
 				ESR_ELx_EC_SOFTSTP_CUR, ss_handler);
+	vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, serror_handler);
 	vcpu_set_pvm_boot_args(vcpu);
 
 	while (!guest_done) {
@@ -893,7 +953,7 @@ static void test_run(enum vm_mem_backing_src_type src_type)
 
 		switch (uc_num = get_ucall(vcpu, &uc)) {
 		case UCALL_SYNC:
-			handle_cmd(vm, uc.args[1]);
+			handle_cmd(vm, vcpu, uc.args[1]);
 			break;
 		case UCALL_DONE:
 			pr_info("Guest done\n");