]> git.proxmox.com Git - qemu.git/blobdiff - hw/usb-msd.c
usb storage: high speed support
[qemu.git] / hw / usb-msd.c
index 222689aa341bdd83d81140fe87d8d53e571d0677..7b8189fe0c1a9f7a7e62a099a0f15e6a2c756f68 100644 (file)
@@ -8,10 +8,15 @@
  */
 
 #include "qemu-common.h"
+#include "qemu-option.h"
+#include "qemu-config.h"
 #include "usb.h"
-#include "block.h"
-#include "scsi-disk.h"
+#include "usb-desc.h"
+#include "scsi.h"
 #include "console.h"
+#include "monitor.h"
+#include "sysemu.h"
+#include "blockdev.h"
 
 //#define DEBUG_MSD
 
@@ -43,8 +48,8 @@ typedef struct {
     uint32_t data_len;
     uint32_t residue;
     uint32_t tag;
-    BlockDriverState *bs;
-    SCSIBus *bus;
+    SCSIBus bus;
+    BlockConf conf;
     SCSIDevice *scsi_dev;
     int result;
     /* For async completion.  */
@@ -68,69 +73,102 @@ struct usb_msd_csw {
     uint8_t status;
 };
 
-static const uint8_t qemu_msd_dev_descriptor[] = {
-       0x12,       /*  u8 bLength; */
-       0x01,       /*  u8 bDescriptorType; Device */
-       0x00, 0x01, /*  u16 bcdUSB; v1.0 */
-
-       0x00,       /*  u8  bDeviceClass; */
-       0x00,       /*  u8  bDeviceSubClass; */
-       0x00,       /*  u8  bDeviceProtocol; [ low/full speeds only ] */
-       0x08,       /*  u8  bMaxPacketSize0; 8 Bytes */
-
-        /* Vendor and product id are arbitrary.  */
-       0x00, 0x00, /*  u16 idVendor; */
-       0x00, 0x00, /*  u16 idProduct; */
-       0x00, 0x00, /*  u16 bcdDevice */
-
-       0x01,       /*  u8  iManufacturer; */
-       0x02,       /*  u8  iProduct; */
-       0x03,       /*  u8  iSerialNumber; */
-       0x01        /*  u8  bNumConfigurations; */
+enum {
+    STR_MANUFACTURER = 1,
+    STR_PRODUCT,
+    STR_SERIALNUMBER,
+    STR_CONFIG_FULL,
+    STR_CONFIG_HIGH,
 };
 
-static const uint8_t qemu_msd_config_descriptor[] = {
-
-       /* one configuration */
-       0x09,       /*  u8  bLength; */
-       0x02,       /*  u8  bDescriptorType; Configuration */
-       0x20, 0x00, /*  u16 wTotalLength; */
-       0x01,       /*  u8  bNumInterfaces; (1) */
-       0x01,       /*  u8  bConfigurationValue; */
-       0x00,       /*  u8  iConfiguration; */
-       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 */
-       0x00,       /*  u8  if_bInterfaceNumber; */
-       0x00,       /*  u8  if_bAlternateSetting; */
-       0x02,       /*  u8  if_bNumEndpoints; */
-       0x08,       /*  u8  if_bInterfaceClass; MASS STORAGE */
-       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 */
-       0x81,       /*  u8  ep_bEndpointAddress; IN Endpoint 1 */
-       0x02,       /*  u8  ep_bmAttributes; Bulk */
-       0x40, 0x00, /*  u16 ep_wMaxPacketSize; */
-       0x00,       /*  u8  ep_bInterval; */
-
-       /* Bulk-Out endpoint */
-       0x07,       /*  u8  ep_bLength; */
-       0x05,       /*  u8  ep_bDescriptorType; Endpoint */
-       0x02,       /*  u8  ep_bEndpointAddress; OUT Endpoint 2 */
-       0x02,       /*  u8  ep_bmAttributes; Bulk */
-       0x40, 0x00, /*  u16 ep_wMaxPacketSize; */
-       0x00        /*  u8  ep_bInterval; */
+static const USBDescStrings desc_strings = {
+    [STR_MANUFACTURER] = "QEMU " QEMU_VERSION,
+    [STR_PRODUCT]      = "QEMU USB HARDDRIVE",
+    [STR_SERIALNUMBER] = "1",
+    [STR_CONFIG_FULL]  = "Full speed config (usb 1.1)",
+    [STR_CONFIG_HIGH]  = "High speed config (usb 2.0)",
+};
+
+static const USBDescIface desc_iface_full = {
+    .bInterfaceNumber              = 0,
+    .bNumEndpoints                 = 2,
+    .bInterfaceClass               = USB_CLASS_MASS_STORAGE,
+    .bInterfaceSubClass            = 0x06, /* SCSI */
+    .bInterfaceProtocol            = 0x50, /* Bulk */
+    .eps = (USBDescEndpoint[]) {
+        {
+            .bEndpointAddress      = USB_DIR_IN | 0x01,
+            .bmAttributes          = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize        = 64,
+        },{
+            .bEndpointAddress      = USB_DIR_OUT | 0x02,
+            .bmAttributes          = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize        = 64,
+        },
+    }
+};
+
+static const USBDescDevice desc_device_full = {
+    .bcdUSB                        = 0x0200,
+    .bMaxPacketSize0               = 8,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 1,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = STR_CONFIG_FULL,
+            .bmAttributes          = 0xc0,
+            .ifs = &desc_iface_full,
+        },
+    },
+};
+
+static const USBDescIface desc_iface_high = {
+    .bInterfaceNumber              = 0,
+    .bNumEndpoints                 = 2,
+    .bInterfaceClass               = USB_CLASS_MASS_STORAGE,
+    .bInterfaceSubClass            = 0x06, /* SCSI */
+    .bInterfaceProtocol            = 0x50, /* Bulk */
+    .eps = (USBDescEndpoint[]) {
+        {
+            .bEndpointAddress      = USB_DIR_IN | 0x01,
+            .bmAttributes          = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize        = 512,
+        },{
+            .bEndpointAddress      = USB_DIR_OUT | 0x02,
+            .bmAttributes          = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize        = 512,
+        },
+    }
+};
+
+static const USBDescDevice desc_device_high = {
+    .bcdUSB                        = 0x0200,
+    .bMaxPacketSize0               = 64,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 1,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = STR_CONFIG_HIGH,
+            .bmAttributes          = 0xc0,
+            .ifs = &desc_iface_high,
+        },
+    },
+};
+
+static const USBDesc desc = {
+    .id = {
+        .idVendor          = 0,
+        .idProduct         = 0,
+        .bcdDevice         = 0,
+        .iManufacturer     = STR_MANUFACTURER,
+        .iProduct          = STR_PRODUCT,
+        .iSerialNumber     = STR_SERIALNUMBER,
+    },
+    .full = &desc_device_full,
+    .high = &desc_device_high,
+    .str  = desc_strings,
 };
 
 static void usb_msd_copy_data(MSDState *s)
