Patchwork [7/7] s390/kvm: In-kernel channel subsystem support.

login
register
mail settings
Submitter Cornelia Huck
Date Aug. 7, 2012, 2:52 p.m.
Message ID <1344351139-1793-8-git-send-email-cornelia.huck@de.ibm.com>
Download mbox | patch
Permalink /patch/175663/
State New
Headers show

Comments

Cornelia Huck - Aug. 7, 2012, 2:52 p.m.
Handle most support for channel I/O instructions in the kernel itself.

Only asynchronous functions (such as the start function) need to be
handled by userspace.

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
---
 Documentation/virtual/kvm/api.txt | 127 +++++
 arch/s390/include/asm/kvm_host.h  |  48 ++
 arch/s390/kvm/Makefile            |   2 +-
 arch/s390/kvm/css.c               | 945 ++++++++++++++++++++++++++++++++++++++
 arch/s390/kvm/intercept.c         |   1 +
 arch/s390/kvm/interrupt.c         | 147 ++++--
 arch/s390/kvm/ioinst.c            | 797 ++++++++++++++++++++++++++++++++
 arch/s390/kvm/kvm-s390.c          |  35 ++
 arch/s390/kvm/kvm-s390.h          |  38 ++
 arch/s390/kvm/priv.c              |   7 +-
 arch/s390/kvm/trace-s390.h        |  67 +++
 arch/s390/kvm/trace.h             |  22 +
 include/linux/kvm.h               |  53 +++
 include/trace/events/kvm.h        |   2 +-
 virt/kvm/kvm_main.c               |   3 +-
 15 files changed, 2247 insertions(+), 47 deletions(-)
 create mode 100644 arch/s390/kvm/css.c
 create mode 100644 arch/s390/kvm/ioinst.c

Patch

diff --git a/Documentation/virtual/kvm/api.txt b/Documentation/virtual/kvm/api.txt
index 140e7e2..678b1d9 100644
--- a/Documentation/virtual/kvm/api.txt
+++ b/Documentation/virtual/kvm/api.txt
@@ -1980,6 +1980,101 @@  return the hash table order in the parameter.  (If the guest is using
 the virtualized real-mode area (VRMA) facility, the kernel will
 re-create the VMRA HPTEs on the next KVM_RUN of any vcpu.)
 
+4.77 KVM_S390_CSS_NOTIFY
+
+Capability: KVM_CAP_S390_CSS_SUPPORT
+Architectures: s390
+Type: vcpu ioctl
+Parameters: struct kvm_css_notify (in)
+Returns: 0 on success, negative value on failure
+
+This ioctl may be used by userspace to notify the kernel that the control
+blocks for a virtual subchannel should be updated and an I/O interrupt
+injected.
+
+It uses the following parameter block:
+
+/* for KVM_S390_CSS_NOTIFY */
+struct kvm_css_notify {
+	__u8 cssid;
+	__u8 ssid;
+	__u16 schid;
+	__u32 scsw[3];
+	__u32 pmcw[7];
+	__u8 sense_data[32];
+	__u8 unsolicited;
+	__u8 func;
+};
+
+cssid, ssid and schid specify the subchannel; scsw, pmcw and sense_data
+are the control blocks to be updated. If the notification is specified
+to be unsolicited, no new interrupt is generated if an interrupt is already
+pending for the subchannel; else an unsolicited interrupt is generated.
+
+The func parameter specifies the asynchronous function that is notified
+for (solicited interrupts only).
+
+This ioctl (like the other interrupt injection ioctls) is executed
+asynchronously to normal vcpu execution.
+
+4.78 KVM_S390_CCW_HOTPLUG
+
+Capability: KVM_CAP_S390_CSS_SUPPORT
+Architectures: s390
+Type: vm ioctl
+Parameters: struct kvm_s390_sch_info (in)
+Returns: 0 on success, negative value on failure
+
+This ioctl allows userspace to notify the kernel about addition or removal
+of subchannels.
+
+It uses the following data structure:
+
+/* for KVM_S390_CCW_HOTPLUG */
+struct kvm_s390_sch_info {
+	__u8 cssid;
+	__u8 ssid;
+	__u16 schid;
+	__u16 devno;
+	__u32 schib[12];
+	int hotplugged;
+	int add;
+	int virtual;
+};
+
+cssid, ssid, schid and devno describe the subchannel. If the subchannel is
+being added, schib contains the initial subchannel information block for it.
+hotplugged (can only be 0 if add is !0) specifies whether the subchannel has
+been dynamically added or removed (as opposed to the initial machine setup,
+when no channel report words will be created). add specifies whether the
+subchannel is coming or going. virtual signifies whether this is a real or
+a purely virtual subchannel.
+
+4.79 KVM_S390_CHP_HOTPLUG
+
+Capability: KVM_CAP_S390_CSS_SUPPORT
+Architectures: s390
+Type: vm ioctl
+Parameters: struct kvm_s390_chp_info (in)
+Returns: 0 on success, negative value on failure
+
+This ioctl allows userspace to notify the kernel about addition or removal
+of a channel path.
+
+It uses the following structure:
+
+/* for KVM_S390_CHP_HOTPLUG */
+struct kvm_s390_chp_info {
+	__u8 cssid;
+	__u8 chpid;
+	__u8 type;
+	int add;
+	int virtual;
+};
+
+cssid and chpid specify the channel path, type the channel path type. add
+determines whether the path is coming or going, and virtual signifies
+whether this is a purely virtual or a real channel path.
 
 5. The kvm_run structure
 ------------------------
@@ -2195,6 +2290,24 @@  The possible hypercalls are defined in the Power Architecture Platform
 Requirements (PAPR) document available from www.power.org (free
 developer registration required to access it).
 
+		/* KVM_EXIT_S390_SCH_IO */
+		struct {
+			__u32 sch_id;
+#define SCH_DO_CSCH 0
+#define SCH_DO_HSCH 1
+#define SCH_DO_SSCH 2
+#define SCH_DO_RSCH 3
+#define SCH_DO_XSCH 4
+			__u8 func;
+			__u8 pad;
+			__u64 orb;
+			__u32 scsw[3];
+			__u32 pmcw[7];
+		} s390_sch_io;
+
+s390 specific. Used for userspace processing of asynchronous subchannel
+functions.
+
 		/* Fix the size of the union. */
 		char padding[256];
 	};
@@ -2316,3 +2429,17 @@  For mmu types KVM_MMU_FSL_BOOKE_NOHV and KVM_MMU_FSL_BOOKE_HV:
    where "num_sets" is the tlb_sizes[] value divided by the tlb_ways[] value.
  - The tsize field of mas1 shall be set to 4K on TLB0, even though the
    hardware ignores this value for TLB0.
+
+6.4 KVM_CAP_S390_CSS_SUPPORT
+
+Architectures: s390
+Parameters: none
+Returns: 0 on success; -1 on error
+
+This capability enables in-kernel support for handling of channel I/O
+instructions like STORE SUBCHANNEL or CHANNEL SUBSYSTEM CALL.
+
+When this capability is enabled, KVM_EXIT_S390_SCH_IO can occur.
+
+When this capability is provided, the KVM_S390_CCW_HOTPLUG,
+KVM_S390_CHP_HOTPLUG and KVM_S390_CSS_NOTIFY ioctls are provided.
diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h
index 556774d..03e154b 100644
--- a/arch/s390/include/asm/kvm_host.h
+++ b/arch/s390/include/asm/kvm_host.h
@@ -17,13 +17,18 @@ 
 #include <linux/interrupt.h>
 #include <linux/kvm_host.h>
 #include <asm/debug.h>
+#include <asm/cio.h>
 #include <asm/cpu.h>
+#include <asm/crw.h>
 
 #define KVM_MAX_VCPUS 64
 #define KVM_MEMORY_SLOTS 32
 /* memory slots that does not exposed to userspace */
 #define KVM_PRIVATE_MEM_SLOTS 4
 
