Blob Blame Raw
diff --git a/Makefile b/Makefile
index 7d511e8..fb9bfaf 100644
--- a/Makefile
+++ b/Makefile
@@ -12,10 +12,10 @@ OUT=out/
 
 # Source files
 SRCBOTH=misc.c stacks.c pmm.c output.c util.c block.c floppy.c ata.c mouse.c \
-        kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \
-        pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
-        usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
-        virtio-ring.c virtio-pci.c virtio-blk.c apm.c ahci.c
+    kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \
+    pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
+    usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
+    virtio-ring.c virtio-pci.c virtio-blk.c virtio-scsi.c apm.c ahci.c
 SRC16=$(SRCBOTH) system.c disk.c font.c
 SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \
       acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \
diff --git a/src/Kconfig b/src/Kconfig
index 338f51a..8de3503 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -109,10 +109,16 @@ menu "Hardware support"
             Support for AHCI disk code.
     config VIRTIO_BLK
         depends on DRIVES && !COREBOOT
-        bool "VirtIO controllers"
+        bool "virtio-blk controllers"
         default y
         help
-            Support boot from virtio storage.
+            Support boot from virtio-blk storage.
+    config VIRTIO_SCSI
+        depends on DRIVES && !COREBOOT
+        bool "virtio-scsi controllers"
+        default y
+        help
+            Support boot from virtio-scsi storage.
     config FLOPPY
         depends on DRIVES
         bool "Floppy controller"
diff --git a/src/block.c b/src/block.c
index f7e7851..e607d67 100644
--- a/src/block.c
+++ b/src/block.c
@@ -11,8 +11,8 @@
 #include "util.h" // dprintf
 #include "ata.h" // process_ata_op
 #include "ahci.h" // process_ahci_op
-#include "usb-msc.h" // process_usb_op
-#include "virtio-blk.h" // process_virtio_op
+#include "virtio-blk.h" // process_virtio_blk_op
+#include "blockcmd.h" // cdb_*
 
 u8 FloppyCount VAR16VISIBLE;
 u8 CDCount;
@@ -276,6 +276,28 @@ map_floppy_drive(struct drive_s *drive_g)
  * 16bit calling interface
  ****************************************************************/
 
+int
+process_scsi_op(struct disk_op_s *op)
+{
+    if (!CONFIG_VIRTIO_SCSI && !CONFIG_USB_MSC)
+        return 0;
+    switch (op->command) {
+    case CMD_READ:
+        return cdb_read(op);
+    case CMD_WRITE:
+        return cdb_write(op);
+    case CMD_FORMAT:
+    case CMD_RESET:
+    case CMD_ISREADY:
+    case CMD_VERIFY:
+    case CMD_SEEK:
+        return DISK_RET_SUCCESS;
+    default:
+        op->count = 0;
+        return DISK_RET_EPARAM;
+    }
+}
+
 // Execute a disk_op request.
 int
 process_op(struct disk_op_s *op)
@@ -293,12 +315,13 @@ process_op(struct disk_op_s *op)
         return process_ramdisk_op(op);
     case DTYPE_CDEMU:
         return process_cdemu_op(op);
-    case DTYPE_USB:
-        return process_usb_op(op);
-    case DTYPE_VIRTIO:
-	return process_virtio_op(op);
+    case DTYPE_VIRTIO_BLK:
+        return process_virtio_blk_op(op);
     case DTYPE_AHCI:
 	return process_ahci_op(op);
+    case DTYPE_USB:
+    case DTYPE_VIRTIO_SCSI:
+        return process_scsi_op(op);
     default:
         op->count = 0;
         return DISK_RET_EPARAM;
diff --git a/src/blockcmd.c b/src/blockcmd.c
index c9c6845..b2a8d71 100644
--- a/src/blockcmd.c
+++ b/src/blockcmd.c
@@ -12,6 +12,7 @@
 #include "ata.h" // atapi_cmd_data
 #include "ahci.h" // atapi_cmd_data
 #include "usb-msc.h" // usb_cmd_data
+#include "virtio-scsi.h" // virtio_scsi_cmd_data
 
 // Route command to low-level handler.
 static int
@@ -25,6 +26,8 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
         return usb_cmd_data(op, cdbcmd, blocksize);
     case DTYPE_AHCI:
         return ahci_cmd_data(op, cdbcmd, blocksize);
+    case DTYPE_VIRTIO_SCSI:
+        return virtio_scsi_cmd_data(op, cdbcmd, blocksize);
     default:
         op->count = 0;
         return DISK_RET_EPARAM;
@@ -32,6 +35,128 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
 }
 
 int
+scsi_is_ready(struct disk_op_s *op)
+{
+    dprintf(6, "scsi_is_ready (drive=%p)\n", op->drive_g);
+
+    /* Retry TEST UNIT READY for 5 seconds unless MEDIUM NOT PRESENT is
+     * reported by the device.  If the device reports "IN PROGRESS",
+     * 30 seconds is added. */
+    int in_progress = 0;
+    u64 end = calc_future_tsc(5000);
+    for (;;) {
+        if (check_tsc(end)) {
+            dprintf(1, "test unit ready failed\n");
+            return -1;
+        }
+
+        int ret = cdb_test_unit_ready(op);
+        if (!ret)
+            // Success
+            break;
+
+        struct cdbres_request_sense sense;
+        ret = cdb_get_sense(op, &sense);
+        if (ret)
+            // Error - retry.
+            continue;
+
+        // Sense succeeded.
+        if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */
+            dprintf(1, "Device reports MEDIUM NOT PRESENT\n");
+            return -1;
+        }
+
+        if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) {
+            /* IN PROGRESS OF BECOMING READY */
+            printf("Waiting for device to detect medium... ");
+            /* Allow 30 seconds more */
+            end = calc_future_tsc(30000);
+            in_progress = 1;
+        }
+    }
+    return 0;
+}
+
+// Validate drive and find block size and sector count.
+int
+scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc)
+{
+    if (!CONFIG_USB_MSC && !CONFIG_VIRTIO_SCSI)
+        return 0;
+
+    struct disk_op_s dop;
+    memset(&dop, 0, sizeof(dop));
+    dop.drive_g = drive;
+    struct cdbres_inquiry data;
+    int ret = cdb_get_inquiry(&dop, &data);
+    if (ret)
+        return ret;
+    char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1];
+    char rev[sizeof(data.rev)+1];
+    strtcpy(vendor, data.vendor, sizeof(vendor));
+    nullTrailingSpace(vendor);
+    strtcpy(product, data.product, sizeof(product));
+    nullTrailingSpace(product);
+    strtcpy(rev, data.rev, sizeof(rev));
+    nullTrailingSpace(rev);
+    *pdt = data.pdt & 0x1f;
+    int removable = !!(data.removable & 0x80);
+    dprintf(1, "%s vendor='%s' product='%s' rev='%s' type=%d removable=%d\n"
+            , s, vendor, product, rev, *pdt, removable);
+    drive->removable = removable;
+
+    if (*pdt == SCSI_TYPE_CDROM) {
+        drive->blksize = CDROM_SECTOR_SIZE;
+        drive->sectors = (u64)-1;
+
+        *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]"
+                              , s, vendor, product, rev);
+        return 0;
+    }
+
+    ret = scsi_is_ready(&dop);
+    if (ret) {
+        dprintf(1, "scsi_is_ready returned %d\n", ret);
+        return ret;
+    }
+
+    struct cdbres_read_capacity capdata;
+    ret = cdb_read_capacity(&dop, &capdata);
+    if (ret)
+        return ret;
+
+    // READ CAPACITY returns the address of the last block.
+    // We do not bother with READ CAPACITY(16) because BIOS does not support
+    // 64-bit LBA anyway.
+    drive->blksize = ntohl(capdata.blksize);
+    drive->sectors = (u64)ntohl(capdata.sectors) + 1;
+    dprintf(1, "%s blksize=%d sectors=%d\n"
+            , s, drive->blksize, (unsigned)drive->sectors);
+
+    struct cdbres_mode_sense_geom geomdata;
+    ret = cdb_mode_sense_geom(&dop, &geomdata);
+    if (ret == 0) {
+        u32 cylinders;
+        cylinders = geomdata.cyl[0] << 16;
+        cylinders |= geomdata.cyl[1] << 8;
+        cylinders |= geomdata.cyl[2];
+        if (cylinders && geomdata.heads &&
+            drive->sectors <= 0xFFFFFFFFULL &&
+            ((u32)drive->sectors % (geomdata.heads * cylinders) == 0)) {
+            drive->pchs.cylinders = cylinders;
+            drive->pchs.heads = geomdata.heads;
+            drive->pchs.spt = (u32)drive->sectors
+     / (geomdata.heads * cylinders);
+        }
+    }
+
+    *desc = znprintf(MAXDESCSIZE, "%s Drive %s %s %s"
+                      , s, vendor, product, rev);
+    return 0;
+}
+
+int
 cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data)
 {
     struct cdb_request_sense cmd;
@@ -56,6 +181,18 @@ cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data)
     return cdb_cmd_data(op, &cmd, sizeof(*data));
 }
 
