KVM: arm64: Opportunistically track HCR_EL2.E2H being flipped

Signed-off-by: Marc Zyngier <maz@kernel.org>
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 8f6aa7e..9070405 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -593,6 +593,9 @@
 	/* State flags for kernel bookkeeping, unused by the hypervisor code */
 	u8 sflags;
 
+	/* Output flags from the hypervisor to the kernel */
+	u8 oflags;
+
 	/*
 	 * Don't run the guest (internal implementation need).
 	 *
@@ -819,6 +822,8 @@
 /* WFI instruction trapped */
 #define IN_WFI			__vcpu_single_flag(sflags, BIT(7))
 
+/* vcpu entered with HCR_EL2.E2H set */
+#define VCPU_HCR_E2H		__vcpu_single_flag(oflags, BIT(0))
 
 /* Pointer to the vcpu's SVE FFR for sve_{save,load}_state() */
 #define vcpu_sve_pffr(vcpu) (kern_hyp_va((vcpu)->arch.sve_state) +	\
diff --git a/arch/arm64/kvm/handle_exit.c b/arch/arm64/kvm/handle_exit.c
index e7bc520..78adc52 100644
--- a/arch/arm64/kvm/handle_exit.c
+++ b/arch/arm64/kvm/handle_exit.c
@@ -384,6 +384,13 @@
 /* For exit types that need handling before we can be preempted */
 void handle_exit_early(struct kvm_vcpu *vcpu, int exception_index)
 {
+	/* Check whether HCR_EL2.E2H was flipped behind our back */
+	if (vcpu_has_nv2(vcpu) && is_hyp_ctxt(vcpu) &&
+	    (!!vcpu_get_flag(vcpu, VCPU_HCR_E2H) != vcpu_el2_e2h_is_set(vcpu))) {
+		kvm_arch_vcpu_put(vcpu);
+		kvm_arch_vcpu_load(vcpu, smp_processor_id());
+	}
+
 	if (ARM_SERROR_PENDING(exception_index)) {
 		if (this_cpu_has_cap(ARM64_HAS_RAS_EXTN)) {
 			u64 disr = kvm_vcpu_get_disr(vcpu);
diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
index b30e515..b09e95f 100644
--- a/arch/arm64/kvm/hyp/vhe/switch.c
+++ b/arch/arm64/kvm/hyp/vhe/switch.c
@@ -434,10 +434,18 @@
 	sysreg_restore_guest_state_vhe(guest_ctxt);
 	__debug_switch_to_guest(vcpu);
 
-	if (is_hyp_ctxt(vcpu))
+	if (is_hyp_ctxt(vcpu)) {
+		if (vcpu_has_nv2(vcpu)) {
+			if (vcpu_el2_e2h_is_set(vcpu))
+				vcpu_set_flag(vcpu, VCPU_HCR_E2H);
+			else
+				vcpu_clear_flag(vcpu, VCPU_HCR_E2H);
+		}
+
 		vcpu_set_flag(vcpu, VCPU_HYP_CONTEXT);
-	else
+	} else {
 		vcpu_clear_flag(vcpu, VCPU_HYP_CONTEXT);
+	}
 
 	do {
 		/* Jump in the fire! */