diff mbox series

[v5,16/16] KVM: x86/xen: Add event channel interrupt vector upcall

Message ID 20210111195725.4601-17-dwmw2@infradead.org (mailing list archive)
State New, archived
Headers show
Series KVM: Add minimal support for Xen HVM guests | expand

Commit Message

David Woodhouse Jan. 11, 2021, 7:57 p.m. UTC
From: David Woodhouse <dwmw@amazon.co.uk>

It turns out that we can't handle event channels *entirely* in userspace
by delivering them as ExtINT, because KVM is a bit picky about when it
accepts ExtINT interrupts from a legacy PIC. The in-kernel local APIC
has to have LVT0 configured in APIC_MODE_EXTINT and unmasked, which
isn't necessarily the case for Xen guests especially on secondary CPUs.

To cope with this, add kvm_xen_get_interrupt() which checks the
evtchn_pending_upcall field in the Xen vcpu_info, and delivers the Xen
upcall vector (configured by KVM_XEN_ATTR_TYPE_UPCALL_VECTOR) if it's
set regardless of LAPIC LVT0 configuration. This gives us the minimum
support we need for completely userspace-based implementation of event
channels.

This does mean that vcpu_enter_guest() needs to check for the
evtchn_pending_upcall flag being set, because it can't rely on someone
having set KVM_REQ_EVENT unless we were to add some way for userspace to
do so manually.

But actually, I don't quite see how that works reliably for interrupts
injected with KVM_INTERRUPT either. In kvm_vcpu_ioctl_interrupt() the
KVM_REQ_EVENT request is set once, but that'll get cleared the first time
through vcpu_enter_guest(). So if the first exit is for something *else*
without interrupts being enabled yet, won't the KVM_REQ_EVENT request
have been consumed already and just be lost?

I wonder if my addition of '|| kvm_xen_has_interrupt(vcpu)' should
actually be '|| kvm_has_injectable_intr(vcpu)' to fix that pre-existing
bug?

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/include/asm/kvm_host.h |  1 +
 arch/x86/kvm/irq.c              |  7 +++++
 arch/x86/kvm/x86.c              |  3 +-
 arch/x86/kvm/xen.c              | 52 +++++++++++++++++++++++++++++++++
 arch/x86/kvm/xen.h              |  1 +
 include/uapi/linux/kvm.h        |  2 ++
 6 files changed, 65 insertions(+), 1 deletion(-)

Comments

Paolo Bonzini Jan. 28, 2021, 12:43 p.m. UTC | #1
On 11/01/21 20:57, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
> 
> It turns out that we can't handle event channels *entirely* in userspace
> by delivering them as ExtINT, because KVM is a bit picky about when it
> accepts ExtINT interrupts from a legacy PIC. The in-kernel local APIC
> has to have LVT0 configured in APIC_MODE_EXTINT and unmasked, which
> isn't necessarily the case for Xen guests especially on secondary CPUs.
> 
> To cope with this, add kvm_xen_get_interrupt() which checks the
> evtchn_pending_upcall field in the Xen vcpu_info, and delivers the Xen
> upcall vector (configured by KVM_XEN_ATTR_TYPE_UPCALL_VECTOR) if it's
> set regardless of LAPIC LVT0 configuration. This gives us the minimum
> support we need for completely userspace-based implementation of event
> channels.
> 
> This does mean that vcpu_enter_guest() needs to check for the
> evtchn_pending_upcall flag being set, because it can't rely on someone
> having set KVM_REQ_EVENT unless we were to add some way for userspace to
> do so manually.
> 
> But actually, I don't quite see how that works reliably for interrupts
> injected with KVM_INTERRUPT either. In kvm_vcpu_ioctl_interrupt() the
> KVM_REQ_EVENT request is set once, but that'll get cleared the first time
> through vcpu_enter_guest(). So if the first exit is for something *else*
> without interrupts being enabled yet, won't the KVM_REQ_EVENT request
> have been consumed already and just be lost?

If the call is for something else and there is an interrupt, 
inject_pending_event sets *req_immediate_exit to true.  This causes an 
immediate vmexit after vmentry, and also schedules another KVM_REQ_EVENT 
processing.

