int __kvm_xen_has_interrupt(struct kvm_vcpu *v)
{
	unsigned long evtchn_pending_sel = READ_ONCE(v->arch.xen.evtchn_pending_sel);
	bool atomic = in_atomic() || !task_is_running(current);
	int err;
	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.
	 */
	struct gfn_to_hva_cache *ghc = &v->arch.xen.vcpu_info_cache;
	struct kvm_memslots *slots = kvm_memslots(v->kvm);
	bool ghc_valid = slots->generation == ghc->generation &&
		!kvm_is_error_hva(ghc->hva) && ghc->memslot;

	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_field(struct vcpu_info, evtchn_upcall_pending));
	BUILD_BUG_ON(sizeof(rc) !=
		     sizeof_field(struct compat_vcpu_info, 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 (!evtchn_pending_sel && ghc_valid) {
		/* Fast path */
		pagefault_disable();
		err = __get_user(rc, (u8 __user *)ghc->hva + offset);
		pagefault_enable();
		if (!err)
			return rc;
	}

	/* Slow path */

	/*
	 * This function gets called from kvm_vcpu_block() after setting the
	 * task to TASK_INTERRUPTIBLE, to see if it needs to wake immediately
	 * from a HLT. So we really mustn't sleep. If the page ended up absent
	 * at that point, just return 1 in order to trigger an immediate wake,
	 * and we'll end up getting called again from a context where we *can*
	 * fault in the page and wait for it.
	 */
	if (atomic)
		return 1;

	if (!ghc_valid) {
		err = kvm_gfn_to_hva_cache_init(v->kvm, ghc, ghc->gpa, ghc->len);
		if (err || !ghc->memslot) {
			/*
			 * If this failed, userspace has screwed up the
			 * vcpu_info mapping. No interrupts for you.
			 */
			return 0;
		}
	}

	/*
	 * Now we have a valid (protected by srcu) userspace HVA in
	 * ghc->hva which points to the struct vcpu_info. If there
	 * are any bits in the in-kernel evtchn_pending_sel then
	 * we need to write those to the guest vcpu_info and set
	 * its evtchn_upcall_pending flag. If there aren't any bits
	 * to add, we only want to *check* evtchn_upcall_pending.
	 */
	if (evtchn_pending_sel) {
		bool long_mode = v->kvm->arch.xen.long_mode;

		if (!user_access_begin((void __user *)ghc->hva, sizeof(struct vcpu_info)))
			return 0;

		if (IS_ENABLED(CONFIG_64BIT) && long_mode) {
			struct vcpu_info __user *vi = (void __user *)ghc->hva;

			/* Attempt to set the evtchn_pending_sel bits in the
			 * guest, and if that succeeds then clear the same
			 * bits in the in-kernel version. */
			asm volatile("1:\t" LOCK_PREFIX "orq %0, %1\n"
				     "\tnotq %0\n"
				     "\t" LOCK_PREFIX "andq %0, %2\n"
				     "2:\n"
				     "\t.section .fixup,\"ax\"\n"
				     "3:\tjmp\t2b\n"
				     "\t.previous\n"
				     _ASM_EXTABLE_UA(1b, 3b)
				     : "=r" (evtchn_pending_sel),
				       "+m" (vi->evtchn_pending_sel),
				       "+m" (v->arch.xen.evtchn_pending_sel)
				     : "0" (evtchn_pending_sel));
		} else {
			struct compat_vcpu_info __user *vi = (void __user *)ghc->hva;
			u32 evtchn_pending_sel32 = evtchn_pending_sel;

			/* Attempt to set the evtchn_pending_sel bits in the
			 * guest, and if that succeeds then clear the same
			 * bits in the in-kernel version. */
			asm volatile("1:\t" LOCK_PREFIX "orl %0, %1\n"
				     "\tnotl %0\n"
				     "\t" LOCK_PREFIX "andl %0, %2\n"
				     "2:\n"
				     "\t.section .fixup,\"ax\"\n"
				     "3:\tjmp\t2b\n"
				     "\t.previous\n"
				     _ASM_EXTABLE_UA(1b, 3b)
				     : "=r" (evtchn_pending_sel32),
				       "+m" (vi->evtchn_pending_sel),
				       "+m" (v->arch.xen.evtchn_pending_sel)
				     : "0" (evtchn_pending_sel32));
		}
		rc = 1;
		unsafe_put_user(rc, (u8 __user *)ghc->hva + offset, err);

	err:
		user_access_end();

		mark_page_dirty_in_slot(v->kvm, ghc->memslot, ghc->gpa >> PAGE_SHIFT);
	} else {
		__get_user(rc, (u8 __user *)ghc->hva + offset);
	}

	return rc;
}