diff mbox

[v2,2/5] s390: Virtual channel subsystem support.

Message ID 1346771633-53081-3-git-send-email-cornelia.huck@de.ibm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Cornelia Huck Sept. 4, 2012, 3:13 p.m. UTC
Provide a mechanism for qemu to provide fully virtual subchannels to
the guest. In the KVM case, this relies on the kernel's css support.
The !KVM case is not yet supported.

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
---

Changes v1 -> v2:
- coding style
- re-organization of hardware structures (channel subsystem vs. channel
  subsystem image)
- use new KVM_S390_ADD_CSS ioctl

---
 hw/s390x/Makefile.objs     |   1 +
 hw/s390x/css.c             | 490 +++++++++++++++++++++++++++++++++++++++++++++
 hw/s390x/css.h             |  60 ++++++
 target-s390x/Makefile.objs |   2 +-
 target-s390x/cpu.h         | 126 ++++++++++++
 target-s390x/ioinst.c      |  38 ++++
 target-s390x/ioinst.h      | 173 ++++++++++++++++
 target-s390x/kvm.c         | 118 +++++++++++
 8 files changed, 1007 insertions(+), 1 deletion(-)
 create mode 100644 hw/s390x/css.c
 create mode 100644 hw/s390x/css.h
 create mode 100644 target-s390x/ioinst.c
 create mode 100644 target-s390x/ioinst.h
diff mbox

Patch

diff --git a/hw/s390x/Makefile.objs b/hw/s390x/Makefile.objs
index dcdcac8..93b41fb 100644
--- a/hw/s390x/Makefile.objs
+++ b/hw/s390x/Makefile.objs
@@ -1,3 +1,4 @@ 
 obj-y = s390-virtio-bus.o s390-virtio.o
 
 obj-y := $(addprefix ../,$(obj-y))