+#define VIRTUAL_CSSID 0xfe
+#define KVM_MAX_CSSID 0xfe /* 0xff is reserved */
+
 struct sca_entry {
 	atomic_t scn;
 	__u32	reserved;
@@ -174,6 +179,7 @@  struct kvm_s390_ext_info {
 #define PGM_ADDRESSING           0x05
 #define PGM_SPECIFICATION        0x06
 #define PGM_DATA                 0x07
+#define PGM_OPERAND              0x15
 
 struct kvm_s390_pgm_info {
 	__u16 code;
@@ -208,6 +214,7 @@  struct kvm_s390_interrupt_info {
 		struct kvm_s390_prefix_info prefix;
 		struct kvm_s390_mchk_info mchk;
 	};
+	int nondyn;
 };
 
 /* for local_interrupt.action_flags */
@@ -259,11 +266,52 @@  struct kvm_vm_stat {
 struct kvm_arch_memory_slot {
 };
 
+struct crw_container {
+	struct crw crw;
+	struct list_head sibling;
+};
+
+struct chp_info {
+	u8 in_use;
+	u8 type;
+};
+
+struct kvm_subch {
+	struct mutex lock;
+	u8 cssid;
+	u8 ssid;
+	u16 schid;
+	u16 devno;
+	u8 sense_data[32];
+	struct schib *curr_status;
+	struct kvm_s390_interrupt_info inti;
+};
+
+struct schid_info {
+	struct kvm_subch *schs[__MAX_SUBCHANNEL + 1];
+	unsigned long bm[0];
+};
+
+struct kvm_s390_css_data {
+	int max_cssid;
+	int max_ssid;
+	struct list_head pending_crws;
+	struct kvm_s390_interrupt_info crw_inti;
+	int do_crw_mchk;
+	int crws_lost;
+	struct schid_info *schids[KVM_MAX_CSSID + 1][__MAX_SSID + 1];
+	struct chp_info chpids[KVM_MAX_CSSID + 1][__MAX_CHPID + 1];
+	atomic_t chnmon_active;
+	u64 chnmon_area;
+};
+
 struct kvm_arch{
 	struct sca_block *sca;
 	debug_info_t *dbf;
 	struct kvm_s390_float_interrupt float_int;
 	struct gmap *gmap;
+	int css_support;
+	struct kvm_s390_css_data *css;
 };
 
 extern int sie64a(struct kvm_s390_sie_block *, u64 *);
diff --git a/arch/s390/kvm/Makefile b/arch/s390/kvm/Makefile
index 3975722..afcf71e 100644
--- a/arch/s390/kvm/Makefile
+++ b/arch/s390/kvm/Makefile
@@ -10,5 +10,5 @@  common-objs = $(addprefix ../../../virt/kvm/, kvm_main.o)
 
 ccflags-y := -Ivirt/kvm -Iarch/s390/kvm
 
-kvm-objs := $(common-objs) kvm-s390.o intercept.o interrupt.o priv.o sigp.o diag.o
+kvm-objs := $(common-objs) kvm-s390.o intercept.o interrupt.o priv.o sigp.o diag.o ioinst.o css.o
 obj-$(CONFIG_KVM) += kvm.o
diff --git a/arch/s390/kvm/css.c b/arch/s390/kvm/css.c
new file mode 100644
index 0000000..36d26ce
--- /dev/null
+++ b/arch/s390/kvm/css.c
@@ -0,0 +1,945 @@ 
+/*
+ * Virtual channel subsystem support for kvm
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (version 2 only)
+ * as published by the Free Software Foundation.
+ *
+ *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ */
+
+#include <linux/kvm.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include <asm/cio.h>
+#include <asm/crw.h>
+#include <asm/schib.h>
+#include <asm/schid.h>
+#include <asm/scsw.h>
+#include "gaccess.h"
+#include "kvm-s390.h"
+#include "trace-s390.h"
+
+static void css_update_chnmon(struct kvm_vcpu *vcpu, struct kvm_subch *sch)
+{
+	if (!sch->curr_status->pmcw.mme)
+		/* Not active. */
+		return;
+
+	/*
+	 * The only field we want to update (ssch_rsch_count) is conveniently
+	 * located at the beginning of the measurement block.
+	 * For format 0, it is a 16 bit value; for format 1, a 32 bit value.
+	 */
+	if (sch->curr_status->pmcw.mbfc) {
+		/* Format 1, per-subchannel area. */
+		u32 uninitialized_var(count);
+
+		if (get_guest_u32(vcpu, sch->curr_status->mba, &count))
+			return;
+		count++;
+		put_guest_u32(vcpu, sch->curr_status->mba, count);
+	} else {
+		/* Format 0, global area. */
+		u64 target;
+		u16 uninitialized_var(count);
+
+		target = vcpu->kvm->arch.css->chnmon_area +
+			(sch->curr_status->pmcw.mbi << 5);
+		if (get_guest_u16(vcpu, target, &count))
+			return;
+		count++;
+		put_guest_u16(vcpu, target, count);
+	}
+}
+
+static int highest_schid(struct kvm *kvm, u8 cssid, u8 ssid)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+
+	if (!css->schids[cssid][ssid])
+		return 0;
+	return find_last_bit(css->schids[cssid][ssid]->bm,
+			     (__MAX_SUBCHANNEL + 1) / sizeof(unsigned long));
+}
+
+int css_schid_final(struct kvm *kvm, u8 cssid, u8 ssid, u16 schid)
+{
+	return (cssid > KVM_MAX_CSSID ||
+		ssid > __MAX_SSID ||
+		schid > highest_schid(kvm, cssid, ssid)) ? 1 : 0;
+}
+
+static int css_add_virtual_chpid(struct kvm *kvm, u8 cssid, u8 chpid, u8 type)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+
+	if (cssid > KVM_MAX_CSSID)
+		return -EINVAL;
+
+	if (css->chpids[cssid][chpid].in_use)
+		return -EEXIST;
+
+	css->chpids[cssid][chpid].in_use = 1;
+	css->chpids[cssid][chpid].type = type;
+	return 0;
+}
+
+static int css_remove_virtual_chpid(struct kvm *kvm, u8 cssid, u8 chpid)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+
+	if (cssid > KVM_MAX_CSSID)
+		return -EINVAL;
+
+	if (!css->chpids[cssid][chpid].in_use)
+		return -EINVAL;
+
+	css->chpids[cssid][chpid].in_use = 0;
+	return 0;
+}
+
+int css_chpid_in_use(struct kvm *kvm, u8 cssid, u8 chpid)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+
+	if ((cssid > KVM_MAX_CSSID) || (chpid > __MAX_CHPID))
+		return 0;
+	return css->chpids[cssid][chpid].in_use;
+}
+
+static int css_chpid_type(struct kvm *kvm, u8 cssid, u8 chpid)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+
+	if ((cssid > KVM_MAX_CSSID) || (chpid > __MAX_CHPID))
+		return 0;
+	return css->chpids[cssid][chpid].type;
+}
+
+int css_collect_chp_desc(struct kvm *kvm, u8 cssid, u8 f_chpid, u8 l_chpid,
+			 int rfmt, void *buf)
+{
+	int i, desc_size;
+	u32 words[8];
+
+	desc_size = 0;
+	for (i = f_chpid; i <= l_chpid; i++) {
+		if (!css_chpid_in_use(kvm, cssid, i))
+			continue;
+		if (rfmt == 0) {
+			words[0] = 0x80000000 |
+				(css_chpid_type(kvm, cssid, i) << 8) | i;
+			words[1] = 0;
+			memcpy(buf + desc_size, words, 8);
+			desc_size += 8;
+		} else if (rfmt == 1) {
+			words[0] = 0x80000000 |
+				(css_chpid_type(kvm, cssid, i) << 8) | i;
+			words[1] = 0;
+			words[2] = 0;
+			words[3] = 0;
+			words[4] = 0;
+			words[5] = 0;
+			words[6] = 0;
+			words[7] = 0;
+			memcpy(buf + desc_size, words, 32);
+			desc_size += 32;
+		}
+	}
+	return desc_size;
+}
+
+struct kvm_subch *css_find_subch(struct kvm *kvm, u8 m, u8 cssid, u8 ssid,
+				 u16 schid)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	u8 real_cssid;
+
+	if (!m) {
+		if (cssid)
+			return NULL;
+		real_cssid = VIRTUAL_CSSID;
+	} else
+		real_cssid = cssid;
+	/* Don't bother for out of range values. */
+	if (css_schid_final(kvm, real_cssid, ssid, schid))
+		return NULL;
+	if (!css->schids[real_cssid][ssid])
+		return NULL;
+	if (!test_bit(schid, css->schids[real_cssid][ssid]->bm))
+		return NULL;
+	return css->schids[real_cssid][ssid]->schs[schid];
+}
+
+void css_queue_crw(struct kvm *kvm, u8 rsc, u8 erc, int chain, u16 rsid)
+{
+	struct crw_container *crw_cont;
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	int ret;
+
+	/* TODO: Maybe use a static crw pool? */
+	crw_cont = kzalloc(sizeof(struct crw_container), GFP_KERNEL);
+
+	mutex_lock(&kvm->lock);
+
+	if (!crw_cont) {
+		css->crws_lost = 1;
+		goto out;
+	}
+	crw_cont->crw.rsc = rsc;
+	crw_cont->crw.erc = erc;
+	crw_cont->crw.chn = chain;
+	crw_cont->crw.rsid = rsid;
+	crw_cont->crw.oflw = css->crws_lost;
+	css->crws_lost = 0;
+
+	list_add_tail(&crw_cont->sibling, &css->pending_crws);
+
+	if (css->do_crw_mchk) {
+		css->do_crw_mchk = 0;
+		ret = kvm_s390_inject_internal(kvm, &css->crw_inti);
+		if (ret)
+			css->do_crw_mchk = 1;
+	}
+out:
+	mutex_unlock(&kvm->lock);
+}
+
+int css_do_stcrw(struct kvm_vcpu *vcpu, u32 cda)
+{
+	struct crw_container *crw_cont;
+	struct kvm_s390_css_data *css = vcpu->kvm->arch.css;
+	int ret;
+
+	mutex_lock(&vcpu->kvm->lock);
+	if (list_empty(&css->pending_crws)) {
+		u32 zeroes = 0;
+		/* List was empty, turn crw machine checks on again. */
+		if (copy_to_guest(vcpu, cda, &zeroes, sizeof(struct crw))) {
+			kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+			ret = -EIO;
+			goto out;
+		}
+		css->do_crw_mchk = 1;
+		ret = 1;
+		goto out;
+	}
+
+	crw_cont = container_of(css->pending_crws.next, struct crw_container,
+				sibling);
+	if (copy_to_guest(vcpu, cda, &crw_cont->crw, sizeof(struct crw))) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		ret = -EIO;
+		goto out;
+	}
+	list_del(&crw_cont->sibling);
+	kfree(crw_cont);
+	ret = 0;
+out:
+	mutex_unlock(&vcpu->kvm->lock);
+	return ret;
+}
+
+void css_do_schm(struct kvm_vcpu *vcpu, u8 mbk, int update, int dct, u64 mbo)
+{
+	struct kvm_s390_css_data *css = vcpu->kvm->arch.css;
+
+	/* dct is currently ignored (not really meaningful for our devices) */
+	/* TODO: Don't ignore mbk. */
+	if (update && !atomic_cmpxchg(&css->chnmon_active, 0, 1))
+		/* Enable measuring. */
+		css->chnmon_area = mbo;
+
+	if (!update && !atomic_cmpxchg(&css->chnmon_active, 1, 0))
+		/* Disable measuring. */
+		css->chnmon_area = 0;
+}
+
+int css_enable_mcsse(struct kvm *kvm)
+{
+	kvm->arch.css->max_cssid = KVM_MAX_CSSID;
+	return 0;
+}
+
+int css_enable_mss(struct kvm *kvm)
+{
+	kvm->arch.css->max_ssid = __MAX_SSID;
+	return 0;
+}
+
+int css_do_tpi(struct kvm_vcpu *vcpu, u32 addr, int lowcore)
+{
+	struct kvm_s390_interrupt_info *inti;
+
+	inti = kvm_s390_get_io_int(vcpu->kvm, vcpu->run->s.regs.crs[6]);
+	if (inti) {
+		if (!lowcore) {
+			put_guest_u16(vcpu, addr, inti->io.subchannel_id);
+			put_guest_u16(vcpu, addr + 2, inti->io.subchannel_nr);
+			put_guest_u32(vcpu, addr + 4, inti->io.io_int_parm);
+		} else {
+			put_guest_u16(vcpu, addr + 184, inti->io.subchannel_id);
+			put_guest_u16(vcpu, addr + 186, inti->io.subchannel_nr);
+			put_guest_u32(vcpu, addr + 188, inti->io.io_int_parm);
+			put_guest_u32(vcpu, addr + 192, inti->io.io_int_word);
+		}
+		return 1;
+	}
+	return 0;
+}
+
+int css_do_msch(struct kvm_vcpu *vcpu, struct kvm_subch *sch,
+		struct schib *schib)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!sch->curr_status->pmcw.dnv) {
+		ret = 0;
+		goto out;
+	}
+
+	if (scsw_stctl(s) & SCSW_STCTL_STATUS_PEND) {
+		ret = -EINPROGRESS;
+		goto out;
+	}
+
+	if (scsw_fctl(s) & (SCSW_FCTL_START_FUNC | SCSW_FCTL_HALT_FUNC |
+			    SCSW_FCTL_CLEAR_FUNC)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Only update the program-modifiable fields. */
+	p->ena = schib->pmcw.ena;
+	p->intparm = schib->pmcw.intparm;
+	p->isc = schib->pmcw.isc;
+	p->mp = schib->pmcw.mp;
+	p->lpm = schib->pmcw.lpm;
+	p->pom = schib->pmcw.pom;
+	p->lm = schib->pmcw.lm;
+	p->csense = schib->pmcw.csense;
+
+	p->mme = schib->pmcw.mme;
+	p->mbi = schib->pmcw.mbi;
+	p->mbfc = schib->pmcw.mbfc;
+	sch->curr_status->mba = schib->mba;
+
+	/*
+	 * No need to exit to userspace since it will get the current state
+	 * with the next exit.
+	 */
+	ret = 0;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_xsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (!scsw_fctl(s) || (scsw_fctl(s) != SCSW_FCTL_START_FUNC) ||
+	    (!(scsw_actl(s) & (SCSW_ACTL_RESUME_PEND | SCSW_ACTL_START_PEND |
+			       SCSW_ACTL_SUSPENDED))) ||
+	    (scsw_actl(s) & SCSW_ACTL_SCHACT)) {
+		ret = -EINPROGRESS;
+		goto out;
+	}
+
+	if (scsw_stctl(s) != 0) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Cancel the current operation. */
+	s->cmd.fctl &= ~SCSW_FCTL_START_FUNC;
+	s->cmd.actl &= ~(SCSW_ACTL_RESUME_PEND | SCSW_ACTL_START_PEND |
+			 SCSW_ACTL_SUSPENDED);
+	s->cmd.dstat = 0;
+	s->cmd.cstat = 0;
+	/*
+	 * Let userspace update its state.
+	 * No hardware related structures need to be updated, since userspace
+	 * will get the current state with the next exit.
+	 */
+	vcpu->run->exit_reason = KVM_EXIT_S390_SCH_IO;
+	vcpu->run->s390_sch_io.func = SCH_DO_XSCH;
+	vcpu->run->s390_sch_io.sch_id = (sch->cssid << 24) | (1 << 19) |
+		(sch->ssid << 17) | 1 << 16 | sch->schid;
+	ret = -EREMOTE;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_csch(struct kvm_vcpu *vcpu, struct kvm_subch *sch)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/* Trigger the clear function. */
+	s->cmd.fctl = SCSW_FCTL_CLEAR_FUNC;
+	s->cmd.actl = SCSW_ACTL_CLEAR_PEND;
+
+	/* Let userspace handle the clear function. */
+	vcpu->run->exit_reason = KVM_EXIT_S390_SCH_IO;
+	vcpu->run->s390_sch_io.func = SCH_DO_CSCH;
+	vcpu->run->s390_sch_io.sch_id = (sch->cssid << 24) | (1 << 19) |
+		(sch->ssid << 17) | 1 << 16 | sch->schid;
+	memcpy(&vcpu->run->s390_sch_io.scsw, s, sizeof(*s));
+	memcpy(&vcpu->run->s390_sch_io.pmcw, p, sizeof(*p));
+	ret = -EREMOTE;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_hsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if ((scsw_stctl(s) == SCSW_STCTL_STATUS_PEND) ||
+	    (scsw_stctl(s) & (SCSW_STCTL_PRIM_STATUS |
+			      SCSW_STCTL_SEC_STATUS |
+			      SCSW_STCTL_ALERT_STATUS))) {
+		ret = -EINPROGRESS;
+		goto out;
+	}
+
+	if (scsw_fctl(s) & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* Trigger the halt function. */
+	s->cmd.fctl |= SCSW_FCTL_HALT_FUNC;
+	s->cmd.fctl &= ~SCSW_FCTL_START_FUNC;
+	if ((scsw_actl(s) == (SCSW_ACTL_SCHACT | SCSW_ACTL_DEVACT)) &&
+	    (scsw_stctl(s) == SCSW_STCTL_INTER_STATUS)) {
+		s->cmd.stctl &= ~SCSW_STCTL_STATUS_PEND;
+	}
+	s->cmd.actl |= SCSW_ACTL_HALT_PEND;
+
+	/* Let userspace handle the halt function. */
+	vcpu->run->exit_reason = KVM_EXIT_S390_SCH_IO;
+	vcpu->run->s390_sch_io.func = SCH_DO_HSCH;
+	vcpu->run->s390_sch_io.sch_id = (sch->cssid << 24) | (1 << 19) |
+		(sch->ssid << 17) | 1 << 16 | sch->schid;
+	memcpy(&vcpu->run->s390_sch_io.scsw, s, sizeof(*s));
+	memcpy(&vcpu->run->s390_sch_io.pmcw, p, sizeof(*p));
+	ret = -EREMOTE;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_ssch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, u64 orb)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (scsw_stctl(s) & SCSW_STCTL_STATUS_PEND) {
+		ret = -EINPROGRESS;
+		goto out;
+	}
+
+	if (scsw_fctl(s) & (SCSW_FCTL_START_FUNC |
+			    SCSW_FCTL_HALT_FUNC |
+			    SCSW_FCTL_CLEAR_FUNC)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	/* If monitoring is active, update counter. */
+	if (atomic_read(&vcpu->kvm->arch.css->chnmon_active))
+		css_update_chnmon(vcpu, sch);
+
+	/* Trigger the start function. */
+	s->cmd.fctl |= SCSW_FCTL_START_FUNC;
+	s->cmd.actl |= SCSW_ACTL_START_PEND;
+	s->cmd.pno = 0;
+
+	/* Let userspace handle the start function. */
+	vcpu->run->exit_reason = KVM_EXIT_S390_SCH_IO;
+	vcpu->run->s390_sch_io.func = SCH_DO_SSCH;
+	vcpu->run->s390_sch_io.sch_id = (sch->cssid << 24) | (1 << 19) |
+		(sch->ssid << 17) | 1 << 16 | sch->schid;
+	memcpy(&vcpu->run->s390_sch_io.scsw, s, sizeof(*s));
+	memcpy(&vcpu->run->s390_sch_io.pmcw, p, sizeof(*p));
+	vcpu->run->s390_sch_io.orb = orb;
+	ret = -EREMOTE;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_tsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, uint32_t addr)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	u8 stctl;
+	u8 fctl;
+	u8 actl;
+	struct irb irb;
+	int ret;
+	u32 *esw;
+
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	stctl = scsw_stctl(s);
+	fctl = scsw_fctl(s);
+	actl = scsw_actl(s);
+
+	memset(&irb, 0, sizeof(struct irb));
+
+	/* Copy scsw. */
+	memcpy(&irb.scsw, s, sizeof(union scsw));
+	esw = (u32 *)&irb.esw;
+	if (stctl & SCSW_STCTL_STATUS_PEND) {
+		if (scsw_cstat(s) & (SCHN_STAT_CHN_DATA_CHK |
+				     SCHN_STAT_CHN_CTRL_CHK |
+				     SCHN_STAT_INTF_CTRL_CHK)) {
+			irb.scsw.cmd.eswf = 1;
+			esw[0] = 0x04804000;
+		} else
+			esw[0] = 0x00800000;
+
+		/* If a unit check is pending, copy sense data. */
+		if ((scsw_dstat(s) & DEV_STAT_UNIT_CHECK) && p->csense) {
+			irb.scsw.cmd.eswf = 1;
+			irb.scsw.cmd.ectl = 1;
+			memcpy(irb.ecw, sch->sense_data,
+			       sizeof(sch->sense_data));
+			esw[1] = 0x02000000 | (sizeof(sch->sense_data) << 8);
+		}
+	}
+	if (copy_to_guest(vcpu, addr, &irb, sizeof(struct irb))) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		ret = -EIO;
+		goto out;
+	}
+
+	/* Clear conditions on subchannel, if applicable. */
+	if (stctl & SCSW_STCTL_STATUS_PEND) {
+		s->cmd.stctl = 0;
+		if ((stctl != (SCSW_STCTL_INTER_STATUS |
+			       SCSW_STCTL_STATUS_PEND)) ||
+		    ((fctl & SCSW_FCTL_HALT_FUNC) &&
+		     (actl & SCSW_ACTL_SUSPENDED)))
+			s->cmd.fctl = 0;
+
+		if (stctl != (SCSW_STCTL_INTER_STATUS |
+			      SCSW_STCTL_STATUS_PEND)) {
+			s->cmd.pno = 0;
+			s->cmd.actl &= ~(SCSW_ACTL_RESUME_PEND |
+					 SCSW_ACTL_START_PEND |
+					 SCSW_ACTL_HALT_PEND |
+					 SCSW_ACTL_CLEAR_PEND |
+					 SCSW_ACTL_SUSPENDED);
+		} else {
+			if ((actl & SCSW_ACTL_SUSPENDED) &&
+			    (fctl & SCSW_FCTL_START_FUNC)) {
+				s->cmd.pno = 0;
+				if (fctl & SCSW_FCTL_HALT_FUNC)
+					s->cmd.actl &= ~(SCSW_ACTL_RESUME_PEND |
+							 SCSW_ACTL_START_PEND |
+							 SCSW_ACTL_HALT_PEND |
+							 SCSW_ACTL_CLEAR_PEND |
+							 SCSW_ACTL_SUSPENDED);
+				else
+					s->cmd.actl &= ~SCSW_ACTL_RESUME_PEND;
+			}
+			/* Clear a possible pending I/O interrupt. */
+			if (!list_empty(&sch->inti.list))
+				kvm_s390_dequeue_internal(vcpu->kvm, &sch->inti);
+		}
+		/* Clear pending sense data. */
+		if (p->csense)
+			memset(sch->sense_data, 0 , sizeof(sch->sense_data));
+	}
+
+	/*
+	 * No need to exit to userspace since it will get the current state
+	 * with the next exit.
+	 */
+	ret = (stctl & SCSW_STCTL_STATUS_PEND) ? -EBUSY : 0;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int css_do_rsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch)
+{
+	union scsw *s = &sch->curr_status->scsw;
+	struct pmcw *p = &sch->curr_status->pmcw;
+	int ret;
+
+	mutex_lock(&sch->lock);
+
+	if (!p->dnv || !p->ena) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (scsw_stctl(s) & SCSW_STCTL_STATUS_PEND) {
+		ret = -EINPROGRESS;
+		goto out;
+	}
+
+	if ((scsw_fctl(s) != SCSW_FCTL_START_FUNC) ||
+	    (scsw_actl(s) & SCSW_ACTL_RESUME_PEND) ||
+	    (!(scsw_actl(s) & SCSW_ACTL_SUSPENDED))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* If monitoring is active, update counter. */
+	if (atomic_read(&vcpu->kvm->arch.css->chnmon_active))
+		css_update_chnmon(vcpu, sch);
+
+	s->cmd.actl |= SCSW_ACTL_RESUME_PEND;
+	/* Let userspace handle the start function. */
+	vcpu->run->exit_reason = KVM_EXIT_S390_SCH_IO;
+	vcpu->run->s390_sch_io.func = SCH_DO_RSCH;
+	vcpu->run->s390_sch_io.sch_id = (sch->cssid << 24) | (1 << 19) |
+		(sch->ssid << 17) | 1 << 16 | sch->schid;
+	memcpy(&vcpu->run->s390_sch_io.scsw, s, sizeof(*s));
+	memcpy(&vcpu->run->s390_sch_io.pmcw, p, sizeof(*p));
+	ret = -EREMOTE;
+
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+int kvm_arch_vcpu_ioctl_css_notify(struct kvm_vcpu *vcpu,
+				   struct kvm_css_notify *notify)
+{
+	struct kvm_subch *sch;
+	int ret;
+
+	trace_kvm_s390_css_notify(notify->cssid, notify->ssid, notify->schid);
+	/* Userspace always gives us the real cssid. */
+	sch = css_find_subch(vcpu->kvm, 1, notify->cssid, notify->ssid,
+			     notify->schid);
+	if (!sch)
+		return -ENODEV;
+	mutex_lock(&sch->lock);
+	if (notify->unsolicited) {
+		/*
+		 * Userspace wants us to inject an unsolicited interrupt
+		 * iff the subchannel is not status pending.
+		 */
+		if (scsw_stctl(&sch->curr_status->scsw) &
+		    SCSW_STCTL_STATUS_PEND) {
+			ret = 0;
+			goto out;
+		}
+		sch->curr_status->scsw.cmd.stctl =
+			SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND;
+	} else {
+		/*
+		 * First, check whether any I/O instructions have been
+		 * issued in the mean time which would preclude normal
+		 * signalling as requested by the control block. This
+		 * might happen e.g. if the kernel accepted a csch while
+		 * the start function was in progress in user space.
+		 */
+		if (((notify->func == SCH_DO_SSCH) ||
+		     (notify->func == SCH_DO_RSCH)) &&
+		    (scsw_fctl(&sch->curr_status->scsw) != SCSW_FCTL_START_FUNC)) {
+			/*
+			 * xsch, hsch, or csch happened.
+			 * For the xsch case, no interrupt will be generated.
+			 * For the hsch/csch case, another notification will
+			 * happen.
+			 */
+			ret = 0;
+			goto out;
+		}
+		if ((notify->func == SCH_DO_HSCH) &&
+		    (scsw_fctl(&sch->curr_status->scsw) & SCSW_FCTL_CLEAR_FUNC)) {
+			/*
+			 * csch happened, and another notification will come
+			 * in later.
+			 */
+			ret = 0;
+			goto out;
+		}
+		/* Update internal status. */
+		memcpy(&sch->curr_status->scsw, &notify->scsw,
+		       sizeof(notify->scsw));
+		memcpy(&sch->curr_status->pmcw, &notify->pmcw,
+		       sizeof(notify->pmcw));
+		memcpy(sch->sense_data, notify->sense_data,
+		       sizeof(notify->sense_data));
+	}
+
+	/* Inject interrupt. */
+	sch->inti.type = (sch->cssid << 24) | (sch->ssid << 22) |
+		(sch->schid << 16);
+	sch->inti.io.subchannel_id = vcpu->kvm->arch.css->max_cssid > 0 ?
+		(sch->cssid << 8) | (1 << 3) | (sch->ssid << 1) | 1 :
+		(sch->ssid << 1) | 1;
+	sch->inti.io.subchannel_nr = sch->schid;
+	sch->inti.io.io_int_parm = sch->curr_status->pmcw.intparm;
+	sch->inti.io.io_int_word = (0x80 >> sch->curr_status->pmcw.isc) << 24;
+	BUG_ON(!list_empty(&sch->inti.list));
+	mutex_lock(&vcpu->kvm->lock);
+	ret = kvm_s390_inject_internal(vcpu->kvm, &sch->inti);
+	mutex_unlock(&vcpu->kvm->lock);
+out:
+	mutex_unlock(&sch->lock);
+	return ret;
+}
+
+static int css_add_to_store(struct kvm *kvm, struct kvm_subch *sch)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	struct schid_info *info;
+	size_t schid_size;
+
+	if (!css->schids[sch->cssid][sch->ssid]) {
+		schid_size = sizeof(struct schid_info) +
+			    __BITOPS_WORDS(__MAX_SUBCHANNEL + 1) *
+			sizeof(unsigned long);
+		css->schids[sch->cssid][sch->ssid] = vmalloc(schid_size);
+		if (!css->schids[sch->cssid][sch->ssid])
+			return -ENOMEM;
+		memset(css->schids[sch->cssid][sch->ssid], 0, schid_size);
+	}
+	info = css->schids[sch->cssid][sch->ssid];
+	info->schs[sch->schid] = sch;
+	set_bit(sch->schid, info->bm);
+
+	return 0;
+}
+
+static int css_remove_from_store(struct kvm *kvm, struct kvm_subch *sch)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	struct schid_info *info;
+
+	info = css->schids[sch->cssid][sch->ssid];
+	if (!info)
+		return -EINVAL;
+	info->schs[sch->schid] = NULL;
+	clear_bit(sch->schid, info->bm);
+
+	return 0;
+}
+
+static int css_add_subchannel(struct kvm *kvm,
+			      struct kvm_s390_sch_info *sch_info)
+{
+	struct kvm_subch *sch;
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	u8 guest_cssid;
+	bool no_crw;
+
+	/* Generate subchannel structure. */
+	sch = kzalloc(sizeof(*sch), GFP_KERNEL);
+	if (!sch)
+		return -ENOMEM;
+	sch->curr_status = kzalloc(sizeof(*sch->curr_status), GFP_KERNEL);
+	if (!sch->curr_status) {
+		kfree(sch);
+		return -ENOMEM;
+	}
+	mutex_init(&sch->lock);
+	sch->cssid = sch_info->cssid;
+	sch->ssid = sch_info->ssid;
+	sch->schid = sch_info->schid;
+	sch->devno = sch_info->devno;
+	memcpy(sch->curr_status, &sch_info->schib, sizeof(*sch->curr_status));
+	INIT_LIST_HEAD(&sch->inti.list);
+	sch->inti.nondyn = 1;
+	/* Add subchannel to store. */
+	css_add_to_store(kvm, sch);
+	if (!sch_info->hotplugged)
+		goto out;
+	/*
+	 * Generate add ccw.
+	 *
+	 * Only notify for higher subchannel sets/channel subsystems if the
+	 * guest has enabled it.
+	 */
+	guest_cssid = ((css->max_cssid == 0) && (sch->cssid == VIRTUAL_CSSID)) ?
+		0 : sch->cssid;
+	no_crw = (sch->ssid > css->max_ssid) ||
+		(guest_cssid > css->max_cssid) ||
+		((css->max_cssid == 0) && (sch->cssid != VIRTUAL_CSSID));
+	if (!no_crw) {
+		css_queue_crw(kvm, CRW_RSC_SCH, CRW_ERC_IPARM,
+			      ((css->max_ssid > 0) || (css->max_cssid > 0)) ?
+			      1 : 0, sch->schid);
+		if ((css->max_ssid > 0) || (css->max_cssid > 0))
+			css_queue_crw(kvm, CRW_RSC_SCH, CRW_ERC_IPARM, 0,
+				      (guest_cssid << 8) | (sch->ssid << 4));
+	}
+out:
+	return 0;
+}
+
+static int css_remove_subchannel(struct kvm *kvm, struct kvm_subch *sch)
+{
+	struct kvm_s390_css_data *css = kvm->arch.css;
+	u8 guest_cssid;
+	bool no_crw;
+
+	/* Make subchannel inaccessible. */
+	mutex_lock(&sch->lock);
+	/* Clear a possible pending I/O interrupt. */
+	if (!list_empty(&sch->inti.list))
+		kvm_s390_dequeue_internal(kvm, &sch->inti);
+	css_remove_from_store(kvm, sch);
+	mutex_unlock(&sch->lock);
+	/*
+	 * Generate removal ccw.
+	 *
+	 * Only notify for higher subchannel sets/channel subsystems if the
+	 * guest has enabled it.
+	 */
+	guest_cssid = ((css->max_cssid == 0) && (sch->cssid == VIRTUAL_CSSID)) ?
+		0 : sch->cssid;
+	no_crw = (sch->ssid > css->max_ssid) ||
+		(guest_cssid > css->max_cssid) ||
+		((css->max_cssid == 0) && (sch->cssid != VIRTUAL_CSSID));
+	if (!no_crw) {
+		css_queue_crw(kvm, CRW_RSC_SCH, CRW_ERC_IPARM,
+			      ((css->max_ssid > 0) || (css->max_cssid > 0)) ?
+			      1 : 0, sch->schid);
+		if ((css->max_ssid > 0) || (css->max_cssid > 0))
+			css_queue_crw(kvm, CRW_RSC_SCH, CRW_ERC_IPARM, 0,
+				      (guest_cssid << 8) | (sch->ssid << 4));
+	}
+	kfree(sch);
+	return 0;
+}
+
+int kvm_s390_process_ccw_hotplug(struct kvm *kvm,
+				 struct kvm_s390_sch_info *sch_info)
+{
+	struct kvm_subch *sch;
+
+	trace_kvm_s390_ccw_hotplug(sch_info->cssid, sch_info->ssid,
+				   sch_info->schid, sch_info->add);
+	/* We currently support only virtual subchannels. */
+	if (!sch_info->virtual)
+		return -EINVAL;
+
+	/* Virtual subchannels must be in the virtual css. */
+	if (sch_info->virtual && (sch_info->cssid != VIRTUAL_CSSID))
+		return -EINVAL;
+	/* Userspace always notifies with the real cssid. */
+	sch = css_find_subch(kvm, 1, sch_info->cssid, sch_info->ssid,
+			     sch_info->schid);
+	if (sch_info->add) {
+		/* New device. */
+		if (sch)
+			return -EINVAL;
+		return css_add_subchannel(kvm, sch_info);
+	} else {
+		/* Device gone. */
+		if (!sch)
+			return -EINVAL;
+		return css_remove_subchannel(kvm, sch);
+	}
+}
+
+int kvm_s390_process_chp_hotplug(struct kvm *kvm,
+				 struct kvm_s390_chp_info *chp_info)
+{
+	if (!chp_info->virtual)
+		/* Not supported for now. */
+		return -EINVAL;
+
+	/* Virtual channel paths must be in the virtual css. */
+	if (chp_info->virtual && (chp_info->cssid != VIRTUAL_CSSID))
+		return -EINVAL;
+	if (chp_info->add)
+		return css_add_virtual_chpid(kvm, chp_info->cssid,
+					     chp_info->chpid, chp_info->type);
+	else
+		return css_remove_virtual_chpid(kvm, chp_info->cssid,
+						chp_info->chpid);
+}
+
+int kvm_s390_enable_css(struct kvm *kvm)
+{
+	if (kvm->arch.css_support)
+		return 0;
+
+	kvm->arch.css = kzalloc(sizeof(*kvm->arch.css), GFP_KERNEL);
+	if (!kvm->arch.css)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&kvm->arch.css->pending_crws);
+	INIT_LIST_HEAD(&kvm->arch.css->crw_inti.list);
+	kvm->arch.css->crw_inti.type = KVM_S390_MCHK;
+	kvm->arch.css->crw_inti.mchk.mcic = 0x00400f1d40330000;
+	kvm->arch.css->crw_inti.mchk.cr14 = 1 << 28;
+	kvm->arch.css->crw_inti.nondyn = 1;
+	kvm->arch.css->do_crw_mchk = 1;
+	atomic_set(&kvm->arch.css->chnmon_active, 0);
+	kvm->arch.css_support = 1;
+	trace_kvm_s390_enable_kernel_css(kvm);
+	return 0;
+}
diff --git a/arch/s390/kvm/intercept.c b/arch/s390/kvm/intercept.c
index 754dc9e..9ab2efd 100644
--- a/arch/s390/kvm/intercept.c
+++ b/arch/s390/kvm/intercept.c
@@ -273,6 +273,7 @@  static const intercept_handler_t intercept_funcs[] = {
 	[0x0C >> 2] = handle_instruction_and_prog,
 	[0x10 >> 2] = handle_noop,
 	[0x14 >> 2] = handle_noop,
+	[0x18 >> 2] = handle_noop,
 	[0x1C >> 2] = kvm_s390_handle_wait,
 	[0x20 >> 2] = handle_validity,
 	[0x28 >> 2] = handle_stop,
diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c
index edc065f..072828b 100644
--- a/arch/s390/kvm/interrupt.c
+++ b/arch/s390/kvm/interrupt.c
@@ -370,6 +370,7 @@  static void __do_deliver_interrupt(struct kvm_vcpu *vcpu,
 		
 		rc = copy_from_guest(vcpu, &vcpu->arch.sie_block->gpsw,
 				     __LC_MCK_NEW_PSW, sizeof(psw_t));
+
 		if (rc == -EFAULT)
 			exception = 1;
 		break;
@@ -596,7 +597,7 @@  void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 			spin_lock_bh(&li->lock);
 			list_for_each_entry_safe(inti, n, &li->list, list) {
 				if (__interrupt_is_deliverable(vcpu, inti)) {
-					list_del(&inti->list);
+					list_del_init(&inti->list);
 					deliver = 1;
 					break;
 				}
@@ -607,7 +608,8 @@  void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 			spin_unlock_bh(&li->lock);
 			if (deliver) {
 				__do_deliver_interrupt(vcpu, inti);
-				kfree(inti);
+				if (!inti->nondyn)
+					kfree(inti);
 			}
 		} while (deliver);
 	}
@@ -622,7 +624,7 @@  void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 			spin_lock(&fi->lock);
 			list_for_each_entry_safe(inti, n, &fi->list, list) {
 				if (__interrupt_is_deliverable(vcpu, inti)) {
-					list_del(&inti->list);
+					list_del_init(&inti->list);
 					deliver = 1;
 					break;
 				}
@@ -633,7 +635,8 @@  void kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu)
 			spin_unlock(&fi->lock);
 			if (deliver) {
 				__do_deliver_interrupt(vcpu, inti);
-				kfree(inti);
+				if (!inti->nondyn)
+					kfree(inti);
 			}
 		} while (deliver);
 	}