+// Test unit ready
+int
+cdb_test_unit_ready(struct disk_op_s *op)
+{
+    struct cdb_request_sense cmd;
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.command = CDB_CMD_TEST_UNIT_READY;
+    op->count = 0;
+    op->buf_fl = NULL;
+    return cdb_cmd_data(op, &cmd, 0);
+}
+
 // Request capacity
 int
 cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data)
@@ -68,6 +205,21 @@ cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data)
     return cdb_cmd_data(op, &cmd, sizeof(*data));
 }
 
+// Mode sense, geometry page.
+int
+cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data)
+{
+    struct cdb_mode_sense cmd;
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.command = CDB_CMD_MODE_SENSE;
+    cmd.flags = 8; /* DBD */
+    cmd.page = MODE_PAGE_HD_GEOMETRY;
+    cmd.count = htons(sizeof(*data));
+    op->count = 1;
+    op->buf_fl = data;
+    return cdb_cmd_data(op, &cmd, sizeof(*data));
+}
+
 // Read sectors.
 int
 cdb_read(struct disk_op_s *op)
@@ -79,3 +231,15 @@ cdb_read(struct disk_op_s *op)
     cmd.count = htons(op->count);
     return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize));
 }
+
+// Write sectors.
+int
+cdb_write(struct disk_op_s *op)
+{
+    struct cdb_rwdata_10 cmd;
+    memset(&cmd, 0, sizeof(cmd));
+    cmd.command = CDB_CMD_WRITE_10;
+    cmd.lba = htonl(op->lba);
+    cmd.count = htons(op->count);
+    return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize));
+}
diff --git a/src/blockcmd.h b/src/blockcmd.h
index 903c435..bace649 100644
--- a/src/blockcmd.h
+++ b/src/blockcmd.h
@@ -32,8 +32,9 @@ struct cdbres_read_capacity {
     u32 blksize;
 } PACKED;
 
-#define CDB_CMD_INQUIRY 0x12
-#define CDB_CMD_REQUEST_SENSE 0x03
+#define CDB_CMD_TEST_UNIT_READY  0x00
+#define CDB_CMD_INQUIRY          0x12
+#define CDB_CMD_REQUEST_SENSE    0x03
 
 struct cdb_request_sense {
     u8 command;
@@ -56,6 +57,9 @@ struct cdbres_request_sense {
     u32 reserved_0e;
 } PACKED;
 
+#define SCSI_TYPE_DISK  0x00
+#define SCSI_TYPE_CDROM 0x05
+
 struct cdbres_inquiry {
     u8 pdt;
     u8 removable;
@@ -67,11 +71,45 @@ struct cdbres_inquiry {
     char rev[4];
 } PACKED;
 
+#define CDB_CMD_MODE_SENSE    0x5A
+#define MODE_PAGE_HD_GEOMETRY 0x04
+
+struct cdb_mode_sense {
+    u8 command;
+    u8 flags;
+    u8 page;
+    u32 reserved_03;
+    u16 count;
+    u8 reserved_09;
+    u8 pad[6];
+} PACKED;
+
+struct cdbres_mode_sense_geom {
+    u8 unused_00[3];
+    u8 read_only;
+    u32 unused_04;
+    u8 page;
+    u8 length;
+    u8 cyl[3];
+    u8 heads;
+    u8 precomp[3];
+    u8 reduced[3];
+    u16 step_rate;
+    u8 landing[3];
+    u16 rpm;
+} PACKED;
+
 // blockcmd.c
 int cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data);
 int cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data);
+int cdb_test_unit_ready(struct disk_op_s *op);
 int cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data);
+int cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data);
 int cdb_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data);
 int cdb_read(struct disk_op_s *op);
+int cdb_write(struct disk_op_s *op);
+
+int scsi_is_ready(struct disk_op_s *op);
+int scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc);
 
 #endif // blockcmd.h
diff --git a/src/boot.c b/src/boot.c
index 93928d3..c0991cd 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -128,6 +128,20 @@ int bootprio_find_pci_device(struct pci_device *pci)
     return find_prio(desc);
 }
 