If the call is for an interrupt but you cannot process it yet (IF=0, 
STI/MOVSS window, etc.), inject_pending_event calls 
kvm_x86_ops.enable_irq_window and this will cause KVM_REQ_EVENT to be 
sent again.

How do you inject the interrupt from userspace?  IIRC 
evtchn_upcall_pending is written by the hypervisor upon receiving a 
hypercall, so wouldn't you need the "dom0" to invoke a KVM_INTERRUPT 
ioctl (e.g. with irq == 256)?  That KVM_INTERRUPT will set KVM_REQ_EVENT.

If you want to write a testcase without having to write all the 
interrupt stuff in the selftests framework, you could set an IDT that 
has room only for 128 vectors and use interrupt 128 as the upcall 
vector.  Then successful delivery of the vector will cause a triple fault.

Independent of the answer to the above, this is really the only place 
where you're adding Xen code to a hot path.  Can you please use a 
STATIC_KEY_FALSE kvm_has_xen_vcpu (and a static inline function) to 
quickly return from kvm_xen_has_interrupt() if no vCPU has a shared info 
set up?

That is, something like

- when first setting vcpu_info_set to true, 
static_key_slow_inc(&kvm_has_xen_vcpu.key)

- when destroying a vCPU with vcpu_info_set to true, 
static_key_slow_dec_deferred(&kvm_has_xen_vcpu)

- rename kvm_xen_has_interrupt to __kvm__xen_has_interrupt

- add a wrapper that usese the static key to avoid the function call

static int kvm_xen_has_interrupt(struct kvm_vcpu *v)
{
	if (static_branch_unlikely(&kvm_has_xen_vcpu.key))
		return __kvm_xen_has_interrupt(v);

	return false;
}

Paolo
David Woodhouse Jan. 28, 2021, 3:35 p.m. UTC | #2
On Thu, 2021-01-28 at 13:43 +0100, Paolo Bonzini wrote:
> On 11/01/21 20:57, David Woodhouse wrote:
> > From: David Woodhouse <dwmw@amazon.co.uk>
> > 
> > It turns out that we can't handle event channels *entirely* in userspace
> > by delivering them as ExtINT, because KVM is a bit picky about when it
> > accepts ExtINT interrupts from a legacy PIC. The in-kernel local APIC
> > has to have LVT0 configured in APIC_MODE_EXTINT and unmasked, which
> > isn't necessarily the case for Xen guests especially on secondary CPUs.
> > 
> > To cope with this, add kvm_xen_get_interrupt() which checks the
> > evtchn_pending_upcall field in the Xen vcpu_info, and delivers the Xen
> > upcall vector (configured by KVM_XEN_ATTR_TYPE_UPCALL_VECTOR) if it's
> > set regardless of LAPIC LVT0 configuration. This gives us the minimum
> > support we need for completely userspace-based implementation of event
> > channels.
> > 
> > This does mean that vcpu_enter_guest() needs to check for the
> > evtchn_pending_upcall flag being set, because it can't rely on someone
> > having set KVM_REQ_EVENT unless we were to add some way for userspace to
> > do so manually.
> > 
> > But actually, I don't quite see how that works reliably for interrupts
> > injected with KVM_INTERRUPT either. In kvm_vcpu_ioctl_interrupt() the
> > KVM_REQ_EVENT request is set once, but that'll get cleared the first time
> > through vcpu_enter_guest(). So if the first exit is for something *else*
> > without interrupts being enabled yet, won't the KVM_REQ_EVENT request
> > have been consumed already and just be lost?
> 
> If the call is for something else and there is an interrupt, 
> inject_pending_event sets *req_immediate_exit to true.  This causes an 
> immediate vmexit after vmentry, and also schedules another KVM_REQ_EVENT 
> processing.
> 
> If the call is for an interrupt but you cannot process it yet (IF=0, 
> STI/MOVSS window, etc.), inject_pending_event calls 
> kvm_x86_ops.enable_irq_window and this will cause KVM_REQ_EVENT to be 
> sent again.

Ah, OK. I see it now; thanks.