+obj-y += css.o
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
new file mode 100644
index 0000000..b9b6e48
--- /dev/null
+++ b/hw/s390x/css.c
@@ -0,0 +1,490 @@ 
+/*
+ * Channel subsystem base support.
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "qemu-thread.h"
+#include "qemu-queue.h"
+#include <hw/qdev.h>
+#include "bitops.h"
+#include "kvm.h"
+#include "cpu.h"
+#include "ioinst.h"
+#include "css.h"
+
+typedef struct ChpInfo {
+    uint8_t in_use;
+    uint8_t type;
+} ChpInfo;
+
+typedef struct SubchSet {
+    SubchDev *sch[MAX_SCHID + 1];
+    unsigned long schids_used[BITS_TO_LONGS(MAX_SCHID + 1)];
+    unsigned long devnos_used[BITS_TO_LONGS(MAX_SCHID + 1)];
+} SubchSet;
+
+typedef struct CssImage {
+    SubchSet *sch_set[MAX_SSID + 1];
+    ChpInfo chpids[MAX_CHPID + 1];
+} CssImage;
+
+typedef struct ChannelSubSys {
+    CssImage *css[MAX_CSSID + 1];
+    uint8_t default_cssid;
+} ChannelSubSys;
+
+static ChannelSubSys *channel_subsys;
+
+int css_create_css_image(uint8_t cssid, bool default_image)
+{
+    if (cssid > MAX_CSSID) {
+        return -EINVAL;
+    }
+    if (channel_subsys->css[cssid]) {
+        return -EBUSY;
+    }
+    channel_subsys->css[cssid] = g_try_malloc0(sizeof(CssImage));
+    if (!channel_subsys->css[cssid]) {
+        return -ENOMEM;
+    }
+    if (default_image) {
+        channel_subsys->default_cssid = cssid;
+    }
+    s390_new_css_image(cssid, default_image);
+    return 0;
+}
+
+static void css_inject_io_interrupt(SubchDev *sch, uint8_t func)
+{
+    s390_io_interrupt(sch->cssid, sch->ssid, sch->schid, &sch->curr_status.scsw,
+                      &sch->curr_status.pmcw, &sch->sense_data, 0,
+                      sch->curr_status.pmcw.isc, sch->curr_status.pmcw.intparm,
+                      func);
+}
+
+void css_conditional_io_interrupt(SubchDev *sch)
+{
+    s390_io_interrupt(sch->cssid, sch->ssid, sch->schid, &sch->curr_status.scsw,
+                      &sch->curr_status.pmcw, &sch->sense_data, 1,
+                      sch->curr_status.pmcw.isc, sch->curr_status.pmcw.intparm, 0);
+}
+
+static void sch_handle_clear_func(SubchDev *sch)
+{
+    PMCW *p = &sch->curr_status.pmcw;
+    SCSW *s = &sch->curr_status.scsw;
+    int path;
+
+    /* Path management: In our simple css, we always choose the only path. */
+    path = 0x80;
+
+    /* Reset values prior to 'issueing the clear signal'. */
+    p->lpum = 0;
+    p->pom = 0xff;
+    s->pno = 0;
+
+    /* We always 'attempt to issue the clear signal', and we always succeed. */
+    sch->orb = NULL;
+    sch->channel_prog = NULL;
+    sch->last_cmd = NULL;
+    s->actl &= ~SCSW_ACTL_CLEAR_PEND;
+    s->stctl |= SCSW_STCTL_STATUS_PEND;
+
+    s->dstat = 0;
+    s->cstat = 0;
+    p->lpum = path;
+
+}
+
+static void sch_handle_halt_func(SubchDev *sch)
+{
+
+    PMCW *p = &sch->curr_status.pmcw;
+    SCSW *s = &sch->curr_status.scsw;
+    int path;
+
+    /* Path management: In our simple css, we always choose the only path. */
+    path = 0x80;
+
+    /* We always 'attempt to issue the halt signal', and we always succeed. */
+    sch->orb = NULL;
+    sch->channel_prog = NULL;
+    sch->last_cmd = NULL;
+    s->actl &= ~SCSW_ACTL_HALT_PEND;
+    s->stctl |= SCSW_STCTL_STATUS_PEND;
+
+    if ((s->actl & (SCSW_ACTL_SUBCH_ACTIVE | SCSW_ACTL_DEVICE_ACTIVE)) ||
+        !((s->actl & SCSW_ACTL_START_PEND) ||
+          (s->actl & SCSW_ACTL_SUSP))) {
+        s->dstat = SCSW_DSTAT_DEVICE_END;
+    }
+    s->cstat = 0;
+    p->lpum = path;
+
+}
+
+static int css_interpret_ccw(SubchDev *sch, CCW1 *ccw)
+{
+    int ret;
+    bool check_len;
+    int len;
+    int i;
+
+    if (!ccw) {
+        return -EIO;
+    }
+
+    /* Check for invalid command codes. */
+    if ((ccw->cmd_code & 0x0f) == 0) {
+        return -EINVAL;
+    }
+    if (((ccw->cmd_code & 0x0f) == CCW_CMD_TIC) &&
+        ((ccw->cmd_code & 0xf0) != 0)) {
+        return -EINVAL;
+    }
+
+    if (ccw->flags & CCW_FLAG_SUSPEND) {
+        return -ERESTART;
+    }
+
+    check_len = !((ccw->flags & CCW_FLAG_SLI) && !(ccw->flags & CCW_FLAG_DC));
+
+    /* Look at the command. */
+    switch (ccw->cmd_code) {
+    case CCW_CMD_NOOP:
+        /* Nothing to do. */
+        ret = 0;
+        break;
+    case CCW_CMD_BASIC_SENSE:
+        if (check_len) {
+            if (ccw->count != sizeof(sch->sense_data)) {
+                ret = -EINVAL;
+                break;
+            }
+        }
+        len = MIN(ccw->count, sizeof(sch->sense_data));
+        cpu_physical_memory_write(ccw->cda, sch->sense_data, len);
+        sch->curr_status.scsw.count = ccw->count - len;
+        memset(sch->sense_data, 0, sizeof(sch->sense_data));
+        ret = 0;
+        break;
+    case CCW_CMD_SENSE_ID:
+    {
+        uint8_t sense_bytes[256];
+
+        /* Sense ID information is device specific. */
+        memcpy(sense_bytes, &sch->id, sizeof(sense_bytes));
+        if (check_len) {
+            if (ccw->count != sizeof(sense_bytes)) {
+                ret = -EINVAL;
+                break;
+            }
+        }
+        len = MIN(ccw->count, sizeof(sense_bytes));
+        /*
+         * Only indicate 0xff in the first sense byte if we actually
+         * have enough place to store at least bytes 0-3.
+         */
+        if (len >= 4) {
+            stb_phys(ccw->cda, 0xff);
+        } else {
+            stb_phys(ccw->cda, 0);
+        }
+        i = 1;
+        for (i = 1; i < len - 1; i++) {
+            stb_phys(ccw->cda + i, sense_bytes[i]);
+        }
+        sch->curr_status.scsw.count = ccw->count - len;
+        ret = 0;
+        break;
+    }
+    case CCW_CMD_TIC:
+        if (sch->last_cmd->cmd_code == CCW_CMD_TIC) {
+            ret = -EINVAL;
+            break;
+        }
+        if (ccw->flags & (CCW_FLAG_CC | CCW_FLAG_DC)) {
+            ret = -EINVAL;
+            break;
+        }
+        sch->channel_prog = qemu_get_ram_ptr(ccw->cda);
+        ret = sch->channel_prog ? -EAGAIN : -EFAULT;
+        break;
+    default:
+        if (sch->ccw_cb) {
+            /* Handle device specific commands. */
+            ret = sch->ccw_cb(sch, ccw);
+        } else {
+            ret = -EOPNOTSUPP;
+        }
+        break;
+    }
+    sch->last_cmd = ccw;
+    if (ret == 0) {
+        if (ccw->flags & CCW_FLAG_CC) {
+            sch->channel_prog += 8;
+            ret = -EAGAIN;
+        }
+    }
+
+    return ret;
+}
+
+static void sch_handle_start_func(SubchDev *sch)
+{
+
+    PMCW *p = &sch->curr_status.pmcw;
+    SCSW *s = &sch->curr_status.scsw;
+    ORB *orb = sch->orb;
+    int path;
+    int ret;
+
+    /* Path management: In our simple css, we always choose the only path. */
+    path = 0x80;
+
+    if (!s->actl & SCSW_ACTL_SUSP) {
+        /* Look at the orb and try to execute the channel program. */
+        p->intparm = orb->intparm;
+        if (!(orb->lpm & path)) {
+            /* Generate a deferred cc 3 condition. */
+            s->cc = 3;
+            s->stctl = (SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND);
+            return;
+        }
+    } else {
+        s->actl &= ~(SCSW_ACTL_SUSP | SCSW_ACTL_RESUME_PEND);
+    }
+    sch->last_cmd = NULL;
+    do {
+        ret = css_interpret_ccw(sch, sch->channel_prog);
+        switch (ret) {
+        case -EAGAIN:
+            /* ccw chain, continue processing */
+            break;
+        case 0:
+            /* success */
+            s->actl &= ~SCSW_ACTL_START_PEND;
+            s->stctl = SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+                SCSW_STCTL_STATUS_PEND;
+            s->dstat = SCSW_DSTAT_CHANNEL_END | SCSW_DSTAT_DEVICE_END;
+            break;
+        case -EOPNOTSUPP:
+            /* unsupported command, generate unit check (command reject) */
+            s->actl &= ~SCSW_ACTL_START_PEND;
+            s->dstat = SCSW_DSTAT_UNIT_CHECK;
+            /* Set sense bit 0 in ecw0. */
+            sch->sense_data[0] = 0x80;
+            s->stctl = SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+                SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+            break;
+        case -EFAULT:
+            /* memory problem, generate channel data check */
+            s->actl &= ~SCSW_ACTL_START_PEND;
+            s->cstat = SCSW_CSTAT_DATA_CHECK;
+            s->stctl = SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+                SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+            break;
+        case -EBUSY:
+            /* subchannel busy, generate deferred cc 1 */
+            s->cc = 1;
+            s->stctl = SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+            break;
+        case -ERESTART:
+            /* channel program has been suspended */
+            s->actl &= ~SCSW_ACTL_START_PEND;
+            s->actl |= SCSW_ACTL_SUSP;
+            break;
+        default:
+            /* error, generate channel program check */
+            s->actl &= ~SCSW_ACTL_START_PEND;
+            s->cstat = SCSW_CSTAT_PROG_CHECK;
+            s->stctl = SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
+                SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
+            break;
+        }
+    } while (ret == -EAGAIN);
+
+}
+
+int css_handle_sch_io(uint32_t sch_id, uint8_t func, uint64_t orb, void *scsw,
+                      void *pmcw)
+{
+    SubchDev *sch;
+    int cssid;
+    int ssid;
+    int schid;
+    int m;
+    int ret;
+    int notify = 0;
+
+    ret = ioinst_disassemble_sch_ident(sch_id, &m, &cssid, &ssid, &schid);
+    if (ret) {
+        return ret;
+    }
+    sch = css_find_subch(m, cssid, ssid, schid);
+    if (!sch) {
+        return -ENODEV;
+    }
+    qemu_mutex_lock(&sch->mutex);
+    memcpy(&sch->curr_status.pmcw, pmcw, sizeof(PMCW));
+    switch (func) {
+    case CSS_DO_CSCH:
+        memcpy(&sch->curr_status.scsw, scsw, sizeof(SCSW));
+        /* fallthrough */
+    case CSS_DO_CSCH_SIMPLE:
+        sch_handle_clear_func(sch);
+        notify = 1;
+        break;
+    case CSS_DO_HSCH:
+        memcpy(&sch->curr_status.scsw, scsw, sizeof(SCSW));
+        /* fallthrough */
+    case CSS_DO_HSCH_SIMPLE:
+        sch_handle_halt_func(sch);
+        notify = 1;
+        break;
+    case CSS_DO_SSCH:
+        memcpy(&sch->curr_status.scsw, scsw, sizeof(SCSW));
+        sch->orb = qemu_get_ram_ptr(orb);
+        sch->channel_prog = qemu_get_ram_ptr(sch->orb->cpa);
+        /* fallthrough */
+    case CSS_DO_SSCH_SIMPLE:
+        sch_handle_start_func(sch);
+        notify = 1;
+        break;
+    case CSS_DO_RSCH:
+        memcpy(&sch->curr_status.scsw, scsw, sizeof(SCSW));
+        sch_handle_start_func(sch);
+        notify = 1;
+        break;
+    case CSS_DO_XSCH:
+        sch->orb = NULL;
+        sch->channel_prog = NULL;
+        sch->last_cmd = NULL;
+        break;
+    }
+    if (notify) {
+        css_inject_io_interrupt(sch, func);
+    }
+    qemu_mutex_unlock(&sch->mutex);
+    return 0;
+}
+
+static int css_add_virtual_chpid(uint8_t cssid, uint8_t chpid, uint8_t type)
+{
+    CssImage *css;
+
+    if (cssid > MAX_CSSID) {
+        return -EINVAL;
+    }
+    css = channel_subsys->css[cssid];
+    if (!css) {
+        return -EINVAL;
+    }
+    if (css->chpids[chpid].in_use) {
+        return -EEXIST;
+    }
+    css->chpids[chpid].in_use = 1;
+    css->chpids[chpid].type = type;
+
+    s390_chp_hotplug(cssid, chpid, type, 1, 1);
+
+    return 0;
+}
+
+void css_sch_build_virtual_schib(SubchDev *sch, uint8_t chpid, uint8_t type)
+{
+    PMCW *p = &sch->curr_status.pmcw;
+    SCSW *s = &sch->curr_status.scsw;
+    int i;
+    CssImage *css = channel_subsys->css[sch->cssid];
+
+    assert(css != NULL);
+    memset(p, 0, sizeof(PMCW));
+    p->dnv = 1;
+    p->dev = sch->devno;
+    /* single path */
+    p->pim = 0x80;
+    p->pom = 0xff;
+    p->pam = 0x80;
+    p->chpid[0] = chpid;
+    if (!css->chpids[chpid].in_use) {
+        css_add_virtual_chpid(sch->cssid, chpid, type);
+    }
+
+    memset(s, 0, sizeof(SCSW));
+    sch->curr_status.mba = 0;
+    for (i = 0; i < 4; i++) {
+        sch->curr_status.mda[i] = 0;
+    }
+}
+
+SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid, uint16_t schid)
+{
+    uint8_t real_cssid;
+
+    real_cssid = (!m && (cssid == 0)) ? channel_subsys->default_cssid : cssid;
+
+    if (!channel_subsys->css[real_cssid]) {
+        return NULL;
+    }
+
+    if (!channel_subsys->css[real_cssid]->sch_set[ssid]) {
+        return NULL;
+    }
+
+    return channel_subsys->css[real_cssid]->sch_set[ssid]->sch[schid];
+}
+
+bool css_present(uint8_t cssid)
+{
+    return (channel_subsys->css[cssid] != NULL);
+}
+
+static void css_init(void)
+{
+    channel_subsys = g_malloc0(sizeof(*channel_subsys));
+}
+machine_init(css_init);
+
+void css_reset_sch(SubchDev *sch)
+{
+    PMCW *p = &sch->curr_status.pmcw;
+
+    p->intparm = 0;
+    p->isc = 0;
+    p->ena = 0;
+    p->lm = 0;
+    p->mme = 0;
+    p->mp = 0;
+    p->tf = 0;
+    p->dnv = 1;
+    p->dev = sch->devno;
+    p->pim = 0x80;
+    p->lpm = p->pim;
+    p->pnom = 0;
+    p->lpum = 0;
+    p->mbi = 0;
+    p->pom = 0xff;
+    p->pam = 0x80;
+    p->mbfc = 0;
+    p->xmwme = 0;
+    p->csense = 0;
+
+    memset(&sch->curr_status.scsw, 0, sizeof(sch->curr_status.scsw));
+    sch->curr_status.mba = 0;
+
+    sch->channel_prog = NULL;
+    sch->last_cmd = NULL;
+    sch->orb = NULL;
+}
+
+void css_reset(void)
+{
+    /* Nothing for now. */
+}
diff --git a/hw/s390x/css.h b/hw/s390x/css.h
new file mode 100644
index 0000000..f3590eb
--- /dev/null
+++ b/hw/s390x/css.h
@@ -0,0 +1,60 @@ 
+/*
+ * Channel subsystem structures and definitions.
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef CSS_H
+#define CSS_H
+
+#include "ioinst.h"
+
+/* Channel subsystem constants. */
+#define MAX_SCHID 65535
+#define MAX_SSID 3
+#define MAX_CSSID 254 /* 255 is reserved */
+#define MAX_CHPID 255
+
+#define MAX_CIWS 62
+
+typedef struct SenseId {
+    /* common part */
+    uint8_t reserved;        /* always 0x'FF' */
+    uint16_t cu_type;        /* control unit type */
+    uint8_t cu_model;        /* control unit model */
+    uint16_t dev_type;       /* device type */
+    uint8_t dev_model;       /* device model */
+    uint8_t unused;          /* padding byte */
+    /* extended part */
+    uint32_t ciw[MAX_CIWS];  /* variable # of CIWs */
+} QEMU_PACKED SenseId;
+
+struct SubchDev {
+    /* channel-subsystem related things: */
+    uint8_t cssid;
+    uint8_t ssid;
+    uint16_t schid;
+    uint16_t devno;
+    SCHIB curr_status;
+    uint8_t sense_data[32];
+    CCW1 *channel_prog;
+    CCW1 *last_cmd;
+    ORB *orb;
+    QemuMutex mutex;
+    /* transport-provided data: */
+    int (*ccw_cb) (SubchDev *, CCW1 *);
+    SenseId id;
+    void *driver_data;
+};
+
+int css_create_css_image(uint8_t cssid, bool default_image);
+void css_sch_build_virtual_schib(SubchDev *sch, uint8_t chpid, uint8_t type);
+void css_reset(void);
+void css_reset_sch(SubchDev *sch);
+
+#endif
diff --git a/target-s390x/Makefile.objs b/target-s390x/Makefile.objs
index 80be3bb..6e5bef3 100644
--- a/target-s390x/Makefile.objs
+++ b/target-s390x/Makefile.objs
@@ -1,5 +1,5 @@ 
 obj-y += translate.o op_helper.o helper.o cpu.o interrupt.o
