SCSI and USB async IO support.


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2107 c046a42c-6fe2-441c-8c8c-71466251a162
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 3419c8e..a2f299e 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -25,6 +25,7 @@
 
 #define SENSE_NO_SENSE        0
 #define SENSE_NOT_READY       2
+#define SENSE_HARDWARE_ERROR  4
 #define SENSE_ILLEGAL_REQUEST 5
 
 struct SCSIDevice
@@ -46,7 +47,13 @@
     int buf_pos;
     int buf_len;
     int sense;
+    BlockDriverAIOCB *aiocb;
+    /* Data still to be transfered after this request completes.  */
+    uint8_t *aiodata;
+    uint32_t aiolen;
     char buf[512];
+    /* Completion functions may be called from either scsi_{read,write}_data
+       or from the AIO completion routines.  */
     scsi_completionfn completion;
     void *opaque;
 };
@@ -54,10 +61,49 @@
 static void scsi_command_complete(SCSIDevice *s, int sense)
 {
     s->sense = sense;
-    s->completion(s->opaque, s->tag, sense);
+    s->completion(s->opaque, SCSI_REASON_DONE, sense);
 }
 
-/* Read data from a scsi device.  Returns nonzero on failure.  */
+static void scsi_transfer_complete(SCSIDevice *s)
+{
+    s->completion(s->opaque, SCSI_REASON_DATA, 0);
+    s->aiocb = NULL;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+    SCSIDevice *s = (SCSIDevice *)opaque;
+
+    if (ret) {
+        DPRINTF("IO error\n");
+        scsi_command_complete(s, SENSE_HARDWARE_ERROR);
+    }
+
+    if (s->aiolen) {
+        /* Read the remaining data.  Full and partial sectors are transferred
+           separately.  */
+        scsi_read_data(s, s->aiodata, s->aiolen);
+    } else {
+        if (s->buf_len == 0 && s->sector_count == 0)
+            scsi_command_complete(s, SENSE_NO_SENSE);
+        else
+            scsi_transfer_complete(s);
+    }
+}
+
+/* Cancel a pending data transfer.  */
+void scsi_cancel_io(SCSIDevice *s)
+{
+    if (!s->aiocb) {
+        BADF("Cancel with no pending IO\n");
+        return;
+    }
+    bdrv_aio_cancel(s->aiocb);
+    s->aiocb = NULL;
+}
+
+/* Read data from a scsi device.  Returns nonzero on failure.
+   The transfer may complete asynchronously.  */
 int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
 {
     uint32_t n;
@@ -84,14 +130,19 @@
       n = s->sector_count;
 
     if (n != 0) {
-        bdrv_read(s->bdrv, s->sector, data, n);
-        data += n * 512;
-        len -= n * 512;
+        s->aiolen = len - n * 512;
+        s->aiodata = data + n * 512;
+        s->aiocb = bdrv_aio_read(s->bdrv, s->sector, data, n,
+                                 scsi_read_complete, s);
+        if (s->aiocb == NULL)
+            scsi_command_complete(s, SENSE_HARDWARE_ERROR);
         s->sector += n;
         s->sector_count -= n;
+        return 0;
     }
 
     if (len && s->sector_count) {
+        /* TODO: Make this use AIO.  */
         bdrv_read(s->bdrv, s->sector, s->buf, 1);
         s->sector++;
         s->sector_count--;
@@ -106,11 +157,53 @@
 
     if (s->buf_len == 0 && s->sector_count == 0)
         scsi_command_complete(s, SENSE_NO_SENSE);
+    else
+        scsi_transfer_complete(s);
 
     return 0;
 }
 
-/* Read data to a scsi device.  Returns nonzero on failure.  */
+static void scsi_write_complete(void * opaque, int ret)
+{
+    SCSIDevice *s = (SCSIDevice *)opaque;
+
+    if (ret) {
+        fprintf(stderr, "scsi-disc: IO write error\n");
+        exit(1);
+    }
+
+    if (s->sector_count == 0)
+        scsi_command_complete(s, SENSE_NO_SENSE);
+    else
+        scsi_transfer_complete(s);
+}
+
+static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data,
+                                          uint32_t len)
+{
+    int n;
+
+    n = 512 - s->buf_len;
+    if (n > len)
+        n = len;
+
+    memcpy(s->buf + s->buf_len, data, n);
+    data += n;
+    s->buf_len += n;
+    len -= n;
+    if (s->buf_len == 512) {
+        /* A full sector has been accumulated. Write it to disk.  */
+        /* TODO: Make this use async IO.  */
+        bdrv_write(s->bdrv, s->sector, s->buf, 1);
+        s->buf_len = 0;
+        s->sector++;
+        s->sector_count--;
+    }
+    return n;
+}
+
+/* Write data to a scsi device.  Returns nonzero on failure.
+   The transfer may complete asynchronously.  */
 int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
 {
     uint32_t n;
@@ -125,48 +218,39 @@
         return 1;
 
     if (s->buf_len != 0 || len < 512) {
-        n = 512 - s->buf_len;
-        if (n > len)
-            n = len;
-
-        memcpy(s->buf + s->buf_len, data, n);
-        data += n;
-        s->buf_len += n;
+        n = scsi_write_partial_sector(s, data, len);
         len -= n;
-        if (s->buf_len == 512) {
-            /* A full sector has been accumulated. Write it to disk.  */
-            bdrv_write(s->bdrv, s->sector, s->buf, 1);
-            s->buf_len = 0;
-            s->sector++;
-            s->sector_count--;
-        }
+        data += n;
     }
 
     n = len / 512;
     if (n > s->sector_count)
-        n = s->sector_count;
+        return 1;
 
     if (n != 0) {
-        bdrv_write(s->bdrv, s->sector, data, n);
+        s->aiocb = bdrv_aio_write(s->bdrv, s->sector, data, n,
+                                   scsi_write_complete, s);
+        if (s->aiocb == NULL)
+            scsi_command_complete(s, SENSE_HARDWARE_ERROR);
         data += n * 512;
         len -= n * 512;
         s->sector += n;
         s->sector_count -= n;
     }
 
-    if (len >= 512)
-        return 1;
-
-    if (len && s->sector_count) {
-        /* Recurse to complete the partial write.  */
-        return scsi_write_data(s, data, len);
+    if (len) {
+        if (s->sector_count == 0)
+            return 1;
+        /* Complete a partial write.  */
+        scsi_write_partial_sector(s, data, len);
     }
-
-    if (len != 0)
-        return 1;
-
-    if (s->sector_count == 0)
-        scsi_command_complete(s, SENSE_NO_SENSE);
+    if (n == 0) {
+        /* Transfer completes immediately.  */
+        if (s->sector_count == 0)
+            scsi_command_complete(s, SENSE_NO_SENSE);
+        else
+            scsi_transfer_complete(s);
+    }
 
     return 0;
 }