@@ -654,7 +657,7 @@  void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu)
 			list_for_each_entry_safe(inti, n, &li->list, list) {
 				if ((inti->type == KVM_S390_MCHK) &&
 				    __interrupt_is_deliverable(vcpu, inti)) {
-					list_del(&inti->list);
+					list_del_init(&inti->list);
 					deliver = 1;
 					break;
 				}
@@ -665,7 +668,8 @@  void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu)
 			spin_unlock_bh(&li->lock);
 			if (deliver) {
 				__do_deliver_interrupt(vcpu, inti);
-				kfree(inti);
+				if (!inti->nondyn)
+					kfree(inti);
 			}
 		} while (deliver);
 	}
@@ -677,7 +681,7 @@  void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu)
 			list_for_each_entry_safe(inti, n, &fi->list, list) {
 				if ((inti->type == KVM_S390_MCHK) &&
 				    __interrupt_is_deliverable(vcpu, inti)) {
-					list_del(&inti->list);
+					list_del_init(&inti->list);
 					deliver = 1;
 					break;
 				}
@@ -688,7 +692,8 @@  void kvm_s390_deliver_pending_machine_checks(struct kvm_vcpu *vcpu)
 			spin_unlock(&fi->lock);
 			if (deliver) {
 				__do_deliver_interrupt(vcpu, inti);
-				kfree(inti);
+				if (!inti->nondyn)
+					kfree(inti);
 			}
 		} while (deliver);
 	}
