aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/cdrom.c156
-rw-r--r--hw/esp.c417
-rw-r--r--hw/ide.c125
-rw-r--r--hw/scsi-disk.c408
-rw-r--r--hw/usb-hid.c8
-rw-r--r--hw/usb-hub.c4
-rw-r--r--hw/usb-msd.c395
-rw-r--r--hw/usb.c5
-rw-r--r--hw/usb.h6
9 files changed, 1056 insertions, 468 deletions
diff --git a/hw/cdrom.c b/hw/cdrom.c
new file mode 100644
index 0000000000..a43b417902
--- /dev/null
+++ b/hw/cdrom.c
@@ -0,0 +1,156 @@
+/*
+ * QEMU ATAPI CD-ROM Emulator
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* ??? Most of the ATAPI emulation is still in ide.c. It should be moved
+ here. */
+
+#include <vl.h>
+
+static void lba_to_msf(uint8_t *buf, int lba)
+{
+ lba += 150;
+ buf[0] = (lba / 75) / 60;
+ buf[1] = (lba / 75) % 60;
+ buf[2] = lba % 75;
+}
+
+/* same toc as bochs. Return -1 if error or the toc length */
+/* XXX: check this */
+int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track)
+{
+ uint8_t *q;
+ int len;
+
+ if (start_track > 1 && start_track != 0xaa)
+ return -1;
+ q = buf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+ if (start_track <= 1) {
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 1; /* track number */
+ *q++ = 0; /* reserved */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, 0);
+ q += 3;
+ } else {
+ /* sector 0 */
+ cpu_to_be32wu((uint32_t *)q, 0);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x16; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, nb_sectors);
+ q += 3;
+ } else {
+ cpu_to_be32wu((uint32_t *)q, nb_sectors);
+ q += 4;
+ }
+ len = q - buf;
+ cpu_to_be16wu((uint16_t *)buf, len - 2);
+ return len;
+}
+
+/* mostly same info as PearPc */
+int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num)
+{
+ uint8_t *q;
+ int len;
+
+ q = buf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa0; /* lead-in */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* first track */
+ *q++ = 0x00; /* disk type */
+ *q++ = 0x00;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa1;
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* last track */
+ *q++ = 0x00;
+ *q++ = 0x00;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa2; /* lead-out */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (msf) {
+ *q++ = 0; /* reserved */
+ lba_to_msf(q, nb_sectors);
+ q += 3;
+ } else {
+ cpu_to_be32wu((uint32_t *)q, nb_sectors);
+ q += 4;
+ }
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0; /* track number */
+ *q++ = 1; /* point */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (msf) {
+ *q++ = 0;
+ lba_to_msf(q, 0);
+ q += 3;
+ } else {
+ *q++ = 0;
+ *q++ = 0;
+ *q++ = 0;
+ *q++ = 0;
+ }
+
+ len = q - buf;
+ cpu_to_be16wu((uint16_t *)buf, len - 2);
+ return len;
+}
+
+
diff --git a/hw/esp.c b/hw/esp.c
index 30708a1a0d..787892bfe0 100644
--- a/hw/esp.c
+++ b/hw/esp.c
@@ -38,17 +38,14 @@ do { printf("ESP: set_irq(%d): %d\n", (irq), (level)); pic_set_irq((irq),(level)
#define ESPDMA_REGS 4
#define ESPDMA_MAXADDR (ESPDMA_REGS * 4 - 1)
#define ESP_MAXREG 0x3f
-#define TI_BUFSZ 1024*1024 // XXX
+#define TI_BUFSZ 32
#define DMA_VER 0xa0000000
#define DMA_INTR 1
#define DMA_INTREN 0x10
+#define DMA_WRITE_MEM 0x100
#define DMA_LOADED 0x04000000
typedef struct ESPState ESPState;
-typedef int ESPDMAFunc(ESPState *s,
- target_phys_addr_t phys_addr,
- int transfer_size1);
-
struct ESPState {
BlockDriverState **bd;
uint8_t rregs[ESP_MAXREG];
@@ -57,12 +54,10 @@ struct ESPState {
uint32_t espdmaregs[ESPDMA_REGS];
uint32_t ti_size;
uint32_t ti_rptr, ti_wptr;
- int ti_dir;
uint8_t ti_buf[TI_BUFSZ];
int dma;
- ESPDMAFunc *dma_cb;
- int64_t offset, len;
- int target;
+ SCSIDevice *scsi_dev[MAX_DISKS];
+ SCSIDevice *current_dev;
};
#define STAT_DO 0x00
@@ -83,195 +78,33 @@ struct ESPState {
#define SEQ_0 0x0
#define SEQ_CD 0x4
-/* XXX: stolen from ide.c, move to common ATAPI/SCSI library */
-static void lba_to_msf(uint8_t *buf, int lba)
-{
- lba += 150;
- buf[0] = (lba / 75) / 60;
- buf[1] = (lba / 75) % 60;
- buf[2] = lba % 75;
-}
-
-static inline void cpu_to_ube16(uint8_t *buf, int val)
-{
- buf[0] = val >> 8;
- buf[1] = val;
-}
-
-static inline void cpu_to_ube32(uint8_t *buf, unsigned int val)
-{
- buf[0] = val >> 24;
- buf[1] = val >> 16;
- buf[2] = val >> 8;
- buf[3] = val;
-}
-
-/* same toc as bochs. Return -1 if error or the toc length */
-/* XXX: check this */
-static int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track)
-{
- uint8_t *q;
- int len;
-
- if (start_track > 1 && start_track != 0xaa)
- return -1;
- q = buf + 2;
- *q++ = 1; /* first session */
- *q++ = 1; /* last session */
- if (start_track <= 1) {
- *q++ = 0; /* reserved */
- *q++ = 0x14; /* ADR, control */
- *q++ = 1; /* track number */
- *q++ = 0; /* reserved */
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, 0);
- q += 3;
- } else {
- /* sector 0 */
- cpu_to_ube32(q, 0);
- q += 4;
- }
- }
- /* lead out track */
- *q++ = 0; /* reserved */
- *q++ = 0x16; /* ADR, control */
- *q++ = 0xaa; /* track number */
- *q++ = 0; /* reserved */
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, nb_sectors);
- q += 3;
- } else {
- cpu_to_ube32(q, nb_sectors);
- q += 4;
- }
- len = q - buf;
- cpu_to_ube16(buf, len - 2);
- return len;
-}
-
-/* mostly same info as PearPc */
-static int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf,
- int session_num)
-{
- uint8_t *q;
- int len;
-
- q = buf + 2;
- *q++ = 1; /* first session */
- *q++ = 1; /* last session */
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa0; /* lead-in */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- *q++ = 0;
- *q++ = 1; /* first track */
- *q++ = 0x00; /* disk type */
- *q++ = 0x00;
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa1;
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- *q++ = 0;
- *q++ = 1; /* last track */
- *q++ = 0x00;
- *q++ = 0x00;
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa2; /* lead-out */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, nb_sectors);
- q += 3;
- } else {
- cpu_to_ube32(q, nb_sectors);
- q += 4;
- }
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* ADR, control */
- *q++ = 0; /* track number */
- *q++ = 1; /* point */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- if (msf) {
- *q++ = 0;
- lba_to_msf(q, 0);
- q += 3;
- } else {
- *q++ = 0;
- *q++ = 0;
- *q++ = 0;
- *q++ = 0;
- }
-
- len = q - buf;
- cpu_to_ube16(buf, len - 2);
- return len;
-}
-
-static int esp_write_dma_cb(ESPState *s,
- target_phys_addr_t phys_addr,
- int transfer_size1)
-{
- int len;
- if (bdrv_get_type_hint(s->bd[s->target]) == BDRV_TYPE_CDROM) {
- len = transfer_size1/2048;
- } else {
- len = transfer_size1/512;
- }
- DPRINTF("Write callback (offset %lld len %lld size %d trans_size %d)\n",
- s->offset, s->len, s->ti_size, transfer_size1);
-
- bdrv_write(s->bd[s->target], s->offset, s->ti_buf+s->ti_rptr, len);
- s->offset+=len;
- return 0;
-}
-
static void handle_satn(ESPState *s)
{
uint8_t buf[32];
uint32_t dmaptr, dmalen;
- unsigned int i;
- int64_t nb_sectors;
int target;
+ int32_t datalen;
dmalen = s->wregs[0] | (s->wregs[1] << 8);
target = s->wregs[4] & 7;
DPRINTF("Select with ATN len %d target %d\n", dmalen, target);
if (s->dma) {
dmaptr = iommu_translate(s->espdmaregs[1]);
- DPRINTF("DMA Direction: %c, addr 0x%8.8x\n", s->espdmaregs[0] & 0x100? 'w': 'r', dmaptr);
+ DPRINTF("DMA Direction: %c, addr 0x%8.8x\n",
+ s->espdmaregs[0] & DMA_WRITE_MEM ? 'w': 'r', dmaptr);
cpu_physical_memory_read(dmaptr, buf, dmalen);
} else {
buf[0] = 0;
memcpy(&buf[1], s->ti_buf, dmalen);
dmalen++;
}
- for (i = 0; i < dmalen; i++) {
- DPRINTF("Command %2.2x\n", buf[i]);
- }
- s->ti_dir = 0;
+
s->ti_size = 0;
s->ti_rptr = 0;
s->ti_wptr = 0;
- if (target >= 4 || !s->bd[target]) { // No such drive
+ if (target >= 4 || !s->scsi_dev[target]) {
+ // No such drive
s->rregs[4] = STAT_IN;
s->rregs[5] = INTR_DC;
s->rregs[6] = SEQ_0;
@@ -279,141 +112,20 @@ static void handle_satn(ESPState *s)
pic_set_irq(s->irq, 1);
return;
}
- switch (buf[1]) {
- case 0x0:
- DPRINTF("Test Unit Ready (len %d)\n", buf[5]);
- break;
- case 0x12:
- DPRINTF("Inquiry (len %d)\n", buf[5]);
- memset(s->ti_buf, 0, 36);
- if (bdrv_get_type_hint(s->bd[target]) == BDRV_TYPE_CDROM) {
- s->ti_buf[0] = 5;
- memcpy(&s->ti_buf[16], "QEMU CDROM ", 16);
- } else {
- s->ti_buf[0] = 0;
- memcpy(&s->ti_buf[16], "QEMU HARDDISK ", 16);
- }
- memcpy(&s->ti_buf[8], "QEMU ", 8);
- s->ti_buf[2] = 1;
- s->ti_buf[3] = 2;
- s->ti_buf[4] = 32;
- s->ti_dir = 1;
- s->ti_size = 36;
- break;
- case 0x1a:
- DPRINTF("Mode Sense(6) (page %d, len %d)\n", buf[3], buf[5]);
- break;
- case 0x25:
- DPRINTF("Read Capacity (len %d)\n", buf[5]);
- memset(s->ti_buf, 0, 8);
- bdrv_get_geometry(s->bd[target], &nb_sectors);
- s->ti_buf[0] = (nb_sectors >> 24) & 0xff;
- s->ti_buf[1] = (nb_sectors >> 16) & 0xff;
- s->ti_buf[2] = (nb_sectors >> 8) & 0xff;
- s->ti_buf[3] = nb_sectors & 0xff;
- s->ti_buf[4] = 0;
- s->ti_buf[5] = 0;
- if (bdrv_get_type_hint(s->bd[target]) == BDRV_TYPE_CDROM)
- s->ti_buf[6] = 8; // sector size 2048
- else
- s->ti_buf[6] = 2; // sector size 512
- s->ti_buf[7] = 0;
- s->ti_dir = 1;
- s->ti_size = 8;
- break;
- case 0x28:
- {
- int64_t offset, len;
-
- if (bdrv_get_type_hint(s->bd[target]) == BDRV_TYPE_CDROM) {
- offset = ((buf[3] << 24) | (buf[4] << 16) | (buf[5] << 8) | buf[6]) * 4;
- len = ((buf[8] << 8) | buf[9]) * 4;
- s->ti_size = len * 2048;
- } else {
- offset = (buf[3] << 24) | (buf[4] << 16) | (buf[5] << 8) | buf[6];
- len = (buf[8] << 8) | buf[9];
- s->ti_size = len * 512;
- }
- DPRINTF("Read (10) (offset %lld len %lld)\n", offset, len);
- if (s->ti_size > TI_BUFSZ) {
- DPRINTF("size too large %d\n", s->ti_size);
- }
- bdrv_read(s->bd[target], offset, s->ti_buf, len);
- // XXX error handling
- s->ti_dir = 1;
- s->ti_rptr = 0;
- break;
- }
- case 0x2a:
- {
- int64_t offset, len;
-
- if (bdrv_get_type_hint(s->bd[target]) == BDRV_TYPE_CDROM) {
- offset = ((buf[3] << 24) | (buf[4] << 16) | (buf[5] << 8) | buf[6]) * 4;
- len = ((buf[8] << 8) | buf[9]) * 4;
- s->ti_size = len * 2048;
- } else {
- offset = (buf[3] << 24) | (buf[4] << 16) | (buf[5] << 8) | buf[6];
- len = (buf[8] << 8) | buf[9];
- s->ti_size = len * 512;
- }
- DPRINTF("Write (10) (offset %lld len %lld)\n", offset, len);
- if (s->ti_size > TI_BUFSZ) {
- DPRINTF("size too large %d\n", s->ti_size);
- }
- s->dma_cb = esp_write_dma_cb;
- s->offset = offset;
- s->len = len;
- s->target = target;
- s->ti_rptr = 0;
- // XXX error handling
- s->ti_dir = 0;
- break;
- }
- case 0x43:
- {
- int start_track, format, msf, len;
-
- msf = buf[2] & 2;
- format = buf[3] & 0xf;
- start_track = buf[7];
- bdrv_get_geometry(s->bd[target], &nb_sectors);
- DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1);
- switch(format) {
- case 0:
- len = cdrom_read_toc(nb_sectors, buf, msf, start_track);
- if (len < 0)
- goto error_cmd;
- s->ti_size = len;
- break;
- case 1:
- /* multi session : only a single session defined */
- memset(buf, 0, 12);
- buf[1] = 0x0a;
- buf[2] = 0x01;
- buf[3] = 0x01;
- s->ti_size = 12;
- break;
- case 2:
- len = cdrom_read_toc_raw(nb_sectors, buf, msf, start_track);
- if (len < 0)
- goto error_cmd;
- s->ti_size = len;
- break;
- default:
- error_cmd:
- DPRINTF("Read TOC error\n");
- // XXX error handling
- break;
- }
- s->ti_dir = 1;
- break;
+ s->current_dev = s->scsi_dev[target];
+ datalen = scsi_send_command(s->current_dev, 0, &buf[1]);
+ if (datalen == 0) {
+ s->ti_size = 0;
+ } else {
+ s->rregs[4] = STAT_IN | STAT_TC;
+ if (datalen > 0) {
+ s->rregs[4] |= STAT_DI;
+ s->ti_size = datalen;
+ } else {
+ s->rregs[4] |= STAT_DO;
+ s->ti_size = -datalen;
}
- default:
- DPRINTF("Unknown SCSI command (%2.2x)\n", buf[1]);
- break;
}
- s->rregs[4] = STAT_IN | STAT_TC | STAT_DI;
s->rregs[5] = INTR_BS | INTR_FC;
s->rregs[6] = SEQ_CD;
s->espdmaregs[0] |= DMA_INTR;
@@ -427,7 +139,8 @@ static void dma_write(ESPState *s, const uint8_t *buf, uint32_t len)
DPRINTF("Transfer status len %d\n", len);
if (s->dma) {
dmaptr = iommu_translate(s->espdmaregs[1]);
- DPRINTF("DMA Direction: %c\n", s->espdmaregs[0] & 0x100? 'w': 'r');
+ DPRINTF("DMA Direction: %c\n",
+ s->espdmaregs[0] & DMA_WRITE_MEM ? 'w': 'r');
cpu_physical_memory_write(dmaptr, buf, len);
s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
s->rregs[5] = INTR_BS | INTR_FC;
@@ -446,10 +159,26 @@ static void dma_write(ESPState *s, const uint8_t *buf, uint32_t len)
static const uint8_t okbuf[] = {0, 0};
+static void esp_command_complete(void *opaque, uint32_t tag, int fail)
+{
+ ESPState *s = (ESPState *)opaque;
+
+ DPRINTF("SCSI Command complete\n");
+ if (s->ti_size != 0)
+ DPRINTF("SCSI command completed unexpectedly\n");
+ s->ti_size = 0;
+ /* ??? Report failures. */
+ if (fail)
+ DPRINTF("Command failed\n");
+ s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
+}
+
static void handle_ti(ESPState *s)
{
uint32_t dmaptr, dmalen, minlen, len, from, to;
unsigned int i;
+ int to_device;
+ uint8_t buf[TARGET_PAGE_SIZE];
dmalen = s->wregs[0] | (s->wregs[1] << 8);
if (dmalen==0) {
@@ -460,7 +189,10 @@ static void handle_ti(ESPState *s)
DPRINTF("Transfer Information len %d\n", minlen);
if (s->dma) {
dmaptr = iommu_translate(s->espdmaregs[1]);
- DPRINTF("DMA Direction: %c, addr 0x%8.8x %08x %d %d\n", s->espdmaregs[0] & 0x100? 'w': 'r', dmaptr, s->ti_size, s->ti_rptr, s->ti_dir);
+ /* Check if the transfer writes to to reads from the device. */
+ to_device = (s->espdmaregs[0] & DMA_WRITE_MEM) == 0;
+ DPRINTF("DMA Direction: %c, addr 0x%8.8x %08x\n",
+ to_device ? 'r': 'w', dmaptr, s->ti_size);
from = s->espdmaregs[1];
to = from + minlen;
for (i = 0; i < minlen; i += len, from += len) {
@@ -471,35 +203,23 @@ static void handle_ti(ESPState *s)
len = to - from;
}
DPRINTF("DMA address p %08x v %08x len %08x, from %08x, to %08x\n", dmaptr, s->espdmaregs[1] + i, len, from, to);
- if (s->ti_dir)
- cpu_physical_memory_write(dmaptr, &s->ti_buf[s->ti_rptr + i], len);
- else
- cpu_physical_memory_read(dmaptr, &s->ti_buf[s->ti_rptr + i], len);
+ s->ti_size -= len;
+ if (to_device) {
+ cpu_physical_memory_read(dmaptr, buf, len);
+ scsi_write_data(s->current_dev, buf, len);
+ } else {
+ scsi_read_data(s->current_dev, buf, len);
+ cpu_physical_memory_write(dmaptr, buf, len);
+ }
}
- if (s->dma_cb) {
- s->dma_cb(s, s->espdmaregs[1], minlen);
- }
- if (minlen < s->ti_size) {
- s->rregs[4] = STAT_IN | STAT_TC | (s->ti_dir ? STAT_DO : STAT_DI);
+ if (s->ti_size) {
+ s->rregs[4] = STAT_IN | STAT_TC | (to_device ? STAT_DO : STAT_DI);
s->ti_size -= minlen;
- s->ti_rptr += minlen;
- } else {
- s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
- s->dma_cb = NULL;
- s->offset = 0;
- s->len = 0;
- s->target = 0;
- s->ti_rptr = 0;
}
s->rregs[5] = INTR_BS;
s->rregs[6] = 0;
s->rregs[7] = 0;
s->espdmaregs[0] |= DMA_INTR;
- } else {
- s->ti_size = minlen;
- s->ti_rptr = 0;
- s->ti_wptr = 0;
- s->rregs[7] = minlen;
}
pic_set_irq(s->irq, 1);
}
@@ -514,9 +234,7 @@ static void esp_reset(void *opaque)
s->ti_size = 0;
s->ti_rptr = 0;
s->ti_wptr = 0;
- s->ti_dir = 0;
s->dma = 0;
- s->dma_cb = NULL;
}
static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr)
@@ -531,7 +249,12 @@ static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr)
// FIFO
if (s->ti_size > 0) {
s->ti_size--;
- s->rregs[saddr] = s->ti_buf[s->ti_rptr++];
+ if ((s->rregs[4] & 6) == 0) {
+ /* Data in/out. */
+ scsi_read_data(s->current_dev, &s->rregs[2], 0);
+ } else {
+ s->rregs[2] = s->ti_buf[s->ti_rptr++];
+ }
pic_set_irq(s->irq, 1);
}
if (s->ti_size == 0) {
@@ -566,8 +289,15 @@ static void esp_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
break;
case 2:
// FIFO
- s->ti_size++;
- s->ti_buf[s->ti_wptr++] = val & 0xff;
+ if ((s->rregs[4] & 6) == 0) {
+ uint8_t buf;
+ buf = val & 0xff;
+ s->ti_size--;
+ scsi_write_data(s->current_dev, &buf, 0);
+ } else {
+ s->ti_size++;
+ s->ti_buf[s->ti_wptr++] = val & 0xff;
+ }
break;
case 3:
s->rregs[saddr] = val;
@@ -723,7 +453,6 @@ static void esp_save(QEMUFile *f, void *opaque)
qemu_put_be32s(f, &s->ti_size);
qemu_put_be32s(f, &s->ti_rptr);
qemu_put_be32s(f, &s->ti_wptr);
- qemu_put_be32s(f, &s->ti_dir);
qemu_put_buffer(f, s->ti_buf, TI_BUFSZ);
qemu_put_be32s(f, &s->dma);
}
@@ -744,7 +473,6 @@ static int esp_load(QEMUFile *f, void *opaque, int version_id)
qemu_get_be32s(f, &s->ti_size);
qemu_get_be32s(f, &s->ti_rptr);
qemu_get_be32s(f, &s->ti_wptr);
- qemu_get_be32s(f, &s->ti_dir);
qemu_get_buffer(f, s->ti_buf, TI_BUFSZ);
qemu_get_be32s(f, &s->dma);
@@ -755,6 +483,7 @@ void esp_init(BlockDriverState **bd, int irq, uint32_t espaddr, uint32_t espdadd
{
ESPState *s;
int esp_io_memory, espdma_io_memory;
+ int i;
s = qemu_mallocz(sizeof(ESPState));
if (!s)
@@ -773,5 +502,11 @@ void esp_init(BlockDriverState **bd, int irq, uint32_t espaddr, uint32_t espdadd
register_savevm("esp", espaddr, 1, esp_save, esp_load, s);
qemu_register_reset(esp_reset, s);
+ for (i = 0; i < MAX_DISKS; i++) {
+ if (bs_table[i]) {
+ s->scsi_dev[i] =
+ scsi_disk_init(bs_table[i], esp_command_complete, s);
+ }
+ }
}
diff --git a/hw/ide.c b/hw/ide.c
index ed63573628..ffe0230d8a 100644
--- a/hw/ide.c
+++ b/hw/ide.c
@@ -1082,127 +1082,6 @@ static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors,
}
}
-/* same toc as bochs. Return -1 if error or the toc length */
-/* XXX: check this */
-static int cdrom_read_toc(IDEState *s, uint8_t *buf, int msf, int start_track)
-{
- uint8_t *q;
- int nb_sectors, len;
-
- if (start_track > 1 && start_track != 0xaa)
- return -1;
- q = buf + 2;
- *q++ = 1; /* first session */
- *q++ = 1; /* last session */
- if (start_track <= 1) {
- *q++ = 0; /* reserved */
- *q++ = 0x14; /* ADR, control */
- *q++ = 1; /* track number */
- *q++ = 0; /* reserved */
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, 0);
- q += 3;
- } else {
- /* sector 0 */
- cpu_to_ube32(q, 0);
- q += 4;
- }
- }
- /* lead out track */
- *q++ = 0; /* reserved */
- *q++ = 0x16; /* ADR, control */
- *q++ = 0xaa; /* track number */
- *q++ = 0; /* reserved */
- nb_sectors = s->nb_sectors >> 2;
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, nb_sectors);
- q += 3;
- } else {
- cpu_to_ube32(q, nb_sectors);
- q += 4;
- }
- len = q - buf;
- cpu_to_ube16(buf, len - 2);
- return len;
-}
-
-/* mostly same info as PearPc */
-static int cdrom_read_toc_raw(IDEState *s, uint8_t *buf, int msf,
- int session_num)
-{
- uint8_t *q;
- int nb_sectors, len;
-
- q = buf + 2;
- *q++ = 1; /* first session */
- *q++ = 1; /* last session */
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa0; /* lead-in */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- *q++ = 0;
- *q++ = 1; /* first track */
- *q++ = 0x00; /* disk type */
- *q++ = 0x00;
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa1;
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- *q++ = 0;
- *q++ = 1; /* last track */
- *q++ = 0x00;
- *q++ = 0x00;
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* data track */
- *q++ = 0; /* track number */
- *q++ = 0xa2; /* lead-out */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- nb_sectors = s->nb_sectors >> 2;
- if (msf) {
- *q++ = 0; /* reserved */
- lba_to_msf(q, nb_sectors);
- q += 3;
- } else {
- cpu_to_ube32(q, nb_sectors);
- q += 4;
- }
-
- *q++ = 1; /* session number */
- *q++ = 0x14; /* ADR, control */
- *q++ = 0; /* track number */
- *q++ = 1; /* point */
- *q++ = 0; /* min */
- *q++ = 0; /* sec */
- *q++ = 0; /* frame */
- if (msf) {
- *q++ = 0;
- lba_to_msf(q, 0);
- q += 3;
- } else {
- *q++ = 0;
- *q++ = 0;
- *q++ = 0;
- *q++ = 0;
- }
-
- len = q - buf;
- cpu_to_ube16(buf, len - 2);
- return len;
-}
-
static void ide_atapi_cmd(IDEState *s)
{
const uint8_t *packet;
@@ -1449,7 +1328,7 @@ static void ide_atapi_cmd(IDEState *s)
start_track = packet[6];
switch(format) {
case 0:
- len = cdrom_read_toc(s, buf, msf, start_track);
+ len = cdrom_read_toc(s->nb_sectors >> 2, buf, msf, start_track);
if (len < 0)
goto error_cmd;
ide_atapi_cmd_reply(s, len, max_len);
@@ -1463,7 +1342,7 @@ static void ide_atapi_cmd(IDEState *s)
ide_atapi_cmd_reply(s, 12, max_len);
break;
case 2:
- len = cdrom_read_toc_raw(s, buf, msf, start_track);
+ len = cdrom_read_toc_raw(s->nb_sectors >> 2, buf, msf, start_track);
if (len < 0)
goto error_cmd;
ide_atapi_cmd_reply(s, len, max_len);
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
new file mode 100644
index 0000000000..937eb94ec2
--- /dev/null
+++ b/hw/scsi-disk.c
@@ -0,0 +1,408 @@
+/*
+ * SCSI Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Paul Brook
+ *
+ * This code is licenced under the LGPL.
+ */
+
+//#define DEBUG_SCSI
+
+#ifdef DEBUG_SCSI
+#define DPRINTF(fmt, args...) \
+do { printf("scsi-disk: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while(0)
+#endif
+
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0)
+
+#include "vl.h"
+
+#define SENSE_NO_SENSE 0
+#define SENSE_ILLEGAL_REQUEST 5
+
+struct SCSIDevice
+{
+ int command;
+ uint32_t tag;
+ BlockDriverState *bdrv;
+ int sector_size;
+ /* When transfering data buf_pos and buf_len contain a partially
+ transferred block of data (or response to a command), and
+ sector/sector_count identify any remaining sectors. */
+ /* ??? We should probably keep track of whether the data trasfer is
+ a read or a write. Currently we rely on the host getting it right. */
+ int sector;
+ int sector_count;
+ int buf_pos;
+ int buf_len;
+ int sense;
+ char buf[2048];
+ scsi_completionfn completion;
+ void *opaque;
+};
+
+static void scsi_command_complete(SCSIDevice *s, int sense)
+{
+ s->sense = sense;
+ s->completion(s->opaque, s->tag, sense != SENSE_NO_SENSE);
+}
+
+/* Read data from a scsi device. Returns nonzero on failure. */
+int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
+{
+ uint32_t n;
+
+ DPRINTF("Read %d (%d/%d)\n", len, s->buf_len, s->sector_count);
+ if (s->buf_len == 0 && s->sector_count == 0)
+ return 1;
+
+ if (s->buf_len) {
+ n = s->buf_len;
+ if (n > len)
+ n = len;
+ memcpy(data, s->buf + s->buf_pos, n);
+ s->buf_pos += n;
+ s->buf_len -= n;
+ data += n;
+ len -= n;
+ if (s->buf_len == 0)
+ s->buf_pos = 0;
+ }
+
+ n = len / s->sector_size;
+ if (n > s->sector_count)
+ n = s->sector_count;
+
+ if (n != 0) {
+ bdrv_read(s->bdrv, s->sector, data, n);
+ data += n * s->sector_size;
+ len -= n * s->sector_size;
+ s->sector += n;
+ s->sector_count -= n;
+ }
+
+ if (len && s->sector_count) {
+ bdrv_read(s->bdrv, s->sector, s->buf, 1);
+ s->sector++;
+ s->sector_count--;
+ s->buf_pos = 0;
+ s->buf_len = s->sector_size;
+ /* Recurse to complete the partial read. */
+ return scsi_read_data(s, data, len);
+ }
+
+ if (len != 0)
+ return 1;
+
+ if (s->buf_len == 0 && s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+
+ return 0;
+}
+
+/* Read data to a scsi device. Returns nonzero on failure. */
+int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
+{
+ uint32_t n;
+
+ DPRINTF("Write %d (%d/%d)\n", len, s->buf_len, s->sector_count);
+ if (s->buf_pos != 0) {
+ BADF("Bad state on write\n");
+ return 1;
+ }
+
+ if (s->sector_count == 0)
+ return 1;
+
+ if (s->buf_len != 0 || len < s->sector_size) {
+ n = s->sector_size - 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 == s->sector_size) {
+ /* 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--;
+ }
+ }
+
+ n = len / s->sector_size;
+ if (n > s->sector_count)
+ n = s->sector_count;
+
+ if (n != 0) {
+ bdrv_write(s->bdrv, s->sector, data, n);
+ data += n * s->sector_size;
+ len -= n * s->sector_size;
+ s->sector += n;
+ s->sector_count -= n;
+ }
+
+ if (len >= s->sector_size)
+ return 1;
+
+ if (len && s->sector_count) {
+ /* Recurse to complete the partial write. */
+ return scsi_write_data(s, data, len);
+ }
+
+ if (len != 0)
+ return 1;
+
+ if (s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+
+ return 0;
+}
+
+/* Execute a scsi command. Returns the length of the data expected by the
+ command. This will be Positive for data transfers from the device
+ (eg. disk reads), negative for transfers to the device (eg. disk writes),
+ and zero if the command does not transfer any data. */
+
+int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf)
+{
+ int64_t nb_sectors;
+ uint32_t lba;
+ uint32_t len;
+ int cmdlen;
+ int is_write;
+
+ s->command = buf[0];
+ s->tag = tag;
+ s->sector_count = 0;
+ s->buf_pos = 0;
+ s->buf_len = 0;
+ is_write = 0;
+ DPRINTF("Command: 0x%02x", buf[0]);
+ switch (s->command >> 5) {
+ case 0:
+ lba = buf[3] | (buf[2] << 8) | ((buf[1] & 0x1f) << 16);
+ len = buf[4];
+ cmdlen = 6;
+ break;
+ case 1:
+ case 2:
+ lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24);
+ len = buf[8] | (buf[7] << 8);
+ cmdlen = 10;
+ break;
+ case 4:
+ lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24);
+ len = buf[13] | (buf[12] << 8) | (buf[11] << 16) | (buf[10] << 24);
+ cmdlen = 16;
+ break;
+ case 5:
+ lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24);
+ len = buf[9] | (buf[8] << 8) | (buf[7] << 16) | (buf[6] << 24);
+ cmdlen = 12;
+ break;
+ default:
+ BADF("Unsupported command length\n");
+ goto fail;
+ }
+#ifdef DEBUG_SCSI
+ {
+ int i;
+ for (i = 1; i < cmdlen; i++) {
+ printf(" 0x%02x", buf[i]);
+ }
+ printf("\n");
+ }
+#endif
+ if (buf[1] >> 5) {
+ /* Only LUN 0 supported. */
+ goto fail;
+ }
+ switch (s->command) {
+ case 0x0:
+ DPRINTF("Test Unit Ready\n");
+ break;
+ case 0x03:
+ DPRINTF("Request Sense (len %d)\n", len);
+ if (len < 4)
+ goto fail;
+ memset(buf, 0, 4);
+ s->buf[0] = 0xf0;
+ s->buf[1] = 0;
+ s->buf[2] = s->sense;
+ s->buf_len = 4;
+ break;
+ case 0x12:
+ DPRINTF("Inquiry (len %d)\n", len);
+ if (len < 36) {
+ BADF("Inquiry buffer too small (%d)\n", len);
+ }
+ memset(s->buf, 0, 36);
+ if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
+ s->buf[0] = 5;
+ s->buf[1] = 0x80;
+ memcpy(&s->buf[16], "QEMU CDROM ", 16);
+ } else {
+ s->buf[0] = 0;
+ memcpy(&s->buf[16], "QEMU HARDDISK ", 16);
+ }
+ memcpy(&s->buf[8], "QEMU ", 8);
+ s->buf[2] = 3; /* SCSI-3 */
+ s->buf[3] = 2; /* Format 2 */
+ s->buf[4] = 32;
+ s->buf_len = 36;
+ break;
+ case 0x16:
+ DPRINTF("Reserve(6)\n");
+ if (buf[1] & 1)
+ goto fail;
+ break;
+ case 0x17:
+ DPRINTF("Release(6)\n");
+ if (buf[1] & 1)
+ goto fail;
+ break;
+ case 0x1a:
+ DPRINTF("Mode Sense(6) (page %d, len %d)\n", buf[2], len);
+ memset(s->buf, 0, 4);
+ s->buf[0] = 0x16; /* Mode data length (4 + 0x12). */
+ 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;
+ s->buf_len = len;
+ 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->sector_size >> 8;
+ s->buf[7] = s->sector_size & 0xff;
+ s->buf_len = 8;
+ break;
+ case 0x08:
+ case 0x28:
+ DPRINTF("Read (sector %d, count %d)\n", lba, len);
+ s->sector = lba;
+ s->sector_count = len;
+ break;
+ case 0x0a:
+ case 0x2a:
+ DPRINTF("Write (sector %d, count %d)\n", lba, len);
+ s->sector = lba;
+ s->sector_count = len;
+ is_write = 1;
+ break;
+ case 0x43:
+ {
+ int start_track, format, msf;
+
+ msf = buf[1] & 2;
+ format = buf[2] & 0xf;
+ start_track = buf[6];
+ bdrv_get_geometry(s->bdrv, &nb_sectors);
+ DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1);
+ switch(format) {
+ case 0:
+ len = cdrom_read_toc(nb_sectors, s->buf, msf, start_track);
+ if (len < 0)
+ goto error_cmd;
+ s->buf_len = len;
+ break;
+ case 1:
+ /* multi session : only a single session defined */
+ memset(s->buf, 0, 12);
+ s->buf[1] = 0x0a;
+ s->buf[2] = 0x01;
+ s->buf[3] = 0x01;
+ s->buf_len = 12;
+ break;
+ case 2:
+ len = cdrom_read_toc_raw(nb_sectors, s->buf, msf, start_track);
+ if (len < 0)
+ goto error_cmd;
+ s->buf_len = len;
+ break;
+ default:
+ error_cmd:
+ DPRINTF("Read TOC error\n");
+ goto fail;
+ }
+ break;
+ }
+ case 0x56:
+ DPRINTF("Reserve(10)\n");
+ if (buf[1] & 3)
+ goto fail;
+ break;
+ case 0x57:
+ DPRINTF("Release(10)\n");
+ if (buf[1] & 3)
+ goto fail;
+ break;
+ case 0xa0:
+ DPRINTF("Report LUNs (len %d)\n", len);
+ if (len < 16)
+ goto fail;
+ memset(s->buf, 0, 16);
+ s->buf[3] = 8;
+ s->buf_len = 16;
+ break;
+ default:
+ DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]);
+ fail:
+ scsi_command_complete(s, SENSE_ILLEGAL_REQUEST);
+ return 0;
+ }
+ if (s->sector_count == 0 && s->buf_len == 0) {
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ }
+ len = s->sector_count * s->sector_size + s->buf_len;
+ return is_write ? -len : len;
+}
+
+void scsi_disk_destroy(SCSIDevice *s)
+{
+ bdrv_close(s->bdrv);
+ qemu_free(s);
+}
+
+SCSIDevice *scsi_disk_init(BlockDriverState *bdrv,
+ scsi_completionfn completion,
+ void *opaque)
+{
+ SCSIDevice *s;
+
+ s = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice));
+ s->bdrv = bdrv;
+ s->completion = completion;
+ s->opaque = opaque;
+ if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) {
+ s->sector_size = 2048;
+ } else {
+ s->sector_size = 512;
+ }
+
+ return s;
+}
+
diff --git a/hw/usb-hid.c b/hw/usb-hid.c
index 17160ebe32..883befcc36 100644
--- a/hw/usb-hid.c
+++ b/hw/usb-hid.c
@@ -323,10 +323,16 @@ static int usb_tablet_poll(USBMouseState *s, uint8_t *buf, int len)
return l;
}
-static void usb_mouse_handle_reset(USBDevice *dev)
+static void usb_mouse_handle_reset(USBDevice *dev, int destroy)
{
USBMouseState *s = (USBMouseState *)dev;
+ if (destroy) {
+ qemu_add_mouse_event_handler(NULL, NULL, 0);
+ qemu_free(s);
+ return;
+ }
+
s->dx = 0;
s->dy = 0;
s->dz = 0;
diff --git a/hw/usb-hub.c b/hw/usb-hub.c
index e2cb283ef9..c69d69cc84 100644
--- a/hw/usb-hub.c
+++ b/hw/usb-hub.c
@@ -199,9 +199,11 @@ static void usb_hub_attach(USBPort *port1, USBDevice *dev)
}
}
-static void usb_hub_handle_reset(USBDevice *dev)
+static void usb_hub_handle_reset(USBDevice *dev, int destroy)
{
/* XXX: do it */
+ if (destroy)
+ qemu_free(dev);
}
static int usb_hub_handle_control(USBDevice *dev, int request, int value,
diff --git a/hw/usb-msd.c b/hw/usb-msd.c
new file mode 100644
index 0000000000..6b4cb81eb9
--- /dev/null
+++ b/hw/usb-msd.c
@@ -0,0 +1,395 @@
+/*
+ * USB Mass Storage Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licenced under the LGPL.
+ */
+
+#include "vl.h"
+
+//#define DEBUG_MSD
+
+#ifdef DEBUG_MSD
+#define DPRINTF(fmt, args...) \
+do { printf("usb-msd: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while(0)
+#endif
+
+/* USB requests. */
+#define MassStorageReset 0xff
+#define GetMaxLun 0xfe
+
+enum USBMSDMode {
+ USB_MSDM_CBW, /* Command Block. */
+ USB_MSDM_DATAOUT, /* Tranfer data to device. */
+ USB_MSDM_DATAIN, /* Transfer data from device. */
+ USB_MSDM_CSW /* Command Status. */
+};
+
+typedef struct {
+ USBDevice dev;
+ enum USBMSDMode mode;
+ uint32_t data_len;
+ uint32_t tag;
+ SCSIDevice *scsi_dev;
+ int result;
+} MSDState;
+
+static const uint8_t qemu_msd_dev_descriptor[] = {
+ 0x12, /* u8 bLength; */
+ 0x01, /* u8 bDescriptorType; Device */
+ 0x10, 0x00, /* 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; */
+};
+
+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 void usb_msd_command_complete(void *opaque, uint32_t tag, int fail)
+{
+ MSDState *s = (MSDState *)opaque;
+
+ DPRINTF("Command complete\n");
+ s->result = fail;
+ s->mode = USB_MSDM_CSW;
+}
+
+static void usb_msd_handle_reset(USBDevice *dev, int destroy)
+{
+ 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,
+ int index, int length, uint8_t *data)
+{
+ MSDState *s = (MSDState *)dev;
+ int 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;
+ break;
+ case DeviceOutRequest | USB_REQ_SET_INTERFACE:
+ ret = 0;
+ break;
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (value == 0 && index != 0x81) { /* clear ep halt */
+ goto fail;
+ }
+ ret = 0;
+ break;
+ /* Class specific requests. */
+ case MassStorageReset:
+ /* Reset state ready for the next CBW. */
+ s->mode = USB_MSDM_CBW;
+ ret = 0;
+ break;
+ case GetMaxLun:
+ data[0] = 0;
+ ret = 1;
+ break;
+ default:
+ fail:
+ ret = USB_RET_STALL;
+ break;
+ }
+ 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 int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep,
+ uint8_t *data, int len)
+{
+ MSDState *s = (MSDState *)dev;
+ int ret = 0;
+ struct usb_msd_cbw cbw;
+ struct usb_msd_csw csw;
+
+ switch (pid) {
+ case USB_TOKEN_OUT:
+ if (devep != 2)
+ goto fail;
+
+ switch (s->mode) {
+ case USB_MSDM_CBW:
+ if (len != 31) {
+ fprintf(stderr, "usb-msd: Bad CBW size");
+ goto fail;
+ }
+ memcpy(&cbw, data, 31);
+ if (le32_to_cpu(cbw.sig) != 0x43425355) {
+ fprintf(stderr, "usb-msd: Bad signature %08x\n",
+ le32_to_cpu(cbw.sig));
+ goto fail;
+ }
+ DPRINTF("Command on LUN %d\n", cbw.lun);
+ if (cbw.lun != 0) {
+ fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun);
+ goto fail;
+ }
+ s->tag = le32_to_cpu(cbw.tag);
+ s->data_len = le32_to_cpu(cbw.data_len);
+ if (s->data_len == 0) {
+ s->mode = USB_MSDM_CSW;
+ } else if (cbw.flags & 0x80) {
+ s->mode = USB_MSDM_DATAIN;
+ } else {
+ s->mode = USB_MSDM_DATAOUT;
+ }
+ 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);
+ ret = len;
+ break;
+
+ case USB_MSDM_DATAOUT:
+ DPRINTF("Data out %d/%d\n", len, s->data_len);
+ 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;
+ break;
+
+ default:
+ DPRINTF("Unexpected write (len %d)\n", len);
+ goto fail;
+ }
+ break;
+
+ case USB_TOKEN_IN:
+ if (devep != 1)
+ goto fail;
+
+ switch (s->mode) {
+ 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->mode = USB_MSDM_CBW;
+ 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;
+ break;
+
+ default:
+ DPRINTF("Unexpected read (len %d)\n", len);
+ goto fail;
+ }
+ break;
+
+ default:
+ DPRINTF("Bad token\n");
+ fail:
+ ret = USB_RET_STALL;
+ break;
+ }
+
+ return ret;
+}
+
+
+USBDevice *usb_msd_init(const char *filename)
+{
+ MSDState *s;
+ BlockDriverState *bdrv;
+
+ s = qemu_mallocz(sizeof(MSDState));
+ if (!s)
+ return NULL;
+
+ bdrv = bdrv_new("usb");
+ bdrv_open(bdrv, filename, 0);
+
+ s->dev.speed = USB_SPEED_FULL;
+ s->dev.handle_packet = usb_generic_handle_packet;
+
+ 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->scsi_dev = scsi_disk_init(bdrv, usb_msd_command_complete, s);
+ usb_msd_handle_reset((USBDevice *)s, 0);
+ return (USBDevice *)s;
+}
diff --git a/hw/usb.c b/hw/usb.c
index 34aac5fa9b..a00d945327 100644
--- a/hw/usb.c
+++ b/hw/usb.c
@@ -55,7 +55,10 @@ int usb_generic_handle_packet(USBDevice *s, int pid,
s->remote_wakeup = 0;
s->addr = 0;
s->state = USB_STATE_DEFAULT;
- s->handle_reset(s);
+ s->handle_reset(s, 0);
+ break;
+ case USB_MSG_DESTROY:
+ s->handle_reset(s, 1);
break;
case USB_TOKEN_SETUP:
if (s->state < USB_STATE_DEFAULT || devaddr != s->addr)
diff --git a/hw/usb.h b/hw/usb.h
index c92fd01b9e..abdbb45d28 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -29,6 +29,7 @@
#define USB_MSG_ATTACH 0x100
#define USB_MSG_DETACH 0x101
#define USB_MSG_RESET 0x102
+#define USB_MSG_DESTROY 0x103
#define USB_RET_NODEV (-1)
#define USB_RET_NAK (-2)
@@ -121,7 +122,7 @@ struct USBDevice {
/* The following fields are used by the generic USB device
layer. They are here just to avoid creating a new structure for
them. */
- void (*handle_reset)(USBDevice *dev);
+ void (*handle_reset)(USBDevice *dev, int destroy);
int (*handle_control)(USBDevice *dev, int request, int value,
int index, int length, uint8_t *data);
int (*handle_data)(USBDevice *dev, int pid, uint8_t devep,
@@ -170,3 +171,6 @@ void usb_host_info(void);
/* usb-hid.c */
USBDevice *usb_mouse_init(void);
USBDevice *usb_tablet_init(void);
+
+/* usb-msd.c */
+USBDevice *usb_msd_init(const char *filename);