summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2006-08-12 01:04:27 +0000
committerpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2006-08-12 01:04:27 +0000
commit4d611c9a2f4c5d9080d8b6a6f0d7431233cd56f9 (patch)
tree6217063ef291bd680f1c81a82bdaef7345356c16 /hw
parent4ca9c76f3620dc20e56d9b7027a6f1115ea48eea (diff)
downloadqemu-4d611c9a2f4c5d9080d8b6a6f0d7431233cd56f9.tar.gz
qemu-4d611c9a2f4c5d9080d8b6a6f0d7431233cd56f9.tar.bz2
qemu-4d611c9a2f4c5d9080d8b6a6f0d7431233cd56f9.zip
SCSI and USB async IO support.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2107 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw')
-rw-r--r--hw/esp.c142
-rw-r--r--hw/lsi53c895a.c80
-rw-r--r--hw/scsi-disk.c152
-rw-r--r--hw/usb-hid.c11
-rw-r--r--hw/usb-hub.c46
-rw-r--r--hw/usb-msd.c68
-rw-r--r--hw/usb-ohci.c156
-rw-r--r--hw/usb-uhci.c244
-rw-r--r--hw/usb.c32
-rw-r--r--hw/usb.h58
10 files changed, 702 insertions, 287 deletions
diff --git a/hw/esp.c b/hw/esp.c
index cdd062f789..17e70dd03b 100644
--- a/hw/esp.c
+++ b/hw/esp.c
@@ -62,6 +62,11 @@ struct ESPState {
uint8_t cmdbuf[TI_BUFSZ];
int cmdlen;
int do_cmd;
+
+ uint32_t dma_left;
+ uint8_t async_buf[TARGET_PAGE_SIZE];
+ uint32_t async_ptr;
+ uint32_t async_len;
};
#define STAT_DO 0x00
@@ -72,6 +77,8 @@ struct ESPState {
#define STAT_MO 0x07
#define STAT_TC 0x10
+#define STAT_PE 0x20
+#define STAT_GE 0x40
#define STAT_IN 0x80
#define INTR_FC 0x08
@@ -195,26 +202,85 @@ static void write_response(ESPState *s)
}
-static void esp_command_complete(void *opaque, uint32_t tag, int sense)
+static void esp_do_dma(ESPState *s)
+{
+ uint32_t dmaptr, minlen, len, from, to;
+ int to_device;
+ dmaptr = iommu_translate(s->espdmaregs[1]);
+ to_device = (s->espdmaregs[0] & DMA_WRITE_MEM) == 0;
+ from = s->espdmaregs[1];
+ minlen = s->dma_left;
+ to = from + minlen;
+ dmaptr = iommu_translate(s->espdmaregs[1]);
+ if ((from & TARGET_PAGE_MASK) != (to & TARGET_PAGE_MASK)) {
+ len = TARGET_PAGE_SIZE - (from & ~TARGET_PAGE_MASK);
+ } else {
+ len = to - from;
+ }
+ DPRINTF("DMA address p %08x v %08x len %08x, from %08x, to %08x\n", dmaptr, s->espdmaregs[1], len, from, to);
+ s->espdmaregs[1] += len;
+ if (s->do_cmd) {
+ s->ti_size -= len;
+ DPRINTF("command len %d + %d\n", s->cmdlen, len);
+ cpu_physical_memory_read(dmaptr, &s->cmdbuf[s->cmdlen], len);
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
+ return;
+ } else {
+ s->async_len = len;
+ s->dma_left -= len;
+ if (to_device) {
+ s->async_ptr = -1;
+ cpu_physical_memory_read(dmaptr, s->async_buf, len);
+ scsi_write_data(s->current_dev, s->async_buf, len);
+ } else {
+ s->async_ptr = dmaptr;
+ scsi_read_data(s->current_dev, s->async_buf, len);
+ }
+ }
+}
+
+static void esp_command_complete(void *opaque, uint32_t reason, int sense)
{
ESPState *s = (ESPState *)opaque;
- DPRINTF("SCSI Command complete\n");
- if (s->ti_size != 0)
- DPRINTF("SCSI command completed unexpectedly\n");
- s->ti_size = 0;
- if (sense)
- DPRINTF("Command failed\n");
- s->sense = sense;
- s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
+ s->ti_size -= s->async_len;
+ s->espdmaregs[1] += s->async_len;
+ if (s->async_ptr != (uint32_t)-1) {
+ cpu_physical_memory_write(s->async_ptr, s->async_buf, s->async_len);
+ }
+ if (reason == SCSI_REASON_DONE) {
+ DPRINTF("SCSI Command complete\n");
+ if (s->ti_size != 0)
+ DPRINTF("SCSI command completed unexpectedly\n");
+ s->ti_size = 0;
+ if (sense)
+ DPRINTF("Command failed\n");
+ s->sense = sense;
+ } else {
+ DPRINTF("transfer %d/%d\n", s->dma_left, s->ti_size);
+ }
+ if (s->dma_left) {
+ esp_do_dma(s);
+ } else {
+ if (s->ti_size) {
+ s->rregs[4] |= STAT_IN | STAT_TC;
+ } else {
+ s->rregs[4] = STAT_IN | STAT_TC | STAT_ST;
+ }
+ s->rregs[5] = INTR_BS;
+ s->rregs[6] = 0;
+ s->rregs[7] = 0;
+ s->espdmaregs[0] |= DMA_INTR;
+ pic_set_irq(s->irq, 1);
+ }
}
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];
+ uint32_t dmalen, minlen;
dmalen = s->wregs[0] | (s->wregs[1] << 8);
if (dmalen==0) {
@@ -227,47 +293,9 @@ static void handle_ti(ESPState *s)
minlen = (dmalen < s->ti_size) ? dmalen : s->ti_size;
DPRINTF("Transfer Information len %d\n", minlen);
if (s->dma) {
- dmaptr = iommu_translate(s->espdmaregs[1]);
- /* 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) {
- dmaptr = iommu_translate(s->espdmaregs[1] + i);
- if ((from & TARGET_PAGE_MASK) != (to & TARGET_PAGE_MASK)) {
- len = TARGET_PAGE_SIZE - (from & ~TARGET_PAGE_MASK);
- } else {
- 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);
- s->ti_size -= len;
- if (s->do_cmd) {
- DPRINTF("command len %d + %d\n", s->cmdlen, len);
- cpu_physical_memory_read(dmaptr, &s->cmdbuf[s->cmdlen], len);
- s->ti_size = 0;
- s->cmdlen = 0;
- s->do_cmd = 0;
- do_cmd(s, s->cmdbuf);
- return;
- } else {
- 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->ti_size) {
- s->rregs[4] = STAT_IN | STAT_TC | (to_device ? STAT_DO : STAT_DI);
- }
- s->rregs[5] = INTR_BS;
- s->rregs[6] = 0;
- s->rregs[7] = 0;
- s->espdmaregs[0] |= DMA_INTR;
+ s->dma_left = minlen;
+ s->rregs[4] &= ~STAT_TC;
+ esp_do_dma(s);
} else if (s->do_cmd) {
DPRINTF("command len %d\n", s->cmdlen);
s->ti_size = 0;
@@ -276,7 +304,6 @@ static void handle_ti(ESPState *s)
do_cmd(s, s->cmdbuf);
return;
}
- pic_set_irq(s->irq, 1);
}
static void esp_reset(void *opaque)
@@ -320,8 +347,8 @@ static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr)
break;
case 5:
// interrupt
- // Clear status bits except TC
- s->rregs[4] &= STAT_TC;
+ // Clear interrupt/error status bits
+ s->rregs[4] &= ~(STAT_IN | STAT_GE | STAT_PE);
pic_set_irq(s->irq, 0);
s->espdmaregs[0] &= ~DMA_INTR;
break;
@@ -342,6 +369,7 @@ static void esp_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
case 0:
case 1:
s->rregs[saddr] = val;
+ s->rregs[4] &= ~STAT_TC;
break;
case 2:
// FIFO
diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c
index 24dff0eff5..8f56725348 100644
--- a/hw/lsi53c895a.c
+++ b/hw/lsi53c895a.c
@@ -152,6 +152,9 @@ do { fprintf(stderr, "lsi_scsi: " fmt , ##args); } while (0)
/* The HBA is ID 7, so for simplicitly limit to 7 devices. */
#define LSI_MAX_DEVS 7
+/* Size of internal DMA buffer for async IO requests. */
+#define LSI_DMA_BLOCK_SIZE 0x10000
+
typedef struct {
PCIDevice pci_dev;
int mmio_io_addr;
@@ -162,7 +165,9 @@ typedef struct {
int carry; /* ??? Should this be an a visible register somewhere? */
int sense;
uint8_t msg;
- /* Nonzero if a Wait Reselect instruction has been issued. */
+ /* 0 if SCRIPTS are running or stopped.
+ * 1 if a Wait Reselect instruction has been issued.
+ * 2 if a DMA operation is in progress. */
int waiting;
SCSIDevice *scsi_dev[LSI_MAX_DEVS];
SCSIDevice *current_dev;
@@ -226,6 +231,7 @@ typedef struct {
uint32_t csbc;
uint32_t scratch[13]; /* SCRATCHA-SCRATCHR */
+ uint8_t dma_buf[LSI_DMA_BLOCK_SIZE];
/* Script ram is stored as 32-bit words in host byteorder. */
uint32_t script_ram[2048];
} LSIState;
@@ -295,6 +301,7 @@ static void lsi_soft_reset(LSIState *s)
static uint8_t lsi_reg_readb(LSIState *s, int offset);
static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val);
+static void lsi_execute_script(LSIState *s);
static inline uint32_t read_dword(LSIState *s, uint32_t addr)
{
@@ -402,21 +409,20 @@ static void lsi_bad_phase(LSIState *s, int out, int new_phase)
lsi_set_phase(s, new_phase);
}
+/* Initiate a SCSI layer data transfer. */
static void lsi_do_dma(LSIState *s, int out)
{
- uint8_t buf[TARGET_PAGE_SIZE];
- uint32_t addr;
uint32_t count;
- int n;
count = s->dbc;
- addr = s->dnad;
- DPRINTF("DMA %s addr=0x%08x len=%d avail=%d\n", out ? "out" : "in",
+ if (count > LSI_DMA_BLOCK_SIZE)
+ count = LSI_DMA_BLOCK_SIZE;
+ DPRINTF("DMA addr=0x%08x len=%d avail=%d\n",
addr, count, s->data_len);
/* ??? Too long transfers are truncated. Don't know if this is the
correct behavior. */
if (count > s->data_len) {
- /* If the DMA length is greater then the device data length then
+ /* If the DMA length is greater than the device data length then
a phase mismatch will occur. */
count = s->data_len;
s->dbc = count;
@@ -426,20 +432,47 @@ static void lsi_do_dma(LSIState *s, int out)
s->csbc += count;
/* ??? Set SFBR to first data byte. */
- while (count) {
- n = (count > TARGET_PAGE_SIZE) ? TARGET_PAGE_SIZE : count;
- if (out) {
- cpu_physical_memory_read(addr, buf, n);
- scsi_write_data(s->current_dev, buf, n);
- } else {
- scsi_read_data(s->current_dev, buf, n);
- cpu_physical_memory_write(addr, buf, n);
- }
- addr += n;
- count -= n;
+ if ((s->sstat1 & PHASE_MASK) == PHASE_DO) {
+ cpu_physical_memory_read(s->dnad, s->dma_buf, count);
+ scsi_write_data(s->current_dev, s->dma_buf, count);
+ } else {
+ scsi_read_data(s->current_dev, s->dma_buf, count);
}
+ /* If the DMA did not complete then suspend execution. */
+ if (s->dbc)
+ s->waiting = 2;
}
+/* Callback to indicate that the SCSI layer has completed a transfer. */
+static void lsi_command_complete(void *opaque, uint32_t reason, int sense)
+{
+ LSIState *s = (LSIState *)opaque;
+ uint32_t count;
+ int out;
+
+ out = ((s->sstat1 & PHASE_MASK) == PHASE_DO);
+ count = s->dbc;
+ if (count > LSI_DMA_BLOCK_SIZE)
+ count = LSI_DMA_BLOCK_SIZE;
+ if (!out)
+ cpu_physical_memory_write(s->dnad, s->dma_buf, count);
+ s->dnad += count;
+ s->dbc -= count;
+
+ if (reason == SCSI_REASON_DONE) {
+ DPRINTF("Command complete sense=%d\n", sense);
+ s->sense = sense;
+ lsi_set_phase(s, PHASE_ST);
+ }
+
+ if (s->dbc) {
+ lsi_do_dma(s, out);
+ } else if (s->waiting == 2) {
+ /* Restart SCRIPTS execution. */
+ s->waiting = 0;
+ lsi_execute_script(s);
+ }
+}
static void lsi_do_command(LSIState *s)
{
@@ -461,15 +494,6 @@ static void lsi_do_command(LSIState *s)
}
}
-static void lsi_command_complete(void *opaque, uint32_t tag, int sense)
-{
- LSIState *s = (LSIState *)opaque;
-
- DPRINTF("Command complete sense=%d\n", sense);
- s->sense = sense;
- lsi_set_phase(s, PHASE_ST);
-}
-
static void lsi_do_status(LSIState *s)
{
DPRINTF("Get status len=%d sense=%d\n", s->dbc, s->sense);
@@ -1134,7 +1158,7 @@ static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val)
s->istat0 &= ~LSI_ISTAT0_INTF;
lsi_update_irq(s);
}
- if (s->waiting && val & LSI_ISTAT0_SIGP) {
+ if (s->waiting == 1 && val & LSI_ISTAT0_SIGP) {
DPRINTF("Woken by SIGP\n");
s->waiting = 0;
s->dsp = s->dnad;
diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c
index 3419c8e2d2..a2f299e951 100644
--- a/hw/scsi-disk.c
+++ b/hw/scsi-disk.c
@@ -25,6 +25,7 @@ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0)
#define SENSE_NO_SENSE 0
#define SENSE_NOT_READY 2
+#define SENSE_HARDWARE_ERROR 4
#define SENSE_ILLEGAL_REQUEST 5
struct SCSIDevice
@@ -46,7 +47,13 @@ struct SCSIDevice
int buf_pos;
int buf_len;
int sense;
+ BlockDriverAIOCB *aiocb;
+ /* Data still to be transfered after this request completes. */
+ uint8_t *aiodata;
+ uint32_t aiolen;
char buf[512];
+ /* Completion functions may be called from either scsi_{read,write}_data
+ or from the AIO completion routines. */
scsi_completionfn completion;
void *opaque;
};
@@ -54,10 +61,49 @@ struct SCSIDevice
static void scsi_command_complete(SCSIDevice *s, int sense)
{
s->sense = sense;
- s->completion(s->opaque, s->tag, sense);
+ s->completion(s->opaque, SCSI_REASON_DONE, sense);
}
-/* Read data from a scsi device. Returns nonzero on failure. */
+static void scsi_transfer_complete(SCSIDevice *s)
+{
+ s->completion(s->opaque, SCSI_REASON_DATA, 0);
+ s->aiocb = NULL;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+ SCSIDevice *s = (SCSIDevice *)opaque;
+
+ if (ret) {
+ DPRINTF("IO error\n");
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
+ }
+
+ if (s->aiolen) {
+ /* Read the remaining data. Full and partial sectors are transferred
+ separately. */
+ scsi_read_data(s, s->aiodata, s->aiolen);
+ } else {
+ if (s->buf_len == 0 && s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
+ }
+}
+
+/* Cancel a pending data transfer. */
+void scsi_cancel_io(SCSIDevice *s)
+{
+ if (!s->aiocb) {
+ BADF("Cancel with no pending IO\n");
+ return;
+ }
+ bdrv_aio_cancel(s->aiocb);
+ s->aiocb = NULL;
+}
+
+/* Read data from a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
@@ -84,14 +130,19 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
n = s->sector_count;
if (n != 0) {
- bdrv_read(s->bdrv, s->sector, data, n);
- data += n * 512;
- len -= n * 512;
+ s->aiolen = len - n * 512;
+ s->aiodata = data + n * 512;
+ s->aiocb = bdrv_aio_read(s->bdrv, s->sector, data, n,
+ scsi_read_complete, s);
+ if (s->aiocb == NULL)
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
s->sector += n;
s->sector_count -= n;
+ return 0;
}
if (len && s->sector_count) {
+ /* TODO: Make this use AIO. */
bdrv_read(s->bdrv, s->sector, s->buf, 1);
s->sector++;
s->sector_count--;
@@ -106,11 +157,53 @@ int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len)
if (s->buf_len == 0 && s->sector_count == 0)
scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
return 0;
}
-/* Read data to a scsi device. Returns nonzero on failure. */
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIDevice *s = (SCSIDevice *)opaque;
+
+ if (ret) {
+ fprintf(stderr, "scsi-disc: IO write error\n");
+ exit(1);
+ }
+
+ if (s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
+}
+
+static uint32_t scsi_write_partial_sector(SCSIDevice *s, uint8_t *data,
+ uint32_t len)
+{
+ int n;
+
+ n = 512 - 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 == 512) {
+ /* A full sector has been accumulated. Write it to disk. */
+ /* TODO: Make this use async IO. */
+ bdrv_write(s->bdrv, s->sector, s->buf, 1);
+ s->buf_len = 0;
+ s->sector++;
+ s->sector_count--;
+ }
+ return n;
+}
+
+/* Write data to a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
{
uint32_t n;
@@ -125,48 +218,39 @@ int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len)
return 1;
if (s->buf_len != 0 || len < 512) {
- n = 512 - s->buf_len;
- if (n > len)
- n = len;
-
- memcpy(s->buf + s->buf_len, data, n);
- data += n;
- s->buf_len += n;
+ n = scsi_write_partial_sector(s, data, len);
len -= n;
- if (s->buf_len == 512) {
- /* 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--;
- }
+ data += n;
}
n = len / 512;
if (n > s->sector_count)
- n = s->sector_count;
+ return 1;
if (n != 0) {
- bdrv_write(s->bdrv, s->sector, data, n);
+ s->aiocb = bdrv_aio_write(s->bdrv, s->sector, data, n,
+ scsi_write_complete, s);
+ if (s->aiocb == NULL)
+ scsi_command_complete(s, SENSE_HARDWARE_ERROR);
data += n * 512;
len -= n * 512;
s->sector += n;
s->sector_count -= n;
}
- if (len >= 512)
- return 1;
-
- if (len && s->sector_count) {
- /* Recurse to complete the partial write. */
- return scsi_write_data(s, data, len);
+ if (len) {
+ if (s->sector_count == 0)
+ return 1;
+ /* Complete a partial write. */
+ scsi_write_partial_sector(s, data, len);
+ }
+ if (n == 0) {
+ /* Transfer completes immediately. */
+ if (s->sector_count == 0)
+ scsi_command_complete(s, SENSE_NO_SENSE);
+ else
+ scsi_transfer_complete(s);
}
-
- if (len != 0)
- return 1;
-
- if (s->sector_count == 0)
- scsi_command_complete(s, SENSE_NO_SENSE);
return 0;
}
diff --git a/hw/usb-hid.c b/hw/usb-hid.c
index 8fc0b744b7..095fcb3e6c 100644
--- a/hw/usb-hid.c
+++ b/hw/usb-hid.c
@@ -474,19 +474,18 @@ static int usb_mouse_handle_control(USBDevice *dev, int request, int value,
return ret;
}
-static int usb_mouse_handle_data(USBDevice *dev, int pid,
- uint8_t devep, uint8_t *data, int len)
+static int usb_mouse_handle_data(USBDevice *dev, USBPacket *p)
{
USBMouseState *s = (USBMouseState *)dev;
int ret = 0;
- switch(pid) {
+ switch(p->pid) {
case USB_TOKEN_IN:
- if (devep == 1) {
+ if (p->devep == 1) {
if (s->kind == USB_MOUSE)
- ret = usb_mouse_poll(s, data, len);
+ ret = usb_mouse_poll(s, p->data, p->len);
else if (s->kind == USB_TABLET)
- ret = usb_tablet_poll(s, data, len);
+ ret = usb_tablet_poll(s, p->data, p->len);
} else {
goto fail;
}
diff --git a/hw/usb-hub.c b/hw/usb-hub.c
index 8350931be6..651dac2109 100644
--- a/hw/usb-hub.c
+++ b/hw/usb-hub.c
@@ -180,8 +180,7 @@ static void usb_hub_attach(USBPort *port1, USBDevice *dev)
port->wPortStatus &= ~PORT_STAT_LOW_SPEED;
port->port.dev = dev;
/* send the attach message */
- dev->handle_packet(dev,
- USB_MSG_ATTACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_ATTACH);
} else {
dev = port->port.dev;
if (dev) {
@@ -192,8 +191,7 @@ static void usb_hub_attach(USBPort *port1, USBDevice *dev)
port->wPortChange |= PORT_STAT_C_ENABLE;
}
/* send the detach message */
- dev->handle_packet(dev,
- USB_MSG_DETACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_DETACH);
port->port.dev = NULL;
}
}
@@ -349,8 +347,7 @@ static int usb_hub_handle_control(USBDevice *dev, int request, int value,
break;
case PORT_RESET:
if (dev) {
- dev->handle_packet(dev,
- USB_MSG_RESET, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_RESET);
port->wPortChange |= PORT_STAT_C_RESET;
/* set enable bit */
port->wPortStatus |= PORT_STAT_ENABLE;
@@ -434,22 +431,21 @@ static int usb_hub_handle_control(USBDevice *dev, int request, int value,
return ret;
}
-static int usb_hub_handle_data(USBDevice *dev, int pid,
- uint8_t devep, uint8_t *data, int len)
+static int usb_hub_handle_data(USBDevice *dev, USBPacket *p)
{
USBHubState *s = (USBHubState *)dev;
int ret;
- switch(pid) {
+ switch(p->pid) {
case USB_TOKEN_IN:
- if (devep == 1) {
+ if (p->devep == 1) {
USBHubPort *port;
unsigned int status;
int i, n;
n = (s->nb_ports + 1 + 7) / 8;
- if (len == 1) { /* FreeBSD workaround */
+ if (p->len == 1) { /* FreeBSD workaround */
n = 1;
- } else if (n > len) {
+ } else if (n > p->len) {
return USB_RET_BABBLE;
}
status = 0;
@@ -460,7 +456,7 @@ static int usb_hub_handle_data(USBDevice *dev, int pid,
}
if (status != 0) {
for(i = 0; i < n; i++) {
- data[i] = status >> (8 * i);
+ p->data[i] = status >> (8 * i);
}
ret = n;
} else {
@@ -479,9 +475,7 @@ static int usb_hub_handle_data(USBDevice *dev, int pid,
return ret;
}
-static int usb_hub_broadcast_packet(USBHubState *s, int pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len)
+static int usb_hub_broadcast_packet(USBHubState *s, USBPacket *p)
{
USBHubPort *port;
USBDevice *dev;
@@ -491,9 +485,7 @@ static int usb_hub_broadcast_packet(USBHubState *s, int pid,
port = &s->ports[i];
dev = port->port.dev;
if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) {
- ret = dev->handle_packet(dev, pid,
- devaddr, devep,
- data, len);
+ ret = dev->handle_packet(dev, p);
if (ret != USB_RET_NODEV) {
return ret;
}
@@ -502,9 +494,7 @@ static int usb_hub_broadcast_packet(USBHubState *s, int pid,
return USB_RET_NODEV;
}
-static int usb_hub_handle_packet(USBDevice *dev, int pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len)
+static int usb_hub_handle_packet(USBDevice *dev, USBPacket *p)
{
USBHubState *s = (USBHubState *)dev;
@@ -513,14 +503,14 @@ static int usb_hub_handle_packet(USBDevice *dev, int pid,
#endif
if (dev->state == USB_STATE_DEFAULT &&
dev->addr != 0 &&
- devaddr != dev->addr &&
- (pid == USB_TOKEN_SETUP ||
- pid == USB_TOKEN_OUT ||
- pid == USB_TOKEN_IN)) {
+ p->devaddr != dev->addr &&
+ (p->pid == USB_TOKEN_SETUP ||
+ p->pid == USB_TOKEN_OUT ||
+ p->pid == USB_TOKEN_IN)) {
/* broadcast the packet to the devices */
- return usb_hub_broadcast_packet(s, pid, devaddr, devep, data, len);
+ return usb_hub_broadcast_packet(s, p);
}
- return usb_generic_handle_packet(dev, pid, devaddr, devep, data, len);
+ return usb_generic_handle_packet(dev, p);
}
static void usb_hub_handle_destroy(USBDevice *dev)
diff --git a/hw/usb-msd.c b/hw/usb-msd.c
index ff2047d4b2..7d1f35d064 100644
--- a/hw/usb-msd.c
+++ b/hw/usb-msd.c
@@ -33,9 +33,12 @@ typedef struct {
USBDevice dev;
enum USBMSDMode mode;
uint32_t data_len;
+ uint32_t transfer_len;
uint32_t tag;
SCSIDevice *scsi_dev;
int result;
+ /* For async completion. */
+ USBPacket *packet;
} MSDState;
static const uint8_t qemu_msd_dev_descriptor[] = {
@@ -103,13 +106,27 @@ 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_command_complete(void *opaque, uint32_t reason, int fail)
{
MSDState *s = (MSDState *)opaque;
-
- DPRINTF("Command complete\n");
- s->result = fail;
- s->mode = USB_MSDM_CSW;
+ USBPacket *p;
+
+ s->data_len -= s->transfer_len;
+ s->transfer_len = 0;
+ if (reason == SCSI_REASON_DONE) {
+ DPRINTF("Command complete %d\n", fail);
+ s->result = fail;
+ s->mode = USB_MSDM_CSW;
+ }
+ 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);
+ }
}
static void usb_msd_handle_reset(USBDevice *dev)
@@ -250,15 +267,24 @@ struct usb_msd_csw {
uint8_t status;
};
-static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep,
- uint8_t *data, int len)
+static void usb_msd_cancel_io(USBPacket *p, void *opaque)
+{
+ MSDState *s = opaque;
+ scsi_cancel_io(s->scsi_dev);
+ s->packet = NULL;
+}
+
+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;
@@ -300,13 +326,18 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep,
if (len > s->data_len)
goto fail;
+ s->transfer_len = len;
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;
+ if (s->transfer_len == 0) {
+ ret = len;
+ } else {
+ DPRINTF("Deferring packet %p\n", p);
+ usb_defer_packet(p, usb_msd_cancel_io, s);
+ s->packet = p;
+ ret = USB_RET_ASYNC;
+ }
break;
default:
@@ -340,13 +371,18 @@ static int usb_msd_handle_data(USBDevice *dev, int pid, uint8_t devep,
if (len > s->data_len)
len = s->data_len;
+ s->transfer_len = 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;
+ if (s->transfer_len == 0) {
+ ret = len;
+ } else {
+ DPRINTF("Deferring packet %p\n", p);
+ usb_defer_packet(p, usb_msd_cancel_io, s);
+ s->packet = p;
+ ret = USB_RET_ASYNC;
+ }
break;
default:
diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c
index e87d9da70a..de113e9a30 100644
--- a/hw/usb-ohci.c
+++ b/hw/usb-ohci.c
@@ -89,6 +89,14 @@ typedef struct {
uint32_t rhdesc_a, rhdesc_b;
uint32_t rhstatus;
OHCIPort rhport[OHCI_MAX_PORTS];
+
+ /* Active packets. */
+ uint32_t old_ctl;
+ USBPacket usb_packet;
+ uint8_t usb_buf[8192];
+ uint32_t async_td;
+ int async_complete;
+
} OHCIState;
/* Host Controller Communications Area */
@@ -288,8 +296,7 @@ static void ohci_attach(USBPort *port1, USBDevice *dev)
port->ctrl &= ~OHCI_PORT_LSDA;
port->port.dev = dev;
/* send the attach message */
- dev->handle_packet(dev,
- USB_MSG_ATTACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_ATTACH);
dprintf("usb-ohci: Attached port %d\n", port1->index);
} else {
/* set connect status */
@@ -305,8 +312,7 @@ static void ohci_attach(USBPort *port1, USBDevice *dev)
dev = port->port.dev;
if (dev) {
/* send the detach message */
- dev->handle_packet(dev,
- USB_MSG_DETACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_DETACH);
}
port->port.dev = NULL;
dprintf("usb-ohci: Detached port %d\n", port1->index);
@@ -323,6 +329,7 @@ static void ohci_reset(OHCIState *ohci)
int i;
ohci->ctl = 0;
+ ohci->old_ctl = 0;
ohci->status = 0;
ohci->intr_status = 0;
ohci->intr = OHCI_INTR_MIE;
@@ -356,6 +363,10 @@ static void ohci_reset(OHCIState *ohci)
if (port->port.dev)
ohci_attach(&port->port, port->port.dev);
}
+ if (ohci->async_td) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ }
dprintf("usb-ohci: Reset %s\n", ohci->pci_dev.name);
}
@@ -423,6 +434,18 @@ static void ohci_copy_td(struct ohci_td *td, uint8_t *buf, int len, int write)
cpu_physical_memory_rw(ptr, buf, len - n, write);
}
+static void ohci_process_lists(OHCIState *ohci);
+
+static void ohci_async_complete_packet(USBPacket * packet, void *opaque)
+{
+ OHCIState *ohci = opaque;
+#ifdef DEBUG_PACKET
+ dprintf("Async packet complete\n");
+#endif
+ ohci->async_complete = 1;
+ ohci_process_lists(ohci);
+}
+
/* Service a transport descriptor.
Returns nonzero to terminate processing of this endpoint. */
@@ -430,7 +453,6 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
{
int dir;
size_t len = 0;
- uint8_t buf[8192];
char *str = NULL;
int pid;
int ret;
@@ -439,8 +461,17 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
struct ohci_td td;
uint32_t addr;
int flag_r;
+ int completion;
addr = ed->head & OHCI_DPTR_MASK;
+ /* See if this TD has already been submitted to the device. */
+ completion = (addr == ohci->async_td);
+ if (completion && !ohci->async_complete) {
+#ifdef DEBUG_PACKET
+ dprintf("Skipping async TD\n");
+#endif
+ return 1;
+ }
if (!ohci_read_td(addr, &td)) {
fprintf(stderr, "usb-ohci: TD read error at %x\n", addr);
return 0;
@@ -481,8 +512,8 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
len = (td.be - td.cbp) + 1;
}
- if (len && dir != OHCI_TD_DIR_IN) {
- ohci_copy_td(&td, buf, len, 0);
+ if (len && dir != OHCI_TD_DIR_IN && !completion) {
+ ohci_copy_td(&td, ohci->usb_buf, len, 0);
}
}
@@ -494,31 +525,58 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
if (len >= 0 && dir != OHCI_TD_DIR_IN) {
dprintf(" data:");
for (i = 0; i < len; i++)
- printf(" %.2x", buf[i]);
+ printf(" %.2x", ohci->usb_buf[i]);
dprintf("\n");
}
#endif
- ret = USB_RET_NODEV;
- for (i = 0; i < ohci->num_ports; i++) {
- dev = ohci->rhport[i].port.dev;
- if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0)
- continue;
-
- ret = dev->handle_packet(dev, pid, OHCI_BM(ed->flags, ED_FA),
- OHCI_BM(ed->flags, ED_EN), buf, len);
- if (ret != USB_RET_NODEV)
- break;
- }
+ if (completion) {
+ ret = ohci->usb_packet.len;
+ ohci->async_td = 0;
+ ohci->async_complete = 0;
+ } else {
+ ret = USB_RET_NODEV;
+ for (i = 0; i < ohci->num_ports; i++) {
+ dev = ohci->rhport[i].port.dev;
+ if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0)
+ continue;
+
+ if (ohci->async_td) {
+ /* ??? The hardware should allow one active packet per
+ endpoint. We only allow one active packet per controller.
+ This should be sufficient as long as devices respond in a
+ timely manner.
+ */
#ifdef DEBUG_PACKET
- dprintf("ret=%d\n", ret);
+ dprintf("Too many pending packets\n");
#endif
+ return 1;
+ }
+ ohci->usb_packet.pid = pid;
+ ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA);
+ ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN);
+ ohci->usb_packet.data = ohci->usb_buf;
+ ohci->usb_packet.len = len;
+ ohci->usb_packet.complete_cb = ohci_async_complete_packet;
+ ohci->usb_packet.complete_opaque = ohci;
+ ret = dev->handle_packet(dev, &ohci->usb_packet);
+ if (ret != USB_RET_NODEV)
+ break;
+ }
+#ifdef DEBUG_PACKET
+ dprintf("ret=%d\n", ret);
+#endif
+ if (ret == USB_RET_ASYNC) {
+ ohci->async_td = addr;
+ return 1;
+ }
+ }
if (ret >= 0) {
if (dir == OHCI_TD_DIR_IN) {
- ohci_copy_td(&td, buf, ret, 1);
+ ohci_copy_td(&td, ohci->usb_buf, ret, 1);
#ifdef DEBUG_PACKET
dprintf(" data:");
for (i = 0; i < ret; i++)
- printf(" %.2x", buf[i]);
+ printf(" %.2x", ohci->usb_buf[i]);
dprintf("\n");
#endif
} else {
@@ -608,8 +666,16 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head)
next_ed = ed.next & OHCI_DPTR_MASK;
- if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K))
+ if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) {
+ uint32_t addr;
+ /* Cancel pending packets for ED that have been paused. */
+ addr = ed.head & OHCI_DPTR_MASK;
+ if (ohci->async_td && addr == ohci->async_td) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
+ }
continue;
+ }
/* Skip isochronous endpoints. */
if (ed.flags & OHCI_ED_F)
@@ -646,6 +712,26 @@ static void ohci_sof(OHCIState *ohci)
ohci_set_interrupt(ohci, OHCI_INTR_SF);
}
+/* Process Control and Bulk lists. */
+static void ohci_process_lists(OHCIState *ohci)
+{
+ if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) {
+ if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head)
+ dprintf("usb-ohci: head %x, cur %x\n", ohci->ctrl_head, ohci->ctrl_cur);
+ if (!ohci_service_ed_list(ohci, ohci->ctrl_head)) {
+ ohci->ctrl_cur = 0;
+ ohci->status &= ~OHCI_STATUS_CLF;
+ }
+ }
+
+ if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) {
+ if (!ohci_service_ed_list(ohci, ohci->bulk_head)) {
+ ohci->bulk_cur = 0;
+ ohci->status &= ~OHCI_STATUS_BLF;
+ }
+ }
+}
+
/* Do frame processing on frame boundary */
static void ohci_frame_boundary(void *opaque)
{
@@ -661,21 +747,15 @@ static void ohci_frame_boundary(void *opaque)
n = ohci->frame_number & 0x1f;
ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]));
}
- if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) {
- if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head)
- dprintf("usb-ohci: head %x, cur %x\n", ohci->ctrl_head, ohci->ctrl_cur);
- if (!ohci_service_ed_list(ohci, ohci->ctrl_head)) {
- ohci->ctrl_cur = 0;
- ohci->status &= ~OHCI_STATUS_CLF;
- }
- }
- if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) {
- if (!ohci_service_ed_list(ohci, ohci->bulk_head)) {
- ohci->bulk_cur = 0;
- ohci->status &= ~OHCI_STATUS_BLF;
- }
+ /* Cancel all pending packets if either of the lists has been disabled. */
+ if (ohci->async_td &&
+ ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) {
+ usb_cancel_packet(&ohci->usb_packet);
+ ohci->async_td = 0;
}
+ ohci->old_ctl = ohci->ctl;
+ ohci_process_lists(ohci);
/* Frame boundary, so do EOF stuf here */
ohci->frt = ohci->fit;
@@ -907,8 +987,7 @@ static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val)
if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) {
dprintf("usb-ohci: port %d: RESET\n", portnum);
- port->port.dev->handle_packet(port->port.dev, USB_MSG_RESET,
- 0, 0, NULL, 0);
+ usb_send_msg(port->port.dev, USB_MSG_RESET);
port->ctrl &= ~OHCI_PORT_PRS;
/* ??? Should this also set OHCI_PORT_PESC. */
port->ctrl |= OHCI_PORT_PES | OHCI_PORT_PRSC;
@@ -1186,5 +1265,6 @@ void usb_ohci_init(struct PCIBus *bus, int num_ports, int devfn)
qemu_register_usb_port(&ohci->rhport[i].port, ohci, i, ohci_attach);
}
+ ohci->async_td = 0;
ohci_reset(ohci);
}
diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c
index 1a6e0130f9..29809f96bf 100644
--- a/hw/usb-uhci.c
+++ b/hw/usb-uhci.c
@@ -76,6 +76,18 @@ typedef struct UHCIState {
uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */
QEMUTimer *frame_timer;
UHCIPort ports[NB_PORTS];
+
+ /* Interrupts that should be raised at the end of the current frame. */
+ uint32_t pending_int_mask;
+ /* For simplicity of implementation we only allow a single pending USB
+ request. This means all usb traffic on this controller is effectively
+ suspended until that transfer completes. When the transfer completes
+ the next transfer from that queue will be processed. However
+ other queues will not be processed until the next frame. The solution
+ is to allow multiple pending requests. */
+ uint32_t async_qh;
+ USBPacket usb_packet;
+ uint8_t usb_buf[1280];
} UHCIState;
typedef struct UHCI_TD {
@@ -188,8 +200,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
port = &s->ports[i];
dev = port->port.dev;
if (dev) {
- dev->handle_packet(dev,
- USB_MSG_RESET, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_RESET);
}
}
uhci_reset(s);
@@ -232,8 +243,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
/* port reset */
if ( (val & UHCI_PORT_RESET) &&
!(port->ctrl & UHCI_PORT_RESET) ) {
- dev->handle_packet(dev,
- USB_MSG_RESET, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_RESET);
}
}
port->ctrl = (port->ctrl & 0x01fb) | (val & ~0x01fb);
@@ -336,8 +346,7 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
port->ctrl &= ~UHCI_PORT_LSDA;
port->port.dev = dev;
/* send the attach message */
- dev->handle_packet(dev,
- USB_MSG_ATTACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_ATTACH);
} else {
/* set connect status */
if (port->ctrl & UHCI_PORT_CCS) {
@@ -352,16 +361,13 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
dev = port->port.dev;
if (dev) {
/* send the detach message */
- dev->handle_packet(dev,
- USB_MSG_DETACH, 0, 0, NULL, 0);
+ usb_send_msg(dev, USB_MSG_DETACH);
}
port->port.dev = NULL;
}
}
-static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len)
+static int uhci_broadcast_packet(UHCIState *s, USBPacket *p)
{
UHCIPort *port;
USBDevice *dev;
@@ -370,18 +376,18 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
#ifdef DEBUG_PACKET
{
const char *pidstr;
- switch(pid) {
+ switch(p->pid) {
case USB_TOKEN_SETUP: pidstr = "SETUP"; break;
case USB_TOKEN_IN: pidstr = "IN"; break;
case USB_TOKEN_OUT: pidstr = "OUT"; break;
default: pidstr = "?"; break;
}
printf("frame %d: pid=%s addr=0x%02x ep=%d len=%d\n",
- s->frnum, pidstr, devaddr, devep, len);
- if (pid != USB_TOKEN_IN) {
+ s->frnum, pidstr, p->devaddr, p->devep, p->len);
+ if (p->pid != USB_TOKEN_IN) {
printf(" data_out=");
- for(i = 0; i < len; i++) {
- printf(" %02x", data[i]);
+ for(i = 0; i < p->len; i++) {
+ printf(" %02x", p->data[i]);
}
printf("\n");
}
@@ -391,17 +397,17 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
port = &s->ports[i];
dev = port->port.dev;
if (dev && (port->ctrl & UHCI_PORT_EN)) {
- ret = dev->handle_packet(dev, pid,
- devaddr, devep,
- data, len);
+ ret = dev->handle_packet(dev, p);
if (ret != USB_RET_NODEV) {
#ifdef DEBUG_PACKET
- {
+ if (ret == USB_RET_ASYNC) {
+ printf("usb-uhci: Async packet\n");
+ } else {
printf(" ret=%d ", ret);
- if (pid == USB_TOKEN_IN && ret > 0) {
+ if (p->pid == USB_TOKEN_IN && ret > 0) {
printf("data_in=");
for(i = 0; i < ret; i++) {
- printf(" %02x", data[i]);
+ printf(" %02x", p->data[i]);
}
}
printf("\n");
@@ -414,6 +420,8 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
return USB_RET_NODEV;
}
+static void uhci_async_complete_packet(USBPacket * packet, void *opaque);
+
/* return -1 if fatal error (frame must be stopped)
0 if TD successful
1 if TD unsuccessful or inactive
@@ -421,9 +429,9 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
{
uint8_t pid;
- uint8_t buf[1280];
int len, max_len, err, ret;
+ /* ??? This is wrong for async completion. */
if (td->ctrl & TD_CTRL_IOC) {
*int_mask |= 0x01;
}
@@ -434,21 +442,8 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
/* TD is active */
max_len = ((td->token >> 21) + 1) & 0x7ff;
pid = td->token & 0xff;
- switch(pid) {
- case USB_TOKEN_OUT:
- case USB_TOKEN_SETUP:
- cpu_physical_memory_read(td->buffer, buf, max_len);
- ret = uhci_broadcast_packet(s, pid,
- (td->token >> 8) & 0x7f,
- (td->token >> 15) & 0xf,
- buf, max_len);
- len = max_len;
- break;
- case USB_TOKEN_IN:
- ret = uhci_broadcast_packet(s, pid,
- (td->token >> 8) & 0x7f,
- (td->token >> 15) & 0xf,
- buf, max_len);
+ if (s->async_qh) {
+ ret = s->usb_packet.len;
if (ret >= 0) {
len = ret;
if (len > max_len) {
@@ -457,17 +452,52 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
}
if (len > 0) {
/* write the data back */
- cpu_physical_memory_write(td->buffer, buf, len);
+ cpu_physical_memory_write(td->buffer, s->usb_buf, len);
}
} else {
len = 0;
}
- break;
- default:
- /* invalid pid : frame interrupted */
- s->status |= UHCI_STS_HCPERR;
- uhci_update_irq(s);
- return -1;
+ s->async_qh = 0;
+ } else {
+ s->usb_packet.pid = pid;
+ s->usb_packet.devaddr = (td->token >> 8) & 0x7f;
+ s->usb_packet.devep = (td->token >> 15) & 0xf;
+ s->usb_packet.data = s->usb_buf;
+ s->usb_packet.len = max_len;
+ s->usb_packet.complete_cb = uhci_async_complete_packet;
+ s->usb_packet.complete_opaque = s;
+ switch(pid) {
+ case USB_TOKEN_OUT:
+ case USB_TOKEN_SETUP:
+ cpu_physical_memory_read(td->buffer, s->usb_buf, max_len);
+ ret = uhci_broadcast_packet(s, &s->usb_packet);
+ len = max_len;
+ break;
+ case USB_TOKEN_IN:
+ ret = uhci_broadcast_packet(s, &s->usb_packet);
+ if (ret >= 0) {
+ len = ret;
+ if (len > max_len) {
+ len = max_len;
+ ret = USB_RET_BABBLE;
+ }
+ if (len > 0) {
+ /* write the data back */
+ cpu_physical_memory_write(td->buffer, s->usb_buf, len);
+ }
+ } else {
+ len = 0;
+ }
+ break;
+ default:
+ /* invalid pid : frame interrupted */
+ s->status |= UHCI_STS_HCPERR;
+ uhci_update_irq(s);
+ return -1;
+ }
+ }
+ if (ret == USB_RET_ASYNC) {
+ return 2;
}
if (td->ctrl & TD_CTRL_IOS)
td->ctrl &= ~TD_CTRL_ACTIVE;
@@ -520,6 +550,61 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
}
}
+static void uhci_async_complete_packet(USBPacket * packet, void *opaque)
+{
+ UHCIState *s = opaque;
+ UHCI_QH qh;
+ UHCI_TD td;
+ uint32_t link;
+ uint32_t old_td_ctrl;
+ uint32_t val;
+ int ret;
+
+ link = s->async_qh;
+ if (!link) {
+ /* This should never happen. It means a TD somehow got removed
+ without cancelling the associated async IO request. */
+ return;
+ }
+ cpu_physical_memory_read(link & ~0xf, (uint8_t *)&qh, sizeof(qh));
+ le32_to_cpus(&qh.link);
+ le32_to_cpus(&qh.el_link);
+ /* Re-process the queue containing the async packet. */
+ while (1) {
+ cpu_physical_memory_read(qh.el_link & ~0xf,
+ (uint8_t *)&td, sizeof(td));
+ le32_to_cpus(&td.link);
+ le32_to_cpus(&td.ctrl);
+ le32_to_cpus(&td.token);
+ le32_to_cpus(&td.buffer);
+ old_td_ctrl = td.ctrl;
+ ret = uhci_handle_td(s, &td, &s->pending_int_mask);
+ /* update the status bits of the TD */
+ if (old_td_ctrl != td.ctrl) {
+ val = cpu_to_le32(td.ctrl);
+ cpu_physical_memory_write((qh.el_link & ~0xf) + 4,
+ (const uint8_t *)&val,
+ sizeof(val));
+ }
+ if (ret < 0)
+ break; /* interrupted frame */
+ if (ret == 2) {
+ s->async_qh = link;
+ break;
+ } else if (ret == 0) {
+ /* update qh element link */
+ qh.el_link = td.link;
+ val = cpu_to_le32(qh.el_link);
+ cpu_physical_memory_write((link & ~0xf) + 4,
+ (const uint8_t *)&val,
+ sizeof(val));
+ if (!(qh.el_link & 4))
+ break;
+ }
+ break;
+ }
+}
+
static void uhci_frame_timer(void *opaque)
{
UHCIState *s = opaque;
@@ -528,6 +613,7 @@ static void uhci_frame_timer(void *opaque)
int int_mask, cnt, ret;
UHCI_TD td;
UHCI_QH qh;
+ uint32_t old_async_qh;
if (!(s->cmd & UHCI_CMD_RS)) {
qemu_del_timer(s->frame_timer);
@@ -535,6 +621,14 @@ static void uhci_frame_timer(void *opaque)
s->status |= UHCI_STS_HCHALTED;
return;
}
+ /* Complete the previous frame. */
+ s->frnum = (s->frnum + 1) & 0x7ff;
+ if (s->pending_int_mask) {
+ s->status2 |= s->pending_int_mask;
+ s->status |= UHCI_STS_USBINT;
+ uhci_update_irq(s);
+ }
+ old_async_qh = s->async_qh;
frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2);
cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
le32_to_cpus(&link);
@@ -546,6 +640,12 @@ static void uhci_frame_timer(void *opaque)
/* valid frame */
if (link & 2) {
/* QH */
+ if (link == s->async_qh) {
+ /* We've found a previously issues packet.
+ Nothing else to do. */
+ old_async_qh = 0;
+ break;
+ }
cpu_physical_memory_read(link & ~0xf, (uint8_t *)&qh, sizeof(qh));
le32_to_cpus(&qh.link);
le32_to_cpus(&qh.el_link);
@@ -556,6 +656,10 @@ static void uhci_frame_timer(void *opaque)
} else if (qh.el_link & 2) {
/* QH */
link = qh.el_link;
+ } else if (s->async_qh) {
+ /* We can only cope with one pending packet. Keep looking
+ for the previously issued packet. */
+ link = qh.link;
} else {
/* TD */
if (--cnt == 0)
@@ -577,7 +681,9 @@ static void uhci_frame_timer(void *opaque)
}
if (ret < 0)
break; /* interrupted frame */
- if (ret == 0) {
+ if (ret == 2) {
+ s->async_qh = link;
+ } else if (ret == 0) {
/* update qh element link */
qh.el_link = td.link;
val = cpu_to_le32(qh.el_link);
@@ -599,25 +705,41 @@ static void uhci_frame_timer(void *opaque)
le32_to_cpus(&td.ctrl);
le32_to_cpus(&td.token);
le32_to_cpus(&td.buffer);
- old_td_ctrl = td.ctrl;
- ret = uhci_handle_td(s, &td, &int_mask);
- /* update the status bits of the TD */
- if (old_td_ctrl != td.ctrl) {
- val = cpu_to_le32(td.ctrl);
- cpu_physical_memory_write((link & ~0xf) + 4,
- (const uint8_t *)&val,
- sizeof(val));
+ /* Ignore isochonous transfers while there is an async packet
+ pending. This is wrong, but we don't implement isochronous
+ transfers anyway. */
+ if (s->async_qh == 0) {
+ old_td_ctrl = td.ctrl;
+ ret = uhci_handle_td(s, &td, &int_mask);
+ /* update the status bits of the TD */
+ if (old_td_ctrl != td.ctrl) {
+ val = cpu_to_le32(td.ctrl);
+ cpu_physical_memory_write((link & ~0xf) + 4,
+ (const uint8_t *)&val,
+ sizeof(val));
+ }
+ if (ret < 0)
+ break; /* interrupted frame */
+ if (ret == 2) {
+ /* We can't handle async isochronous transfers.
+ Cancel The packet. */
+ fprintf(stderr, "usb-uhci: Unimplemented async packet\n");
+ usb_cancel_packet(&s->usb_packet);
+ }
}
- if (ret < 0)
- break; /* interrupted frame */
link = td.link;
}
}
- s->frnum = (s->frnum + 1) & 0x7ff;
- if (int_mask) {
- s->status2 |= int_mask;
- s->status |= UHCI_STS_USBINT;
- uhci_update_irq(s);
+ s->pending_int_mask = int_mask;
+ if (old_async_qh) {
+ /* A previously started transfer has disappeared from the transfer
+ list. There's nothing useful we can do with it now, so just
+ discard the packet and hope it wasn't too important. */
+#ifdef DEBUG
+ printf("Discarding USB packet\n");
+#endif
+ usb_cancel_packet(&s->usb_packet);
+ s->async_qh = 0;
}
/* prepare the timer for the next frame */
expire_time = qemu_get_clock(vm_clock) +
diff --git a/hw/usb.c b/hw/usb.c
index 34aac5fa9b..efbc6dbe98 100644
--- a/hw/usb.c
+++ b/hw/usb.c
@@ -38,13 +38,13 @@ void usb_attach(USBPort *port, USBDevice *dev)
#define SETUP_STATE_DATA 1
#define SETUP_STATE_ACK 2
-int usb_generic_handle_packet(USBDevice *s, int pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len)
+int usb_generic_handle_packet(USBDevice *s, USBPacket *p)
{
int l, ret = 0;
+ int len = p->len;
+ uint8_t *data = p->data;
- switch(pid) {
+ switch(p->pid) {
case USB_MSG_ATTACH:
s->state = USB_STATE_ATTACHED;
break;
@@ -58,7 +58,7 @@ int usb_generic_handle_packet(USBDevice *s, int pid,
s->handle_reset(s);
break;
case USB_TOKEN_SETUP:
- if (s->state < USB_STATE_DEFAULT || devaddr != s->addr)
+ if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr)
return USB_RET_NODEV;
if (len != 8)
goto fail;
@@ -85,9 +85,9 @@ int usb_generic_handle_packet(USBDevice *s, int pid,
}
break;
case USB_TOKEN_IN:
- if (s->state < USB_STATE_DEFAULT || devaddr != s->addr)
+ if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr)
return USB_RET_NODEV;
- switch(devep) {
+ switch(p->devep) {
case 0:
switch(s->setup_state) {
case SETUP_STATE_ACK:
@@ -125,14 +125,14 @@ int usb_generic_handle_packet(USBDevice *s, int pid,
}
break;
default:
- ret = s->handle_data(s, pid, devep, data, len);
+ ret = s->handle_data(s, p);
break;
}
break;
case USB_TOKEN_OUT:
- if (s->state < USB_STATE_DEFAULT || devaddr != s->addr)
+ if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr)
return USB_RET_NODEV;
- switch(devep) {
+ switch(p->devep) {
case 0:
switch(s->setup_state) {
case SETUP_STATE_ACK:
@@ -163,7 +163,7 @@ int usb_generic_handle_packet(USBDevice *s, int pid,
}
break;
default:
- ret = s->handle_data(s, pid, devep, data, len);
+ ret = s->handle_data(s, p);
break;
}
break;
@@ -191,3 +191,13 @@ int set_usb_string(uint8_t *buf, const char *str)
}
return q - buf;
}
+
+/* Send an internal message to a USB device. */
+void usb_send_msg(USBDevice *dev, int msg)
+{
+ USBPacket p;
+ memset(&p, 0, sizeof(p));
+ p.pid = msg;
+ dev->handle_packet(dev, &p);
+}
+
diff --git a/hw/usb.h b/hw/usb.h
index 98fde06569..ed8890e39a 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -34,6 +34,7 @@
#define USB_RET_NAK (-2)
#define USB_RET_STALL (-3)
#define USB_RET_BABBLE (-4)
+#define USB_RET_ASYNC (-5)
#define USB_SPEED_LOW 0
#define USB_SPEED_FULL 1
@@ -109,13 +110,12 @@
typedef struct USBPort USBPort;
typedef struct USBDevice USBDevice;
+typedef struct USBPacket USBPacket;
/* definition of a USB device */
struct USBDevice {
void *opaque;
- int (*handle_packet)(USBDevice *dev, int pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len);
+ int (*handle_packet)(USBDevice *dev, USBPacket *p);
void (*handle_destroy)(USBDevice *dev);
int speed;
@@ -126,8 +126,7 @@ struct USBDevice {
void (*handle_reset)(USBDevice *dev);
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,
- uint8_t *data, int len);
+ int (*handle_data)(USBDevice *dev, USBPacket *p);
uint8_t addr;
char devname[32];
@@ -151,11 +150,54 @@ struct USBPort {
struct USBPort *next; /* Used internally by qemu. */
};
+typedef void USBCallback(USBPacket * packet, void *opaque);
+
+/* Structure used to hold information about an active USB packet. */
+struct USBPacket {
+ /* Data fields for use by the driver. */
+ int pid;
+ uint8_t devaddr;
+ uint8_t devep;
+ uint8_t *data;
+ int len;
+ /* Internal use by the USB layer. */
+ USBCallback *complete_cb;
+ void *complete_opaque;
+ USBCallback *cancel_cb;
+ void * *cancel_opaque;
+};
+
+/* Defer completion of a USB packet. The hadle_packet routine should then
+ return USB_RET_ASYNC. Packets that complete immediately (before
+ handle_packet returns) should not call this method. */
+static inline void usb_defer_packet(USBPacket *p, USBCallback *cancel,
+ void * opaque)
+{
+ p->cancel_cb = cancel;
+ p->cancel_opaque = opaque;
+}
+
+/* Notify the controller that an async packet is complete. This should only
+ be called for packets previously deferred with usb_defer_packet, and
+ should never be called from within handle_packet. */
+static inline void usb_packet_complete(USBPacket *p)
+{
+ p->complete_cb(p, p->complete_opaque);
+}
+
+/* Cancel an active packet. The packed must have been deferred with
+ usb_defer_packet, and not yet completed. */
+static inline void usb_cancel_packet(USBPacket * p)
+{
+ p->cancel_cb(p, p->cancel_opaque);
+}
+
void usb_attach(USBPort *port, USBDevice *dev);
-int usb_generic_handle_packet(USBDevice *s, int pid,
- uint8_t devaddr, uint8_t devep,
- uint8_t *data, int len);
+int usb_generic_handle_packet(USBDevice *s, USBPacket *p);
int set_usb_string(uint8_t *buf, const char *str);
+void usb_send_msg(USBDevice *dev, int msg);
+
+void usb_packet_complete(USBPacket *p);
/* usb hub */
USBDevice *usb_hub_init(int nb_ports);