qdev/usb: add usb bus support to qdev, convert drivers.
[qemu] / hw / usb-uhci.c
index 0f0c185..a112a69 100644 (file)
@@ -1,8 +1,12 @@
 /*
  * USB UHCI controller emulation
- * 
+ *
  * Copyright (c) 2005 Fabrice Bellard
- * 
+ *
+ * Copyright (c) 2008 Max Krasnyansky
+ *     Magor rewrite of the UHCI data structures parser and frame processor
+ *     Support for fully async operation and multiple outstanding transactions
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#include "vl.h"
+#include "hw.h"
+#include "usb.h"
+#include "pci.h"
+#include "qemu-timer.h"
 
 //#define DEBUG
-//#define DEBUG_PACKET
+//#define DEBUG_DUMP_DATA
 
+#define UHCI_CMD_FGR      (1 << 4)
+#define UHCI_CMD_EGSM     (1 << 3)
 #define UHCI_CMD_GRESET   (1 << 2)
 #define UHCI_CMD_HCRESET  (1 << 1)
 #define UHCI_CMD_RS       (1 << 0)
 
 #define NB_PORTS 2
 
+#ifdef DEBUG
+#define dprintf printf
+
+static const char *pid2str(int pid)
+{
+    switch (pid) {
+    case USB_TOKEN_SETUP: return "SETUP";
+    case USB_TOKEN_IN:    return "IN";
+    case USB_TOKEN_OUT:   return "OUT";
+    }
+    return "?";
+}
+
+#else
+#define dprintf(...)
+#endif
+
+#ifdef DEBUG_DUMP_DATA
+static void dump_data(const uint8_t *data, int len)
+{
+    int i;
+
+    printf("uhci: data: ");
+    for(i = 0; i < len; i++)
+        printf(" %02x", data[i]);
+    printf("\n");
+}
+#else
+static void dump_data(const uint8_t *data, int len) {}
+#endif
+
+/* 
+ * Pending async transaction.
+ * 'packet' must be the first field because completion
+ * handler does "(UHCIAsync *) pkt" cast.
+ */
+typedef struct UHCIAsync {
+    USBPacket packet;
+    struct UHCIAsync *next;
+    uint32_t  td;
+    uint32_t  token;
+    int8_t    valid;
+    uint8_t   done;
+    uint8_t   buffer[2048];
+} UHCIAsync;
+
 typedef struct UHCIPort {
     USBPort port;
     uint16_t ctrl;
@@ -76,6 +131,13 @@ 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;
+
+    /* Active packets */
+    UHCIAsync *async_pending;
+    UHCIAsync *async_pool;
 } UHCIState;
 
 typedef struct UHCI_TD {
@@ -90,6 +152,155 @@ typedef struct UHCI_QH {
     uint32_t el_link;
 } UHCI_QH;
 
+static UHCIAsync *uhci_async_alloc(UHCIState *s)
+{
+    UHCIAsync *async = qemu_malloc(sizeof(UHCIAsync));
+
+    memset(&async->packet, 0, sizeof(async->packet));
+    async->valid = 0;
+    async->td    = 0;
+    async->token = 0;
+    async->done  = 0;
+    async->next  = NULL;
+
+    return async;
+}
+
+static void uhci_async_free(UHCIState *s, UHCIAsync *async)
+{
+    qemu_free(async);
+}
+
+static void uhci_async_link(UHCIState *s, UHCIAsync *async)
+{
+    async->next = s->async_pending;
+    s->async_pending = async;
+}
+
+static void uhci_async_unlink(UHCIState *s, UHCIAsync *async)
+{
+    UHCIAsync *curr = s->async_pending;
+    UHCIAsync **prev = &s->async_pending;
+
+    while (curr) {
+       if (curr == async) {
+            *prev = curr->next;
+            return;
+        }
+
+        prev = &curr->next;
+        curr = curr->next;
+    }
+}
+
+static void uhci_async_cancel(UHCIState *s, UHCIAsync *async)
+{
+    dprintf("uhci: cancel td 0x%x token 0x%x done %u\n",
+           async->td, async->token, async->done);
+
+    if (!async->done)
+        usb_cancel_packet(&async->packet);
+    uhci_async_free(s, async);
+}
+
+/*
+ * Mark all outstanding async packets as invalid.
+ * This is used for canceling them when TDs are removed by the HCD.
+ */
+static UHCIAsync *uhci_async_validate_begin(UHCIState *s)
+{
+    UHCIAsync *async = s->async_pending;
+
+    while (async) {
+        async->valid--;
+        async = async->next;
+    }
+    return NULL;
+}
+
+/*
+ * Cancel async packets that are no longer valid
+ */
+static void uhci_async_validate_end(UHCIState *s)
+{
+    UHCIAsync *curr = s->async_pending;
+    UHCIAsync **prev = &s->async_pending;
+    UHCIAsync *next;
+
+    while (curr) {
+        if (curr->valid > 0) {
+            prev = &curr->next;
+            curr = curr->next;
+            continue;
+        }
+
+        next = curr->next;
+
+        /* Unlink */
+        *prev = next;
+
+        uhci_async_cancel(s, curr);
+
+        curr = next;
+    }
+}
+
+static void uhci_async_cancel_all(UHCIState *s)
+{
+    UHCIAsync *curr = s->async_pending;
+    UHCIAsync *next;
+
+    while (curr) {
+        next = curr->next;
+
+        uhci_async_cancel(s, curr);
+
+        curr = next;
+    }
+
+    s->async_pending = NULL;
+}
+
+static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, uint32_t token)
+{
+    UHCIAsync *async = s->async_pending;
+    UHCIAsync *match = NULL;
+    int count = 0;
+
+    /*
+     * We're looking for the best match here. ie both td addr and token.
+     * Otherwise we return last good match. ie just token.
+     * It's ok to match just token because it identifies the transaction
+     * rather well, token includes: device addr, endpoint, size, etc.
+     *
+     * Also since we queue async transactions in reverse order by returning
+     * last good match we restores the order.
+     *
+     * It's expected that we wont have a ton of outstanding transactions.
+     * If we ever do we'd want to optimize this algorithm.
+     */
+
+    while (async) {
+        if (async->token == token) {
+            /* Good match */
+            match = async;
+
+            if (async->td == addr) {
+                /* Best match */
+                break;
+            }
+        }
+
+        async = async->next;
+        count++;
+    }
+
+    if (count > 64)
+       fprintf(stderr, "uhci: warning lots of async transactions\n");
+
+    return match;
+}
+
 static void uhci_attach(USBPort *port1, USBDevice *dev);
 
 static void uhci_update_irq(UHCIState *s)
