diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index 11c5dd4799..4208cb4295 100644
--- a/pc-bios/s390-ccw/Makefile
+++ b/pc-bios/s390-ccw/Makefile
@@ -9,7 +9,7 @@ $(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)
 
 .PHONY : all clean build-all
 
-OBJECTS = start.o main.o bootmap.o sclp-ascii.o virtio.o
+OBJECTS = start.o main.o bootmap.o sclp-ascii.o virtio.o virtio-scsi.o
 CFLAGS += -fPIE -fno-stack-protector -ffreestanding -march=z900
 CFLAGS += -fno-delete-null-pointer-checks -msoft-float
 LDFLAGS += -Wl,-pie -nostdlib
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 69a02feb87..1c9e0791ab 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -91,15 +91,11 @@ static void virtio_setup(uint64_t dev_info)
         }
     }
 
-    if (!found) {
-        panic("No virtio-blk device found!\n");
-    }
+    IPL_assert(found, "No virtio device found");
 
     virtio_setup_device(blk_schid);
 
-    if (!virtio_ipl_disk_is_valid()) {
-        panic("No valid hard disk detected.\n");
-    }
+    IPL_assert(virtio_ipl_disk_is_valid(), "No valid IPL device detected");
 }
 
 int main(void)
diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
index 4ab4d475c0..1d34e8c1aa 100644
--- a/pc-bios/s390-ccw/virtio.c
+++ b/pc-bios/s390-ccw/virtio.c
@@ -10,6 +10,7 @@
 
 #include "s390-ccw.h"
 #include "virtio.h"
+#include "virtio-scsi.h"
 
 #define VRING_WAIT_REPLY_TIMEOUT 3
 
@@ -26,6 +27,8 @@ static VDev vdev = {
     .ring_area = ring_area,
     .wait_reply_timeout = VRING_WAIT_REPLY_TIMEOUT,
     .schid = { .one = 1 },
+    .scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE,
+    .blk_factor = 1,
 };
 
 VDev *virtio_get_device(void)
@@ -284,6 +287,8 @@ int virtio_read_many(ulong sector, void *load_addr, int sec_num)
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
         return virtio_blk_read_many(&vdev, sector, load_addr, sec_num);
+    case VIRTIO_ID_SCSI:
+        return virtio_scsi_read_many(&vdev, sector, load_addr, sec_num);
     }
     panic("\n! No readable IPL device !\n");
     return -1;
@@ -317,62 +322,6 @@ int virtio_read(ulong sector, void *load_addr)
     return virtio_read_many(sector, load_addr, 1);
 }
 