+int bootprio_find_scsi_device(struct pci_device *pci, int target, int lun)
+{
+    if (!CONFIG_BOOTORDER)
+        return -1;
+    if (!pci)
+        // support only pci machine for now
+        return -1;
+    // Find scsi drive - for example: /pci@i0cf8/scsi@5/channel@0/disk@1,0
+    char desc[256], *p;
+    p = build_pci_path(desc, sizeof(desc), "*", pci);
+    snprintf(p, desc+sizeof(desc)-p, "/*@0/*@%d,%d", target, lun);
+    return find_prio(desc);
+}
+
 int bootprio_find_ata_device(struct pci_device *pci, int chanid, int slave)
 {
     if (!CONFIG_BOOTORDER)
diff --git a/src/boot.h b/src/boot.h
index d776aa1..686f04d 100644
--- a/src/boot.h
+++ b/src/boot.h
@@ -14,6 +14,7 @@ void boot_add_cbfs(void *data, const char *desc, int prio);
 void boot_prep(void);
 struct pci_device;
 int bootprio_find_pci_device(struct pci_device *pci);
+int bootprio_find_scsi_device(struct pci_device *pci, int target, int lun);
 int bootprio_find_ata_device(struct pci_device *pci, int chanid, int slave);
 int bootprio_find_fdc_device(struct pci_device *pci, int port, int fdid);
 int bootprio_find_pci_rom(struct pci_device *pci, int instance);
diff --git a/src/cdrom.c b/src/cdrom.c
index 6351fec..170ffc4 100644
--- a/src/cdrom.c
+++ b/src/cdrom.c
@@ -184,60 +184,6 @@ cdemu_134b(struct bregs *regs)
  * CD booting
  ****************************************************************/
 
-static int
-atapi_is_ready(struct disk_op_s *op)
-{
-    dprintf(6, "atapi_is_ready (drive=%p)\n", op->drive_g);
-
-    /* Retry READ CAPACITY for 5 seconds unless MEDIUM NOT PRESENT is
-     * reported by the device.  If the device reports "IN PROGRESS",
-     * 30 seconds is added. */
-    struct cdbres_read_capacity info;
-    int in_progress = 0;
-    u64 end = calc_future_tsc(5000);
-    for (;;) {
-        if (check_tsc(end)) {
-            dprintf(1, "read capacity failed\n");
-            return -1;
-        }
-
-        int ret = cdb_read_capacity(op, &info);
-        if (!ret)
-            // Success
-            break;
-
-        struct cdbres_request_sense sense;
-        ret = cdb_get_sense(op, &sense);
-        if (ret)
-            // Error - retry.
-            continue;
-
-        // Sense succeeded.
-        if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */
-            dprintf(1, "Device reports MEDIUM NOT PRESENT\n");
-            return -1;
-        }
-
-        if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) {
-            /* IN PROGRESS OF BECOMING READY */
-            printf("Waiting for device to detect medium... ");
-            /* Allow 30 seconds more */
-            end = calc_future_tsc(30000);
-            in_progress = 1;
-        }
-    }
-
-    u32 blksize = ntohl(info.blksize), sectors = ntohl(info.sectors);
-    if (blksize != GET_GLOBAL(op->drive_g->blksize)) {
-        printf("Unsupported sector size %u\n", blksize);
-        return -1;
-    }
-
-    dprintf(6, "sectors=%u\n", sectors);
-    printf("%dMB medium detected\n", sectors>>(20-11));
-    return 0;
-}
-
 int
 cdrom_boot(struct drive_s *drive_g)
 {
@@ -248,9 +194,9 @@ cdrom_boot(struct drive_s *drive_g)
     if (!dop.drive_g || cdid < 0)
         return 1;
 
-    int ret = atapi_is_ready(&dop);
+    int ret = scsi_is_ready(&dop);
     if (ret)
-        dprintf(1, "atapi_is_ready returned %d\n", ret);
+        dprintf(1, "scsi_is_ready returned %d\n", ret);
 
     // Read the Boot Record Volume Descriptor
     u8 buffer[2048];
diff --git a/src/disk.c b/src/disk.c
index 8f7c61f..6a170fd 100644
--- a/src/disk.c
+++ b/src/disk.c
@@ -546,7 +546,8 @@ disk_1348(struct bregs *regs, struct drive_s *drive_g)
     SET_INT13DPT(regs, blksize, blksize);
 
     if (size < 30 ||
-        (type != DTYPE_ATA && type != DTYPE_ATAPI && type != DTYPE_VIRTIO)) {
+        (type != DTYPE_ATA && type != DTYPE_ATAPI &&
+         type != DTYPE_VIRTIO_BLK && type != DTYPE_VIRTIO_SCSI)) {
         disk_ret(regs, DISK_RET_SUCCESS);
         return;
     }
@@ -651,7 +652,7 @@ disk_1348(struct bregs *regs, struct drive_s *drive_g)
         SET_INT13DPT(regs, iface_path, iobase1);
     }
 
-    if (type != DTYPE_VIRTIO) {
+    if (type != DTYPE_VIRTIO_BLK) {
         SET_INT13DPT(regs, iface_type[0], 'A');
         SET_INT13DPT(regs, iface_type[1], 'T');
         SET_INT13DPT(regs, iface_type[2], 'A');
diff --git a/src/disk.h b/src/disk.h
index ac33518..d344399 100644
--- a/src/disk.h
+++ b/src/disk.h
@@ -198,15 +198,16 @@ struct drive_s {
 #define DISK_SECTOR_SIZE  512
 #define CDROM_SECTOR_SIZE 2048
 
-#define DTYPE_NONE     0x00
-#define DTYPE_FLOPPY   0x01
-#define DTYPE_ATA      0x02
-#define DTYPE_ATAPI    0x03
-#define DTYPE_RAMDISK  0x04
-#define DTYPE_CDEMU    0x05
-#define DTYPE_USB      0x06
-#define DTYPE_VIRTIO   0x07
-#define DTYPE_AHCI     0x08
+#define DTYPE_NONE         0x00
+#define DTYPE_FLOPPY       0x01
+#define DTYPE_ATA          0x02
+#define DTYPE_ATAPI        0x03
+#define DTYPE_RAMDISK      0x04
+#define DTYPE_CDEMU        0x05
+#define DTYPE_USB          0x06
+#define DTYPE_VIRTIO_BLK   0x07
+#define DTYPE_AHCI         0x08
+#define DTYPE_VIRTIO_SCSI  0x09
 
 #define MAXDESCSIZE 80
 
diff --git a/src/pci_ids.h b/src/pci_ids.h
index e1cded2..4b59585 100644
--- a/src/pci_ids.h
+++ b/src/pci_ids.h
@@ -2608,3 +2608,4 @@
 
 #define PCI_VENDOR_ID_REDHAT_QUMRANET	0x1af4
 #define PCI_DEVICE_ID_VIRTIO_BLK	0x1001
+#define PCI_DEVICE_ID_VIRTIO_SCSI	0x1004
diff --git a/src/post.c b/src/post.c
index b4ad1fa..d7bbcdd 100644
--- a/src/post.c
+++ b/src/post.c
@@ -26,6 +26,7 @@
 #include "xen.h" // xen_probe_hvm_info
 #include "ps2port.h" // ps2port_setup
 #include "virtio-blk.h" // virtio_blk_setup
+#include "virtio-scsi.h" // virtio_scsi_setup
 
 
 /****************************************************************
@@ -190,6 +191,7 @@ init_hw(void)
     cbfs_payload_setup();
     ramdisk_setup();
     virtio_blk_setup();
+    virtio_scsi_setup();
 }
 
 // Begin the boot process by invoking an int0x19 in 16bit mode.
diff --git a/src/usb-ehci.c b/src/usb-ehci.c
index a60c607..9bdd638 100644
--- a/src/usb-ehci.c
+++ b/src/usb-ehci.c
@@ -303,22 +303,12 @@ ehci_init(struct pci_device *pci, int busid, struct pci_device *comppci)
  * End point communication
  ****************************************************************/
 
-static int
-ehci_wait_qh(struct usb_ehci_s *cntl, struct ehci_qh *qh)
-{
-    // XXX - 500ms just a guess
-    u64 end = calc_future_tsc(500);
-    for (;;) {
-        if (qh->qtd_next & EHCI_PTR_TERM)
-            // XXX - confirm
-            return 0;
-        if (check_tsc(end)) {
-            warn_timeout();
-            return -1;
-        }
-        yield();
-    }
-}
+struct ehci_pipe {
+    struct ehci_qh qh;
+    struct ehci_qtd *next_td, *tds;
+    void *data;
+    struct usb_pipe pipe;
+};
 
 // Wait for next USB async frame to start - for ensuring safe memory release.
 static void
@@ -362,12 +352,46 @@ ehci_waittick(struct usb_ehci_s *cntl)
     writel(&cntl->regs->usbsts, STS_IAA);
 }
 
-struct ehci_pipe {
-    struct ehci_qh qh;
-    struct ehci_qtd *next_td, *tds;
-    void *data;
-    struct usb_pipe pipe;
-};
+static void
+ehci_reset_pipe(struct ehci_pipe *pipe)
+{
+    SET_FLATPTR(pipe->qh.qtd_next, EHCI_PTR_TERM);
+    SET_FLATPTR(pipe->qh.alt_next, EHCI_PTR_TERM);
+    barrier();
+    SET_FLATPTR(pipe->qh.token, GET_FLATPTR(pipe->qh.token) & QTD_TOGGLE);
+}
+
+static int
+ehci_wait_td(struct ehci_pipe *pipe, struct ehci_qtd *td, int timeout)
+{
+    u64 end = calc_future_tsc(timeout);
+    u32 status;
+    for (;;) {
+        status = td->token;
+        if (!(status & QTD_STS_ACTIVE))
+            break;
+        if (check_tsc(end)) {
+            u32 cur = GET_FLATPTR(pipe->qh.current);
+            u32 tok = GET_FLATPTR(pipe->qh.token);
+            u32 next = GET_FLATPTR(pipe->qh.qtd_next);
+            warn_timeout();
+            dprintf(1, "ehci pipe=%p cur=%08x tok=%08x next=%x td=%p status=%x\n"
+                    , pipe, cur, tok, next, td, status);
+            ehci_reset_pipe(pipe);
+            struct usb_ehci_s *cntl = container_of(
+                GET_FLATPTR(pipe->pipe.cntl), struct usb_ehci_s, usb);
+            ehci_waittick(cntl);
+            return -1;
+        }
+        yield();
+    }
+    if (status & QTD_STS_HALT) {
+        dprintf(1, "ehci_wait_td error - status=%x\n", status);
+        ehci_reset_pipe(pipe);
+        return -2;
+    }
+    return 0;
+}
 
 void
 ehci_free_pipe(struct usb_pipe *p)
@@ -416,7 +440,6 @@ ehci_alloc_control_pipe(struct usb_pipe *dummy)
     memset(pipe, 0, sizeof(*pipe));
     memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe));
     pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