@@ -105,15 +316,18 @@ static void uhci_update_irq(UHCIState *s)
     } else {
         level = 0;
     }
-    pci_set_irq(&s->dev, 3, level);
+    qemu_set_irq(s->dev.irq[3], level);
 }
 
-static void uhci_reset(UHCIState *s)
+static void uhci_reset(void *opaque)
 {
+    UHCIState *s = opaque;
     uint8_t *pci_conf;
     int i;
     UHCIPort *port;
 
+    dprintf("uhci: full reset\n");
+
     pci_conf = s->dev.config;
 
     pci_conf[0x6a] = 0x01; /* usb clock */
@@ -124,18 +338,75 @@ static void uhci_reset(UHCIState *s)
     s->intr = 0;
     s->fl_base_addr = 0;
     s->sof_timing = 64;
+
     for(i = 0; i < NB_PORTS; i++) {
         port = &s->ports[i];
         port->ctrl = 0x0080;
         if (port->port.dev)
             uhci_attach(&port->port, port->port.dev);
     }
+
+    uhci_async_cancel_all(s);
+}
+
+static void uhci_save(QEMUFile *f, void *opaque)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports = NB_PORTS;
+    int i;
+
+    uhci_async_cancel_all(s);
+
+    pci_device_save(&s->dev, f);
+
+    qemu_put_8s(f, &num_ports);
+    for (i = 0; i < num_ports; ++i)
+        qemu_put_be16s(f, &s->ports[i].ctrl);
+    qemu_put_be16s(f, &s->cmd);
+    qemu_put_be16s(f, &s->status);
+    qemu_put_be16s(f, &s->intr);
+    qemu_put_be16s(f, &s->frnum);
+    qemu_put_be32s(f, &s->fl_base_addr);
+    qemu_put_8s(f, &s->sof_timing);
+    qemu_put_8s(f, &s->status2);
+    qemu_put_timer(f, s->frame_timer);
+}
+
+static int uhci_load(QEMUFile *f, void *opaque, int version_id)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports;
+    int i, ret;
+
+    if (version_id > 1)
+        return -EINVAL;
+
+    ret = pci_device_load(&s->dev, f);
+    if (ret < 0)
+        return ret;
+
+    qemu_get_8s(f, &num_ports);
+    if (num_ports != NB_PORTS)
+        return -EINVAL;
+
+    for (i = 0; i < num_ports; ++i)
+        qemu_get_be16s(f, &s->ports[i].ctrl);
+    qemu_get_be16s(f, &s->cmd);
+    qemu_get_be16s(f, &s->status);
+    qemu_get_be16s(f, &s->intr);
+    qemu_get_be16s(f, &s->frnum);
+    qemu_get_be32s(f, &s->fl_base_addr);
+    qemu_get_8s(f, &s->sof_timing);
+    qemu_get_8s(f, &s->status2);
+    qemu_get_timer(f, s->frame_timer);
+
+    return 0;
 }
 
 static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
 {
     UHCIState *s = opaque;
-    
+
     addr &= 0x1f;
     switch(addr) {
     case 0x0c:
@@ -153,6 +424,7 @@ static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr)
     switch(addr) {
     case 0x0c:
         val = s->sof_timing;
+        break;
     default:
         val = 0xff;
         break;
@@ -163,16 +435,18 @@ static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr)
 static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
 {
     UHCIState *s = opaque;
-    
+
     addr &= 0x1f;
-#ifdef DEBUG
-    printf("uhci writew port=0x%04x val=0x%04x\n", addr, val);
-#endif
+    dprintf("uhci: writew port=0x%04x val=0x%04x\n", addr, val);
+
     switch(addr) {
     case 0x00:
         if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) {
             /* start frame processing */
             qemu_mod_timer(s->frame_timer, qemu_get_clock(vm_clock));
+            s->status &= ~UHCI_STS_HCHALTED;
+        } else if (!(val & UHCI_CMD_RS)) {
+            s->status |= UHCI_STS_HCHALTED;
         }
         if (val & UHCI_CMD_GRESET) {
             UHCIPort *port;
@@ -184,14 +458,13 @@ 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);
             return;
         }
-        if (val & UHCI_CMD_GRESET) {
+        if (val & UHCI_CMD_HCRESET) {
             uhci_reset(s);
             return;
         }
@@ -226,10 +499,9 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
             dev = port->port.dev;
             if (dev) {
                 /* port reset */
-                if ( (val & UHCI_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);
@@ -264,7 +536,7 @@ static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr)
             UHCIPort *port;
             int n;
             n = (addr >> 1) & 7;
-            if (n >= NB_PORTS) 
+            if (n >= NB_PORTS)
                 goto read_default;
             port = &s->ports[n];
             val = port->ctrl;
@@ -275,9 +547,9 @@ static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr)
         val = 0xff7f; /* disabled port */
         break;
     }
-#ifdef DEBUG
-    printf("uhci readw port=0x%04x val=0x%04x\n", addr, val);
-#endif
+
+    dprintf("uhci: readw port=0x%04x val=0x%04x\n", addr, val);
+
     return val;
 }
 