-obj-$(CONFIG_SOFTMMU) += machine.o
+obj-$(CONFIG_SOFTMMU) += machine.o ioinst.o
 obj-$(CONFIG_KVM) += kvm.o
 
 $(obj)/op_helper.o: QEMU_CFLAGS += $(HELPER_CFLAGS)
diff --git a/target-s390x/cpu.h b/target-s390x/cpu.h
index 18ac6e3..c072e1d 100644
--- a/target-s390x/cpu.h
+++ b/target-s390x/cpu.h
@@ -339,6 +339,25 @@  static inline unsigned s390_del_running_cpu(CPUS390XState *env)
 void cpu_lock(void);
 void cpu_unlock(void);
 
+typedef struct SubchDev SubchDev;
+typedef struct SCHIB SCHIB;
+typedef struct ORB ORB;
+
+#ifndef CONFIG_USER_ONLY
+SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid, uint16_t schid);
+void css_conditional_io_interrupt(SubchDev *sch);
+bool css_present(uint8_t cssid);
+#else
+static inline SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid,
+                                       uint16_t schid)
+{
+    return NULL;
+}
+static inline void css_conditional_io_interrupt(SubchDev *sch)
+{
+}
+#endif
+
 static inline void cpu_set_tls(CPUS390XState *env, target_ulong newtls)
 {
     env->aregs[0] = newtls >> 32;
@@ -999,4 +1018,111 @@  static inline void cpu_pc_from_tb(CPUS390XState *env, TranslationBlock* tb)
     env->psw.addr = tb->pc;
 }
 