-    pipe->qh.token = QTD_STS_HALT;
 
     // Add queue head to controller list.
     struct ehci_qh *async_qh = cntl->async_qh;
@@ -455,15 +478,14 @@ ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
     ASSERT32FLAT();
     if (! CONFIG_USB_EHCI)
         return -1;
-    dprintf(5, "ehci_control %p\n", p);
+    dprintf(5, "ehci_control %p (dir=%d cmd=%d data=%d)\n"
+            , p, dir, cmdsize, datasize);
     if (datasize > 4*4096 || cmdsize > 4*4096) {
         // XXX - should support larger sizes.
         warn_noalloc();
         return -1;
     }
     struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe);
-    struct usb_ehci_s *cntl = container_of(
-        pipe->pipe.cntl, struct usb_ehci_s, usb);
 
     u16 maxpacket = pipe->pipe.maxpacket;
     int speed = pipe->pipe.speed;
@@ -513,14 +535,12 @@ ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
     // Transfer data
     barrier();
     pipe->qh.qtd_next = (u32)tds;
-    barrier();
-    pipe->qh.token = 0;
-    int ret = ehci_wait_qh(cntl, &pipe->qh);
-    pipe->qh.token = QTD_STS_HALT;
-    if (ret) {
-        pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
-        // XXX - halt qh?
-        ehci_waittick(cntl);
+    int i, ret=0;
+    for (i=0; i<3; i++) {
+        struct ehci_qtd *td = &tds[i];
+        ret = ehci_wait_td(pipe, td, 500);
+        if (ret)
+            break;
     }
     free(tds);
     return ret;
@@ -545,7 +565,6 @@ ehci_alloc_bulk_pipe(struct usb_pipe *dummy)
     memset(pipe, 0, sizeof(*pipe));
     memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe));
     pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
-    pipe->qh.token = QTD_STS_HALT;
 
     // Add queue head to controller list.
     struct ehci_qh *async_qh = cntl->async_qh;
@@ -555,28 +574,6 @@ ehci_alloc_bulk_pipe(struct usb_pipe *dummy)
     return &pipe->pipe;
 }
 
-static int
-ehci_wait_td(struct ehci_qtd *td)
-{
-    u64 end = calc_future_tsc(5000); // XXX - lookup real time.
-    u32 status;
-    for (;;) {
-        status = td->token;
-        if (!(status & QTD_STS_ACTIVE))
-            break;
-        if (check_tsc(end)) {
-            warn_timeout();
-            return -1;
-        }
-        yield();
-    }
-    if (status & QTD_STS_HALT) {
-        dprintf(1, "ehci_wait_td error - status=%x\n", status);
-        return -2;
-    }
-    return 0;
-}
-
 #define STACKQTDS 4
 
 int
@@ -607,15 +604,13 @@ ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
                    | (GET_FLATPTR(pipe->pipe.tt_devaddr) << QH_HUBADDR_SHIFT)));
     barrier();
     SET_FLATPTR(pipe->qh.qtd_next, (u32)MAKE_FLATPTR(GET_SEG(SS), tds));
-    barrier();
-    SET_FLATPTR(pipe->qh.token, GET_FLATPTR(pipe->qh.token) & QTD_TOGGLE);
 
     int tdpos = 0;
     while (datasize) {
         struct ehci_qtd *td = &tds[tdpos++ % STACKQTDS];
-        int ret = ehci_wait_td(td);
+        int ret = ehci_wait_td(pipe, td, 5000);
         if (ret)
-            goto fail;
+            return -1;
 
         struct ehci_qtd *nexttd_fl = MAKE_FLATPTR(GET_SEG(SS)
                                                  , &tds[tdpos % STACKQTDS]);
@@ -633,21 +628,12 @@ ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
     int i;
     for (i=0; i<STACKQTDS; i++) {
         struct ehci_qtd *td = &tds[tdpos++ % STACKQTDS];
-        int ret = ehci_wait_td(td);
+        int ret = ehci_wait_td(pipe, td, 5000);
         if (ret)
-            goto fail;
+            return -1;
     }
 
     return 0;
-fail:
-    dprintf(1, "ehci_send_bulk failed\n");
-    SET_FLATPTR(pipe->qh.qtd_next, EHCI_PTR_TERM);
-    SET_FLATPTR(pipe->qh.alt_next, EHCI_PTR_TERM);
-    // XXX - halt qh?
-    struct usb_ehci_s *cntl = container_of(
-        GET_FLATPTR(pipe->pipe.cntl), struct usb_ehci_s, usb);
-    ehci_waittick(cntl);
-    return -1;
 }
 
 struct usb_pipe *