@@ -286,9 +558,8 @@ static void uhci_ioport_writel(void *opaque, uint32_t addr, uint32_t val)
     UHCIState *s = opaque;
 
     addr &= 0x1f;
-#ifdef DEBUG
-    printf("uhci writel port=0x%04x val=0x%08x\n", addr, val);
-#endif
+    dprintf("uhci: writel port=0x%04x val=0x%08x\n", addr, val);
+
     switch(addr) {
     case 0x08:
         s->fl_base_addr = val & ~0xfff;
@@ -313,6 +584,21 @@ static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr)
     return val;
 }
 
+/* signal resume if controller suspended */
+static void uhci_resume (void *opaque)
+{
+    UHCIState *s = (UHCIState *)opaque;
+
+    if (!s)
+        return;
+
+    if (s->cmd & UHCI_CMD_EGSM) {
+        s->cmd |= UHCI_CMD_FGR;
+        s->status |= UHCI_STS_RD;
+        uhci_update_irq(s);
+    }
+}
+
 static void uhci_attach(USBPort *port1, USBDevice *dev)
 {
     UHCIState *s = port1->opaque;
@@ -323,303 +609,455 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
             usb_attach(port1, NULL);
         }
         /* set connect status */
-        if (!(port->ctrl & UHCI_PORT_CCS)) {
-            port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC;
-        }
+        port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC;
+
         /* update speed */
         if (dev->speed == USB_SPEED_LOW)
             port->ctrl |= UHCI_PORT_LSDA;
         else
             port->ctrl &= ~UHCI_PORT_LSDA;
