SCSI and USB async IO support.
authorpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 12 Aug 2006 01:04:27 +0000 (01:04 +0000)
committerpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 12 Aug 2006 01:04:27 +0000 (01:04 +0000)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2107 c046a42c-6fe2-441c-8c8c-71466251a162

14 files changed:
hw/esp.c
hw/lsi53c895a.c
hw/scsi-disk.c
hw/usb-hid.c
hw/usb-hub.c
hw/usb-msd.c
hw/usb-ohci.c
hw/usb-uhci.c
hw/usb.c
hw/usb.h
pc-bios/openbios-esp.diff [new file with mode: 0644]
pc-bios/openbios-sparc32
usb-linux.c
vl.h

index cdd062f..17e70dd 100644 (file)
--- 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
index 24dff0e..8f56725 100644 (file)
@@ -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;
index 3419c8e..a2f299e 100644 (file)
@@ -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;
 }
index 8fc0b74..095fcb3 100644 (file)
@@ -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;
         }
index 8350931..651dac2 100644 (file)
@@ -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)
index ff2047d..7d1f35d 100644 (file)
@@ -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:
index e87d9da..de113e9 100644 (file)
@@ -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);
 }
index 1a6e013..29809f9 100644 (file)
@@ -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) + 
index 34aac5f..efbc6db 100644 (file)
--- 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);
+}
+
index 98fde06..ed8890e 100644 (file)
--- 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
 
 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);
diff --git a/pc-bios/openbios-esp.diff b/pc-bios/openbios-esp.diff
new file mode 100644 (file)
index 0000000..5d4f836
--- /dev/null
@@ -0,0 +1,30 @@
+The ESP SCSI driver currently doesn't check whether a DMA requests has 
+completed before checking its status. On older qemu versions this works ok 
+because DMA happens instantly. On never qemu DMA can take an indeterminate 
+amount of time ooto complete, just like on real hardware.
+
+The patch below waits for the controller to raise the DMA interrupt after 
+initiating a DMA request.
+
+Index: drivers/esp.c
+===================================================================
+--- drivers/esp.c      (revision 61)
++++ drivers/esp.c      (working copy)
+@@ -113,6 +113,8 @@ do_command(esp_private_t *esp, sd_privat
+     esp->espdma.regs->cond_reg = 0;
+     // Set ATN, issue command
+     esp->ll->regs[ESP_CMD] = ESP_CMD_SELA | ESP_CMD_DMA;
++    // Wait for DMA to complete
++    while ((esp->espdma.regs->cond_reg & DMA_HNDL_INTR) == 0) /* no-op */;
+     // Check status
+     status = esp->ll->regs[ESP_STATUS];
+@@ -129,6 +131,8 @@ do_command(esp_private_t *esp, sd_privat
+     esp->espdma.regs->cond_reg = DMA_ST_WRITE;
+     // Transfer
+     esp->ll->regs[ESP_CMD] = ESP_CMD_TI | ESP_CMD_DMA;
++    // Wait for DMA to complete
++    while ((esp->espdma.regs->cond_reg & DMA_HNDL_INTR) == 0) /* no-op */;
+     // Check status
+     status = esp->ll->regs[ESP_STATUS];
index 7a729aa..04b6077 100644 (file)
Binary files a/pc-bios/openbios-sparc32 and b/pc-bios/openbios-sparc32 differ
index 0a13753..7743692 100644 (file)
@@ -114,22 +114,21 @@ static int usb_host_handle_control(USBDevice *dev,
    }
 }
 
-static int usb_host_handle_data(USBDevice *dev, int pid, 
-                                uint8_t devep,
-                                uint8_t *data, int len)
+static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
 {
     USBHostDevice *s = (USBHostDevice *)dev;
     struct usbdevfs_bulktransfer bt;
     int ret;
+    uint8_t devep = p->devep;
 
     /* XXX: optimize and handle all data types by looking at the
        config descriptor */
-    if (pid == USB_TOKEN_IN)
+    if (p->pid == USB_TOKEN_IN)
         devep |= 0x80;
     bt.ep = devep;
-    bt.len = len;
+    bt.len = p->len;
     bt.timeout = 50;
-    bt.data = data;
+    bt.data = p->data;
     ret = ioctl(s->fd, USBDEVFS_BULK, &bt);
     if (ret < 0) {
         switch(errno) {
diff --git a/vl.h b/vl.h
index 1632e35..5a10326 100644 (file)
--- a/vl.h
+++ b/vl.h
@@ -1146,6 +1146,11 @@ void do_usb_del(const char *devname);
 void usb_info(void);
 
 /* scsi-disk.c */
+enum scsi_reason {
+    SCSI_REASON_DONE, /* Command complete.  */
+    SCSI_REASON_DATA  /* Transfer complete, more data required.  */
+};
+
 typedef struct SCSIDevice SCSIDevice;
 typedef void (*scsi_completionfn)(void *, uint32_t, int);
 
@@ -1155,8 +1160,12 @@ SCSIDevice *scsi_disk_init(BlockDriverState *bdrv,
 void scsi_disk_destroy(SCSIDevice *s);
 
 int32_t scsi_send_command(SCSIDevice *s, uint32_t tag, uint8_t *buf, int lun);
+/* SCSI data transfers are asynchrnonous.  However, unlike the block IO
+   layer the completion routine may be called directly by
+   scsi_{read,write}_data.  */
 int scsi_read_data(SCSIDevice *s, uint8_t *data, uint32_t len);
 int scsi_write_data(SCSIDevice *s, uint8_t *data, uint32_t len);
+void scsi_cancel_io(SCSIDevice *s);
 
 /* lsi53c895a.c */
 void lsi_scsi_attach(void *opaque, BlockDriverState *bd, int id);