diff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile
index 11c5dd4..4208cb4 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 69a02fe..1c9e079 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 4ab4d47..1d34e8c 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,6 +322,25 @@ int virtio_read(ulong sector, void *load_addr)
     return virtio_read_many(sector, load_addr, 1);
 }
 
+/*
+ * Other supported value pairs, if any, would need to be added here.
+ * Note: head count is always 15.
+ */
+static inline u8 virtio_eckd_sectors_for_block_size(int size)
+{
+    switch (size) {
+    case 512:
+        return 49;
+    case 1024:
+        return 33;
+    case 2048:
+        return 21;
+    case 4096:
+        return 12;
+    }
+    return 0;
+}
+
 VirtioGDN virtio_guessed_disk_nature(void)
 {
     return vdev.guessed_disk_nature;
@@ -324,22 +348,30 @@ VirtioGDN virtio_guessed_disk_nature(void)
 
 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.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)
 {
-    vdev.guessed_disk_nature = VIRTIO_GDN_CDROM;
     switch (vdev.senseid.cu_model) {
     case VIRTIO_ID_BLOCK:
-        vdev.config.blk.blk_size = 2048;
+        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;
     }
 }
@@ -347,16 +379,19 @@ void virtio_assume_iso9660(void)
 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;
-        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;
+    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)
@@ -368,30 +403,13 @@ bool virtio_disk_is_scsi(void)
     case VIRTIO_ID_BLOCK:
         return (vdev.config.blk.geometry.heads == 255)
             && (vdev.config.blk.geometry.sectors == 63)
-            && (virtio_get_block_size()  == 512);
+            && (virtio_get_block_size()  == VIRTIO_SCSI_BLOCK_SIZE);
+    case VIRTIO_ID_SCSI:
+        return true;
     }
     return false;
 }
 
-/*
- * Other supported value pairs, if any, would need to be added here.
- * Note: head count is always 15.
- */
-static inline u8 virtio_eckd_sectors_for_block_size(int size)
-{
-    switch (size) {
-    case 512:
-        return 49;
-    case 1024:
-        return 33;
-    case 2048:
-        return 21;
-    case 4096:
-        return 12;
-    }
-    return 0;
-}
-
 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 57c71a2..3c6e915 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;
 