@@ -716,14 +721,100 @@  int kvm_s390_inject_program_int(struct kvm_vcpu *vcpu, u16 code)
 	return 0;
 }
 
-int kvm_s390_inject_vm(struct kvm *kvm,
-		       struct kvm_s390_interrupt *s390int)
+int kvm_s390_inject_internal(struct kvm *kvm,
+			     struct kvm_s390_interrupt_info *inti)
 {
 	struct kvm_s390_local_interrupt *li;
 	struct kvm_s390_float_interrupt *fi;
-	struct kvm_s390_interrupt_info *inti, *iter;
+	struct kvm_s390_interrupt_info *iter;
 	int sigcpu;
 
+	fi = &kvm->arch.float_int;
+	spin_lock(&fi->lock);
+	if (!is_ioint(inti->type))
+		list_add_tail(&inti->list, &fi->list);
+	else {
+		/* Keep I/O interrupts sorted in isc order. */
+		list_for_each_entry(iter, &fi->list, list) {
+			if (!is_ioint(iter->type))
+				continue;
+			if (iter->io.io_int_word <= inti->io.io_int_word)
+				continue;
+			break;
+		}
+		list_add_tail(&inti->list, &iter->list);
+	}
+	atomic_set(&fi->active, 1);
+	sigcpu = find_first_bit(fi->idle_mask, KVM_MAX_VCPUS);
+	if (sigcpu == KVM_MAX_VCPUS) {
+		do {
+			sigcpu = fi->next_rr_cpu++;
+			if (sigcpu == KVM_MAX_VCPUS)
+				sigcpu = fi->next_rr_cpu = 0;
+		} while (fi->local_int[sigcpu] == NULL);
+	}
+	li = fi->local_int[sigcpu];
+	spin_lock_bh(&li->lock);
+	atomic_set_mask(CPUSTAT_EXT_INT, li->cpuflags);
+	if (waitqueue_active(&li->wq))
+		wake_up_interruptible(&li->wq);
+	spin_unlock_bh(&li->lock);
+	spin_unlock(&fi->lock);
+	return 0;
+}
+
+int kvm_s390_dequeue_internal(struct kvm *kvm,
+			      struct kvm_s390_interrupt_info *inti)
+{
+	struct kvm_s390_float_interrupt *fi;
+
+	if (!inti)
+		return -EINVAL;
+
+	mutex_lock(&kvm->lock);
+	fi = &kvm->arch.float_int;
+	spin_lock(&fi->lock);
+	list_del_init(&inti->list);
+	if (list_empty(&fi->list))
+		atomic_set(&fi->active, 0);
+	spin_unlock(&fi->lock);
+	mutex_unlock(&kvm->lock);
+	return 0;
+}
+
+struct kvm_s390_interrupt_info *kvm_s390_get_io_int(struct kvm *kvm, u64 cr6)
+{
+	struct kvm_s390_float_interrupt *fi;
+	struct kvm_s390_interrupt_info *inti, *iter;
+
+	mutex_lock(&kvm->lock);
+	fi = &kvm->arch.float_int;
+	spin_lock(&fi->lock);
+	inti = NULL;
+	list_for_each_entry(iter, &fi->list, list) {
+		if (!is_ioint(iter->type))
+			continue;
+		if ((cr6 & iter->io.io_int_word) == 0)
+			continue;
+		inti = iter;
+		break;
+	}
+	if (inti)
+		list_del_init(&inti->list);
+	if (list_empty(&fi->list))
+		atomic_set(&fi->active, 0);
+	spin_unlock(&fi->lock);
+	mutex_unlock(&kvm->lock);
+	return inti;
+}
+
+
+int kvm_s390_inject_vm(struct kvm *kvm,
+		       struct kvm_s390_interrupt *s390int)
+{
+	struct kvm_s390_interrupt_info *inti;
+	int rc;
+
 	inti = kzalloc(sizeof(*inti), GFP_KERNEL);
 	if (!inti)
 		return -ENOMEM;
@@ -776,39 +867,9 @@  int kvm_s390_inject_vm(struct kvm *kvm,
 				 2);
 
 	mutex_lock(&kvm->lock);
-	fi = &kvm->arch.float_int;
-	spin_lock(&fi->lock);
-	if (!is_ioint(inti->type))
-		list_add_tail(&inti->list, &fi->list);
-	else {
-		/* Keep I/O interrupts sorted in isc order. */
-		list_for_each_entry(iter, &fi->list, list) {
-			if (!is_ioint(iter->type))
-				continue;
-			if (iter->io.io_int_word <= inti->io.io_int_word)
-				continue;
-			break;
-		}
-		list_add_tail(&inti->list, &iter->list);
-	}
-	atomic_set(&fi->active, 1);
-	sigcpu = find_first_bit(fi->idle_mask, KVM_MAX_VCPUS);
-	if (sigcpu == KVM_MAX_VCPUS) {
-		do {
-			sigcpu = fi->next_rr_cpu++;
-			if (sigcpu == KVM_MAX_VCPUS)
-				sigcpu = fi->next_rr_cpu = 0;
-		} while (fi->local_int[sigcpu] == NULL);
-	}
-	li = fi->local_int[sigcpu];
-	spin_lock_bh(&li->lock);
-	atomic_set_mask(CPUSTAT_EXT_INT, li->cpuflags);
-	if (waitqueue_active(&li->wq))
-		wake_up_interruptible(&li->wq);
-	spin_unlock_bh(&li->lock);
-	spin_unlock(&fi->lock);
+	rc = kvm_s390_inject_internal(kvm, inti);
 	mutex_unlock(&kvm->lock);
-	return 0;
+	return rc;
 }
 
 int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu,
