diff options
author | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2006-08-12 01:04:27 +0000 |
---|---|---|
committer | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2006-08-12 01:04:27 +0000 |
commit | 4d611c9a2f4c5d9080d8b6a6f0d7431233cd56f9 (patch) | |
tree | 6217063ef291bd680f1c81a82bdaef7345356c16 /hw | |
parent | 4ca9c76f3620dc20e56d9b7027a6f1115ea48eea (diff) | |
download | qemu-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.c | 142 | ||||
-rw-r--r-- | hw/lsi53c895a.c | 80 | ||||
-rw-r--r-- | hw/scsi-disk.c | 152 | ||||
-rw-r--r-- | hw/usb-hid.c | 11 | ||||
-rw-r--r-- | hw/usb-hub.c | 46 | ||||
-rw-r--r-- | hw/usb-msd.c | 68 | ||||
-rw-r--r-- | hw/usb-ohci.c | 156 | ||||
-rw-r--r-- | hw/usb-uhci.c | 244 | ||||
-rw-r--r-- | hw/usb.c | 32 | ||||
-rw-r--r-- | hw/usb.h | 58 |
10 files changed, 702 insertions, 287 deletions
@@ -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) + @@ -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); +} + @@ -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); |