aboutsummaryrefslogtreecommitdiffstats
path: root/hw/usb-msd.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:43:59 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-02-10 15:43:59 -0800
commitc27f813900a3c114562efbb8df1065e94766fc48 (patch)
treed95919283707dcab61009e27007374a745c9541e /hw/usb-msd.c
parent0852ad57fa372f9b2854e4df685eaba8d8ef6790 (diff)
downloadexternal_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.c304
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;
}