SCSI and USB async IO support.
[qemu] / hw / scsi-disk.c
index 2268401..a2f299e 100644 (file)
@@ -24,6 +24,8 @@ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0)
 #include "vl.h"
 
 #define SENSE_NO_SENSE        0
+#define SENSE_NOT_READY       2
+#define SENSE_HARDWARE_ERROR  4
 #define SENSE_ILLEGAL_REQUEST 5
 
 struct SCSIDevice
@@ -45,7 +47,13 @@ struct SCSIDevice
     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;
 };
@@ -53,10 +61,49 @@ struct SCSIDevice
 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;
@@ -83,14 +130,19 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
       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--;
@@ -105,11 +157,53 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
 
     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;
@@ -124,48 +218,39 @@ int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
         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 (n == 0) {
+        /* Transfer completes immediately.  */
+        if (s->sector_count == 0)
+            scsi_command_complete(s, SENSE_NO_SENSE);
+        else
+            scsi_transfer_complete(s);
     }
-
-    if (len != 0)
-        return 1;
-
-    if (s->sector_count == 0)
-        scsi_command_complete(s, SENSE_NO_SENSE);
 
     return 0;
 }
@@ -213,7 +298,7 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun)
         cmdlen = 12;
         break;
     default:
-        BADF("Unsupported command length\n");
+        BADF("Unsupported command length, command %x\n", s->command);
         goto fail;
     }
 #ifdef DEBUG_SCSI
@@ -260,7 +345,9 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun)
        }
        memcpy(&s->buf[8], "QEMU   ", 8);
         memcpy(&s->buf[32], QEMU_VERSION, 4);
-       s->buf[2] = 3; /* SCSI-3 */
+        /* Identify device as SCSI-3 rev 1.
+           Some later commands are also implemented. */
+       s->buf[2] = 3;
        s->buf[3] = 2; /* Format 2 */
        s->buf[4] = 32;
        s->buf_len = 36;
@@ -277,42 +364,90 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun)
         break;
     case 0x1a:
     case 0x5a:
-       DPRINTF("Mode Sense (page %d, len %d)\n", buf[2], len);
-        if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
-            memset(s->buf, 0, 4);
-            s->buf[0] = 4; /* Mode data length.  */
-            s->buf[1] = 0; /* Default media type.  */
-            s->buf[2] = 0x80; /* Readonly.  */
-            s->buf[3] = 0; /* Block descriptor length.  */
-        } else {
-            memset(s->buf, 0, 0x16);
-            s->buf[0] = 0x16; /* Mode data length (4 + 0x12).  */
+        {
+            char *p;
+            int page;
+
+            page = buf[2] & 0x3f;
+            DPRINTF("Mode Sense (page %d, len %d)\n", page, len);
+            p = s->buf;
+            memset(p, 0, 4);
             s->buf[1] = 0; /* Default media type.  */
-            s->buf[2] = 0; /* Write enabled.  */
             s->buf[3] = 0; /* Block descriptor length.  */
-            /* Caching page.  */
-            s->buf[4 + 0] = 8;
-            s->buf[4 + 1] = 0x12;
-            s->buf[4 + 2] = 4; /* WCE */
-            if (len > 0x16)
-                len = 0x16;
+            if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
+                s->buf[2] = 0x80; /* Readonly.  */
+            }
+            p += 4;
+            if ((page == 8 || page == 0x3f)) {
+                /* Caching page.  */
+                p[0] = 8;
+                p[1] = 0x12;
+                p[2] = 4; /* WCE */
+                p += 19;
+            }
+            if ((page == 0x3f || page == 0x2a)
+                    && (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM)) {
+                /* CD Capabilities and Mechanical Status page. */
+                p[0] = 0x2a;
+                p[1] = 0x14;
+                p[2] = 3; // CD-R & CD-RW read
+                p[3] = 0; // Writing not supported
+                p[4] = 0x7f; /* Audio, composite, digital out,
+                                         mode 2 form 1&2, multi session */
+                p[5] = 0xff; /* CD DA, DA accurate, RW supported,
+                                         RW corrected, C2 errors, ISRC,
+                                         UPC, Bar code */
+                p[6] = 0x2d | (bdrv_is_locked(s->bdrv)? 2 : 0);
+                /* Locking supported, jumper present, eject, tray */
+                p[7] = 0; /* no volume & mute control, no
+                                      changer */
+                p[8] = (50 * 176) >> 8; // 50x read speed
+                p[9] = (50 * 176) & 0xff;
+                p[10] = 0 >> 8; // No volume
+                p[11] = 0 & 0xff;
+                p[12] = 2048 >> 8; // 2M buffer
+                p[13] = 2048 & 0xff;
+                p[14] = (16 * 176) >> 8; // 16x read speed current
+                p[15] = (16 * 176) & 0xff;
+                p[18] = (16 * 176) >> 8; // 16x write speed
+                p[19] = (16 * 176) & 0xff;
+                p[20] = (16 * 176) >> 8; // 16x write speed current
+                p[21] = (16 * 176) & 0xff;
+                p += 21;
+            }
+            s->buf_len = p - s->buf;
+            s->buf[0] = s->buf_len - 4;
+            if (s->buf_len > len)
+                s->buf_len = len;
         }