diff --git a/src/usb-msc.c b/src/usb-msc.c
index 13ef93e..4a09972 100644
--- a/src/usb-msc.c
+++ b/src/usb-msc.c
@@ -46,6 +46,17 @@ struct csw_s {
     u8 bCSWStatus;
 } PACKED;
 
+static int
+usb_msc_send(struct usbdrive_s *udrive_g, int dir, void *buf, u32 bytes)
+{
+    struct usb_pipe *pipe;
+    if (dir == USB_DIR_OUT)
+        pipe = GET_GLOBAL(udrive_g->bulkout);
+    else
+        pipe = GET_GLOBAL(udrive_g->bulkin);
+    return usb_send_bulk(pipe, dir, buf, bytes);
+}
+
 // Low-level usb command transmit function.
 int
 usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
@@ -57,35 +68,35 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
             , op->drive_g, 0, op->count, blocksize, op->buf_fl);
     struct usbdrive_s *udrive_g = container_of(
         op->drive_g, struct usbdrive_s, drive);
-    struct usb_pipe *bulkin = GET_GLOBAL(udrive_g->bulkin);
-    struct usb_pipe *bulkout = GET_GLOBAL(udrive_g->bulkout);
 
     // Setup command block wrapper.
     u32 bytes = blocksize * op->count;
     struct cbw_s cbw;
     memset(&cbw, 0, sizeof(cbw));
+    memcpy(cbw.CBWCB, cdbcmd, USB_CDB_SIZE);
     cbw.dCBWSignature = CBW_SIGNATURE;
     cbw.dCBWTag = 999; // XXX
     cbw.dCBWDataTransferLength = bytes;
-    cbw.bmCBWFlags = USB_DIR_IN; // XXX
+    cbw.bmCBWFlags = (cbw.CBWCB[0] == CDB_CMD_WRITE_10) ? USB_DIR_OUT : USB_DIR_IN;
     cbw.bCBWLUN = 0; // XXX
     cbw.bCBWCBLength = USB_CDB_SIZE;
-    memcpy(cbw.CBWCB, cdbcmd, USB_CDB_SIZE);
 
     // Transfer cbw to device.
-    int ret = usb_send_bulk(bulkout, USB_DIR_OUT
+    int ret = usb_msc_send(udrive_g, USB_DIR_OUT
                             , MAKE_FLATPTR(GET_SEG(SS), &cbw), sizeof(cbw));
     if (ret)
         goto fail;
 
-    // Transfer data from device.
-    ret = usb_send_bulk(bulkin, USB_DIR_IN, op->buf_fl, bytes);
-    if (ret)
-        goto fail;
+    // Transfer data to/from device.
+    if (bytes) {
+        ret = usb_msc_send(udrive_g, cbw.bmCBWFlags, op->buf_fl, bytes);
+        if (ret)
+            goto fail;
+    }
 
     // Transfer csw info.
     struct csw_s csw;
-    ret = usb_send_bulk(bulkin, USB_DIR_IN
+    ret = usb_msc_send(udrive_g, USB_DIR_IN
                         , MAKE_FLATPTR(GET_SEG(SS), &csw), sizeof(csw));
     if (ret)
         goto fail;
@@ -95,7 +106,8 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
     if (csw.bCSWStatus == 2)
         goto fail;
 
-    op->count -= csw.dCSWDataResidue / blocksize;
+    if (blocksize)
+        op->count -= csw.dCSWDataResidue / blocksize;
     return DISK_RET_EBADTRACK;
 
 fail:
@@ -107,75 +119,33 @@ fail:
 
 
 /****************************************************************
- * Drive ops
- ****************************************************************/
-
-// 16bit command demuxer for ATAPI cdroms.
-int
-process_usb_op(struct disk_op_s *op)
-{
-    if (!CONFIG_USB_MSC)
-        return 0;
-    switch (op->command) {
-    case CMD_READ:
-        return cdb_read(op);
-    case CMD_FORMAT:
-    case CMD_WRITE:
-        return DISK_RET_EWRITEPROTECT;
-    case CMD_RESET:
-    case CMD_ISREADY:
-    case CMD_VERIFY:
-    case CMD_SEEK:
-        return DISK_RET_SUCCESS;
-    default:
-        op->count = 0;
-        return DISK_RET_EPARAM;
-    }
-}
-
-
-/****************************************************************
  * Setup
  ****************************************************************/
 
 static int
-setup_drive_cdrom(struct disk_op_s *op, char *desc)
+setup_drive_cdrom(struct drive_s *drive, char *desc)
 {
-    op->drive_g->blksize = CDROM_SECTOR_SIZE;
-    op->drive_g->sectors = (u64)-1;
+    drive->sectors = (u64)-1;
     struct usb_pipe *pipe = container_of(
-        op->drive_g, struct usbdrive_s, drive)->bulkout;
+        drive, struct usbdrive_s, drive)->bulkout;
     int prio = bootprio_find_usb(pipe->cntl->pci, pipe->path);
-    boot_add_cd(op->drive_g, desc, prio);
+    boot_add_cd(drive, desc, prio);
     return 0;
 }
 
 static int
-setup_drive_hd(struct disk_op_s *op, char *desc)
+setup_drive_hd(struct drive_s *drive, char *desc)
 {
-    struct cdbres_read_capacity info;
-    int ret = cdb_read_capacity(op, &info);
-    if (ret)
-        return ret;
-    // XXX - retry for some timeout?
-
-    u32 blksize = ntohl(info.blksize), sectors = ntohl(info.sectors);
-    if (blksize != DISK_SECTOR_SIZE) {
-        if (blksize == CDROM_SECTOR_SIZE)
-            return setup_drive_cdrom(op, desc);
-        dprintf(1, "Unsupported USB MSC block size %d\n", blksize);
+    if (drive->blksize != DISK_SECTOR_SIZE) {
+        dprintf(1, "Unsupported USB MSC block size %d\n", drive->blksize);
         return -1;
     }
-    op->drive_g->blksize = blksize;
-    op->drive_g->sectors = sectors;
-    dprintf(1, "USB MSC blksize=%d sectors=%d\n", blksize, sectors);
 
     // Register with bcv system.
     struct usb_pipe *pipe = container_of(
-        op->drive_g, struct usbdrive_s, drive)->bulkout;
+        drive, struct usbdrive_s, drive)->bulkout;
     int prio = bootprio_find_usb(pipe->cntl->pci, pipe->path);
-    boot_add_hd(op->drive_g, desc, prio);
-
+    boot_add_hd(drive, desc, prio);
     return 0;
 }
 
@@ -218,37 +188,17 @@ usb_msc_init(struct usb_pipe *pipe
     if (!udrive_g->bulkin || !udrive_g->bulkout)
         goto fail;
 
-    // Validate drive and find block size and sector count.
-    struct disk_op_s dop;
-    memset(&dop, 0, sizeof(dop));
-    dop.drive_g = &udrive_g->drive;
-    struct cdbres_inquiry data;
-    int ret = cdb_get_inquiry(&dop, &data);
+    int ret, pdt;
+    char *desc = NULL;
+    ret = scsi_init_drive(&udrive_g->drive, "USB MSC", &pdt, &desc);
     if (ret)
         goto fail;
-    char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1];
-    char rev[sizeof(data.rev)+1];
-    strtcpy(vendor, data.vendor, sizeof(vendor));
-    nullTrailingSpace(vendor);
-    strtcpy(product, data.product, sizeof(product));
-    nullTrailingSpace(product);
-    strtcpy(rev, data.rev, sizeof(rev));
-    nullTrailingSpace(rev);
-    int pdt = data.pdt & 0x1f;
-    int removable = !!(data.removable & 0x80);
-    dprintf(1, "USB MSC vendor='%s' product='%s' rev='%s' type=%d removable=%d\n"
-            , vendor, product, rev, pdt, removable);
-    udrive_g->drive.removable = removable;
-
-    if (pdt == USB_MSC_TYPE_CDROM) {
-        char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]"
-                              , vendor, product, rev);
-        ret = setup_drive_cdrom(&dop, desc);
-    } else {
-        char *desc = znprintf(MAXDESCSIZE, "USB Drive %s %s %s"
-                              , vendor, product, rev);
-        ret = setup_drive_hd(&dop, desc);
-    }
+
+    if (pdt == SCSI_TYPE_CDROM)
+        ret = setup_drive_cdrom(&udrive_g->drive, desc);
+    else
+        ret = setup_drive_hd(&udrive_g->drive, desc);
+
     if (ret)
         goto fail;
 