+int css_handle_sch_io(uint32_t sch_id, uint8_t func, uint64_t orb, void *scsw,
+                      void *pmcw);
+#ifdef CONFIG_KVM
+int kvm_s390_sch_hotplug(uint8_t cssid, uint8_t ssid, uint16_t schid,
+                         uint16_t devno, void *data, int hotplugged, int add,
+                         int virtual);
+int kvm_s390_chp_hotplug(uint8_t cssid, uint8_t chpid, uint8_t type, int add,
+                         int virtual);
+int kvm_s390_io_interrupt(uint8_t cssid, uint8_t ssid, uint16_t schid,
+                          void *scsw, void *pmcw, void *sense,
+                          int unsolicited, uint8_t func);
+void kvm_s390_enable_css_support(CPUS390XState *env);
+int kvm_s390_new_css_image(uint8_t cssid, bool default_image);
+#else
+static inline int kvm_s390_sch_hotplug(uint8_t cssid, uint8_t ssid,
+                                       uint16_t schid, uint16_t devno,
+                                       void *data, int hotplugged, int add,
+                                       int virtual)
+{
+    return -EOPNOTSUPP;
+}
+static inline int kvm_s390_chp_hotplug(uint8_t cssid, uint8_t chpid,
+                                       uint8_t type, int add, int virtual)
+{
+    return -EOPNOTSUPP;
+}
+static inline int kvm_s390_io_interrupt(uint8_t cssid, uint8_t ssid,
+                                        uint16_t schid, void *scsw, void *pmcw,
+                                        void *sense, int unsolicited, uint8_t func)
+{
+    return -EOPNOTSUPP;
+}
+static inline void kvm_s390_enable_css_support(CPUS390XState *env)
+{
+}
+static inline int kvm_s390_new_css_image(uint8_t cssid, bool default_image)
+{
+    return -EOPNOTSUPP;
+}
+#endif
+
+static inline void s390_sch_hotplug(uint8_t cssid, uint8_t ssid, uint16_t schid,
+                                    uint16_t devno, void *data, int hotplugged,
+                                    int add, int virtual)
+{
+    int ret;
+
+    ret = kvm_s390_sch_hotplug(cssid, ssid, schid, devno, data, hotplugged,
+                               add, virtual);
+    if (ret == -EOPNOTSUPP) {
+        fprintf(stderr, "Hotplugging subchannels not supported\n");
+    }
+}
+
+static inline void s390_chp_hotplug(uint8_t cssid, uint8_t chpid, uint8_t type,
+                                    int add, int virtual)
+{
+    int ret;
+
+    ret = kvm_s390_chp_hotplug(cssid, chpid, type, add, virtual);
+    if (ret == -EOPNOTSUPP) {
+        fprintf(stderr, "Hotplugging chpids not supported\n");
+    }
+}
+
+static inline void s390_io_interrupt(uint8_t cssid, uint8_t ssid,
+                                     uint16_t schid, void *scsw, void *pmcw,
+                                     void *sense, int unsolicited,
+                                     uint8_t isc, uint32_t intparm, uint8_t func)
+{
+    int ret;
+
+    ret = kvm_s390_io_interrupt(cssid, ssid, schid, scsw, pmcw, sense,
+                                unsolicited, func);
+    if (ret == -EOPNOTSUPP) {
+        fprintf(stderr, "Injecting I/O interrupts not supported\n");
+    }
+}
+
+static inline void s390_new_css_image(uint8_t cssid, bool default_image)
+{
+    int ret;
+
+    ret = kvm_s390_new_css_image(cssid, default_image);
+
+    if (ret == -EOPNOTSUPP) {
+        /* Currently nothing needs to be done. */
+    }
+}
+
+#ifdef CONFIG_KVM
+#define CSS_DO_CSCH SCH_DO_CSCH
+#define CSS_DO_HSCH SCH_DO_HSCH
+#define CSS_DO_SSCH SCH_DO_SSCH
+#define CSS_DO_RSCH SCH_DO_RSCH
+#define CSS_DO_XSCH SCH_DO_XSCH
+#else
+#define CSS_DO_CSCH 0
+#define CSS_DO_HSCH 1
+#define CSS_DO_SSCH 2
+#define CSS_DO_RSCH 3
+#define CSS_DO_XSCH 4
+#endif
+#define CSS_DO_CSCH_SIMPLE 0xf0
+#define CSS_DO_HSCH_SIMPLE 0xf1
+#define CSS_DO_SSCH_SIMPLE 0xf2
+
 #endif
