diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:43:59 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-02-10 15:43:59 -0800 |
commit | c27f813900a3c114562efbb8df1065e94766fc48 (patch) | |
tree | d95919283707dcab61009e27007374a745c9541e /hw/usb-msd.c | |
parent | 0852ad57fa372f9b2854e4df685eaba8d8ef6790 (diff) | |
download | external_qemu-c27f813900a3c114562efbb8df1065e94766fc48.zip external_qemu-c27f813900a3c114562efbb8df1065e94766fc48.tar.gz external_qemu-c27f813900a3c114562efbb8df1065e94766fc48.tar.bz2 |
auto import from //branches/cupcake/...@130745
Diffstat (limited to 'hw/usb-msd.c')
-rw-r--r-- | hw/usb-msd.c | 304 |
1 files changed, 242 insertions, 62 deletions
diff --git a/hw/usb-msd.c b/hw/usb-msd.c index 3dccfb9..f7ad25e 100644 --- a/hw/usb-msd.c +++ b/hw/usb-msd.c @@ -1,4 +1,4 @@ -/* +/* * USB Mass Storage Device emulation * * Copyright (c) 2006 CodeSourcery. @@ -7,7 +7,10 @@ * This code is licenced under the LGPL. */ -#include "vl.h" +#include "qemu-common.h" +#include "usb.h" +#include "block.h" +#include "scsi-disk.h" //#define DEBUG_MSD @@ -32,16 +35,41 @@ 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 residue; uint32_t tag; + BlockDriverState *bs; SCSIDevice *scsi_dev; int result; + /* For async completion. */ + 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; */ @@ -68,13 +96,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 */ @@ -85,7 +113,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 */ @@ -103,25 +131,99 @@ static const uint8_t qemu_msd_config_descriptor[] = { 0x00 /* u8 ep_bInterval; */ }; -static void usb_msd_command_complete(void *opaque, uint32_t tag, 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 = s->packet; - DPRINTF("Command complete\n"); - s->result = fail; - s->mode = USB_MSDM_CSW; + 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", 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; + } + 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); + } + } } -static void usb_msd_handle_reset(USBDevice *dev, int destroy) +static void usb_msd_handle_reset(USBDevice *dev) { MSDState *s = (MSDState *)dev; DPRINTF("Reset\n"); s->mode = USB_MSDM_CBW; - if (destroy) { - scsi_disk_destroy(s->scsi_dev); - qemu_free(s); - } } static int usb_msd_handle_control(USBDevice *dev, int request, int value, @@ -160,12 +262,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; @@ -237,32 +339,24 @@ 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; + 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, int pid, uint8_t devep, - uint8_t *data, int len) +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; - switch (pid) { + switch (p->pid) { case USB_TOKEN_OUT: if (devep != 2) goto fail; @@ -295,7 +389,17 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep, } 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; @@ -304,13 +408,25 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep, if (len > s->data_len) goto fail; - if (scsi_write_data(s->scsi_dev, data, len)) - goto fail; - - s->data_len -= len; - if (s->data_len == 0) - s->mode = USB_MSDM_CSW; - ret = len; + 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; default: @@ -324,33 +440,52 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep, 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; - - if (scsi_read_data(s->scsi_dev, data, len)) - goto fail; - - s->data_len -= len; - if (s->data_len == 0) - s->mode = USB_MSDM_CSW; - ret = len; + 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; default: @@ -369,18 +504,59 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep, return ret; } +static void usb_msd_handle_destroy(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + s->scsi_dev->destroy(s->scsi_dev); + bdrv_delete(s->bs); + qemu_free(s); +} 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; + } + + filename = p1; + } + + if (!*filename) { + printf("block device specification needed\n"); + return NULL; + } s = qemu_mallocz(sizeof(MSDState)); if (!s) return NULL; bdrv = bdrv_new("usb"); - bdrv_open(bdrv, filename, 0); + if (bdrv_open2(bdrv, filename, 0, drv) < 0) + goto fail; + if (qemu_key_check(bdrv, filename)) + goto fail; + s->bs = bdrv; s->dev.speed = USB_SPEED_FULL; s->dev.handle_packet = usb_generic_handle_packet; @@ -388,11 +564,15 @@ USBDevice *usb_msd_init(const char *filename) s->dev.handle_reset = usb_msd_handle_reset; s->dev.handle_control = usb_msd_handle_control; s->dev.handle_data = usb_msd_handle_data; + s->dev.handle_destroy = usb_msd_handle_destroy; 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); - usb_msd_handle_reset((USBDevice *)s, 0); + 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; } |