+
+        uhci_resume(s);
+
         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)) {
-            port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC;
+        if (port->ctrl & UHCI_PORT_CCS) {
+            port->ctrl &= ~UHCI_PORT_CCS;
+            port->ctrl |= UHCI_PORT_CSC;
         }
         /* disable port */
         if (port->ctrl & UHCI_PORT_EN) {
             port->ctrl &= ~UHCI_PORT_EN;
             port->ctrl |= UHCI_PORT_ENC;
         }
+
+        uhci_resume(s);
+
         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;
     int i, ret;
 
-#ifdef DEBUG_PACKET
-    {
-        const char *pidstr;
-        switch(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) {
-            printf("     data_out=");
-            for(i = 0; i < len; i++) {
-                printf(" %02x", data[i]);
-            }
-            printf("\n");
-        }
-    }
-#endif
-    for(i = 0; i < NB_PORTS; i++) {
-        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);
-            if (ret != USB_RET_NODEV) {
-#ifdef DEBUG_PACKET
-                {
-                    printf("     ret=%d ", ret);
-                    if (pid == USB_TOKEN_IN && ret > 0) {
-                        printf("data_in=");
-                        for(i = 0; i < ret; i++) {
-                            printf(" %02x", data[i]);
-                        }
-                    }
-                    printf("\n");
-                }
-#endif
-                return ret;
-            }
-        }
+    dprintf("uhci: packet enter. pid %s addr 0x%02x ep %d len %d\n",
+           pid2str(p->pid), p->devaddr, p->devep, p->len);
+    if (p->pid == USB_TOKEN_OUT || p->pid == USB_TOKEN_SETUP)
+        dump_data(p->data, p->len);
+
+    ret = USB_RET_NODEV;
+    for (i = 0; i < NB_PORTS && ret == USB_RET_NODEV; i++) {
+        UHCIPort *port = &s->ports[i];
+        USBDevice *dev = port->port.dev;
+
+        if (dev && (port->ctrl & UHCI_PORT_EN))
+            ret = dev->info->handle_packet(dev, p);
     }
-    return USB_RET_NODEV;
+
+    dprintf("uhci: packet exit. ret %d len %d\n", ret, p->len);
+    if (p->pid == USB_TOKEN_IN && ret > 0)
+        dump_data(p->data, ret);
+
+    return ret;
 }
 
+static void uhci_async_complete(USBPacket * packet, void *opaque);
+static void uhci_process_frame(UHCIState *s);
+
 /* return -1 if fatal error (frame must be stopped)
           0 if TD successful
           1 if TD unsuccessful or inactive
 */