diff --git a/target-s390x/ioinst.c b/target-s390x/ioinst.c
new file mode 100644
index 0000000..8f358d5
--- /dev/null
+++ b/target-s390x/ioinst.c
@@ -0,0 +1,38 @@ 
+/*
+ * I/O instructions for S/390
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include "cpu.h"
+#include "ioinst.h"
+
+int ioinst_disassemble_sch_ident(uint32_t value, int *m, int *cssid, int *ssid,
+                                 int *schid)
+{
+    if (!(value & 0x00010000)) {
+        return -EINVAL;
+    }
+    if (!(value & 0x00080000)) {
+        if (value & 0xff000000) {
+            return -EINVAL;
+        }
+        *cssid = 0;
+        *m = 0;
+    } else {
+        *cssid = (value & 0xff000000) >> 24;
+        *m = 1;
+    }
+    *ssid = (value & 0x00060000) >> 17;
+    *schid = value & 0x0000ffff;
+    return 0;
+}
diff --git a/target-s390x/ioinst.h b/target-s390x/ioinst.h
new file mode 100644
index 0000000..c1377ca
--- /dev/null
+++ b/target-s390x/ioinst.h
@@ -0,0 +1,173 @@ 
+/*
+ * S/390 channel I/O instructions
+ *
+ * Copyright 2012 IBM Corp.
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+*/
+
+#ifndef IOINST_S390X_H
+#define IOINST_S390X_H
+
+/*
+ * Channel I/O related definitions, as defined in the Principles
+ * Of Operation (and taken from the Linux implementation).
+ */
+
+/* subchannel status word (command mode only) */
+typedef struct SCSW {
+    uint32_t key:4;
+    uint32_t sctl:1;
+    uint32_t eswf:1;
+    uint32_t cc:2;
+    uint32_t fmt:1;
+    uint32_t pfch:1;
+    uint32_t isic:1;
+    uint32_t alcc:1;
+    uint32_t ssi:1;
+    uint32_t zcc:1;
+    uint32_t ectl:1;
+    uint32_t pno:1;
+    uint32_t res:1;
+    uint32_t fctl:3;
+    uint32_t actl:7;
+    uint32_t stctl:5;
+    uint32_t cpa;
+    uint32_t dstat:8;
+    uint32_t cstat:8;
+    uint32_t count:16;
+} SCSW;
+
+/* path management control word */
+typedef struct PMCW {
+    uint32_t intparm;
+    uint32_t qf:1;
+    uint32_t w:1;
+    uint32_t isc:3;
+    uint32_t zeroes0:3;
+    uint32_t ena:1;
+    uint32_t lm:2;
+    uint32_t mme:2;
+    uint32_t mp:1;
+    uint32_t tf:1;
+    uint32_t dnv:1;
+    uint32_t dev:16;
+    uint8_t  lpm;
+    uint8_t  pnom;
+    uint8_t  lpum;
+    uint8_t  pim;
+    uint16_t mbi;
+    uint8_t  pom;
+    uint8_t  pam;
+    uint8_t  chpid[8];
+    uint32_t zeroes1:8;
+    uint32_t st:3;
+    uint32_t zeroes2:18;
+    uint32_t mbfc:1;
+    uint32_t xmwme:1;
+    uint32_t csense:1;
+} PMCW;
+
+/* subchannel information block */
+struct SCHIB {
+    PMCW pmcw;
+    SCSW scsw;
+    uint64_t mba;
+    uint8_t mda[4];
+};
+
+/* interruption response block */
+typedef struct IRB {
+    SCSW scsw;
+    uint32_t esw[5];
+    uint32_t ecw[8];
+    uint32_t emw[8];
+} IRB;
+
+/* operation request block */
+struct ORB {
+    uint32_t intparm;
+    uint32_t key:4;
+    uint32_t spnd:1;
+    uint32_t str:1;
+    uint32_t mod:1;
+    uint32_t sync:1;
+    uint32_t fmt:1;
+    uint32_t pfch:1;
+    uint32_t isic:1;
+    uint32_t alcc:1;
+    uint32_t ssic:1;
+    uint32_t zero0:1;
+    uint32_t c64:1;
+    uint32_t i2k:1;
+    uint32_t lpm:8;
+    uint32_t ils:1;
+    uint32_t midaw:1;
+    uint32_t zero1:5;
+    uint32_t orbx:1;
+    uint32_t cpa;
+};
+
+/* channel command word (type 1) */
+typedef struct CCW1 {
+    uint8_t cmd_code;
+    uint8_t flags;
+    uint16_t count;
+    uint32_t cda;
+} CCW1;
+
+#define CCW_FLAG_DC              0x80
+#define CCW_FLAG_CC              0x40
+#define CCW_FLAG_SLI             0x20
+#define CCW_FLAG_SKIP            0x10
+#define CCW_FLAG_PCI             0x08
+#define CCW_FLAG_IDA             0x04
+#define CCW_FLAG_SUSPEND         0x02
+
+#define CCW_CMD_NOOP             0x03
+#define CCW_CMD_BASIC_SENSE      0x04
+#define CCW_CMD_TIC              0x08
+#define CCW_CMD_SENSE_ID         0xe4
+
+#define SCSW_FCTL_CLEAR_FUNC     0x1
+#define SCSW_FCTL_HALT_FUNC      0x2
+#define SCSW_FCTL_START_FUNC     0x4
+
+#define SCSW_ACTL_SUSP           0x1
+#define SCSW_ACTL_DEVICE_ACTIVE  0x2
+#define SCSW_ACTL_SUBCH_ACTIVE   0x4
+#define SCSW_ACTL_CLEAR_PEND     0x8
+#define SCSW_ACTL_HALT_PEND      0x10
+#define SCSW_ACTL_START_PEND     0x20
+#define SCSW_ACTL_RESUME_PEND    0x40
+
+#define SCSW_STCTL_STATUS_PEND   0x1
+#define SCSW_STCTL_SECONDARY     0x2
+#define SCSW_STCTL_PRIMARY       0x4
+#define SCSW_STCTL_INTERMEDIATE  0x8
+#define SCSW_STCTL_ALERT         0x10
+
+#define SCSW_DSTAT_ATTENTION     0x80
+#define SCSW_DSTAT_STAT_MOD      0x40
+#define SCSW_DSTAT_CU_END        0x20
+#define SCSW_DSTAT_BUSY          0x10
+#define SCSW_DSTAT_CHANNEL_END   0x08
+#define SCSW_DSTAT_DEVICE_END    0x04
+#define SCSW_DSTAT_UNIT_CHECK    0x02
+#define SCSW_DSTAT_UNIT_EXCEP    0x01
+
+#define SCSW_CSTAT_PCI           0x80
+#define SCSW_CSTAT_INCORR_LEN    0x40
+#define SCSW_CSTAT_PROG_CHECK    0x20
+#define SCSW_CSTAT_PROT_CHECK    0x10
+#define SCSW_CSTAT_DATA_CHECK    0x08
+#define SCSW_CSTAT_CHN_CTRL_CHK  0x04
+#define SCSW_CSTAT_INTF_CTRL_CHK 0x02
+#define SCSW_CSTAT_CHAIN_CHECK   0x01
+
+int ioinst_disassemble_sch_ident(uint32_t value, int *m, int *cssid, int *ssid,
+                                 int *schid);
+#endif
diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c
index 07edf93..9aab6a8 100644
--- a/target-s390x/kvm.c
+++ b/target-s390x/kvm.c
@@ -26,6 +26,7 @@ 
 
 #include "qemu-common.h"
 #include "qemu-timer.h"