@@ -232,84 +270,15 @@ static int usb_msd_handle_control(USBDevice *dev, int request, int value,
                                   int index, int length, uint8_t *data)
 {
     MSDState *s = (MSDState *)dev;
-    int ret = 0;
+    int ret;
+
+    ret = usb_desc_handle_control(dev, request, value, index, length, data);
+    if (ret >= 0) {
+        return ret;
+    }
 
+    ret = 0;
     switch (request) {
-    case DeviceRequest | USB_REQ_GET_STATUS:
-        data[0] = (1 << USB_DEVICE_SELF_POWERED) |
-            (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP);
-        data[1] = 0x00;
-        ret = 2;
-        break;
-    case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
-        if (value == USB_DEVICE_REMOTE_WAKEUP) {
-            dev->remote_wakeup = 0;
-        } else {
-            goto fail;
-        }
-        ret = 0;
-        break;
-    case DeviceOutRequest | USB_REQ_SET_FEATURE:
-        if (value == USB_DEVICE_REMOTE_WAKEUP) {
-            dev->remote_wakeup = 1;
-        } else {
-            goto fail;
-        }
-        ret = 0;
-        break;
-    case DeviceOutRequest | USB_REQ_SET_ADDRESS:
-        dev->addr = value;
-        ret = 0;
-        break;
-    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
-        switch(value >> 8) {
-        case USB_DT_DEVICE:
-            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,
-                   sizeof(qemu_msd_config_descriptor));
-            ret = sizeof(qemu_msd_config_descriptor);
-            break;
-        case USB_DT_STRING:
-            switch(value & 0xff) {
-            case 0:
-                /* language ids */
-                data[0] = 4;
-                data[1] = 3;
-                data[2] = 0x09;
-                data[3] = 0x04;
-                ret = 4;
-                break;
-            case 1:
-                /* vendor description */
-                ret = set_usb_string(data, "QEMU " QEMU_VERSION);
-                break;
-            case 2:
-                /* product description */
-                ret = set_usb_string(data, "QEMU USB HARDDRIVE");
-                break;
-            case 3:
-                /* serial number */
-                ret = set_usb_string(data, "1");
-                break;
-            default:
-                goto fail;
-            }
-            break;
-        default:
-            goto fail;
-        }
-        break;
-    case DeviceRequest | USB_REQ_GET_CONFIGURATION:
-        data[0] = 1;
-        ret = 1;
-        break;
-    case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
-        ret = 0;
-        break;
     case DeviceRequest | USB_REQ_GET_INTERFACE:
         data[0] = 0;
         ret = 1;
@@ -318,23 +287,22 @@ static int usb_msd_handle_control(USBDevice *dev, int request, int value,
         ret = 0;
         break;
     case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
-        if (value == 0 && index != 0x81) { /* clear ep halt */
-            goto fail;
-        }
+        ret = 0;
+        break;
+    case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
         ret = 0;
         break;
         /* Class specific requests.  */
-    case MassStorageReset:
+    case ClassInterfaceOutRequest | MassStorageReset:
         /* Reset state ready for the next CBW.  */
         s->mode = USB_MSDM_CBW;
         ret = 0;
         break;
-    case GetMaxLun:
+    case ClassInterfaceRequest | GetMaxLun:
         data[0] = 0;
         ret = 1;
         break;
     default:
-    fail:
         ret = USB_RET_STALL;
         break;
     }
@@ -506,32 +474,80 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p)
     return ret;
 }
 