-static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
+static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask)
 {
+    int len = 0, max_len, err, ret;
     uint8_t pid;
-    uint8_t buf[1280];
-    int len, max_len, err, ret;
 
-    if (td->ctrl & TD_CTRL_IOC) {
+    max_len = ((td->token >> 21) + 1) & 0x7ff;
+    pid = td->token & 0xff;
+
+    ret = async->packet.len;
+
+    if (td->ctrl & TD_CTRL_IOC)
         *int_mask |= 0x01;
+
+    if (td->ctrl & TD_CTRL_IOS)
+        td->ctrl &= ~TD_CTRL_ACTIVE;
+
+    if (ret < 0)
+        goto out;
+
+    len = async->packet.len;
+    td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
+
+    /* The NAK bit may have been set by a previous frame, so clear it
+       here.  The docs are somewhat unclear, but win2k relies on this
+       behavior.  */
+    td->ctrl &= ~(TD_CTRL_ACTIVE | TD_CTRL_NAK);
+
+    if (pid == USB_TOKEN_IN) {
+        if (len > max_len) {
+            len = max_len;
+            ret = USB_RET_BABBLE;
+            goto out;
+        }
+
+        if (len > 0) {
+            /* write the data back */
+            cpu_physical_memory_write(td->buffer, async->buffer, len);
+        }
+
+        if ((td->ctrl & TD_CTRL_SPD) && len < max_len) {
+            *int_mask |= 0x02;
+            /* short packet: do not update QH */
+            dprintf("uhci: short packet. td 0x%x token 0x%x\n", async->td, async->token);
+            return 1;
+        }
+    }
+
+    /* success */
+    return 0;
+
+out:
+    switch(ret) {
+    case USB_RET_STALL:
+        td->ctrl |= TD_CTRL_STALL;
+        td->ctrl &= ~TD_CTRL_ACTIVE;
+        return 1;
+
+    case USB_RET_BABBLE:
+        td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL;
+        td->ctrl &= ~TD_CTRL_ACTIVE;
+        /* frame interrupted */
+        return -1;
+
+    case USB_RET_NAK:
+        td->ctrl |= TD_CTRL_NAK;
+        if (pid == USB_TOKEN_SETUP)
+            break;
+       return 1;
+
+    case USB_RET_NODEV:
+    default:
+       break;
     }
-    
+
+    /* Retry the TD if error count is not zero */
+
+    td->ctrl |= TD_CTRL_TIMEOUT;
+    err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3;
+    if (err != 0) {
+        err--;
+        if (err == 0) {
+            td->ctrl &= ~TD_CTRL_ACTIVE;
+            s->status |= UHCI_STS_USBERR;
+            uhci_update_irq(s);
+        }
+    }
+    td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) |
+        (err << TD_CTRL_ERROR_SHIFT);
+    return 1;
+}
+
+static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *int_mask)
+{
+    UHCIAsync *async;
+    int len = 0, max_len;
+    uint8_t pid;
+
+    /* Is active ? */
     if (!(td->ctrl & TD_CTRL_ACTIVE))
         return 1;
 
-    /* TD is active */
+    async = uhci_async_find_td(s, addr, td->token);
+    if (async) {
+        /* Already submitted */
+        async->valid = 32;
+
+        if (!async->done)
+            return 1;
+
+        uhci_async_unlink(s, async);
+        goto done;
+    }
+
+    /* Allocate new packet */
+    async = uhci_async_alloc(s);
+    if (!async)
+        return 1;
+
+    async->valid = 10;
+    async->td    = addr;
+    async->token = td->token;
+
     max_len = ((td->token >> 21) + 1) & 0x7ff;
     pid = td->token & 0xff;
+
+    async->packet.pid     = pid;
+    async->packet.devaddr = (td->token >> 8) & 0x7f;
+    async->packet.devep   = (td->token >> 15) & 0xf;
+    async->packet.data    = async->buffer;
+    async->packet.len     = max_len;
+    async->packet.complete_cb     = uhci_async_complete;
+    async->packet.complete_opaque = s;
+
     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;
+        cpu_physical_memory_read(td->buffer, async->buffer, max_len);
+        len = uhci_broadcast_packet(s, &async->packet);
+        if (len >= 0)
+            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 (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, buf, len);
-            }
-        } else {
-            len = 0;
-        }
+        len = uhci_broadcast_packet(s, &async->packet);
         break;
+
     default:
         /* invalid pid : frame interrupted */
+        uhci_async_free(s, async);
         s->status |= UHCI_STS_HCPERR;
         uhci_update_irq(s);
         return -1;
     }