> How do you inject the interrupt from userspace? 

The VMM injects the interrupt purely by setting ->evtchn_upcall_pending
in the vcpu_info. That is actually also a Xen guest ABI — guests can
retrigger the vector purely by setting ->evtchn_upcall_pending in their
own vcpu_info and doing anything which triggers a vmexit.

Xen checks it and potentially injects the vector, each time it enters
the guest — just as I've done it here in vcpu_enter_guest().

> IIRC evtchn_upcall_pending is written by the hypervisor upon receiving 
> a hypercall, so wouldn't you need the "dom0" to invoke a KVM_INTERRUPT 
> ioctl (e.g. with irq == 256)?  That KVM_INTERRUPT will set KVM_REQ_EVENT.

Well, right now that would return -EINVAL, so you're suggesting we add
a special case code path to kvm_vcpu_ioctl_interrupt which just sets
KVM_REQ_EVENT without calling kvm_queue_interrupt(), in the case where
irq->irq == KVM_NR_INTERRUPTS?

Then we require that the userspace VMM make that ioctl not only when
it's set ->evtchn_upcall_pending for itself, but *also* poll for the
guest having done so?

In fact, not only the VMM would have to do that polling, but we'd
probably also have to do it on any hypercalls we accelerate in the
kernel (as we're planning to do for IPIs, etc.)

So it has to live in the kernel anyway in *some* form.

So requiring KVM_REQ_EVENT to be set manually probably ends up being
more complex than just checking it directly in vcpu_enter_guest() as I
have done here.

Even before the static key improvement you suggest below, it's a fairly
lightweight check in the common case. If the vcpu_info is set and the
memslots didn't change, it's a single dereference of a userspace
pointer which will rarely fault and need any handling.

> If you want to write a testcase without having to write all the 
> interrupt stuff in the selftests framework, you could set an IDT that 
> has room only for 128 vectors and use interrupt 128 as the upcall 
> vector.  Then successful delivery of the vector will cause a triple fault.

Yeah, my testing of this part so far consists of actually booting Xen
guests — delivery of event channel vectors was the final thing we
needed to make them actually work. I'm planning to come back and work
out how to do a more comprehensive self-test once I do the in-kernel
IPI acceleration and polling support.

I really think I'll have to come up with something better than "I can
make it crash" for those more complex tests, so I haven't bothered with
doing that as an interim step for the basic vector delivery.

> Independent of the answer to the above, this is really the only place 
> where you're adding Xen code to a hot path.  Can you please use a 
> STATIC_KEY_FALSE kvm_has_xen_vcpu (and a static inline function) to 
> quickly return from kvm_xen_has_interrupt() if no vCPU has a shared info 
> set up?

Ack. I'll do that.
Paolo Bonzini Jan. 28, 2021, 5:01 p.m. UTC | #3
On 28/01/21 16:35, David Woodhouse wrote:
> Well, right now that would return -EINVAL, so you're suggesting we add
> a special case code path to kvm_vcpu_ioctl_interrupt which just sets
> KVM_REQ_EVENT without calling kvm_queue_interrupt(), in the case where
> irq->irq == KVM_NR_INTERRUPTS?
> 
> Then we require that the userspace VMM make that ioctl not only when
> it's set ->evtchn_upcall_pending for itself, but *also*  poll for the
> guest having done so?

Hmm, right I forgot that the guest can do it for itself.  So the static 
key would be enough.

Paolo

> In fact, not only the VMM would have to do that polling, but we'd
> probably also have to do it on any hypercalls we accelerate in the
> kernel (as we're planning to do for IPIs, etc.)
> 
> So it has to live in the kernel anyway in*some*  form.
David Woodhouse Jan. 29, 2021, 5:33 p.m. UTC | #4
On Thu, 2021-01-28 at 13:43 +0100, Paolo Bonzini wrote:
> Independent of the answer to the above, this is really the only place 
> where you're adding Xen code to a hot path.  Can you please use a 
> STATIC_KEY_FALSE kvm_has_xen_vcpu (and a static inline function) to 
> quickly return from kvm_xen_has_interrupt() if no vCPU has a shared info 
> set up?

Something like this, then?

From 6504c78f76efd8c60630959111bd77c28d43fca7 Mon Sep 17 00:00:00 2001
From: David Woodhouse <dwmw@amazon.co.uk>
Date: Fri, 29 Jan 2021 17:30:40 +0000
Subject: [PATCH] KVM: x86/xen: Add static branch to avoid overhead of checking
 Xen upcall vector

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c |  1 +
 arch/x86/kvm/xen.c | 68 +++++++++++++++++++++++++++-------------------
 arch/x86/kvm/xen.h | 15 +++++++++-
 3 files changed, 55 insertions(+), 29 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 870dea74ea94..fb2bc362efd8 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10580,6 +10580,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
 		kvm_x86_ops.vm_destroy(kvm);
 	for (i = 0; i < kvm->arch.msr_filter.count; i++)
 		kfree(kvm->arch.msr_filter.ranges[i].bitmap);
+	kvm_xen_destroy(kvm);
 	kvm_pic_destroy(kvm);
 	kvm_ioapic_destroy(kvm);
 	kvm_free_vcpus(kvm);
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 3041f774493e..9b6766e5d84f 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -19,6 +19,8 @@
 
 #include "trace.h"
 
+DEFINE_STATIC_KEY_DEFERRED_FALSE(kvm_xen_has_vector, HZ);
+
 static int kvm_xen_shared_info_init(struct kvm *kvm, gfn_t gfn)
 {
 	gpa_t gpa = gfn_to_gpa(gfn);
@@ -176,7 +178,7 @@ void kvm_xen_setup_runstate_page(struct kvm_vcpu *v)
 	kvm_xen_update_runstate(v, RUNSTATE_running, steal_time);
 }
 
-int kvm_xen_has_interrupt(struct kvm_vcpu *v)
+int __kvm_xen_has_interrupt(struct kvm_vcpu *v)
 {
 	u8 rc = 0;
 
@@ -184,37 +186,42 @@ int kvm_xen_has_interrupt(struct kvm_vcpu *v)
 	 * If the global upcall vector (HVMIRQ_callback_vector) is set and
 	 * the vCPU's evtchn_upcall_pending flag is set, the IRQ is pending.
 	 */
-	if (v->arch.xen.vcpu_info_set && v->kvm->arch.xen.upcall_vector) {
-		struct gfn_to_hva_cache *ghc = &v->arch.xen.vcpu_info_cache;
-		struct kvm_memslots *slots = kvm_memslots(v->kvm);
-		unsigned int offset = offsetof(struct vcpu_info, evtchn_upcall_pending);
-
-		/* No need for compat handling here */
-		BUILD_BUG_ON(offsetof(struct vcpu_info, evtchn_upcall_pending) !=
-			     offsetof(struct compat_vcpu_info, evtchn_upcall_pending));
-		BUILD_BUG_ON(sizeof(rc) !=
-			     sizeof(((struct vcpu_info *)0)->evtchn_upcall_pending));
-		BUILD_BUG_ON(sizeof(rc) !=
-			     sizeof(((struct compat_vcpu_info *)0)->evtchn_upcall_pending));
-
-		/*
-		 * For efficiency, this mirrors the checks for using the valid
-		 * cache in kvm_read_guest_offset_cached(), but just uses
-		 * __get_user() instead. And falls back to the slow path.
-		 */
-		if (likely(slots->generation == ghc->generation &&
-			   !kvm_is_error_hva(ghc->hva) && ghc->memslot)) {
-			/* Fast path */
-			__get_user(rc, (u8 __user *)ghc->hva + offset);
-		} else {
-			/* Slow path */
-			kvm_read_guest_offset_cached(v->kvm, ghc, &rc, offset,
-						      sizeof(rc));
-		}
+	struct gfn_to_hva_cache *ghc = &v->arch.xen.vcpu_info_cache;
+	struct kvm_memslots *slots = kvm_memslots(v->kvm);
+	unsigned int offset = offsetof(struct vcpu_info, evtchn_upcall_pending);
+
+	/* No need for compat handling here */
+	BUILD_BUG_ON(offsetof(struct vcpu_info, evtchn_upcall_pending) !=
+		     offsetof(struct compat_vcpu_info, evtchn_upcall_pending));
+	BUILD_BUG_ON(sizeof(rc) !=
+		     sizeof(((struct vcpu_info *)0)->evtchn_upcall_pending));
+	BUILD_BUG_ON(sizeof(rc) !=
+		     sizeof(((struct compat_vcpu_info *)0)->evtchn_upcall_pending));
+
+	/*
+	 * For efficiency, this mirrors the checks for using the valid
+	 * cache in kvm_read_guest_offset_cached(), but just uses
+	 * __get_user() instead. And falls back to the slow path.
+	 */
+	if (likely(slots->generation == ghc->generation &&
+		   !kvm_is_error_hva(ghc->hva) && ghc->memslot)) {
+		/* Fast path */
+		__get_user(rc, (u8 __user *)ghc->hva + offset);
+	} else {
+		/* Slow path */
+		kvm_read_guest_offset_cached(v->kvm, ghc, &rc, offset,
+					     sizeof(rc));
 	}
+
 	return rc;
 }
 
+void kvm_xen_destroy(struct kvm *kvm)
+{
+	if (kvm->arch.xen.upcall_vector)
+		static_branch_slow_dec_deferred(&kvm_xen_has_vector);
+}
+
 int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
 {
 	struct kvm_vcpu *v;
@@ -300,6 +307,11 @@ int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
 			break;
 		}
 
+		if (data->u.vector && !kvm->arch.xen.upcall_vector)
+			static_branch_inc(&kvm_xen_has_vector.key);
+		else if (kvm->arch.xen.upcall_vector && !data->u.vector)
+			static_branch_slow_dec_deferred(&kvm_xen_has_vector);
+
 		kvm->arch.xen.upcall_vector = data->u.vector;
 		r = 0;
 		break;
diff --git a/arch/x86/kvm/xen.h b/arch/x86/kvm/xen.h
index d64916ac4a12..d49cd8ea8da9 100644
--- a/arch/x86/kvm/xen.h
+++ b/arch/x86/kvm/xen.h
@@ -9,13 +9,16 @@
 #ifndef __ARCH_X86_KVM_XEN_H__
 #define __ARCH_X86_KVM_XEN_H__
 
+#include <linux/jump_label_ratelimit.h>
+
 void kvm_xen_setup_runstate_page(struct kvm_vcpu *vcpu);
 void kvm_xen_runstate_set_preempted(struct kvm_vcpu *vcpu);
-int kvm_xen_has_interrupt(struct kvm_vcpu *vcpu);
+int __kvm_xen_has_interrupt(struct kvm_vcpu *vcpu);
 int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
 int kvm_xen_hvm_get_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
 int kvm_xen_hypercall(struct kvm_vcpu *vcpu);
 int kvm_xen_hvm_config(struct kvm_vcpu *vcpu, u64 data);
+void kvm_xen_destroy(struct kvm *kvm);
 
 static inline bool kvm_xen_hypercall_enabled(struct kvm *kvm)
 {
@@ -23,6 +26,16 @@ static inline bool kvm_xen_hypercall_enabled(struct kvm *kvm)
 		KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL;
 }
 
+extern struct static_key_false_deferred kvm_xen_has_vector;
+
+static inline int kvm_xen_has_interrupt(struct kvm_vcpu *vcpu)
+{
+	if (static_branch_unlikely(&kvm_xen_has_vector.key) &&
+	    vcpu->arch.xen.vcpu_info_set && vcpu->kvm->arch.xen.upcall_vector)
+		return __kvm_xen_has_interrupt(vcpu);
+
+	return 0;
+}
 
 /* 32-bit compatibility definitions, also used natively in 32-bit build */
 #include <asm/pvclock-abi.h>
Paolo Bonzini Jan. 29, 2021, 7:19 p.m. UTC | #5
On 29/01/21 18:33, David Woodhouse wrote:
> On Thu, 2021-01-28 at 13:43 +0100, Paolo Bonzini wrote:
>> Independent of the answer to the above, this is really the only place
>> where you're adding Xen code to a hot path.  Can you please use a
>> STATIC_KEY_FALSE kvm_has_xen_vcpu (and a static inline function) to
>> quickly return from kvm_xen_has_interrupt() if no vCPU has a shared info
>> set up?
> 
> Something like this, then?
> 
>  From 6504c78f76efd8c60630959111bd77c28d43fca7 Mon Sep 17 00:00:00 2001
> From: David Woodhouse <dwmw@amazon.co.uk>
> Date: Fri, 29 Jan 2021 17:30:40 +0000
> Subject: [PATCH] KVM: x86/xen: Add static branch to avoid overhead of checking
>   Xen upcall vector
> 
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>

Yes, that was the idea.  Thanks!

Paolo
David Woodhouse Jan. 30, 2021, 1:01 p.m. UTC | #6
On 29 January 2021 19:19:55 GMT, Paolo Bonzini <pbonzini@redhat.com> wrote:
>On 29/01/21 18:33, David Woodhouse wrote:
>> On Thu, 2021-01-28 at 13:43 +0100, Paolo Bonzini wrote:
>>> Independent of the answer to the above, this is really the only
>place
>>> where you're adding Xen code to a hot path.  Can you please use a
>>> STATIC_KEY_FALSE kvm_has_xen_vcpu (and a static inline function) to
>>> quickly return from kvm_xen_has_interrupt() if no vCPU has a shared
>info
>>> set up?
>> 
>> Something like this, then?
>> 
>
>Yes, that was the idea.  Thanks!

Ok, I'll take a look at the get/set ioctls, maybe post an incremental patch for those to get confirmation, then rework it all into the series and repost based on kvm/next.

Thanks.
diff mbox series

Patch

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 73f285ebb181..b1cc73a19021 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -907,6 +907,7 @@  struct msr_bitmap_range {
 struct kvm_xen {
 	bool long_mode;
 	bool shinfo_set;
+	u8 upcall_vector;
 	struct gfn_to_hva_cache shinfo_cache;
 };
 
diff --git a/arch/x86/kvm/irq.c b/arch/x86/kvm/irq.c
index 814698e5b152..24668b51b5c8 100644
--- a/arch/x86/kvm/irq.c
+++ b/arch/x86/kvm/irq.c
@@ -14,6 +14,7 @@ 
 #include "irq.h"
 #include "i8254.h"
 #include "x86.h"
+#include "xen.h"
 
 /*
  * check if there are pending timer events
@@ -56,6 +57,9 @@  int kvm_cpu_has_extint(struct kvm_vcpu *v)
 	if (!lapic_in_kernel(v))
 		return v->arch.interrupt.injected;
 
+	if (kvm_xen_has_interrupt(v))
+		return 1;
+
 	if (!kvm_apic_accept_pic_intr(v))
 		return 0;
 
@@ -110,6 +114,9 @@  static int kvm_cpu_get_extint(struct kvm_vcpu *v)
 	if (!lapic_in_kernel(v))
 		return v->arch.interrupt.nr;
 
+	if (kvm_xen_has_interrupt(v))
+		return v->kvm->arch.xen.upcall_vector;
+
 	if (irqchip_split(v->kvm)) {
 		int vector = v->arch.pending_external_vector;
 
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 25ef3b7ad49d..aa3c38fa3f31 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8950,7 +8950,8 @@  static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
 			kvm_x86_ops.msr_filter_changed(vcpu);
 	}
 
-	if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) {
+	if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win ||
+	    kvm_xen_has_interrupt(vcpu)) {
 		++vcpu->stat.req_event;
 		kvm_apic_accept_events(vcpu);
 		if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 17cbb4462b7e..4bc9da9fcfb8 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -176,6 +176,45 @@  void kvm_xen_setup_runstate_page(struct kvm_vcpu *v)
 	kvm_xen_update_runstate(v, RUNSTATE_running, steal_time);
 }
 
+int kvm_xen_has_interrupt(struct kvm_vcpu *v)
+{
+	u8 rc = 0;
+
+	/*
+	 * If the global upcall vector (HVMIRQ_callback_vector) is set and
+	 * the vCPU's evtchn_upcall_pending flag is set, the IRQ is pending.
+	 */
+	if (v->arch.xen.vcpu_info_set && v->kvm->arch.xen.upcall_vector) {
+		struct gfn_to_hva_cache *ghc = &v->arch.xen.vcpu_info_cache;
+		struct kvm_memslots *slots = kvm_memslots(v->kvm);
+		unsigned int offset = offsetof(struct vcpu_info, evtchn_upcall_pending);
+
+		/* No need for compat handling here */
+		BUILD_BUG_ON(offsetof(struct vcpu_info, evtchn_upcall_pending) !=
+			     offsetof(struct compat_vcpu_info, evtchn_upcall_pending));
+		BUILD_BUG_ON(sizeof(rc) !=
+			     sizeof(((struct vcpu_info *)0)->evtchn_upcall_pending));
+		BUILD_BUG_ON(sizeof(rc) !=
+			     sizeof(((struct compat_vcpu_info *)0)->evtchn_upcall_pending));
+
+		/*
+		 * For efficiency, this mirrors the checks for using the valid
+		 * cache in kvm_read_guest_offset_cached(), but just uses
+		 * __get_user() instead. And falls back to the slow path.
+		 */
+		if (likely(slots->generation == ghc->generation &&
+			   !kvm_is_error_hva(ghc->hva) && ghc->memslot)) {
+			/* Fast path */
+			__get_user(rc, (u8 __user *)ghc->hva + offset);
+		} else {
+			/* Slow path */
+			kvm_read_guest_offset_cached(v->kvm, ghc, &rc, offset,
+						      sizeof(rc));
+		}
+	}
+	return rc;
+}
+
 int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
 {
 	struct kvm_vcpu *v;
@@ -245,6 +284,14 @@  int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
 		v->arch.xen.last_state_ns = ktime_get_ns();
 		break;
 
+	case KVM_XEN_ATTR_TYPE_UPCALL_VECTOR:
+		if (data->u.vector < 0x10)
+			return -EINVAL;
+
+		kvm->arch.xen.upcall_vector = data->u.vector;
+		r = 0;
+		break;
+
 	default:
 		break;
 	}
@@ -303,6 +350,11 @@  int kvm_xen_hvm_get_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
 		}
 		break;
 
+	case KVM_XEN_ATTR_TYPE_UPCALL_VECTOR:
+		data->u.vector = kvm->arch.xen.upcall_vector;
+		r = 0;
+		break;
+
 	default:
 		break;
 	}
diff --git a/arch/x86/kvm/xen.h b/arch/x86/kvm/xen.h
index 407e717476d6..d64916ac4a12 100644
--- a/arch/x86/kvm/xen.h
+++ b/arch/x86/kvm/xen.h
@@ -11,6 +11,7 @@ 
 
 void kvm_xen_setup_runstate_page(struct kvm_vcpu *vcpu);
 void kvm_xen_runstate_set_preempted(struct kvm_vcpu *vcpu);
+int kvm_xen_has_interrupt(struct kvm_vcpu *vcpu);
 int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
 int kvm_xen_hvm_get_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
 int kvm_xen_hypercall(struct kvm_vcpu *vcpu);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index e468f923e7dd..f814cccc0d00 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1595,6 +1595,7 @@  struct kvm_xen_hvm_attr {
 	__u16 pad[3];
 	union {
 		__u8 long_mode;
+		__u8 vector;
 		struct {
 			__u64 gfn;
 		} shared_info;
@@ -1612,6 +1613,7 @@  struct kvm_xen_hvm_attr {
 #define KVM_XEN_ATTR_TYPE_VCPU_INFO		0x2
 #define KVM_XEN_ATTR_TYPE_VCPU_TIME_INFO	0x3
 #define KVM_XEN_ATTR_TYPE_VCPU_RUNSTATE		0x4
+#define KVM_XEN_ATTR_TYPE_UPCALL_VECTOR		0x5
 
 /* Secure Encrypted Virtualization command */
 enum sev_cmd_id {