diff --git a/arch/s390/kvm/ioinst.c b/arch/s390/kvm/ioinst.c
new file mode 100644
index 0000000..29c4629
--- /dev/null
+++ b/arch/s390/kvm/ioinst.c
@@ -0,0 +1,797 @@ 
+/*
+ * Handling of channel I/O instructions for kvm
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (version 2 only)
+ * as published by the Free Software Foundation.
+ *
+ *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ */
+
+#include <linux/kvm.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/types.h>
+#include <asm/cio.h>
+#include <asm/crw.h>
+#include <asm/orb.h>
+#include <asm/schib.h>
+#include <asm/schid.h>
+#include <asm/scsw.h>
+#include "kvm-s390.h"
+#include "gaccess.h"
+#include "trace.h"
+
+#define PRIV_CSCH                       0x30
+#define PRIV_HSCH                       0x31
+#define PRIV_MSCH                       0x32
+#define PRIV_SSCH                       0x33
+#define PRIV_STSCH                      0x34
+#define PRIV_TSCH                       0x35
+#define PRIV_TPI                        0x36
+#define PRIV_SAL                        0x37
+#define PRIV_RSCH                       0x38
+#define PRIV_STCRW                      0x39
+#define PRIV_STCPS                      0x3a
+#define PRIV_RCHP                       0x3b
+#define PRIV_SCHM                       0x3c
+#define PRIV_CHSC                       0x5f
+#define PRIV_XSCH                       0x76
+
+static int ioinst_disassemble_sch_ident(u32 value, int *m, int *cssid, int *ssid,
+					int *schid)
+{
+	if (!(value & 0x00010000))
+		return -EINVAL;
+
+	if (!(value & 0x00080000)) {
+		if (value & 0xff000000)
+			return -EINVAL;
+		*m = 0;
+		*cssid = 0;
+	} else {
+		*m = 1;
+		*cssid = (value & 0xff000000) >> 24;
+	}
+	*ssid = (value & 0x00060000) >> 17;
+	*schid = value & 0x0000ffff;
+	return 0;
+}
+
+static int ioinst_handle_xsch(struct kvm_vcpu *vcpu, int *cc, u64 reg1)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("xsch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_xsch(vcpu, sch);
+
+	switch (ret) {
+	case -ENODEV:
+		*cc = 3;
+		break;
+	case -EBUSY:
+		*cc = 2;
+		break;
+	case -EREMOTE:
+		*cc = 0;
+		break;
+	default:
+		*cc = 1;
+		break;
+	}
+
+	return ret;
+}
+
+static int ioinst_handle_csch(struct kvm_vcpu *vcpu, int *cc, u64 reg1)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("csch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_csch(vcpu, sch);
+
+	if (ret == -ENODEV) {
+		*cc = 3;
+	} else {
+		*cc = 0;
+	}
+	return ret;
+}
+
+static int ioinst_handle_hsch(struct kvm_vcpu *vcpu, int *cc, u64 reg1)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("hsch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_hsch(vcpu, sch);
+
+	switch (ret) {
+	case -ENODEV:
+		*cc = 3;
+		break;
+	case -EBUSY:
+		*cc = 2;
+		break;
+	case -EREMOTE:
+		*cc = 0;
+		break;
+	default:
+		*cc = 1;
+		break;
+	}
+
+	return ret;
+}
+
+static int ioinst_schib_valid(struct schib *schib)
+{
+	if (schib->pmcw.res5 != 0)
+		return 0;
+
+	if ((schib->pmcw.unused1 != 0) || (schib->pmcw.unused2 != 0))
+		return 0;
+
+	/* Disallow extended measurements for now. */
+	if (schib->pmcw.xmwme)
+		return 0;
+
+	return 1;
+}
+
+static int ioinst_handle_msch(struct kvm_vcpu *vcpu, int *cc, u64 reg1, u32 ipb)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	struct schib schib;
+	u32 addr;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	if (copy_from_guest(vcpu, &schib, addr, sizeof(struct schib))) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		return -EIO;
+	}
+	if (!ioinst_schib_valid(&schib)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("msch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_msch(vcpu, sch, &schib);
+
+	switch (ret) {
+	case -ENODEV:
+		*cc = 3;
+		break;
+	case -EBUSY:
+		*cc = 2;
+		break;
+	case 0:
+		*cc = 0;
+		break;
+	default:
+		*cc = 1;
+		break;
+	}
+
+	return ret;
+}
+
+static int ioinst_orb_valid(union orb *orb)
+{
+	if (orb->cmd.res2 != 0)
+		return 0;
+
+	if (orb->cmd.zero != 0)
+		return 0;
+
+	if ((orb->cmd.cpa & 0x80000000) != 0)
+		return 0;
+
+	return 1;
+}
+
+static int ioinst_handle_ssch(struct kvm_vcpu *vcpu, int *cc, u64 reg1, u32 ipb)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	union orb orb;
+	u32 addr;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	if (copy_from_guest(vcpu, &orb, addr, sizeof(union orb))) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		return -EIO;
+	}
+	if (!ioinst_orb_valid(&orb)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("ssch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_ssch(vcpu, sch, addr);
+
+	switch (ret) {
+	case -ENODEV:
+		*cc = 3;
+		break;
+	case -EBUSY:
+		*cc = 2;
+		break;
+	case -EREMOTE:
+		*cc = 0;
+		break;
+	default:
+		*cc = 1;
+		break;
+	}
+
+	return ret;
+}
+
+static int ioinst_handle_stcrw(struct kvm_vcpu *vcpu, int *cc, u32 ipb)
+{
+	int ret;
+	u32 addr;
+
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	ret = css_do_stcrw(vcpu, addr);
+	/* 0 - crw stored, 1 - zeroes stored */
+	if (ret >= 0) {
+		*cc = ret;
+		ret = 0;
+	}
+	return 0;
+}
+
+static int ioinst_handle_stsch(struct kvm_vcpu *vcpu, int *cc, u64 reg1, u32 ipb)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	u32 addr;
+	int ret;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	if (addr & 3) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("stsch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch) {
+		ret = copy_to_guest(vcpu, addr, sch->curr_status,
+				    sizeof(*sch->curr_status));
+		if (ret < 0)
+			kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
+		else
+			*cc = 0;
+	} else {
+		if (css_schid_final(vcpu->kvm, m ? cssid :
+				    (cssid ? cssid : VIRTUAL_CSSID),
+				    ssid, schid)) {
+			*cc = 3; /* No more subchannels in this css/ss */
+			ret = 0;
+		} else {
+			struct schib schib;
+
+			/* Store an empty schib. */
+			memset(&schib, 0, sizeof(struct schib));
+			ret = copy_to_guest(vcpu, addr, &schib, sizeof(schib));
+			if (ret < 0)
+				kvm_s390_inject_program_int(vcpu,
+							    PGM_ADDRESSING);
+			else
+				*cc = 0;
+		}
+	}
+	return ret;
+}
+
+static int ioinst_handle_tsch(struct kvm_vcpu *vcpu, int *cc, u64 reg1, u32 ipb)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	u32 addr;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	trace_kvm_s390_handle_ioinst("tsch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_tsch(vcpu, sch, addr);
+	/* 0 - status pending, 1 - not status pending */
+	switch (ret) {
+	case -EBUSY:
+		*cc = 0;
+		break;
+	case 0:
+		*cc = 1;
+		break;
+	case -ENODEV:
+		*cc = 3;
+		break;
+	}
+	return ret;
+}
+
+struct chsc_req {
+	u16 len;
+	u16 command;
+	u32 param0;
+	u32 param1;
+	u32 param2;
+} __attribute__((packed));
+
+struct chsc_resp {
+	u16 len;
+	u16 code;
+	u32 param;
+	char data[0];
+} __attribute__((packed));
+
+#define CHSC_SCPD 0x0002
+#define CHSC_SCSC 0x0010
+#define CHSC_SDA  0x0031
+
+static void ioinst_handle_chsc_scpd(struct kvm *kvm, struct chsc_req *req,
+				    struct chsc_resp *res)
+{
+	u16 resp_code;
+	int rfmt;
+	u16 cssid;
+	u8 f_chpid, l_chpid;
+	int desc_size;
+
+	rfmt = (req->param0 & 0x00000f00) >> 8;
+	if ((rfmt == 0) ||  (rfmt == 1))
+		rfmt = (req->param0 & 0x10000000) >> 28;
+
+	if ((req->len != 0x0010) || (req->param0 & 0xc000f000) ||
+	    (req->param1 & 0xffffff00) || req->param2) {
+		resp_code = 0x0003;
+		goto out_err;
+	}
+	if (req->param0 & 0x0f000000) {
+		resp_code = 0x0007;
+		goto out_err;
+	}
+	cssid = (req->param0 & 0x00ff0000) >> 16;
+	if (cssid != 0)
+		if (!(req->param0 & 0x20000000) || (cssid != VIRTUAL_CSSID)) {
+			resp_code = 0x0008;
+			goto out_err;
+		}
+
+	if ((cssid == 0) && (!(req->param0 & 0x20000000)))
+		cssid = VIRTUAL_CSSID;
+
+	f_chpid = req->param0 & 0x000000ff;
+	l_chpid = req->param1 & 0x000000ff;
+	if (l_chpid < f_chpid) {
+		resp_code = 0x0003;
+		goto out_err;
+	}
+	desc_size = css_collect_chp_desc(kvm, cssid, f_chpid, l_chpid, rfmt,
+					 &res->data);
+	res->code = 0x0001;
+	res->len = 8 + desc_size;
+	res->param = rfmt;
+	return;
+
+out_err:
+	res->code = resp_code;
+	res->len = 8;
+	res->param = rfmt;
+}
+
+/* For now, always the same characteristics. */
+static u32 general_chars[510] = { 0x03000000, 0x00059000, 0, };
+static u32 chsc_chars[508] = { 0x40000000, 0x00040000, 0, };
+
+static void ioinst_handle_chsc_scsc(struct kvm *kvm, struct chsc_req *req,
+				    struct chsc_resp *res)
+{
+	u8 cssid;
+	u16 resp_code;
+
+	if (req->param0 & 0x000f0000) {
+		resp_code = 0x0007;
+		goto out_err;
+	}
+	cssid = (req->param0 & 0x0000ff00) >> 8;
+	if (cssid != 0)
+		if (!(req->param0 & 0x20000000) || (cssid != VIRTUAL_CSSID)) {
+			resp_code = 0x0008;
+			goto out_err;
+		}
+
+	if ((req->param0 & 0xdff000ff) || req->param1 || req->param2) {
+		resp_code = 0x0003;
+		goto out_err;
+	}
+	res->code = 0x0001;
+	res->len = 4080;
+	res->param = 0;
+
+	memcpy(res->data, general_chars, sizeof(general_chars));
+	memcpy(res->data + sizeof(general_chars), chsc_chars,
+	       sizeof(chsc_chars));
+	return;
+
+out_err:
+	res->code = resp_code;
+	res->len = 8;
+	res->param = 0;
+}
+
+#define CHSC_SDA_SC_MCSSE 0x0
+#define CHSC_SDA_SC_MSS 0x2
+
+static void ioinst_handle_chsc_sda(struct kvm *kvm, struct chsc_req *req,
+				   struct chsc_resp *res)
+{
+	u16 resp_code = 0x0001;
+	u16 oc;
+	int ret;
+
+	if ((req->len != 0x0400) || (req->param0 & 0xf0ff0000)) {
+		resp_code = 0x0003;
+		goto out;
+	}
+
+	if (req->param0 & 0x0f000000) {
+		resp_code = 0x0007;
+		goto out;
+	}
+
+	oc = req->param0 & 0x0000ffff;
+	switch (oc) {
+	case CHSC_SDA_SC_MCSSE:
+		ret = css_enable_mcsse(kvm);
+		if (ret == -EINVAL) {
+			resp_code = 0x0101;
+			goto out;
+		}
+		break;
+	case CHSC_SDA_SC_MSS:
+		ret = css_enable_mss(kvm);
+		if (ret == -EINVAL) {
+			resp_code = 0x0101;
+			goto out;
+		}
+		break;
+	default:
+		resp_code = 0x0003;
+		goto out;
+	}
+
+out:
+	res->code = resp_code;
+	res->len = 8;
+	res->param = 0;
+}
+
+static void ioinst_handle_chsc_unimplemented(struct chsc_resp *res)
+{
+	res->len = 8;
+	res->code = 0x0004;
+	res->param = 0;
+}
+
+static int ioinst_handle_chsc(struct kvm_vcpu *vcpu, int *cc, u32 ipb)
+{
+	struct chsc_req *req;
+	struct chsc_resp *res;
+	u64 addr;
+	int reg;
+	int ret;
+
+	reg = (ipb >> 20) & 0x00f;
+	addr = vcpu->run->s.regs.gprs[reg];
+	if (addr & 0x0000000000000fff) {
+		kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
+		return -EIO;
+	}
+	req = (struct chsc_req *)get_zeroed_page(GFP_KERNEL);
+	if (!req)
+		return -EFAULT;
+	if (copy_from_guest(vcpu, req, addr, sizeof(*req))) {
+		kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
+		return -EFAULT;
+	}
+	if ((req->len & 3) || (req->len < 16) || (req->len > 4088)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	res = (struct chsc_resp *)((unsigned long)req + req->len);
+	switch (req->command) {
+	case CHSC_SCSC:
+		ioinst_handle_chsc_scsc(vcpu->kvm, req, res);
+		break;
+	case CHSC_SCPD:
+		ioinst_handle_chsc_scpd(vcpu->kvm, req, res);
+		break;
+	case CHSC_SDA:
+		ioinst_handle_chsc_sda(vcpu->kvm, req, res);
+		break;
+	default:
+		ioinst_handle_chsc_unimplemented(res);
+		break;
+	}
+	ret = copy_to_guest(vcpu, addr + req->len, res, res->len);
+	if (ret < 0)
+		kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
+	else
+		*cc = 0;
+	free_page((unsigned long)req);
+	return ret;
+}
+
+static int ioinst_handle_tpi(struct kvm_vcpu *vcpu, int *cc, u32 ipb)
+{
+	u32 addr;
+	int lowcore;
+
+	addr = ipb >> 28;
+	if (addr > 0)
+		addr = vcpu->run->s.regs.gprs[addr];
+
+	addr += (ipb & 0xfff0000) >> 16;
+	lowcore = addr ? 0 : 1;
+	*cc = css_do_tpi(vcpu, addr, lowcore);
+	return 0;
+}
+
+static int ioinst_handle_schm(struct kvm_vcpu *vcpu, u64 reg1, u64 reg2,
+			      u32 ipb)
+{
+	u8 mbk;
+	int update;
+	int dct;
+
+	if (reg1 & 0x000000000ffffffc) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+
+	mbk = (reg1 & 0x00000000f0000000) >> 28;
+	update = (reg1 & 0x0000000000000002) >> 1;
+	dct = reg1 & 0x0000000000000001;
+
+	if (update && (reg2 & 0x0000000000000fff)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+
+	css_do_schm(vcpu, mbk, update, dct, update ? reg2 : 0);
+
+	return 0;
+}
+
+static int ioinst_handle_rsch(struct kvm_vcpu *vcpu, int *cc, u64 reg1)
+{
+	int m, cssid, ssid, schid;
+	struct kvm_subch *sch;
+	int ret = -ENODEV;
+
+	if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	trace_kvm_s390_handle_ioinst("rsch", cssid, ssid, schid);
+	sch = css_find_subch(vcpu->kvm, m, cssid, ssid, schid);
+	if (sch)
+		ret = css_do_rsch(vcpu, sch);
+
+	switch (ret) {
+	case -ENODEV:
+		*cc = 3;
+		break;
+	case -EINVAL:
+		*cc = 2;
+		break;
+	case -EREMOTE:
+		*cc = 0;
+		break;
+	default:
+		*cc = 1;
+		break;
+	}
+
+	return ret;
+
+}
+
+static int ioinst_handle_rchp(struct kvm_vcpu *vcpu, int *cc, u64 reg1)
+{
+	u8 cssid;
+	u8 chpid;
+	int ret;
+	struct kvm_s390_css_data *css = vcpu->kvm->arch.css;
+
+	if (reg1 & 0xff00ff00) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+
+	cssid = (reg1 >> 16) & 0xff;
+	chpid = reg1 & 0xff;
+
+	if (cssid > css->max_cssid) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		ret = -EIO;
+	} else if (!css_chpid_in_use(vcpu->kvm, cssid, chpid)) {
+		ret = 0;
+		*cc = 3;
+	} else {
+		/*
+		 * Since we only support virtual (i.e. not real) channel paths,
+		 * there's nothing left for us to do save signaling success.
+		 */
+		css_queue_crw(vcpu->kvm, CRW_RSC_CPATH, CRW_ERC_INIT,
+			      css->max_cssid > 0 ? 1 : 0, chpid);
+		if (css->max_cssid > 0)
+			css_queue_crw(vcpu->kvm, CRW_RSC_CPATH, CRW_ERC_INIT, 0,
+				      cssid << 8);
+		ret = 0;
+		*cc = 0;
+	}
+
+	return ret;
+}
+
+static int ioinst_handle_sal(struct kvm_vcpu *vcpu, u64 reg1)
+{
+	/* We do not provide address limit checking, so let's suppress it. */
+	if (reg1 & 0x000000008000ffff) {
+		kvm_s390_inject_program_int(vcpu, PGM_OPERAND);
+		return -EIO;
+	}
+	return 0;
+}
+
+int kvm_css_instruction(struct kvm_vcpu *vcpu)
+{
+	int ret;
+	int cc;
+	int no_cc = 0;
+
+	if ((vcpu->arch.sie_block->ipa & 0xff00) != 0xb200)
+		/* Not handled for now. */
+		return -EOPNOTSUPP;
+
+	switch (vcpu->arch.sie_block->ipa & 0x00ff) {
+	case PRIV_XSCH:
+		ret = ioinst_handle_xsch(vcpu, &cc, vcpu->run->s.regs.gprs[1]);
+		break;
+	case PRIV_CSCH:
+		ret = ioinst_handle_csch(vcpu, &cc, vcpu->run->s.regs.gprs[1]);
+		break;
+	case PRIV_HSCH:
+		ret = ioinst_handle_hsch(vcpu, &cc, vcpu->run->s.regs.gprs[1]);
+		break;
+	case PRIV_MSCH:
+		ret = ioinst_handle_msch(vcpu, &cc, vcpu->run->s.regs.gprs[1],
+					 vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_SSCH:
+		ret = ioinst_handle_ssch(vcpu, &cc, vcpu->run->s.regs.gprs[1],
+					 vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_STCRW:
+		ret = ioinst_handle_stcrw(vcpu, &cc, vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_STSCH:
+		ret = ioinst_handle_stsch(vcpu, &cc, vcpu->run->s.regs.gprs[1],
+					  vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_TSCH:
+		ret = ioinst_handle_tsch(vcpu, &cc, vcpu->run->s.regs.gprs[1],
+					 vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_CHSC:
+		ret = ioinst_handle_chsc(vcpu, &cc, vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_TPI:
+		ret = ioinst_handle_tpi(vcpu, &cc, vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_SCHM:
+		no_cc = 1;
+		ret = ioinst_handle_schm(vcpu, vcpu->run->s.regs.gprs[1],
+					 vcpu->run->s.regs.gprs[2],
+					 vcpu->arch.sie_block->ipb);
+		break;
+	case PRIV_RSCH:
+		ret = ioinst_handle_rsch(vcpu, &cc, vcpu->run->s.regs.gprs[1]);
+		break;
+	case PRIV_RCHP:
+		ret = ioinst_handle_rchp(vcpu, &cc, vcpu->run->s.regs.gprs[1]);
+		break;
+	case PRIV_STCPS:
+		/* We do not provide this instruction, it is suppressed. */
+		no_cc = 1;
+		ret = 0;
+		break;
+	case PRIV_SAL:
+		no_cc = 1;
+		ret = ioinst_handle_sal(vcpu, vcpu->run->s.regs.gprs[1]);
+		break;
+	default:
+		/* Give user space a go at this. */
+		return -EOPNOTSUPP;
+	}
+	if ((ret != -EFAULT) && (ret != -EIO) && (ret != -EREMOTE))
+		ret = 0;
+
+	if ((!ret || (ret == -EREMOTE)) && !no_cc) {
+		vcpu->arch.sie_block->gpsw.mask &= ~(3ul << 44);
+		vcpu->arch.sie_block->gpsw.mask |= (cc & 3ul) << 44;
+	}
+
+	return (ret == -EREMOTE) ? ret : 0;
+}
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index 4b0681c..5ee9e5e 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -141,6 +141,7 @@  int kvm_dev_ioctl_check_extension(long ext)
 	case KVM_CAP_SYNC_REGS:
 	case KVM_CAP_ONE_REG:
 	case KVM_CAP_ENABLE_CAP:
+	case KVM_CAP_S390_CSS_SUPPORT:
 		r = 1;
 		break;
 	case KVM_CAP_NR_VCPUS:
@@ -183,6 +184,24 @@  long kvm_arch_vm_ioctl(struct file *filp,
 		r = kvm_s390_inject_vm(kvm, &s390int);
 		break;
 	}
+	case KVM_S390_CCW_HOTPLUG: {
+		struct kvm_s390_sch_info sch_info;
+
+		r = -EFAULT;
+		if (copy_from_user(&sch_info, argp, sizeof(sch_info)))
+			break;
+		r = kvm_s390_process_ccw_hotplug(kvm, &sch_info);
+		break;
+	}
+	case KVM_S390_CHP_HOTPLUG: {
+		struct kvm_s390_chp_info chp_info;
+
+		r = -EFAULT;
+		if (copy_from_user(&chp_info, argp, sizeof(chp_info)))
+			break;
+		r = kvm_s390_process_chp_hotplug(kvm, &chp_info);
+		break;
+	}
 	default:
 		r = -ENOTTY;
 	}
@@ -235,6 +254,9 @@  int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 		if (!kvm->arch.gmap)
 			goto out_nogmap;
 	}
+
+	kvm->arch.css_support = 0;
+
 	return 0;
 out_nogmap:
 	debug_unregister(kvm->arch.dbf);
@@ -657,6 +679,7 @@  rerun_vcpu:
 	case KVM_EXIT_INTR:
 	case KVM_EXIT_S390_RESET:
 	case KVM_EXIT_S390_UCONTROL:
+	case KVM_EXIT_S390_SCH_IO:
 		break;
 	default:
 		BUG();
@@ -817,6 +840,9 @@  static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
 		return -EINVAL;
 
 	switch (cap->cap) {
+	case KVM_CAP_S390_CSS_SUPPORT:
+		r = kvm_s390_enable_css(vcpu->kvm);
+		break;
 	default:
 		r = -EINVAL;
 		break;
@@ -919,6 +945,15 @@  long kvm_arch_vcpu_ioctl(struct file *filp,
 		r = kvm_vcpu_ioctl_enable_cap(vcpu, &cap);
 		break;
 	}
+	case KVM_S390_CSS_NOTIFY:
+	{
+		struct kvm_css_notify notify;
+		r = -EFAULT;
+		if (copy_from_user(&notify, argp, sizeof(notify)))
+			break;
+		r = kvm_arch_vcpu_ioctl_css_notify(vcpu, &notify);
+		break;
+	}
 	default:
 		r = -ENOTTY;
 	}
diff --git a/arch/s390/kvm/kvm-s390.h b/arch/s390/kvm/kvm-s390.h
index 7f50229..8bf25f9 100644
--- a/arch/s390/kvm/kvm-s390.h
+++ b/arch/s390/kvm/kvm-s390.h
@@ -76,6 +76,11 @@  int kvm_s390_inject_vcpu(struct kvm_vcpu *vcpu,
 		struct kvm_s390_interrupt *s390int);
 int kvm_s390_inject_program_int(struct kvm_vcpu *vcpu, u16 code);
 int kvm_s390_inject_sigp_stop(struct kvm_vcpu *vcpu, int action);
+int kvm_s390_inject_internal(struct kvm *kvm,
+			     struct kvm_s390_interrupt_info *inti);
+int kvm_s390_dequeue_internal(struct kvm *kvm,
+			      struct kvm_s390_interrupt_info *inti);
+struct kvm_s390_interrupt_info *kvm_s390_get_io_int(struct kvm *kvm, u64 cr6);
 
 /* implemented in priv.c */
 int kvm_s390_handle_b2(struct kvm_vcpu *vcpu);
@@ -94,4 +99,37 @@  int kvm_s390_vcpu_store_status(struct kvm_vcpu *vcpu,
 /* implemented in diag.c */
 int kvm_s390_handle_diag(struct kvm_vcpu *vcpu);
 
+/* implemented in ioinst.c */
+int kvm_css_instruction(struct kvm_vcpu *vcpu);
+
+/* implemented in css.c */
+struct schib;
+int kvm_arch_vcpu_ioctl_css_notify(struct kvm_vcpu *vcpu,
+				   struct kvm_css_notify *notify);
+int kvm_s390_process_ccw_hotplug(struct kvm *kvm,
+				 struct kvm_s390_sch_info *sch_info);
+int kvm_s390_process_chp_hotplug(struct kvm *kvm,
+				 struct kvm_s390_chp_info *chp_info);
+int kvm_s390_enable_css(struct kvm *kvm);
+struct kvm_subch *css_find_subch(struct kvm *kvm, u8 m, u8 cssid, u8 ssid,
+				 u16 schid);
+int css_do_stsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, u32 addr);
+int css_schid_final(struct kvm *kvm, u8 cssid, u8 ssid, u16 schid);
+int css_do_msch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, struct schib *schib);
+int css_do_xsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch);
+int css_do_csch(struct kvm_vcpu *vcpu, struct kvm_subch *sch);
+int css_do_hsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch);
+int css_do_ssch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, u64 orb);
+int css_do_tsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch, u32 addr);
+int css_do_stcrw(struct kvm_vcpu *vcpu, u32 addr);
+int css_do_tpi(struct kvm_vcpu *vcpu, u32 addr, int lowcore);
+int css_collect_chp_desc(struct kvm *kvm, u8 cssid, u8 f_chpid, u8 l_chpid,
+                         int rfmt, void *buf);
+void css_do_schm(struct kvm_vcpu *vcpu, u8 mbk, int update, int dct, uint64_t mbo);
+int css_enable_mcsse(struct kvm *kvm);
+int css_enable_mss(struct kvm *kvm);
+int css_do_rsch(struct kvm_vcpu *vcpu, struct kvm_subch *sch);
+int css_do_rchp(struct kvm_vcpu *vcpu, u8 cssid, u8 chpid);
+int css_chpid_in_use(struct kvm *kvm, u8 cssid, u8 chpid);
+void css_queue_crw(struct kvm *kvm, u8 rsc, u8 erc, int chain, u16 rsid);
 #endif
diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c
index 8b79a94..8b128e4 100644
--- a/arch/s390/kvm/priv.c
+++ b/arch/s390/kvm/priv.c
@@ -138,7 +138,12 @@  static int handle_skey(struct kvm_vcpu *vcpu)
 static int handle_io_inst(struct kvm_vcpu *vcpu)
 {
 	VCPU_EVENT(vcpu, 4, "%s", "I/O instruction");
-	/* condition code 3 */
+
+	if (vcpu->kvm->arch.css_support)
+		/* Use in-kernel css support. */
+		return kvm_css_instruction(vcpu);
+
+	/* Set cc 3 to stop guest issueing I/O instructions. */
 	vcpu->arch.sie_block->gpsw.mask &= ~(3ul << 44);
 	vcpu->arch.sie_block->gpsw.mask |= (3 & 3ul) << 44;
 	return 0;
diff --git a/arch/s390/kvm/trace-s390.h b/arch/s390/kvm/trace-s390.h
index 95fbc1a..6d2059e 100644
--- a/arch/s390/kvm/trace-s390.h
+++ b/arch/s390/kvm/trace-s390.h
@@ -203,6 +203,73 @@  TRACE_EVENT(kvm_s390_stop_request,
 		      __entry->action_bits)
 	);
 
+/*
+ * Trace point for enabling in-kernel channel subsystem support.
+ */
+TRACE_EVENT(kvm_s390_enable_kernel_css,
+	    TP_PROTO(void *kvm),
+	    TP_ARGS(kvm),
+
+	    TP_STRUCT__entry(
+		    __field(void *, kvm)
+		),
+
+	    TP_fast_assign(
+		    __entry->kvm = kvm;
+		),
+
+	    TP_printk("enabling in-kernel css support (kvm @ %p)\n",
+		    __entry->kvm)
+    );
+
+/*
+ * Trace point for user space subchannel I/O notification.
+ */
+TRACE_EVENT(kvm_s390_css_notify,
+	    TP_PROTO(u8 cssid, u8 ssid, u16 schid),
+	    TP_ARGS(cssid, ssid, schid),
+
+	    TP_STRUCT__entry(
+		    __field(u8, cssid)
+		    __field(u8, ssid)
+		    __field(u16, schid)
+		),
+
+	    TP_fast_assign(
+		    __entry->cssid = cssid;
+		    __entry->ssid = ssid;
+		    __entry->schid = schid;
+		),
+
+	    TP_printk("css notification for subchannel %x.%x.%04x\n",
+		      __entry->cssid, __entry->ssid, __entry->schid)
+    );
+
+/*
+ * Trace point for user space subchannel hotplug notification.
+ */
+TRACE_EVENT(kvm_s390_ccw_hotplug,
+	    TP_PROTO(u8 cssid, u8 ssid, u16 schid, int add),
+	    TP_ARGS(cssid, ssid, schid, add),
+
+	    TP_STRUCT__entry(
+		    __field(u8, cssid)
+		    __field(u8, ssid)
+		    __field(u16, schid)
+		    __field(int, add)
+		),
+
+	    TP_fast_assign(
+		    __entry->cssid = cssid;
+		    __entry->ssid = ssid;
+		    __entry->schid = schid;
+		    __entry->add = add;
+		),
+
+	    TP_printk("hotplug event for subchannel %x.%x.%04x (%s)\n",
+		      __entry->cssid, __entry->ssid, __entry->schid,
+		      __entry->add ? "attach" : "detach")
+    );
 
 #endif /* _TRACE_KVMS390_H */
 
diff --git a/arch/s390/kvm/trace.h b/arch/s390/kvm/trace.h
index 2b29e62..5f743f3 100644
--- a/arch/s390/kvm/trace.h
+++ b/arch/s390/kvm/trace.h
@@ -335,6 +335,28 @@  TRACE_EVENT(kvm_s390_handle_stsi,
 			   __entry->addr)
 	);
 