-    if (td->ctrl & TD_CTRL_IOS)
-        td->ctrl &= ~TD_CTRL_ACTIVE;
-    if (ret >= 0) {
-        td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
-        td->ctrl &= ~TD_CTRL_ACTIVE;
-        if (pid == USB_TOKEN_IN && 
-            (td->ctrl & TD_CTRL_SPD) &&
-            len < max_len) {
-            *int_mask |= 0x02;
-            /* short packet: do not update QH */
-            return 1;
-        } else {
-            /* success */
-            return 0;
-        }
-    } else {
-        switch(ret) {
-        default:
-        case USB_RET_NODEV:
-        do_timeout:
-            td->ctrl |= TD_CTRL_TIMEOUT;
-            err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3;
-            if (err != 0) {
-                err--;
-                if (err == 0) {
-                    td->ctrl &= ~TD_CTRL_ACTIVE;
-                    s->status |= UHCI_STS_USBERR;
-                    uhci_update_irq(s);
-                }
-            }
-            td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | 
-                (err << TD_CTRL_ERROR_SHIFT);
-            return 1;
-        case USB_RET_NAK:
-            td->ctrl |= TD_CTRL_NAK;
-            if (pid == USB_TOKEN_SETUP)
-                goto do_timeout;
-            return 1;
-        case USB_RET_STALL:
-            td->ctrl |= TD_CTRL_STALL;
-            td->ctrl &= ~TD_CTRL_ACTIVE;
-            return 1;
-        case USB_RET_BABBLE:
-            td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL;
-            td->ctrl &= ~TD_CTRL_ACTIVE;
-            /* frame interrupted */
-            return -1;
-        }
+    if (len == USB_RET_ASYNC) {
+        uhci_async_link(s, async);
+        return 2;
     }
+
+    async->packet.len = len;
+
+done:
+    len = uhci_complete_td(s, td, async, int_mask);
+    uhci_async_free(s, async);
+    return len;
 }
 