-VirtioGDN virtio_guessed_disk_nature(void)
-{
-    return vdev.guessed_disk_nature;
-}
-
-void virtio_assume_scsi(void)
-{
-    vdev.guessed_disk_nature = VIRTIO_GDN_SCSI;
-    switch (vdev.senseid.cu_model) {
-    case VIRTIO_ID_BLOCK:
-        vdev.config.blk.blk_size = 512;
-        vdev.config.blk.physical_block_exp = 0;
-        break;
-    }
-}
-
-void virtio_assume_iso9660(void)
-{
-    vdev.guessed_disk_nature = VIRTIO_GDN_CDROM;
-    switch (vdev.senseid.cu_model) {
-    case VIRTIO_ID_BLOCK:
-        vdev.config.blk.blk_size = 2048;
-        vdev.config.blk.physical_block_exp = 0;
-        break;
-    }
-}
-
-void virtio_assume_eckd(void)
-{
-    vdev.guessed_disk_nature = VIRTIO_GDN_DASD;
-    switch (vdev.senseid.cu_model) {
-    case VIRTIO_ID_BLOCK:
-        vdev.config.blk.blk_size = 4096;
-        vdev.config.blk.physical_block_exp = 0;
-
-        /* this must be here to calculate code segment position */
-        vdev.config.blk.geometry.heads = 15;
-        vdev.config.blk.geometry.sectors = 12;
-        break;
-    }
-}
-
-bool virtio_disk_is_scsi(void)
-{
-    if (vdev.guessed_disk_nature == VIRTIO_GDN_SCSI) {
-        return true;
-    }
-    switch (vdev.senseid.cu_model) {
-    case VIRTIO_ID_BLOCK:
-        return (vdev.config.blk.geometry.heads == 255)
-            && (vdev.config.blk.geometry.sectors == 63)
-            && (virtio_get_block_size()  == 512);
-    }
-    return false;
-}
-
 /*
  * Other supported value pairs, if any, would need to be added here.
  * Note: head count is always 15.
@@ -392,6 +341,75 @@ static inline u8 virtio_eckd_sectors_for_block_size(int size)
     return 0;
 }
 
+VirtioGDN virtio_guessed_disk_nature(void)
+{
+    return vdev.guessed_disk_nature;
+}
+
+void virtio_assume_scsi(void)
+{
+    switch (vdev.senseid.cu_model) {
+    case VIRTIO_ID_BLOCK:
+        vdev.guessed_disk_nature = VIRTIO_GDN_SCSI;
+        vdev.config.blk.blk_size = VIRTIO_SCSI_BLOCK_SIZE;
+        vdev.config.blk.physical_block_exp = 0;
+        vdev.blk_factor = 1;
+        break;
+    case VIRTIO_ID_SCSI:
+        vdev.scsi_block_size = VIRTIO_SCSI_BLOCK_SIZE;
+        break;
+    }
+}
+
+void virtio_assume_iso9660(void)
+{
+    switch (vdev.senseid.cu_model) {
+    case VIRTIO_ID_BLOCK:
+        vdev.guessed_disk_nature = VIRTIO_GDN_SCSI;
+        vdev.config.blk.blk_size = VIRTIO_ISO_BLOCK_SIZE;
+        vdev.config.blk.physical_block_exp = 0;
+        vdev.blk_factor = VIRTIO_ISO_BLOCK_SIZE / VIRTIO_SECTOR_SIZE;
+        break;
+    case VIRTIO_ID_SCSI:
+        vdev.scsi_block_size = VIRTIO_ISO_BLOCK_SIZE;
+        break;
+    }
+}
+
+void virtio_assume_eckd(void)
+{
+    vdev.guessed_disk_nature = VIRTIO_GDN_DASD;
+    vdev.blk_factor = 1;
+    vdev.config.blk.physical_block_exp = 0;
+    switch (vdev.senseid.cu_model) {
+    case VIRTIO_ID_BLOCK:
+        vdev.config.blk.blk_size = 4096;
+        break;
+    case VIRTIO_ID_SCSI:
+        vdev.config.blk.blk_size = vdev.scsi_block_size;
+        break;
+    }
+    vdev.config.blk.geometry.heads = 15;
+    vdev.config.blk.geometry.sectors =
+        virtio_eckd_sectors_for_block_size(vdev.config.blk.blk_size);
+}
+
+bool virtio_disk_is_scsi(void)
+{
+    if (vdev.guessed_disk_nature == VIRTIO_GDN_SCSI) {
+        return true;
+    }
+    switch (vdev.senseid.cu_model) {
+    case VIRTIO_ID_BLOCK:
+        return (vdev.config.blk.geometry.heads == 255)
+            && (vdev.config.blk.geometry.sectors == 63)
+            && (virtio_get_block_size()  == VIRTIO_SCSI_BLOCK_SIZE);
+    case VIRTIO_ID_SCSI:
+        return true;
+    }
+    return false;
+}
+
 bool virtio_disk_is_eckd(void)
 {
     const int block_size = virtio_get_block_size();
@@ -404,6 +422,8 @@ bool virtio_disk_is_eckd(void)
         return (vdev.config.blk.geometry.heads == 15)
             && (vdev.config.blk.geometry.sectors ==
                 virtio_eckd_sectors_for_block_size(block_size));
+    case VIRTIO_ID_SCSI:
+        return false;
     }
     return false;
 }
@@ -418,6 +438,8 @@ int virtio_get_block_size(void)
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
         return vdev.config.blk.blk_size << vdev.config.blk.physical_block_exp;
+    case VIRTIO_ID_SCSI:
+        return vdev.scsi_block_size;
     }
     return 0;
 }
@@ -427,6 +449,9 @@ uint8_t virtio_get_heads(void)
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
         return vdev.config.blk.geometry.heads;
+    case VIRTIO_ID_SCSI:
+        return vdev.guessed_disk_nature == VIRTIO_GDN_DASD
+               ? vdev.config.blk.geometry.heads : 255;
     }
     return 0;
 }
@@ -436,25 +461,33 @@ uint8_t virtio_get_sectors(void)
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
         return vdev.config.blk.geometry.sectors;
+    case VIRTIO_ID_SCSI:
+        return vdev.guessed_disk_nature == VIRTIO_GDN_DASD
+               ? vdev.config.blk.geometry.sectors : 63;
     }
     return 0;
 }
 
 uint64_t virtio_get_blocks(void)
 {
+    const uint64_t factor = virtio_get_block_size() / VIRTIO_SECTOR_SIZE;
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
-        return vdev.config.blk.capacity /
-               (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
+        return vdev.config.blk.capacity / factor;
+    case VIRTIO_ID_SCSI:
+        return vdev.scsi_last_block / factor;
     }
     return 0;
 }
 
 static void virtio_setup_ccw(VDev *vdev)
 {
-    int i, cfg_size;
+    int i, cfg_size = 0;
     unsigned char status = VIRTIO_CONFIG_S_DRIVER_OK;
 
+    IPL_assert(virtio_is_supported(vdev->schid), "PE");
+    /* device ID has been established now */
+
     vdev->config.blk.blk_size = 0; /* mark "illegal" - setup started... */
     vdev->guessed_disk_nature = VIRTIO_GDN_NONE;
 