+TRACE_EVENT(kvm_s390_handle_ioinst,
+	    TP_PROTO(char *name, u8 cssid, u8 ssid, u16 schid),
+	    TP_ARGS(name, cssid, ssid, schid),
+
+	    TP_STRUCT__entry(
+		__field(char *, name)
+		__field(u8, cssid)
+		__field(u8, ssid)
+		__field(u16, schid)
+		),
+
+	    TP_fast_assign(
+		__entry->name = name;
+		__entry->cssid = cssid;
+		__entry->ssid = ssid;
+		__entry->schid = schid;
+		),
+
+	    TP_printk("I/O instruction %s (%x.%x.%04x)", __entry->name,
+		      __entry->cssid, __entry->ssid, __entry->schid)
+    );
+
 #endif /* _TRACE_KVM_H */
 
 /* This part must be outside protection */
diff --git a/include/linux/kvm.h b/include/linux/kvm.h
index e0c3d87..4c48923 100644
--- a/include/linux/kvm.h
+++ b/include/linux/kvm.h
@@ -163,6 +163,7 @@  struct kvm_pit_config {
 #define KVM_EXIT_OSI              18
 #define KVM_EXIT_PAPR_HCALL	  19
 #define KVM_EXIT_S390_UCONTROL	  20
+#define KVM_EXIT_S390_SCH_IO      21
 
 /* For KVM_EXIT_INTERNAL_ERROR */
 #define KVM_INTERNAL_ERROR_EMULATION 1
@@ -276,6 +277,20 @@  struct kvm_run {
 			__u64 ret;
 			__u64 args[9];
 		} papr_hcall;
+		/* KVM_EXIT_S390_SCH_IO */
+		struct {
+			__u32 sch_id;
+#define SCH_DO_CSCH 0
+#define SCH_DO_HSCH 1
+#define SCH_DO_SSCH 2
+#define SCH_DO_RSCH 3
+#define SCH_DO_XSCH 4
+			__u8 func;
+			__u8 pad;
+			__u64 orb;
+			__u32 scsw[3];
+			__u32 pmcw[7];
+		} s390_sch_io;
 		/* Fix the size of the union. */
 		char padding[256];
 	};