diff --git a/src/usb-msc.h b/src/usb-msc.h
index 71adb20..a8686a3 100644
--- a/src/usb-msc.h
+++ b/src/usb-msc.h
@@ -21,7 +21,4 @@ int process_usb_op(struct disk_op_s *op);
 
 #define US_PR_BULK         0x50
 
-#define USB_MSC_TYPE_DISK  0x00
-#define USB_MSC_TYPE_CDROM 0x05
-
 #endif // ush-msc.h
diff --git a/src/usb-uhci.c b/src/usb-uhci.c
index f3680d3..a78dbca 100644
--- a/src/usb-uhci.c
+++ b/src/usb-uhci.c
@@ -212,26 +212,13 @@ uhci_init(struct pci_device *pci, int busid)
  * End point communication
  ****************************************************************/
 
-static int
-wait_qh(struct usb_uhci_s *cntl, struct uhci_qh *qh)
-{
-    // XXX - 500ms just a guess
-    u64 end = calc_future_tsc(500);
-    for (;;) {
-        if (qh->element & UHCI_PTR_TERM)
-            return 0;
-        if (check_tsc(end)) {
-            warn_timeout();
-            struct uhci_td *td = (void*)(qh->element & ~UHCI_PTR_BITS);
-            dprintf(1, "Timeout on wait_qh %p (td=%p s=%x c=%x/%x)\n"
-                    , qh, td, td->status
-                    , inw(cntl->iobase + USBCMD)
-                    , inw(cntl->iobase + USBSTS));
-            return -1;
-        }
-        yield();
-    }
-}
+struct uhci_pipe {
+    struct uhci_qh qh;
+    struct uhci_td *next_td;
+    struct usb_pipe pipe;
+    u16 iobase;
+    u8 toggle;
+};
 
 // Wait for next USB frame to start - for ensuring safe memory release.
 static void
@@ -251,13 +238,29 @@ uhci_waittick(u16 iobase)
     }
 }
 
-struct uhci_pipe {
-    struct uhci_qh qh;
-    struct uhci_td *next_td;
-    struct usb_pipe pipe;
-    u16 iobase;
-    u8 toggle;
-};
+static int
+wait_pipe(struct uhci_pipe *pipe, int timeout)
+{
+    u64 end = calc_future_tsc(timeout);
+    for (;;) {
+        u32 el_link = GET_FLATPTR(pipe->qh.element);
+        if (el_link & UHCI_PTR_TERM)
+            return 0;
+        if (check_tsc(end)) {
+            warn_timeout();
+            u16 iobase = GET_FLATPTR(pipe->iobase);
+            struct uhci_td *td = (void*)(el_link & ~UHCI_PTR_BITS);
+            dprintf(1, "Timeout on wait_pipe %p (td=%p s=%x c=%x/%x)\n"
+                    , pipe, (void*)el_link, GET_FLATPTR(td->status)
+                    , inw(iobase + USBCMD)
+                    , inw(iobase + USBSTS));
+            SET_FLATPTR(pipe->qh.element, UHCI_PTR_TERM);
+            uhci_waittick(iobase);
+            return -1;
+        }
+        yield();
+    }
+}
 
 void
 uhci_free_pipe(struct usb_pipe *p)
@@ -331,8 +334,6 @@ uhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
         return -1;
     dprintf(5, "uhci_control %p\n", p);
     struct uhci_pipe *pipe = container_of(p, struct uhci_pipe, pipe);
-    struct usb_uhci_s *cntl = container_of(
-        pipe->pipe.cntl, struct usb_uhci_s, usb);
 
     int maxpacket = pipe->pipe.maxpacket;
     int lowspeed = pipe->pipe.speed;
@@ -376,11 +377,7 @@ uhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
     // Transfer data
     barrier();
     pipe->qh.element = (u32)&tds[0];
-    int ret = wait_qh(cntl, &pipe->qh);
-    if (ret) {
-        pipe->qh.element = UHCI_PTR_TERM;
-        uhci_waittick(pipe->iobase);
-    }
+    int ret = wait_pipe(pipe, 500);
     free(tds);
     return ret;
 }
@@ -487,16 +484,8 @@ uhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
         data += transfer;
         datasize -= transfer;
     }
-    int i;
-    for (i=0; i<STACKTDS; i++) {
-        struct uhci_td *td = &tds[tdpos++ % STACKTDS];
-        int ret = wait_td(td);
-        if (ret)
-            goto fail;
-    }
-
     SET_FLATPTR(pipe->toggle, !!toggle);
-    return 0;
+    return wait_pipe(pipe, 5000);
 fail:
     dprintf(1, "uhci_send_bulk failed\n");
     SET_FLATPTR(pipe->qh.element, UHCI_PTR_TERM);
diff --git a/src/virtio-blk.c b/src/virtio-blk.c
index b1274fc..b869189 100644
--- a/src/virtio-blk.c
+++ b/src/virtio-blk.c
@@ -75,7 +75,7 @@ virtio_blk_op(struct disk_op_s *op, int write)
 }
 
 int
