diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h
index 16bd5d1..8dad9dc 100644
--- a/arch/s390/include/asm/kvm_host.h
+++ b/arch/s390/include/asm/kvm_host.h
@@ -18,6 +18,7 @@
 #include <linux/kvm_host.h>
 #include <asm/debug.h>
 #include <asm/cpu.h>
+#include <asm/schid.h>
 
 #define KVM_MAX_VCPUS 64
 #define KVM_USER_MEM_SLOTS 32
@@ -262,8 +263,30 @@ struct kvm_arch{
 	debug_info_t *dbf;
 	struct kvm_s390_float_interrupt float_int;
 	struct gmap *gmap;
+	struct list_head sch_fds;
+	struct rw_semaphore sch_fds_sem;
 	int css_support;
 };
 
 extern int sie64a(struct kvm_s390_sie_block *, u64 *);
+#define __KVM_HAVE_ARCH_IOEVENTFD
+
+#define KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY 1
+
+struct kvm_s390_ioeventfd_data {
+	__u8 type;
+	union {
+		/* VIRTIO_CCW_NOTIFY */
+		struct {
+			__u64 vq;
+			struct subchannel_id schid;
+		} virtio_ccw_vq;
+		char padding[35];
+	};
+} __packed;
+
+struct kvm_arch_ioeventfd {
+	struct list_head entry;
+	struct kvm_s390_ioeventfd_data data;
+};
 #endif
diff --git a/arch/s390/kvm/Kconfig b/arch/s390/kvm/Kconfig
index b58dd86..3c43e30 100644
--- a/arch/s390/kvm/Kconfig
+++ b/arch/s390/kvm/Kconfig
@@ -22,6 +22,7 @@ config KVM
 	select PREEMPT_NOTIFIERS
 	select ANON_INODES
 	select HAVE_KVM_CPU_RELAX_INTERCEPT
+	select HAVE_KVM_EVENTFD
 	---help---
 	  Support hosting paravirtualized guest machines using the SIE
 	  virtualization capability on the mainframe. This should work
diff --git a/arch/s390/kvm/Makefile b/arch/s390/kvm/Makefile
index 2441ffd..dbd8cc9 100644
--- a/arch/s390/kvm/Makefile
+++ b/arch/s390/kvm/Makefile
@@ -6,7 +6,7 @@
 # it under the terms of the GNU General Public License (version 2 only)
 # as published by the Free Software Foundation.
 
-common-objs = $(addprefix ../../../virt/kvm/, kvm_main.o)
+common-objs = $(addprefix ../../../virt/kvm/, kvm_main.o eventfd.o)
 
 ccflags-y := -Ivirt/kvm -Iarch/s390/kvm
 
diff --git a/arch/s390/kvm/diag.c b/arch/s390/kvm/diag.c
index a390687..51ea66f 100644
--- a/arch/s390/kvm/diag.c
+++ b/arch/s390/kvm/diag.c
@@ -104,6 +104,27 @@ static int __diag_ipl_functions(struct kvm_vcpu *vcpu)
 	return -EREMOTE;
 }
 
+static int __diag_virtio_hypercall(struct kvm_vcpu *vcpu)
+{
+	struct kvm_s390_ioeventfd_data data;
+	u32 tmp;
+
+	/* No channel I/O? Get out quickly. */
+	if (!vcpu->kvm->arch.css_support ||
+	    (vcpu->run->s.regs.gprs[1] != 3))
+		return -EOPNOTSUPP;
+
+	/* subchannel id is in gpr 2, queue in gpr 3 */
+	tmp = vcpu->run->s.regs.gprs[2] & 0xffffffff;
+	memcpy(&data.virtio_ccw_vq.schid, &tmp,
+	       sizeof(data.virtio_ccw_vq.schid));
+	data.virtio_ccw_vq.vq = vcpu->run->s.regs.gprs[3];
+	data.type = KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY;
+
+	/* If signalling via eventfd fails, we want to drop to userspace. */
+	return kvm_s390_ioeventfd_signal(vcpu->kvm, &data) ? -EOPNOTSUPP : 0;
+}
+
 int kvm_s390_handle_diag(struct kvm_vcpu *vcpu)
 {
 	int code = (vcpu->arch.sie_block->ipb & 0xfff0000) >> 16;
@@ -118,6 +139,8 @@ int kvm_s390_handle_diag(struct kvm_vcpu *vcpu)
 		return __diag_time_slice_end_directed(vcpu);
 	case 0x308:
 		return __diag_ipl_functions(vcpu);
+	case 0x500:
+		return __diag_virtio_hypercall(vcpu);
 	default:
 		return -EOPNOTSUPP;
 	}
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index 58a5f03..cd9eb0e 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -15,6 +15,7 @@
 
 #include <linux/compiler.h>
 #include <linux/err.h>