-static void usb_msd_handle_destroy(USBDevice *dev)
+static void usb_msd_password_cb(void *opaque, int err)
 {
-    MSDState *s = (MSDState *)dev;
+    MSDState *s = opaque;
 
-    s->scsi_dev->info->destroy(s->scsi_dev);
-    bdrv_delete(s->bs);
-    qemu_free(s);
+    if (!err)
+        usb_device_attach(&s->dev);
+    else
+        qdev_unplug(&s->dev.qdev);
 }
 
 static int usb_msd_initfn(USBDevice *dev)
 {
     MSDState *s = DO_UPCAST(MSDState, dev, dev);
+    BlockDriverState *bs = s->conf.bs;
+    DriveInfo *dinfo;
+
+    if (!bs) {
+        error_report("usb-msd: drive property not set");
+        return -1;
+    }
+
+    /*
+     * Hack alert: this pretends to be a block device, but it's really
+     * a SCSI bus that can serve only a single device, which it
+     * creates automatically.  But first it needs to detach from its
+     * blockdev, or else scsi_bus_legacy_add_drive() dies when it
+     * attaches again.
+     *
+     * The hack is probably a bad idea.
+     */
+    bdrv_detach(bs, &s->dev.qdev);
+    s->conf.bs = NULL;
+
+    dinfo = drive_get_by_blockdev(bs);
+    if (dinfo && dinfo->serial) {
+        usb_desc_set_string(dev, STR_SERIALNUMBER, dinfo->serial);
+    }
+
+    usb_desc_init(dev);
+    scsi_bus_new(&s->bus, &s->dev.qdev, 0, 1, usb_msd_command_complete);
+    s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0);
+    if (!s->scsi_dev) {
+        return -1;
+    }
+    s->bus.qbus.allow_hotplug = 0;
+    usb_msd_handle_reset(dev);
+
+    if (bdrv_key_required(bs)) {
+        if (cur_mon) {
+            monitor_read_bdrv_key_start(cur_mon, bs, usb_msd_password_cb, s);
+            s->dev.auto_attach = 0;
+        } else {
+            autostart = 0;
+        }
+    }
 