-process_virtio_op(struct disk_op_s *op)
+process_virtio_blk_op(struct disk_op_s *op)
 {
     if (! CONFIG_VIRTIO_BLK || CONFIG_COREBOOT)
         return 0;
@@ -103,27 +103,17 @@ init_virtio_blk(struct pci_device *pci)
     dprintf(1, "found virtio-blk at %x:%x\n", pci_bdf_to_bus(bdf),
             pci_bdf_to_dev(bdf));
     struct virtiodrive_s *vdrive_g = malloc_fseg(sizeof(*vdrive_g));
-    struct vring_virtqueue *vq = memalign_low(PAGE_SIZE, sizeof(*vq));
-    if (!vdrive_g || !vq) {
+    if (!vdrive_g) {
         warn_noalloc();
-        goto fail;
+        return;
     }
     memset(vdrive_g, 0, sizeof(*vdrive_g));
-    memset(vq, 0, sizeof(*vq));
-    vdrive_g->drive.type = DTYPE_VIRTIO;
+    vdrive_g->drive.type = DTYPE_VIRTIO_BLK;
     vdrive_g->drive.cntl_id = bdf;
-    vdrive_g->vq = vq;
-
-    u16 ioaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0) &
-        PCI_BASE_ADDRESS_IO_MASK;
 
+    u16 ioaddr = vp_init_simple(bdf);
     vdrive_g->ioaddr = ioaddr;
-
-    vp_reset(ioaddr);
-    vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
-                  VIRTIO_CONFIG_S_DRIVER );
-
-    if (vp_find_vq(ioaddr, 0, vdrive_g->vq) < 0 ) {
+    if (vp_find_vq(ioaddr, 0, &vdrive_g->vq) < 0 ) {
         dprintf(1, "fail to find vq for virtio-blk %x:%x\n",
                 pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
         goto fail;
@@ -161,8 +151,8 @@ init_virtio_blk(struct pci_device *pci)
     return;
 
 fail:
+    free(vdrive_g->vq);
     free(vdrive_g);
-    free(vq);
 }
 
 void
diff --git a/src/virtio-blk.h b/src/virtio-blk.h
index 7243704..0825f09 100644
--- a/src/virtio-blk.h
+++ b/src/virtio-blk.h
@@ -37,7 +37,7 @@ struct virtio_blk_outhdr {
 #define VIRTIO_BLK_S_UNSUPP	2
 
 struct disk_op_s;
-int process_virtio_op(struct disk_op_s *op);
+int process_virtio_blk_op(struct disk_op_s *op);
 void virtio_blk_setup(void);
 
 #endif /* _VIRTIO_BLK_H */
diff --git a/src/virtio-pci.c b/src/virtio-pci.c
index db19e97..7e0c1a5 100644
--- a/src/virtio-pci.c
+++ b/src/virtio-pci.c
@@ -21,12 +21,18 @@
 #include "util.h" // dprintf
 
 int vp_find_vq(unsigned int ioaddr, int queue_index,
-               struct vring_virtqueue *vq)
+               struct vring_virtqueue **p_vq)
 {
-   struct vring * vr = &vq->vring;
    u16 num;
 
    ASSERT32FLAT();
+   struct vring_virtqueue *vq = *p_vq = memalign_low(PAGE_SIZE, sizeof(*vq));
+   if (!vq) {
+       warn_noalloc();
+       goto fail;
+   }
+   memset(vq, 0, sizeof(*vq));
+
    /* select the queue */
 
    outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
@@ -36,25 +42,26 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
    num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM);
    if (!num) {
        dprintf(1, "ERROR: queue size is 0\n");
-       return -1;
+       goto fail;
    }
 
    if (num > MAX_QUEUE_NUM) {
        dprintf(1, "ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM);
-       return -1;
+       goto fail;
    }
 
    /* check if the queue is already active */
 
    if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
        dprintf(1, "ERROR: queue already active\n");
-       return -1;
+       goto fail;
    }
 
    vq->queue_index = queue_index;
 
    /* initialize the queue */
 
+   struct vring * vr = &vq->vring;
    vring_init(vr, num, (unsigned char*)&vq->queue);
 
    /* activate the queue
@@ -66,4 +73,20 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
         ioaddr + VIRTIO_PCI_QUEUE_PFN);
 
    return num;
+
+fail:
+   free(vq);
+   *p_vq = NULL;
+   return -1;
+}
+
+u16 vp_init_simple(u16 bdf)
+{
+    u16 ioaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0) &
+        PCI_BASE_ADDRESS_IO_MASK;
+
+    vp_reset(ioaddr);
+    vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+                  VIRTIO_CONFIG_S_DRIVER );
+    return ioaddr;
 }
diff --git a/src/virtio-pci.h b/src/virtio-pci.h
index d21d5a5..e1d972d 100644
--- a/src/virtio-pci.h
+++ b/src/virtio-pci.h
@@ -99,6 +99,7 @@ static inline void vp_del_vq(unsigned int ioaddr, int queue_index)
 }
 
 struct vring_virtqueue;
+u16 vp_init_simple(u16 bdf);
 int vp_find_vq(unsigned int ioaddr, int queue_index,
-               struct vring_virtqueue *vq);
+               struct vring_virtqueue **p_vq);
 #endif /* _VIRTIO_PCI_H_ */
diff --git a/src/virtio-scsi.c b/src/virtio-scsi.c
new file mode 100644
index 0000000..2f6c48a
--- /dev/null
+++ b/src/virtio-scsi.c
@@ -0,0 +1,211 @@
+// Virtio SCSI boot support.
+//
+// Copyright (C) 2011 Red Hat Inc.
+//
+// Authors:
+//  Paolo Bonzini <pbonzini@redhat.com>
+//
+// This file may be distributed under the terms of the GNU LGPLv3 license.
+
+#include "util.h" // dprintf
+#include "pci.h" // foreachpci
+#include "config.h" // CONFIG_*
+#include "biosvar.h" // GET_GLOBAL
+#include "pci_ids.h" // PCI_DEVICE_ID_VIRTIO_BLK
+#include "pci_regs.h" // PCI_VENDOR_ID
+#include "boot.h" // boot_add_hd
+#include "blockcmd.h" // CDB_CMD_WRITE_10, SCSI_TYPE_CDROM
+#include "virtio-pci.h"
+#include "virtio-ring.h"
+#include "virtio-scsi.h"
+#include "disk.h"
+
+struct virtio_lun_s {
+    struct drive_s drive;
+    struct pci_device *pci;
+    struct vring_virtqueue *vq;
+    u16 ioaddr;
+    u16 target;
+    u16 lun;
+};
+
+static int
+virtio_scsi_cmd(u16 ioaddr, struct vring_virtqueue *vq, struct disk_op_s *op,
+                void *cdbcmd, u16 target, u16 lun, u32 len)
+{
+    struct virtio_scsi_req_cmd req;
+    struct virtio_scsi_resp_cmd resp;
+    struct vring_list sg[3];
+
+    memset(&req, 0, sizeof(req));
+    req.lun[0] = 1;
+    req.lun[1] = target;
+    req.lun[2] = (lun >> 8) | 0x40;
+    req.lun[3] = (lun & 0xff);
+    memcpy(req.cdb, cdbcmd, 16);
+
+    int datain = (req.cdb[0] != CDB_CMD_WRITE_10);
+    int data_idx = (datain ? 2 : 1);
+    int out_num = (datain ? 1 : 2);
+    int in_num = (len ? 3 : 2) - out_num;
+
+    sg[0].addr   = MAKE_FLATPTR(GET_SEG(SS), &req);
+    sg[0].length = sizeof(req);
+
+    sg[out_num].addr   = MAKE_FLATPTR(GET_SEG(SS), &resp);
+    sg[out_num].length = sizeof(resp);
+
+    sg[data_idx].addr   = op->buf_fl;
+    sg[data_idx].length = len;
+
+    /* Add to virtqueue and kick host */
+    vring_add_buf(vq, sg, out_num, in_num, 0, 0);
+    vring_kick(ioaddr, vq, 1);
+
+    /* Wait for reply */
+    while (!vring_more_used(vq))
+        usleep(5);
+
+    /* Reclaim virtqueue element */
+    vring_get_buf(vq, NULL);
+
+    /* Clear interrupt status register.  Avoid leaving interrupts stuck if
+     * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised.
+     */
+    vp_get_isr(ioaddr);
+
+    if (resp.response == VIRTIO_SCSI_S_OK && resp.status == 0) {
+        return DISK_RET_SUCCESS;
+    }
+    return DISK_RET_EBADTRACK;
+}
+
+int
+virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
+{
+    struct virtio_lun_s *vlun =
+        container_of(op->drive_g, struct virtio_lun_s, drive);
+
+    return virtio_scsi_cmd(GET_GLOBAL(vlun->ioaddr),
+                           GET_GLOBAL(vlun->vq), op, cdbcmd,
+                           GET_GLOBAL(vlun->target), GET_GLOBAL(vlun->lun),
+                           blocksize * op->count);
+}
+
+static int
+setup_lun_cdrom(struct virtio_lun_s *vlun, char *desc)
+{
+    int prio = bootprio_find_scsi_device(vlun->pci, vlun->target, vlun->lun);
+    boot_add_cd(&vlun->drive, desc, prio);
+    return 0;
+}
+
+static int
+setup_lun_hd(struct virtio_lun_s *vlun, char *desc)
+{
+    if (vlun->drive.blksize != DISK_SECTOR_SIZE) {
+        dprintf(1, "Unsupported block size %d\n", vlun->drive.blksize);
+        return -1;
+    }
+
+    // Register with bcv system.
+    int prio = bootprio_find_scsi_device(vlun->pci, vlun->target, vlun->lun);
+    boot_add_hd(&vlun->drive, desc, prio);
+
+    return 0;
+}
+
+static int
+virtio_scsi_add_lun(struct pci_device *pci, u16 ioaddr,
+                    struct vring_virtqueue *vq, u16 target, u16 lun)
+{
+    struct virtio_lun_s *vlun = malloc_fseg(sizeof(*vlun));
+    if (!vlun) {
+        warn_noalloc();
+        return -1;
+    }
+    memset(vlun, 0, sizeof(*vlun));
+    vlun->drive.type = DTYPE_VIRTIO_SCSI;
+    vlun->drive.cntl_id = pci->bdf;
+    vlun->pci = pci;
+    vlun->ioaddr = ioaddr;
+    vlun->vq = vq;
+    vlun->target = target;
+    vlun->lun = lun;
+
+    int pdt, ret;
+    char *desc = NULL;
+    ret = scsi_init_drive(&vlun->drive, "virtio-scsi", &pdt, &desc);
+    if (ret)
+        goto fail;
+
+    if (pdt == SCSI_TYPE_CDROM)
+        ret = setup_lun_cdrom(vlun, desc);
+    else
+        ret = setup_lun_hd(vlun, desc);
+    if (ret)
+        goto fail;
+    return ret;
+
+fail:
+    free(vlun);
+    return -1;
+}
+
+static int
+virtio_scsi_scan_target(struct pci_device *pci, u16 ioaddr,
+                        struct vring_virtqueue *vq, u16 target)
+{
+    /* TODO: send REPORT LUNS.  For now, only LUN 0 is recognized.  */
+    int ret = virtio_scsi_add_lun(pci, ioaddr, vq, target, 0);
+    return ret < 0 ? ret : 1;
+}
+
+static void
+init_virtio_scsi(struct pci_device *pci)
+{
+    u16 bdf = pci->bdf;
+    dprintf(1, "found virtio-scsi at %x:%x\n", pci_bdf_to_bus(bdf),
+            pci_bdf_to_dev(bdf));
+    struct vring_virtqueue *vq = NULL;
+    u16 ioaddr = vp_init_simple(bdf);
+    if (vp_find_vq(ioaddr, 2, &vq) < 0 ) {
+        if (vq) {
+            dprintf(1, "fail to find vq for virtio-scsi %x:%x\n",
+                    pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
+        }
+        goto fail;
+    }
+
+    int i, tot;
+    for (tot = 0, i = 0; i < 256; i++)
+        tot += virtio_scsi_scan_target(pci, ioaddr, vq, i);
+
+    if (!tot)
+        goto fail;
+
+    vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+                  VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK);
+    return;
+
+fail:
+    free(vq);
+}
+
+void
+virtio_scsi_setup(void)
+{
+    ASSERT32FLAT();
+    if (! CONFIG_VIRTIO_SCSI || CONFIG_COREBOOT)
+        return;
+
+    dprintf(3, "init virtio-scsi\n");
+
+    struct pci_device *pci;
+    foreachpci(pci) {
+        if (pci->vendor != PCI_VENDOR_ID_REDHAT_QUMRANET
+            || pci->device != PCI_DEVICE_ID_VIRTIO_SCSI)
+            continue;
+        init_virtio_scsi(pci);
+    }
+}
diff --git a/src/virtio-scsi.h b/src/virtio-scsi.h
new file mode 100644
index 0000000..28d48b0
--- /dev/null
+++ b/src/virtio-scsi.h
@@ -0,0 +1,48 @@
+#ifndef _VIRTIO_SCSI_H
+#define _VIRTIO_SCSI_H
+
+#define VIRTIO_SCSI_CDB_SIZE      32
+#define VIRTIO_SCSI_SENSE_SIZE    96
+
+struct virtio_scsi_config
+{
+    u32 num_queues;
+    u32 seg_max;
+    u32 max_sectors;
+    u32 cmd_per_lun;
+    u32 event_info_size;
+    u32 sense_size;
+    u32 cdb_size;
+    u16 max_channel;
+    u16 max_target;
+    u32 max_lun;
+} __attribute__((packed));
+
+/* This is the first element of the "out" scatter-gather list. */
+struct virtio_scsi_req_cmd {
+    u8 lun[8];
+    u64 id;
+    u8 task_attr;
+    u8 prio;
+    u8 crn;
+    char cdb[VIRTIO_SCSI_CDB_SIZE];
+};
+
+/* This is the first element of the "in" scatter-gather list. */
+struct virtio_scsi_resp_cmd {
+    u32 sense_len;
+    u32 residual;
+    u16 status_qualifier;
+    u8 status;
+    u8 response;
+    u8 sense[VIRTIO_SCSI_SENSE_SIZE];
+};
+
+#define VIRTIO_SCSI_S_OK            0
+
+struct disk_op_s;
+int process_virtio_scsi_op(struct disk_op_s *op);
+int virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize);
+void virtio_scsi_setup(void);
+
+#endif /* _VIRTIO_SCSI_H */