+#include <linux/eventfd.h>
 #include <linux/fs.h>
 #include <linux/hrtimer.h>
 #include <linux/init.h>
@@ -143,6 +144,7 @@ int kvm_dev_ioctl_check_extension(long ext)
 	case KVM_CAP_ONE_REG:
 	case KVM_CAP_ENABLE_CAP:
 	case KVM_CAP_S390_CSS_SUPPORT:
+	case KVM_CAP_IOEVENTFD:
 		r = 1;
 		break;
 	case KVM_CAP_NR_VCPUS:
@@ -237,6 +239,8 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 		if (!kvm->arch.gmap)
 			goto out_nogmap;
 	}
+	INIT_LIST_HEAD(&kvm->arch.sch_fds);
+	init_rwsem(&kvm->arch.sch_fds_sem);
 
 	kvm->arch.css_support = 0;
 
@@ -1028,3 +1032,164 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
 				   struct kvm_memory_slot *slot)
 {
 }
+static void kvm_s390_ioeventfd_add(struct kvm *kvm,
+				   struct kvm_arch_ioeventfd *arch)
+{
+	switch (arch->data.type) {
+	case KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY:
+		down_write(&kvm->arch.sch_fds_sem);
+		list_add_tail(&arch->entry, &kvm->arch.sch_fds);
+		up_write(&kvm->arch.sch_fds_sem);
+		break;
+	default:
+		pr_warn("Trying to add ioeventfd type %x\n", arch->data.type);
+	}
+}
+
+static void kvm_s390_ioeventfd_remove(struct kvm *kvm,
+				      struct kvm_arch_ioeventfd *arch)
+{
+	switch (arch->data.type) {
+	case KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY:
+		down_write(&kvm->arch.sch_fds_sem);
+		list_del(&arch->entry);
+		up_write(&kvm->arch.sch_fds_sem);
+		break;
+	default:
+		pr_warn("Trying to remove ioeventfd type %x\n",
+			arch->data.type);
+	}
+}
+
+int kvm_s390_ioeventfd_signal(struct kvm *kvm,
+			      struct kvm_s390_ioeventfd_data *data)
+{
+	struct kvm_arch_ioeventfd *arch, match;
+	int ret;
+
+	if (data->type != KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY)
+		return -ENOENT;
+	down_read(&kvm->arch.sch_fds_sem);
+	if (list_empty(&kvm->arch.sch_fds)) {
+		ret = -ENOENT;
+		goto out_unlock;
+	}
+	memcpy(&match.data, data, sizeof(match.data));
+	list_for_each_entry(arch, &kvm->arch.sch_fds, entry) {
+		if (!kvm_arch_ioeventfd_match(arch, &match))
+			continue;
+		ret = eventfd_signal(kvm_ioeventfd_get_eventfd(arch), 1);
+		goto out_unlock;
+	}
+	ret = -ENOENT;
+out_unlock:
+	if (ret > 0)
+		ret = 0;
+	up_read(&kvm->arch.sch_fds_sem);
+	return ret;
+}
+
+int kvm_arch_ioeventfd_check(struct kvm_ioeventfd *args)
+{
+	struct kvm_s390_ioeventfd_data *data;
+
+	if (!(args->flags & KVM_IOEVENTFD_FLAG_ARCH))
+		return -EINVAL;
+	if (args->flags & (KVM_IOEVENTFD_FLAG_DATAMATCH |
+			   KVM_IOEVENTFD_FLAG_PIO))
+		return -EINVAL;
+
+	data = (struct kvm_s390_ioeventfd_data *) args->data;
+	if (data->type != KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY)
+		return -EINVAL;
+	if (((data->virtio_ccw_vq.schid.m == 1) &&
+	     (data->virtio_ccw_vq.schid.cssid != 0xfe)) ||
+	    ((data->virtio_ccw_vq.schid.m == 0) &&
+	     (data->virtio_ccw_vq.schid.cssid != 0)))
+		return -EINVAL;
+	if (data->virtio_ccw_vq.schid.one != 1)
+		return -EINVAL;
+	if (data->virtio_ccw_vq.vq > 128)
+		return -EINVAL;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kvm_arch_ioeventfd_check);
+
+void kvm_arch_ioeventfd_init(struct kvm_arch_ioeventfd *arch,
+			     struct kvm_ioeventfd *args)
+{
+	INIT_LIST_HEAD(&arch->entry);
+	memcpy(&arch->data, &args->data, sizeof(arch->data));
+}
+EXPORT_SYMBOL_GPL(kvm_arch_ioeventfd_init);
+
+int kvm_arch_ioeventfd_activate(struct kvm *kvm,
+				struct kvm_arch_ioeventfd *arch,
+				struct kvm_ioeventfd *args)
+{
+	int ret;
+
+	switch (arch->data.type) {
+	case KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY:
+		/* Fail if channel subsystem support is not active. */
+		if (!kvm->arch.css_support)
+			ret = -EINVAL;
+		else {
+			kvm_s390_ioeventfd_add(kvm, arch);
+			ret = 0;
+		}
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(kvm_arch_ioeventfd_activate);
+
+bool kvm_arch_ioeventfd_match(struct kvm_arch_ioeventfd *arch,
+			      struct kvm_arch_ioeventfd *to_match)
+{
+	if (arch->data.type != to_match->data.type)
+		return false;
+
+	switch (arch->data.type) {
+	case KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY:
+		if (memcmp(&arch->data.virtio_ccw_vq.schid,
+			   &to_match->data.virtio_ccw_vq.schid,
+			   sizeof(arch->data.virtio_ccw_vq.schid)))
+			return false;
+		if (arch->data.virtio_ccw_vq.vq !=
+		    to_match->data.virtio_ccw_vq.vq)
+			return false;
+		return true;
+	default:
+		return false;
+	}
+}
+EXPORT_SYMBOL_GPL(kvm_arch_ioeventfd_match);
+
+bool kvm_arch_ioeventfd_match_and_release(struct kvm *kvm,
+					  struct kvm_arch_ioeventfd *arch,
+					  struct kvm_ioeventfd *args)
+{
+	struct kvm_s390_ioeventfd_data *data;
+
+	data = (struct kvm_s390_ioeventfd_data *)args->data;
+	if (arch->data.type != data->type)
+		return false;
+
+	switch (arch->data.type) {
+	case KVM_S390_IOEVENTFD_VIRTIO_CCW_NOTIFY:
+		if (memcmp(&arch->data.virtio_ccw_vq.schid,
+			   &data->virtio_ccw_vq.schid,
+			   sizeof(arch->data.virtio_ccw_vq.schid)))
+			return false;
+		if (arch->data.virtio_ccw_vq.vq != data->virtio_ccw_vq.vq)
+			return false;
+		kvm_s390_ioeventfd_remove(kvm, arch);
+		return true;
+	default:
+		return false;
+	}
+}
+EXPORT_SYMBOL_GPL(kvm_arch_ioeventfd_match_and_release);
diff --git a/arch/s390/kvm/kvm-s390.h b/arch/s390/kvm/kvm-s390.h
index 4d89d64..9794906 100644
--- a/arch/s390/kvm/kvm-s390.h
+++ b/arch/s390/kvm/kvm-s390.h
@@ -136,4 +136,7 @@ int kvm_s390_vcpu_store_status(struct kvm_vcpu *vcpu,
 /* implemented in diag.c */
 int kvm_s390_handle_diag(struct kvm_vcpu *vcpu);
 
+int kvm_s390_ioeventfd_signal(struct kvm *kvm,
+			      struct kvm_s390_ioeventfd_data *data);
+
 #endif