@@ -480,6 +495,39 @@  struct kvm_ppc_smmu_info {
 	struct kvm_ppc_one_seg_page_size sps[KVM_PPC_PAGE_SIZES_MAX_SZ];
 };
 
+/* for KVM_S390_CSS_NOTIFY */
+struct kvm_css_notify {
+	__u8 cssid;
+	__u8 ssid;
+	__u16 schid;
+	__u32 scsw[3];
+	__u32 pmcw[7];
+	__u8 sense_data[32];
+	__u8 unsolicited;
+	__u8 func;
+};
+
+/* for KVM_S390_CCW_HOTPLUG */
+struct kvm_s390_sch_info {
+	__u8 cssid;
+	__u8 ssid;
+	__u16 schid;
+	__u16 devno;
+	__u32 schib[12];
+	int hotplugged;
+	int add;
+	int virtual;
+};
+
+/* for KVM_S390_CHP_HOTPLUG */
+struct kvm_s390_chp_info {
+	__u8 cssid;
+	__u8 chpid;
+	__u8 type;
+	int add;
+	int virtual;
+};
+
 #define KVMIO 0xAE
 
 /* machine type bits, to be used as argument to KVM_CREATE_VM */
@@ -625,6 +673,7 @@  struct kvm_ppc_smmu_info {
 #define KVM_CAP_PPC_GET_SMMU_INFO 78
 #define KVM_CAP_S390_COW 79
 #define KVM_CAP_PPC_ALLOC_HTAB 80
+#define KVM_CAP_S390_CSS_SUPPORT 81
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -838,6 +887,10 @@  struct kvm_s390_ucas_mapping {
 #define KVM_PPC_GET_SMMU_INFO	  _IOR(KVMIO,  0xa6, struct kvm_ppc_smmu_info)
 /* Available with KVM_CAP_PPC_ALLOC_HTAB */
 #define KVM_PPC_ALLOCATE_HTAB	  _IOWR(KVMIO, 0xa7, __u32)
+/* Available with KVM_CAP_S390_CSS_SUPPORT */
+#define KVM_S390_CSS_NOTIFY       _IOW(KVMIO, 0xae, struct kvm_css_notify)
+#define KVM_S390_CCW_HOTPLUG      _IOW(KVMIO, 0xab, struct kvm_s390_sch_info)
+#define KVM_S390_CHP_HOTPLUG      _IOW(KVMIO, 0xac, struct kvm_s390_chp_info)
 
 /*
  * ioctls for vcpu fds
diff --git a/include/trace/events/kvm.h b/include/trace/events/kvm.h
index 7ef9e75..939ba8b 100644
--- a/include/trace/events/kvm.h
+++ b/include/trace/events/kvm.h
@@ -14,7 +14,7 @@ 
 	ERSN(SHUTDOWN), ERSN(FAIL_ENTRY), ERSN(INTR), ERSN(SET_TPR),	\
 	ERSN(TPR_ACCESS), ERSN(S390_SIEIC), ERSN(S390_RESET), ERSN(DCR),\
 	ERSN(NMI), ERSN(INTERNAL_ERROR), ERSN(OSI), ERSN(PAPR_HCALL),	\
-	ERSN(S390_UCONTROL)
+	ERSN(S390_UCONTROL), ERSN(S390_SCH_IO)
 
 TRACE_EVENT(kvm_userspace_exit,
 	    TP_PROTO(__u32 reason, int errno),
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index bcf973e..59c40aa 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1817,7 +1817,8 @@  static long kvm_vcpu_ioctl(struct file *filp,
 	 * Special cases: vcpu ioctls that are asynchronous to vcpu execution,
 	 * so vcpu_load() would break it.
 	 */
-	if (ioctl == KVM_S390_INTERRUPT || ioctl == KVM_INTERRUPT)
+	if (ioctl == KVM_S390_INTERRUPT || ioctl == KVM_INTERRUPT ||
+	    ioctl == KVM_S390_CSS_NOTIFY)
 		return kvm_arch_vcpu_ioctl(filp, ioctl, arg);
 #endif