+#include "qemu-thread.h"
 #include "sysemu.h"
 #include "kvm.h"
 #include "cpu.h"
@@ -110,6 +111,7 @@  int kvm_arch_put_registers(CPUS390XState *env, int level)
 
     env->kvm_run->psw_addr = env->psw.addr;
     env->kvm_run->psw_mask = env->psw.mask;
+    env->kvm_run->s.regs.prefix = env->psa;
 
     return ret;
 }
@@ -131,6 +133,7 @@  int kvm_arch_get_registers(CPUS390XState *env)
 
     env->psw.addr = env->kvm_run->psw_addr;
     env->psw.mask = env->kvm_run->psw_mask;
+    env->psa = env->kvm_run->s.regs.prefix;
 
     return 0;
 }
@@ -507,6 +510,13 @@  int kvm_arch_handle_exit(CPUS390XState *env, struct kvm_run *run)
         case KVM_EXIT_S390_RESET:
             qemu_system_reset_request();
             break;
+        case KVM_EXIT_S390_SCH_IO:
+            ret = css_handle_sch_io(run->s390_sch_io.sch_id,
+                                    run->s390_sch_io.func,
+                                    run->s390_sch_io.orb,
+                                    run->s390_sch_io.scsw,
+                                    run->s390_sch_io.pmcw);
+            break;
         default:
             fprintf(stderr, "Unknown KVM exit: %d\n", run->exit_reason);
             break;