-        s->buf_len = len;
+        break;
+    case 0x1b:
+        DPRINTF("Start Stop Unit\n");
+       break;
+    case 0x1e:
+        DPRINTF("Prevent Allow Medium Removal (prevent = %d)\n", buf[4] & 3);
+        bdrv_set_locked(s->bdrv, buf[4] & 1);
        break;
     case 0x25:
        DPRINTF("Read Capacity\n");
         /* The normal LEN field for this command is zero.  */
        memset(s->buf, 0, 8);
        bdrv_get_geometry(s->bdrv, &nb_sectors);
-       s->buf[0] = (nb_sectors >> 24) & 0xff;
-       s->buf[1] = (nb_sectors >> 16) & 0xff;
-       s->buf[2] = (nb_sectors >> 8) & 0xff;
-       s->buf[3] = nb_sectors & 0xff;
-       s->buf[4] = 0;
-       s->buf[5] = 0;
-        s->buf[6] = s->cluster_size * 2;
-       s->buf[7] = 0;
-       s->buf_len = 8;
+        /* Returned value is the address of the last sector.  */
+        if (nb_sectors) {
+            nb_sectors--;
+            s->buf[0] = (nb_sectors >> 24) & 0xff;
+            s->buf[1] = (nb_sectors >> 16) & 0xff;
+            s->buf[2] = (nb_sectors >> 8) & 0xff;
+            s->buf[3] = nb_sectors & 0xff;
+            s->buf[4] = 0;
+            s->buf[5] = 0;
+            s->buf[6] = s->cluster_size * 2;
+            s->buf[7] = 0;
+            s->buf_len = 8;
+        } else {
+            scsi_command_complete(s, SENSE_NOT_READY);
+        }
        break;
     case 0x08:
     case 0x28:
@@ -329,7 +464,7 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun)
         break;
     case 0x35:
         DPRINTF("Syncronise cache (sector %d, count %d)\n", lba, len);
-        /* ??? Extend block layer and use fsync to implement this.  */
+        bdrv_flush(s->bdrv);
         break;
     case 0x43:
         {
@@ -368,6 +503,14 @@ int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun)
             DPRINTF("Read TOC error\n");
             goto fail;
         }
+    case 0x46:
+        DPRINTF("Get Configuration (rt %d, maxlen %d)\n", buf[1] & 3, len);
+        memset(s->buf, 0, 8);
+        /* ??? This shoud probably return much more information.  For now
+           just return the basic header indicating the CD-ROM profile.  */
+        s->buf[7] = 8; // CD-ROM
+        s->buf_len = 8;
+        break;
     case 0x56:
         DPRINTF("Reserve(10)\n");
         if (buf[1] & 3)