diff --git a/hw/s390x/css.c b/hw/s390x/css.c
index 1c526fd7e2..c1ec83f08f 100644
--- a/hw/s390x/css.c
+++ b/hw/s390x/css.c
@@ -1297,11 +1297,16 @@ static void copy_schib_to_guest(SCHIB *dest, const SCHIB *src)
     }
 }
 
-int css_do_stsch(SubchDev *sch, SCHIB *schib)
+IOInstEnding css_do_stsch(SubchDev *sch, SCHIB *schib)
 {
+    if (sch->update_schib &&
+        (sch->update_schib(sch) != IOINST_CC_EXPECTED)) {
+        return IOINST_CC_NOT_OPERATIONAL;
+    }
+
     /* Use current status. */
     copy_schib_to_guest(schib, &sch->curr_status);
-    return 0;
+    return IOINST_CC_EXPECTED;
 }
 
 static void copy_pmcw_from_guest(PMCW *dest, const PMCW *src)
@@ -1586,6 +1591,11 @@ int css_do_tsch_get_irb(SubchDev *sch, IRB *target_irb, int *irb_len)
     uint16_t stctl;
     IRB irb;
 
+    if (sch->update_schib &&
+        (sch->update_schib(sch) != IOINST_CC_EXPECTED)) {
+        return IOINST_CC_NOT_OPERATIONAL;
+    }
+
     if (~(p->flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) {
         return 3;
     }
diff --git a/hw/s390x/s390-ccw.c b/hw/s390x/s390-ccw.c
index 4a9d4d2534..491697137c 100644
--- a/hw/s390x/s390-ccw.c
+++ b/hw/s390x/s390-ccw.c
@@ -28,6 +28,17 @@ IOInstEnding s390_ccw_cmd_request(SubchDev *sch)
     return cdc->handle_request(sch);
 }
 
+static IOInstEnding s390_ccw_update_schib(SubchDev *sch)
+{
+    S390CCWDeviceClass *cdc = S390_CCW_DEVICE_GET_CLASS(sch->driver_data);
+
+    if (!cdc->update_schib) {
+        /* g_assert_not_reached()? */
+        return IOINST_CC_NOT_OPERATIONAL;
+    }
+    return cdc->update_schib(sch);
+}
+
 static void s390_ccw_get_dev_info(S390CCWDevice *cdev,
                                   char *sysfsdev,
                                   Error **errp)
@@ -83,6 +94,7 @@ static void s390_ccw_realize(S390CCWDevice *cdev, char *sysfsdev, Error **errp)
     }
     sch->driver_data = cdev;
     sch->do_subchannel_work = do_subchannel_work_passthrough;
+    sch->update_schib = s390_ccw_update_schib;
 
     ccw_dev->sch = sch;
     ret = css_sch_build_schib(sch, &cdev->hostid);
diff --git a/hw/vfio/ccw.c b/hw/vfio/ccw.c
index d812cecfe0..0eca3453af 100644
--- a/hw/vfio/ccw.c
+++ b/hw/vfio/ccw.c
@@ -33,6 +33,7 @@ typedef struct VFIOCCWDevice {
     struct ccw_io_region *io_region;
     EventNotifier io_notifier;
 
+    bool schib_need_update;
     uint64_t schib_region_size;
     uint64_t schib_region_offset;
     struct ccw_schib_region *schib_region;
@@ -97,6 +98,57 @@ again:
     }
 }
 
+static IOInstEnding vfio_ccw_update_schib(SubchDev *sch)
+{
+
+    S390CCWDevice *cdev = sch->driver_data;
+    VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
+    struct ccw_schib_region *region = vcdev->schib_region;
+    PMCW *p = &sch->curr_status.pmcw;
+    SCSW *s = &sch->curr_status.scsw;
+    SCHIB *schib;
+    int size;
+    int i;
+
+    /*
+     * If there is no update that interested us since last read,
+     * we do not read then.
+     */
+    if (!vcdev->schib_need_update) {
+        return IOINST_CC_EXPECTED;
+    }
+    vcdev->schib_need_update = false;
+
+    /* Read schib region, and update schib for virtual subchannel. */
+    size = pread(vcdev->vdev.fd, region, vcdev->schib_region_size,
+                 vcdev->schib_region_offset);
+    if (size != vcdev->schib_region_size) {
+        return IOINST_CC_NOT_OPERATIONAL;
+    }
+    if (region->cc) {
+        g_assert(region->cc == IOINST_CC_NOT_OPERATIONAL);
+        return region->cc;
+    }
+
+    schib = (SCHIB *)region->schib_area;
+
+    /* Path mask. */
+    p->pim = schib->pmcw.pim;
+    p->pam = schib->pmcw.pam;
+    p->pom = schib->pmcw.pom;
+
+    /* We use PNO and PNOM to indicate path related events. */
+    p->pnom = ~schib->pmcw.pam;
+    s->flags |= SCSW_FLAGS_MASK_PNO;
+
+    /* Chp id. */
+    for (i = 0; i < ARRAY_SIZE(p->chpid); i++) {
+        p->chpid[i] = schib->pmcw.chpid[i];
+    }
+
+    return region->cc;
+}
+
 static void vfio_ccw_reset(DeviceState *dev)
 {
     CcwDevice *ccw_dev = DO_UPCAST(CcwDevice, parent_obj, dev);
@@ -182,7 +234,9 @@ static void vfio_ccw_chp_notifier_handler(void *opaque)
         return;
     }
 