@@ -532,3 +542,111 @@  int kvm_arch_on_sigbus(int code, void *addr)
 {
     return 1;
 }
+
+int kvm_s390_sch_hotplug(uint8_t cssid, uint8_t ssid, uint16_t schid,
+                         uint16_t devno, void *data, int hotplugged, int add,
+                         int virtual)
+{
+    struct kvm_s390_sch_info sch_info;
+    S390CPU *cpu = s390_cpu_addr2state(0);
+    int ret;
+
+    if (!kvm_enabled()) {
+        return -EOPNOTSUPP;
+    }
+
+    /* Notify the kernel. */
+    sch_info.cssid = cssid;
+    sch_info.ssid = ssid;
+    sch_info.schid = schid;
+    sch_info.devno = devno;
+    memcpy(&sch_info.schib, data, sizeof(sch_info.schib));
+    sch_info.hotplugged = hotplugged;
+    sch_info.add = add;
+    sch_info.virtual = virtual;
+    ret = kvm_vm_ioctl(cpu->env.kvm_state, KVM_S390_CCW_HOTPLUG, &sch_info);
+    assert(ret == 0);
+    return ret;
+}
+
+int kvm_s390_chp_hotplug(uint8_t cssid, uint8_t chpid, uint8_t type, int add,
+                         int virtual)
+{
+    S390CPU *cpu = s390_cpu_addr2state(0);
+    struct kvm_s390_chp_info chpid_info;
+    int ret;
+
+    if (!kvm_enabled()) {
+        return -EOPNOTSUPP;
+    }
+
+    /* Notify the kernel. */
+    chpid_info.cssid = cssid;
+    chpid_info.chpid = chpid;
+    chpid_info.type = type;
+    chpid_info.add = 1;
+    chpid_info.virtual = 1;
+    ret = kvm_vm_ioctl(cpu->env.kvm_state, KVM_S390_CHP_HOTPLUG, &chpid_info);
+    assert(ret == 0);
+    return ret;
+}
+
+int kvm_s390_io_interrupt(uint8_t cssid, uint8_t ssid, uint16_t schid,
+                          void *scsw, void *pmcw, void *sense,
+                          int unsolicited, uint8_t func)
+{
+    S390CPU *cpu = s390_cpu_addr2state(0);
+    struct kvm_css_notify notify;
+    int ret;
+
+    if (!kvm_enabled()) {
+        return -EOPNOTSUPP;
+    }
+
+    notify.cssid = cssid;
+    notify.ssid = ssid;
+    notify.schid = schid;
+    if (!unsolicited) {
+        memcpy(&notify.scsw, scsw, sizeof(notify.scsw));
+        memcpy(&notify.pmcw, pmcw, sizeof(notify.pmcw));
+        memcpy(&notify.sense_data, sense, sizeof(notify.sense_data));
+        notify.func = func;
+    }
+    notify.unsolicited = unsolicited;
+    ret = kvm_vcpu_ioctl(&cpu->env, KVM_S390_CSS_NOTIFY, &notify);
+    assert(ret == 0);
+    return ret;
+}
+
+void kvm_s390_enable_css_support(CPUS390XState *env)
+{
+    struct kvm_enable_cap cap = {};
+    int r;
+
+    /* Activate host kernel channel subsystem support. */
+    if (kvm_enabled()) {
+        /* One CPU has to run */
+        s390_add_running_cpu(env);
+
+        cap.cap = KVM_CAP_S390_CSS_SUPPORT;
+        r = kvm_vcpu_ioctl(env, KVM_ENABLE_CAP, &cap);
+        assert(r == 0);
+    }
+}
+
+int kvm_s390_new_css_image(uint8_t cssid, bool default_image)
+{
+    S390CPU *cpu = s390_cpu_addr2state(0);
+    struct kvm_s390_css_info css_info;
+    int ret;
+
+    if (!kvm_enabled()) {
+        return -EOPNOTSUPP;
+    }
+
+    css_info.cssid = cssid;
+    css_info.default_image = default_image;
+    ret = kvm_vm_ioctl(cpu->env.kvm_state, KVM_S390_ADD_CSS, &css_info);
+    assert(ret == 0);
+    return ret;
+}