@@ -466,6 +499,11 @@ static void virtio_setup_ccw(VDev *vdev)
         vdev->cmd_vr_idx = 0;
         cfg_size = sizeof(vdev->config.blk);
         break;
+    case VIRTIO_ID_SCSI:
+        vdev->nr_vqs = 3;
+        vdev->cmd_vr_idx = VR_REQUEST;
+        cfg_size = sizeof(vdev->config.scsi);
+        break;
     default:
         panic("Unsupported virtio device\n");
     }
@@ -511,6 +549,7 @@ void virtio_setup_device(SubChannelId schid)
 
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
+        sclp_print("Using virtio-blk.\n");
         if (!virtio_ipl_disk_is_valid()) {
             /* make sure all getters but blocksize return 0 for
              * invalid IPL disk
@@ -519,6 +558,15 @@ void virtio_setup_device(SubChannelId schid)
             virtio_assume_scsi();
         }
         break;
+    case VIRTIO_ID_SCSI:
+        IPL_assert(vdev.config.scsi.sense_size == VIRTIO_SCSI_SENSE_SIZE,
+            "Config: sense size mismatch");
+        IPL_assert(vdev.config.scsi.cdb_size == VIRTIO_SCSI_CDB_SIZE,
+            "Config: CDB size mismatch");
+
+        sclp_print("Using virtio-scsi.\n");
+        virtio_scsi_setup(&vdev);
+        break;
     default:
         panic("\n! No IPL device available !\n");
     }
@@ -535,6 +583,7 @@ bool virtio_is_supported(SubChannelId schid)
     if (vdev.senseid.cu_type == 0x3832) {
         switch (vdev.senseid.cu_model) {
         case VIRTIO_ID_BLOCK:
+        case VIRTIO_ID_SCSI:
             return true;
         }
     }
diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h
index 57c71a2c93..3c6e91510e 100644
--- a/pc-bios/s390-ccw/virtio.h
+++ b/pc-bios/s390-ccw/virtio.h
@@ -28,6 +28,7 @@ enum VirtioDevType {
     VIRTIO_ID_BLOCK = 2,
     VIRTIO_ID_CONSOLE = 3,
     VIRTIO_ID_BALLOON = 5,
+    VIRTIO_ID_SCSI = 8,
 };
 typedef enum VirtioDevType VirtioDevType;
 
@@ -224,12 +225,35 @@ extern uint64_t virtio_get_blocks(void);
 extern int virtio_read_many(ulong sector, void *load_addr, int sec_num);
 
 #define VIRTIO_SECTOR_SIZE 512
+#define VIRTIO_ISO_BLOCK_SIZE 2048
+#define VIRTIO_SCSI_BLOCK_SIZE 512
 
 static inline ulong virtio_sector_adjust(ulong sector)
 {
     return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
 }
 
+struct VirtioScsiConfig {
+    uint32_t num_queues;
+    uint32_t seg_max;
+    uint32_t max_sectors;
+    uint32_t cmd_per_lun;
+    uint32_t event_info_size;
+    uint32_t sense_size;
+    uint32_t cdb_size;
+    uint16_t max_channel;
+    uint16_t max_target;
+    uint32_t max_lun;
+} __attribute__((packed));
+typedef struct VirtioScsiConfig VirtioScsiConfig;
+
+struct ScsiDevice {
+    uint16_t channel;   /* Always 0 in QEMU     */
+    uint16_t target;    /* will be scanned over */
+    uint32_t lun;       /* will be reported     */
+};
+typedef struct ScsiDevice ScsiDevice;
+
 struct VDev {
     int nr_vqs;
     VRing *vrings;
@@ -241,7 +265,15 @@ struct VDev {
     SenseId senseid;
     union {
         VirtioBlkConfig blk;
+        VirtioScsiConfig scsi;
     } config;
+    ScsiDevice *scsi_device;
+    bool is_cdrom;
+    int scsi_block_size;
+    int blk_factor;
+    uint64_t scsi_last_block;
+    uint32_t scsi_dev_cyls;
+    uint8_t scsi_dev_heads;
 };
 typedef struct VDev VDev;