-    s->dev.speed = USB_SPEED_FULL;
     return 0;
 }
 
-USBDevice *usb_msd_init(const char *filename)
+static USBDevice *usb_msd_init(const char *filename)
 {
+    static int nr=0;
+    char id[8];
+    QemuOpts *opts;
+    DriveInfo *dinfo;
     USBDevice *dev;
-    MSDState *s;
-    BlockDriverState *bdrv;
-    BlockDriver *drv = NULL;
+    int fatal_error;
     const char *p1;
     char fmt[32];
 
+    /* parse -usbdevice disk: syntax into drive opts */
+    snprintf(id, sizeof(id), "usb%d", nr++);
+    opts = qemu_opts_create(qemu_find_opts("drive"), id, 0);
+
     p1 = strchr(filename, ':');
     if (p1++) {
         const char *p2;
@@ -539,59 +555,59 @@ USBDevice *usb_msd_init(const char *filename)
         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;
-            }
+            qemu_opt_set(opts, "format", fmt);
         } else if (*filename != ':') {
             printf("unrecognized USB mass-storage option %s\n", filename);
             return NULL;
         }
-
         filename = p1;
     }
-
     if (!*filename) {
         printf("block device specification needed\n");
         return NULL;
     }
+    qemu_opt_set(opts, "file", filename);
+    qemu_opt_set(opts, "if", "none");
 
-    bdrv = bdrv_new("usb");
-    if (bdrv_open2(bdrv, filename, 0, drv) < 0)
+    /* create host drive */
+    dinfo = drive_init(opts, 0, &fatal_error);
+    if (!dinfo) {
+        qemu_opts_del(opts);
         return NULL;
+    }
 
-    dev = usb_create_simple(NULL /* FIXME */, "QEMU USB MSD");
-    s = DO_UPCAST(MSDState, dev, dev);
-    s->bs = bdrv;
-    snprintf(s->dev.devname, sizeof(s->dev.devname), "QEMU USB MSD(%.16s)",
-             filename);
-
-    s->bus = scsi_bus_new(&s->dev.qdev, 0, 1, usb_msd_command_complete);
-#if 0
-    s->scsi_dev = scsi_disk_init(s->bus, bdrv);
-#endif
-    usb_msd_handle_reset((USBDevice *)s);
-    return (USBDevice *)s;
-}
-
-BlockDriverState *usb_msd_get_bdrv(USBDevice *dev)
-{
-    MSDState *s = (MSDState *)dev;
+    /* create guest device */
+    dev = usb_create(NULL /* FIXME */, "usb-storage");
+    if (!dev) {
+        return NULL;
+    }
+    if (qdev_prop_set_drive(&dev->qdev, "drive", dinfo->bdrv) < 0) {
+        qdev_free(&dev->qdev);
+        return NULL;
+    }
+    if (qdev_init(&dev->qdev) < 0)
+        return NULL;
 
-    return s->bs;
+    return dev;
 }
 
 static struct USBDeviceInfo msd_info = {
-    .qdev.name      = "QEMU USB MSD",
+    .product_desc   = "QEMU USB MSD",
+    .qdev.name      = "usb-storage",
     .qdev.size      = sizeof(MSDState),
+    .usb_desc       = &desc,
     .init           = usb_msd_initfn,
     .handle_packet  = usb_generic_handle_packet,
+    .handle_attach  = usb_desc_attach,
     .handle_reset   = usb_msd_handle_reset,
     .handle_control = usb_msd_handle_control,
     .handle_data    = usb_msd_handle_data,
-    .handle_destroy = usb_msd_handle_destroy,
+    .usbdevice_name = "disk",
+    .usbdevice_init = usb_msd_init,
+    .qdev.props     = (Property[]) {
+        DEFINE_BLOCK_PROPERTIES(MSDState, conf),
+        DEFINE_PROP_END_OF_LIST(),
+    },
 };
 
 static void usb_msd_register_devices(void)