-    /* TODO: further process on path informaion. */
+    vcdev->schib_need_update = true;
+
+    /* TODO: Generate channel path crw? */
 }
 
 static void vfio_ccw_register_event_notifier(VFIOCCWDevice *vcdev, int irq,
@@ -444,6 +498,8 @@ static void vfio_ccw_realize(DeviceState *dev, Error **errp)
         goto out_notifier_err;
     }
 
+    vcdev->schib_need_update = true;
+
     return;
 
 out_notifier_err:
@@ -503,6 +559,7 @@ static void vfio_ccw_class_init(ObjectClass *klass, void *data)
     dc->reset = vfio_ccw_reset;
 
     cdc->handle_request = vfio_ccw_handle_request;
+    cdc->update_schib = vfio_ccw_update_schib;
 }
 
 static const TypeInfo vfio_ccw_info = {
diff --git a/include/hw/s390x/css.h b/include/hw/s390x/css.h
index 35facb47d2..bcb433af4a 100644
--- a/include/hw/s390x/css.h
+++ b/include/hw/s390x/css.h
@@ -137,6 +137,7 @@ struct SubchDev {
     int (*ccw_cb) (SubchDev *, CCW1);
     void (*disable_cb)(SubchDev *);
     IOInstEnding (*do_subchannel_work) (SubchDev *);
+    IOInstEnding (*update_schib) (SubchDev *);
     SenseId id;
     void *driver_data;
 };
@@ -237,7 +238,7 @@ SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid,
                          uint16_t schid);
 bool css_subch_visible(SubchDev *sch);
 void css_conditional_io_interrupt(SubchDev *sch);
-int css_do_stsch(SubchDev *sch, SCHIB *schib);
+IOInstEnding css_do_stsch(SubchDev *sch, SCHIB *schib);
 bool css_schid_final(int m, uint8_t cssid, uint8_t ssid, uint16_t schid);
 IOInstEnding css_do_msch(SubchDev *sch, const SCHIB *schib);
 IOInstEnding css_do_xsch(SubchDev *sch);
diff --git a/include/hw/s390x/s390-ccw.h b/include/hw/s390x/s390-ccw.h
index 7d15a1a5d4..96e6a00e3e 100644
--- a/include/hw/s390x/s390-ccw.h
+++ b/include/hw/s390x/s390-ccw.h
@@ -34,6 +34,7 @@ typedef struct S390CCWDeviceClass {
     void (*realize)(S390CCWDevice *dev, char *sysfsdev, Error **errp);
     void (*unrealize)(S390CCWDevice *dev, Error **errp);
     IOInstEnding (*handle_request) (SubchDev *sch);
+    IOInstEnding (*update_schib) (SubchDev *);
 } S390CCWDeviceClass;
 
 #endif
diff --git a/target/s390x/ioinst.c b/target/s390x/ioinst.c
index 83c164a168..2c841bb66f 100644
--- a/target/s390x/ioinst.c
+++ b/target/s390x/ioinst.c
@@ -257,8 +257,7 @@ void ioinst_handle_stsch(S390CPU *cpu, uint64_t reg1, uint32_t ipb,
     sch = css_find_subch(m, cssid, ssid, schid);
     if (sch) {
         if (css_subch_visible(sch)) {
-            css_do_stsch(sch, &schib);
-            cc = 0;
+            cc = css_do_stsch(sch, &schib);
         } else {
             /* Indicate no more subchannels in this css/ss */
             cc = 3;