-static void uhci_frame_timer(void *opaque)
+static void uhci_async_complete(USBPacket *packet, void *opaque)
 {
     UHCIState *s = opaque;
-    int64_t expire_time;
-    uint32_t frame_addr, link, old_td_ctrl, val;
-    int int_mask, cnt, ret;
+    UHCIAsync *async = (UHCIAsync *) packet;
+
+    dprintf("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token);
+
+    async->done = 1;
+
+    uhci_process_frame(s);
+}
+
+static int is_valid(uint32_t link)
+{
+    return (link & 1) == 0;
+}
+
+static int is_qh(uint32_t link)
+{
+    return (link & 2) != 0;
+}
+
+static int depth_first(uint32_t link)
+{
+    return (link & 4) != 0;
+}
+
+/* QH DB used for detecting QH loops */
+#define UHCI_MAX_QUEUES 128
+typedef struct {
+    uint32_t addr[UHCI_MAX_QUEUES];
+    int      count;
+} QhDb;
+
+static void qhdb_reset(QhDb *db)
+{
+    db->count = 0;
+}
+
+/* Add QH to DB. Returns 1 if already present or DB is full. */
+static int qhdb_insert(QhDb *db, uint32_t addr)
+{
+    int i;
+    for (i = 0; i < db->count; i++)
+        if (db->addr[i] == addr)
+            return 1;
+
+    if (db->count >= UHCI_MAX_QUEUES)
+        return 1;
+
+    db->addr[db->count++] = addr;
+    return 0;
+}
+
+static void uhci_process_frame(UHCIState *s)
+{
+    uint32_t frame_addr, link, old_td_ctrl, val, int_mask;
+    uint32_t curr_qh;
+    int cnt, ret;
     UHCI_TD td;
     UHCI_QH qh;
+    QhDb qhdb;
 
-    if (!(s->cmd & UHCI_CMD_RS)) {
-        qemu_del_timer(s->frame_timer);
-        return;
-    }
     frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2);
+
+    dprintf("uhci: processing frame %d addr 0x%x\n" , s->frnum, frame_addr);
+
     cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
     le32_to_cpus(&link);
+
     int_mask = 0;
-    cnt = FRAME_MAX_LOOPS;
-    while ((link & 1) == 0) {
-        if (--cnt == 0)
-            break;
-        /* valid frame */
-        if (link & 2) {
+    curr_qh  = 0;
+
+    qhdb_reset(&qhdb);
+
+    for (cnt = FRAME_MAX_LOOPS; is_valid(link) && cnt; cnt--) {
+        if (is_qh(link)) {
             /* QH */
-            cpu_physical_memory_read(link & ~0xf, (uint8_t *)&qh, sizeof(qh));
+
+            if (qhdb_insert(&qhdb, link)) {
+                /*
+                 * We're going in circles. Which is not a bug because
+                 * HCD is allowed to do that as part of the BW management. 
+                 * In our case though it makes no sense to spin here. Sync transations 
+                 * are already done, and async completion handler will re-process 
+                 * the frame when something is ready.
+                 */
+                dprintf("uhci: detected loop. qh 0x%x\n", link);
+                break;
+            }
+
+            cpu_physical_memory_read(link & ~0xf, (uint8_t *) &qh, sizeof(qh));
             le32_to_cpus(&qh.link);
             le32_to_cpus(&qh.el_link);
-        depth_first:
-            if (qh.el_link & 1) {
-                /* no element : go to next entry */
+
+            dprintf("uhci: QH 0x%x load. link 0x%x elink 0x%x\n",
+                    link, qh.link, qh.el_link);
+
+            if (!is_valid(qh.el_link)) {
+                /* QH w/o elements */
+                curr_qh = 0;
                 link = qh.link;
-            } else if (qh.el_link & 2) {
-                /* QH */
-                link = qh.el_link;
             } else {
-                /* TD */
-                if (--cnt == 0)
-                    break;
-                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, &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 == 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) {
-                        /* depth first */
-                        goto depth_first;
-                    }
-                }
-                /* go to next entry */
-                link = qh.link;
+                /* QH with elements */
+               curr_qh = link;
+               link = qh.el_link;
             }
-        } else {
-            /* TD */
-            cpu_physical_memory_read(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, &int_mask);
+            continue;
+        }
+
+        /* TD */
+        cpu_physical_memory_read(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);
+
+        dprintf("uhci: TD 0x%x load. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", 
+                link, td.link, td.ctrl, td.token, curr_qh);
+
+        old_td_ctrl = td.ctrl;
+        ret = uhci_handle_td(s, link, &td, &int_mask);
+        if (old_td_ctrl != td.ctrl) {
             /* 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));
+            val = cpu_to_le32(td.ctrl);
+            cpu_physical_memory_write((link & ~0xf) + 4,
+                                      (const uint8_t *)&val, sizeof(val));
+        }
+
+        if (ret < 0) {
+            /* interrupted frame */
+            break;
+        }
+
+        if (ret == 2 || ret == 1) {
+            dprintf("uhci: TD 0x%x %s. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n",
+                    link, ret == 2 ? "pend" : "skip",
+                    td.link, td.ctrl, td.token, curr_qh);
+
+            link = curr_qh ? qh.link : td.link;
+            continue;
+        }
+
+        /* completed TD */
+
+        dprintf("uhci: TD 0x%x done. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", 
+                link, td.link, td.ctrl, td.token, curr_qh);
+
+        link = td.link;
+
+        if (curr_qh) {
+           /* update QH element link */
+            qh.el_link = link;
+            val = cpu_to_le32(qh.el_link);
+            cpu_physical_memory_write((curr_qh & ~0xf) + 4,
+                                          (const uint8_t *)&val, sizeof(val));
+
+            if (!depth_first(link)) {
+               /* done with this QH */
+
+               dprintf("uhci: QH 0x%x done. link 0x%x elink 0x%x\n",
+                       curr_qh, qh.link, qh.el_link);
+
+               curr_qh = 0;
+               link    = qh.link;
             }
-            if (ret < 0)
-                break; /* interrupted frame */
-            link = td.link;
         }
+
+        /* go to the next entry */
     }
-    s->frnum = (s->frnum + 1) & 0x7ff;
-    if (int_mask) {
-        s->status2 |= int_mask;
-        s->status |= UHCI_STS_USBINT;
+
+    s->pending_int_mask = int_mask;
+}
+
+static void uhci_frame_timer(void *opaque)
+{
+    UHCIState *s = opaque;
+    int64_t expire_time;
+
+    if (!(s->cmd & UHCI_CMD_RS)) {
+        /* Full stop */
+        qemu_del_timer(s->frame_timer);
+        /* set hchalted bit in status - UHCI11D 2.1.2 */
+        s->status |= UHCI_STS_HCHALTED;
+
+        dprintf("uhci: halted\n");
+        return;
+    }
+
+    /* Complete the previous frame */
+    if (s->pending_int_mask) {
+        s->status2 |= s->pending_int_mask;
+        s->status  |= UHCI_STS_USBINT;
         uhci_update_irq(s);
     }
+
+    /* Start new frame */
+    s->frnum = (s->frnum + 1) & 0x7ff;
+
+    dprintf("uhci: new frame #%u\n" , s->frnum);
+
+    uhci_async_validate_begin(s);
+
+    uhci_process_frame(s);
+
+    uhci_async_validate_end(s);
+
     /* prepare the timer for the next frame */
-    expire_time = qemu_get_clock(vm_clock) + 
+    expire_time = qemu_get_clock(vm_clock) +
         (ticks_per_sec / FRAME_TIMER_FREQ);
     qemu_mod_timer(s->frame_timer, expire_time);
 }
 
-static void uhci_map(PCIDevice *pci_dev, int region_num, 
+static void uhci_map(PCIDevice *pci_dev, int region_num,
                     uint32_t addr, uint32_t size, int type)
 {
     UHCIState *s = (UHCIState *)pci_dev;
@@ -632,40 +1070,72 @@ static void uhci_map(PCIDevice *pci_dev, int region_num,
     register_ioport_read(addr, 32, 1, uhci_ioport_readb, s);
 }
 
-void usb_uhci_init(PCIBus *bus, USBPort **usb_ports)
+void usb_uhci_piix3_init(PCIBus *bus, int devfn)
 {
     UHCIState *s;
     uint8_t *pci_conf;
-    UHCIPort *port;
     int i;
 
     s = (UHCIState *)pci_register_device(bus,
                                         "USB-UHCI", sizeof(UHCIState),
-                                        ((PCIDevice *)piix3_state)->devfn + 2, 
-                                        NULL, NULL);
+                                        devfn, NULL, NULL);
     pci_conf = s->dev.config;
-    pci_conf[0x00] = 0x86;
-    pci_conf[0x01] = 0x80;
-    pci_conf[0x02] = 0x20;
-    pci_conf[0x03] = 0x70;
+    pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
+    pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371SB_2);
     pci_conf[0x08] = 0x01; // revision number
     pci_conf[0x09] = 0x00;
-    pci_conf[0x0a] = 0x03;
-    pci_conf[0x0b] = 0x0c;
-    pci_conf[0x0e] = 0x00; // header_type
+    pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB);
+    pci_conf[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; // header_type
     pci_conf[0x3d] = 4; // interrupt pin 3
-    
+    pci_conf[0x60] = 0x10; // release number
+
     for(i = 0; i < NB_PORTS; i++) {
-        port = &s->ports[i];
-        port->port.opaque = s;
-        port->port.index = i;
-        port->port.attach = uhci_attach;
-        usb_ports[i] = &port->port;
+        qemu_register_usb_port(&s->ports[i].port, s, i, uhci_attach);
+    }
+    s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s);
+
+    qemu_register_reset(uhci_reset, s);
+    uhci_reset(s);
+
+    /* Use region 4 for consistency with real hardware.  BSD guests seem
+       to rely on this.  */
+    pci_register_bar(&s->dev, 4, 0x20,
+                           PCI_ADDRESS_SPACE_IO, uhci_map);
+
+    register_savevm("uhci", 0, 1, uhci_save, uhci_load, s);
+}
+
+void usb_uhci_piix4_init(PCIBus *bus, int devfn)
+{
+    UHCIState *s;
+    uint8_t *pci_conf;
+    int i;
+
+    s = (UHCIState *)pci_register_device(bus,
+                                        "USB-UHCI", sizeof(UHCIState),
+                                        devfn, NULL, NULL);
+    pci_conf = s->dev.config;
+    pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
+    pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_2);
+    pci_conf[0x08] = 0x01; // revision number
+    pci_conf[0x09] = 0x00;
+    pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB);
+    pci_conf[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; // header_type
+    pci_conf[0x3d] = 4; // interrupt pin 3
+    pci_conf[0x60] = 0x10; // release number
+
+    for(i = 0; i < NB_PORTS; i++) {
+        qemu_register_usb_port(&s->ports[i].port, s, i, uhci_attach);
     }
     s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s);
 
+    qemu_register_reset(uhci_reset, s);
     uhci_reset(s);
 
-    pci_register_io_region(&s->dev, 0, 0x20, 
+    /* Use region 4 for consistency with real hardware.  BSD guests seem
+       to rely on this.  */
+    pci_register_bar(&s->dev, 4, 0x20,
                            PCI_ADDRESS_SPACE_IO, uhci_map);
+
+    register_savevm("uhci", 0, 1, uhci_save, uhci_load, s);
 }