microblaze: linux-user support.
[qemu] / hw / usb-msd.c
index e4cfca0..3a3eb4a 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
  * USB Mass Storage Device emulation
  *
  * Copyright (c) 2006 CodeSourcery.
@@ -7,15 +7,19 @@
  * This code is licenced under the LGPL.
  */
 
-#include "vl.h"
+#include "qemu-common.h"
+#include "usb.h"
+#include "block.h"
+#include "scsi-disk.h"
+#include "console.h"
 
 //#define DEBUG_MSD
 
 #ifdef DEBUG_MSD
-#define DPRINTF(fmt, args...) \
-do { printf("usb-msd: " fmt , ##args); } while (0)
+#define DPRINTF(fmt, ...) \
+do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0)
 #else
-#define DPRINTF(fmt, args...) do {} while(0)
+#define DPRINTF(fmt, ...) do {} while(0)
 #endif
 
 /* USB requests.  */
@@ -32,8 +36,12 @@ enum USBMSDMode {
 typedef struct {
     USBDevice dev;
     enum USBMSDMode mode;
+    uint32_t scsi_len;
+    uint8_t *scsi_buf;
+    uint32_t usb_len;
+    uint8_t *usb_buf;
     uint32_t data_len;
-    uint32_t transfer_len;
+    uint32_t residue;
     uint32_t tag;
     BlockDriverState *bs;
     SCSIDevice *scsi_dev;
@@ -42,10 +50,27 @@ typedef struct {
     USBPacket *packet;
 } MSDState;
 
+struct usb_msd_cbw {
+    uint32_t sig;
+    uint32_t tag;
+    uint32_t data_len;
+    uint8_t flags;
+    uint8_t lun;
+    uint8_t cmd_len;
+    uint8_t cmd[16];
+};
+
+struct usb_msd_csw {
+    uint32_t sig;
+    uint32_t tag;
+    uint32_t residue;
+    uint8_t status;
+};
+
 static const uint8_t qemu_msd_dev_descriptor[] = {
        0x12,       /*  u8 bLength; */
        0x01,       /*  u8 bDescriptorType; Device */
-       0x10, 0x00, /*  u16 bcdUSB; v1.0 */
+       0x00, 0x01, /*  u16 bcdUSB; v1.0 */
 
        0x00,       /*  u8  bDeviceClass; */
        0x00,       /*  u8  bDeviceSubClass; */
@@ -72,13 +97,13 @@ static const uint8_t qemu_msd_config_descriptor[] = {
        0x01,       /*  u8  bNumInterfaces; (1) */
        0x01,       /*  u8  bConfigurationValue; */
        0x00,       /*  u8  iConfiguration; */
-       0xc0,       /*  u8  bmAttributes; 
+       0xc0,       /*  u8  bmAttributes;
                                 Bit 7: must be set,
                                     6: Self-powered,
                                     5: Remote wakeup,
                                     4..0: resvd */
        0x00,       /*  u8  MaxPower; */
-      
+
        /* one interface */
        0x09,       /*  u8  if_bLength; */
        0x04,       /*  u8  if_bDescriptorType; Interface */
@@ -89,7 +114,7 @@ static const uint8_t qemu_msd_config_descriptor[] = {
        0x06,       /*  u8  if_bInterfaceSubClass; SCSI */
        0x50,       /*  u8  if_bInterfaceProtocol; Bulk Only */
        0x00,       /*  u8  if_iInterface; */
-     
+
        /* Bulk-In endpoint */
        0x07,       /*  u8  ep_bLength; */
        0x05,       /*  u8  ep_bDescriptorType; Endpoint */
@@ -107,26 +132,90 @@ static const uint8_t qemu_msd_config_descriptor[] = {
        0x00        /*  u8  ep_bInterval; */
 };
 
-static void usb_msd_command_complete(void *opaque, uint32_t reason, int fail)
+static void usb_msd_copy_data(MSDState *s)
+{
+    uint32_t len;
+    len = s->usb_len;
+    if (len > s->scsi_len)
+        len = s->scsi_len;
+    if (s->mode == USB_MSDM_DATAIN) {
+        memcpy(s->usb_buf, s->scsi_buf, len);
+    } else {
+        memcpy(s->scsi_buf, s->usb_buf, len);
+    }
+    s->usb_len -= len;
+    s->scsi_len -= len;
+    s->usb_buf += len;
+    s->scsi_buf += len;
+    s->data_len -= len;
+    if (s->scsi_len == 0) {
+        if (s->mode == USB_MSDM_DATAIN) {
+            s->scsi_dev->read_data(s->scsi_dev, s->tag);
+        } else if (s->mode == USB_MSDM_DATAOUT) {
+            s->scsi_dev->write_data(s->scsi_dev, s->tag);
+        }
+    }
+}
+
+static void usb_msd_send_status(MSDState *s)
+{
+    struct usb_msd_csw csw;
+
+    csw.sig = cpu_to_le32(0x53425355);
+    csw.tag = cpu_to_le32(s->tag);
+    csw.residue = s->residue;
+    csw.status = s->result;
+    memcpy(s->usb_buf, &csw, 13);
+}
+
+static void usb_msd_command_complete(void *opaque, int reason, uint32_t tag,
+                                     uint32_t arg)
 {
     MSDState *s = (MSDState *)opaque;
-    USBPacket *p;
+    USBPacket *p = s->packet;
 
-    s->data_len -= s->transfer_len;
-    s->transfer_len = 0;
+    if (tag != s->tag) {
+        fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", tag);
+    }
     if (reason == SCSI_REASON_DONE) {
-        DPRINTF("Command complete %d\n", fail);
-        s->result = fail;
-        s->mode = USB_MSDM_CSW;
+        DPRINTF("Command complete %d\n", arg);
+        s->residue = s->data_len;
+        s->result = arg != 0;
+        if (s->packet) {
+            if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) {
+                /* A deferred packet with no write data remaining must be
+                   the status read packet.  */
+                usb_msd_send_status(s);
+                s->mode = USB_MSDM_CBW;
+            } else {
+                if (s->data_len) {
+                    s->data_len -= s->usb_len;
+                    if (s->mode == USB_MSDM_DATAIN)
+                        memset(s->usb_buf, 0, s->usb_len);
+                    s->usb_len = 0;
+                }
+                if (s->data_len == 0)
+                    s->mode = USB_MSDM_CSW;
+            }
+            s->packet = NULL;
+            usb_packet_complete(p);
+        } else if (s->data_len == 0) {
+            s->mode = USB_MSDM_CSW;
+        }
+        return;
     }
-    if (s->packet) {
-        /* Set s->packet to NULL before calling usb_packet_complete because
-           annother request may be issues before usb_packet_complete returns.
-         */
-        DPRINTF("Packet complete %p\n", p);
-        p = s->packet;
-        s->packet = NULL;
-        usb_packet_complete(p);
+    s->scsi_len = arg;
+    s->scsi_buf = s->scsi_dev->get_buf(s->scsi_dev, tag);
+    if (p) {
+        usb_msd_copy_data(s);
+        if (s->usb_len == 0) {
+            /* Set s->packet to NULL before calling usb_packet_complete
+               because annother request may be issued before
+               usb_packet_complete returns.  */
+            DPRINTF("Packet complete %p\n", p);
+            s->packet = NULL;
+            usb_packet_complete(p);
+        }
     }
 }
 
@@ -174,12 +263,12 @@ static int usb_msd_handle_control(USBDevice *dev, int request, int value,
     case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
         switch(value >> 8) {
         case USB_DT_DEVICE:
-            memcpy(data, qemu_msd_dev_descriptor, 
+            memcpy(data, qemu_msd_dev_descriptor,
                    sizeof(qemu_msd_dev_descriptor));
             ret = sizeof(qemu_msd_dev_descriptor);
             break;
         case USB_DT_CONFIG:
-            memcpy(data, qemu_msd_config_descriptor, 
+            memcpy(data, qemu_msd_config_descriptor,
                    sizeof(qemu_msd_config_descriptor));
             ret = sizeof(qemu_msd_config_descriptor);
             break;
@@ -251,28 +340,12 @@ static int usb_msd_handle_control(USBDevice *dev, int request, int value,
     return ret;
 }
 
-struct usb_msd_cbw {
-    uint32_t sig;
-    uint32_t tag;
-    uint32_t data_len;
-    uint8_t flags;
-    uint8_t lun;
-    uint8_t cmd_len;
-    uint8_t cmd[16];
-};
-
-struct usb_msd_csw {
-    uint32_t sig;
-    uint32_t tag;
-    uint32_t residue;
-    uint8_t status;
-};
-
 static void usb_msd_cancel_io(USBPacket *p, void *opaque)
 {
     MSDState *s = opaque;
-    scsi_cancel_io(s->scsi_dev);
+    s->scsi_dev->cancel_io(s->scsi_dev, s->tag);
     s->packet = NULL;
+    s->scsi_len = 0;
 }
 
 static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
@@ -280,7 +353,6 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
     MSDState *s = (MSDState *)dev;
     int ret = 0;
     struct usb_msd_cbw cbw;
-    struct usb_msd_csw csw;
     uint8_t devep = p->devep;
     uint8_t *data = p->data;
     int len = p->len;
@@ -318,7 +390,17 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
             }
             DPRINTF("Command tag 0x%x flags %08x len %d data %d\n",
                     s->tag, cbw.flags, cbw.cmd_len, s->data_len);
-            scsi_send_command(s->scsi_dev, s->tag, cbw.cmd, 0);
+            s->residue = 0;
+            s->scsi_dev->send_command(s->scsi_dev, s->tag, cbw.cmd, 0);
+            /* ??? Should check that USB and SCSI data transfer
+               directions match.  */
+            if (s->residue == 0) {
+                if (s->mode == USB_MSDM_DATAIN) {
+                    s->scsi_dev->read_data(s->scsi_dev, s->tag);
+                } else if (s->mode == USB_MSDM_DATAOUT) {
+                    s->scsi_dev->write_data(s->scsi_dev, s->tag);
+                }
+            }
             ret = len;
             break;
 
@@ -327,17 +409,24 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
             if (len > s->data_len)
                 goto fail;
 
-            s->transfer_len = len;
-            if (scsi_write_data(s->scsi_dev, data, len))
-                goto fail;
-
-            if (s->transfer_len == 0) {
-                ret = len;
-            } else {
+            s->usb_buf = data;
+            s->usb_len = len;
+            if (s->scsi_len) {
+                usb_msd_copy_data(s);
+            }
+            if (s->residue && s->usb_len) {
+                s->data_len -= s->usb_len;
+                if (s->data_len == 0)
+                    s->mode = USB_MSDM_CSW;
+                s->usb_len = 0;
+            }
+            if (s->usb_len) {
                 DPRINTF("Deferring packet %p\n", p);
                 usb_defer_packet(p, usb_msd_cancel_io, s);
                 s->packet = p;
                 ret = USB_RET_ASYNC;
+            } else {
+                ret = len;
             }
             break;
 
@@ -352,37 +441,51 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
             goto fail;
 
         switch (s->mode) {
+        case USB_MSDM_DATAOUT:
+            if (s->data_len != 0 || len < 13)
+                goto fail;
+            /* Waiting for SCSI write to complete.  */
+            usb_defer_packet(p, usb_msd_cancel_io, s);
+            s->packet = p;
+            ret = USB_RET_ASYNC;
+            break;
+
         case USB_MSDM_CSW:
             DPRINTF("Command status %d tag 0x%x, len %d\n",
                     s->result, s->tag, len);
             if (len < 13)
                 goto fail;
 
-            csw.sig = cpu_to_le32(0x53425355);
-            csw.tag = cpu_to_le32(s->tag);
-            csw.residue = 0;
-            csw.status = s->result;
-            memcpy(data, &csw, 13);
-            ret = 13;
+            s->usb_len = len;
+            s->usb_buf = data;
+            usb_msd_send_status(s);
             s->mode = USB_MSDM_CBW;
+            ret = 13;
             break;
 
         case USB_MSDM_DATAIN:
             DPRINTF("Data in %d/%d\n", len, s->data_len);
             if (len > s->data_len)
                 len = s->data_len;
-
-            s->transfer_len = len;
-            if (scsi_read_data(s->scsi_dev, data, len))
-                goto fail;
-
-            if (s->transfer_len == 0) {
-                ret = len;
-            } else {
+            s->usb_buf = data;
+            s->usb_len = len;
+            if (s->scsi_len) {
+                usb_msd_copy_data(s);
+            }
+            if (s->residue && s->usb_len) {
+                s->data_len -= s->usb_len;
+                memset(s->usb_buf, 0, s->usb_len);
+                if (s->data_len == 0)
+                    s->mode = USB_MSDM_CSW;
+                s->usb_len = 0;
+            }
+            if (s->usb_len) {
                 DPRINTF("Deferring packet %p\n", p);
                 usb_defer_packet(p, usb_msd_cancel_io, s);
                 s->packet = p;
                 ret = USB_RET_ASYNC;
+            } else {
+                ret = len;
             }
             break;
 
@@ -406,7 +509,7 @@ static void usb_msd_handle_destroy(USBDevice *dev)
 {
     MSDState *s = (MSDState *)dev;
 
-    scsi_disk_destroy(s->scsi_dev);
+    s->scsi_dev->destroy(s->scsi_dev);
     bdrv_delete(s->bs);
     qemu_free(s);
 }
@@ -415,13 +518,40 @@ USBDevice *usb_msd_init(const char *filename)
 {
     MSDState *s;
     BlockDriverState *bdrv;
+    BlockDriver *drv = NULL;
+    const char *p1;
+    char fmt[32];
+
+    p1 = strchr(filename, ':');
+    if (p1++) {
+        const char *p2;
+
+        if (strstart(filename, "format=", &p2)) {
+            int len = MIN(p1 - p2, sizeof(fmt));
+            pstrcpy(fmt, len, p2);
+
+            drv = bdrv_find_format(fmt);
+            if (!drv) {
+                printf("invalid format %s\n", fmt);
+                return NULL;
+            }
+        } else if (*filename != ':') {
+            printf("unrecognized USB mass-storage option %s\n", filename);
+            return NULL;
+        }
 
-    s = qemu_mallocz(sizeof(MSDState));
-    if (!s)
+        filename = p1;
+    }
+
+    if (!*filename) {
+        printf("block device specification needed\n");
         return NULL;
+    }
+
+    s = qemu_mallocz(sizeof(MSDState));
 
     bdrv = bdrv_new("usb");
-    if (bdrv_open(bdrv, filename, 0) < 0)
+    if (bdrv_open2(bdrv, filename, 0, drv) < 0)
         goto fail;
     s->bs = bdrv;
 
@@ -436,10 +566,17 @@ USBDevice *usb_msd_init(const char *filename)
     snprintf(s->dev.devname, sizeof(s->dev.devname), "QEMU USB MSD(%.16s)",
              filename);
 
-    s->scsi_dev = scsi_disk_init(bdrv, usb_msd_command_complete, s);
+    s->scsi_dev = scsi_disk_init(bdrv, 0, usb_msd_command_complete, s);
     usb_msd_handle_reset((USBDevice *)s);
     return (USBDevice *)s;
  fail:
     qemu_free(s);
     return NULL;
 }
+
+BlockDriverState *usb_msd_get_bdrv(USBDevice *dev)
+{
+    MSDState *s = (MSDState *)dev;
+
+    return s->bs;
+}