Add PowerPC power-management state check callback.
[qemu] / hw / usb-uhci.c
index 42417fe..95dd4a0 100644 (file)
@@ -1,8 +1,8 @@
 /*
  * USB UHCI controller emulation
- * 
+ *
  * Copyright (c) 2005 Fabrice Bellard
- * 
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
 
 //#define DEBUG
 //#define DEBUG_PACKET
+//#define DEBUG_ISOCH
 
+#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)
@@ -63,7 +66,6 @@
 typedef struct UHCIPort {
     USBPort port;
     uint16_t ctrl;
-    USBDevice *dev; /* connected device */
 } UHCIPort;
 
 typedef struct UHCIState {
@@ -77,6 +79,19 @@ 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;
+    uint32_t async_frame_addr;
+    USBPacket usb_packet;
+    uint8_t usb_buf[2048];
 } UHCIState;
 
 typedef struct UHCI_TD {
@@ -106,7 +121,7 @@ static void uhci_update_irq(UHCIState *s)
     } else {
         level = 0;
     }
-    pci_set_irq(&s->dev, 0, level);
+    qemu_set_irq(s->dev.irq[3], level);
 }
 
 static void uhci_reset(UHCIState *s)
@@ -128,15 +143,67 @@ static void uhci_reset(UHCIState *s)
     for(i = 0; i < NB_PORTS; i++) {
         port = &s->ports[i];
         port->ctrl = 0x0080;
-        if (port->dev)
-            uhci_attach(&port->port, port->dev);
+        if (port->port.dev)
+            uhci_attach(&port->port, port->port.dev);
     }
 }
 
+static void uhci_save(QEMUFile *f, void *opaque)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports = NB_PORTS;
+    int i;
+
+    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:
@@ -154,6 +221,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;
@@ -164,7 +232,7 @@ 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);
@@ -174,6 +242,9 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
         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;
@@ -183,16 +254,15 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
             /* send reset on the USB bus */
             for(i = 0; i < NB_PORTS; i++) {
                 port = &s->ports[i];
-                dev = port->dev;
+                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;
         }
@@ -224,13 +294,12 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
             if (n >= NB_PORTS)
                 return;
             port = &s->ports[n];
-            dev = port->dev;
+            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);
@@ -265,7 +334,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;
@@ -314,51 +383,68 @@ 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;
     UHCIPort *port = &s->ports[port1->index];
 
     if (dev) {
-        if (port->dev) {
+        if (port->port.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;
-        port->dev = dev;
+
+        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;
         }
-        dev = port->dev;
+
+        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->dev = NULL;
+        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;
@@ -367,18 +453,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");
         }
@@ -386,19 +472,19 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
 #endif
     for(i = 0; i < NB_PORTS; i++) {
         port = &s->ports[i];
-        dev = port->dev;
+        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");
@@ -411,41 +497,32 @@ 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
 */
-static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
+static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask,
+                          int completion)
 {
     uint8_t pid;
-    uint8_t buf[1280];
-    int len, max_len, err, ret;
+    int len = 0, max_len, err, ret = 0;
 
+    /* ??? This is wrong for async completion.  */
     if (td->ctrl & TD_CTRL_IOC) {
         *int_mask |= 0x01;
     }
-    
+
     if (!(td->ctrl & TD_CTRL_ACTIVE))
         return 1;
 
     /* 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 (completion && (s->async_qh || s->async_frame_addr)) {
+        ret = s->usb_packet.len;
         if (ret >= 0) {
             len = ret;
             if (len > max_len) {
@@ -454,24 +531,64 @@ 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;
+        s->async_frame_addr = 0;
+    } else if (!completion) {
+        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;
     if (ret >= 0) {
         td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
-        td->ctrl &= ~TD_CTRL_ACTIVE;
-        if (pid == USB_TOKEN_IN && 
+        /* 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 &&
             (td->ctrl & TD_CTRL_SPD) &&
             len < max_len) {
             *int_mask |= 0x02;
@@ -496,7 +613,7 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
                     uhci_update_irq(s);
                 }
             }
-            td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | 
+            td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) |
                 (err << TD_CTRL_ERROR_SHIFT);
             return 1;
         case USB_RET_NAK:
@@ -517,6 +634,96 @@ 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;
+    uint32_t frame_addr;
+    int ret;
+
+    /* Handle async isochronous packet completion */
+    frame_addr = s->async_frame_addr;
+    if (frame_addr) {
+        cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
+        le32_to_cpus(&link);
+
+        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, &s->pending_int_mask, 1);
+
+        /* 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 == 2) {
+            s->async_frame_addr = frame_addr;
+        } else if (ret == 0) {
+            /* update qh element link */
+            val = cpu_to_le32(td.link);
+            cpu_physical_memory_write(frame_addr,
+                                      (const uint8_t *)&val,
+                                      sizeof(val));
+        }
+        return;
+    }
+
+    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, 1);
+
+        /* 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;
@@ -525,11 +732,22 @@ 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);
+        /* set hchalted bit in status - UHCI11D 2.1.2 */
+        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);
@@ -541,6 +759,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);
@@ -551,33 +775,40 @@ 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)
                     break;
-                cpu_physical_memory_read(qh.el_link & ~0xf, 
+                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);
+                ret = uhci_handle_td(s, &td, &int_mask, 0);
+
                 /* 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, 
+                    cpu_physical_memory_write((qh.el_link & ~0xf) + 4,
+                                              (const uint8_t *)&val,
                                               sizeof(val));
                 }
                 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);
-                    cpu_physical_memory_write((link & ~0xf) + 4, 
-                                              (const uint8_t *)&val, 
+                    cpu_physical_memory_write((link & ~0xf) + 4,
+                                              (const uint8_t *)&val,
                                               sizeof(val));
                     if (qh.el_link & 4) {
                         /* depth first */
@@ -594,33 +825,46 @@ static void uhci_frame_timer(void *opaque)
             le32_to_cpus(&td.ctrl);
             le32_to_cpus(&td.token);
             le32_to_cpus(&td.buffer);
+
+            /* Handle isochonous transfer.  */
+            /* FIXME: might be more than one isoc in frame */
             old_td_ctrl = td.ctrl;
-            ret = uhci_handle_td(s, &td, &int_mask);
+            ret = uhci_handle_td(s, &td, &int_mask, 0);
+
             /* 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, 
+                cpu_physical_memory_write((link & ~0xf) + 4,
+                                          (const uint8_t *)&val,
                                           sizeof(val));
             }
             if (ret < 0)
                 break; /* interrupted frame */
+            if (ret == 2) {
+                s->async_frame_addr = frame_addr;
+            }
             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) + 
+    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;
@@ -633,17 +877,15 @@ 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),
-                                        -1, 
-                                        NULL, NULL);
+                                        devfn, NULL, NULL);
     pci_conf = s->dev.config;
     pci_conf[0x00] = 0x86;
     pci_conf[0x01] = 0x80;
@@ -654,19 +896,53 @@ void usb_uhci_init(PCIBus *bus, USBPort **usb_ports)
     pci_conf[0x0a] = 0x03;
     pci_conf[0x0b] = 0x0c;
     pci_conf[0x0e] = 0x00; // header_type
-    pci_conf[0x3d] = 1; // interrupt pin 0
-    
+    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);
+
+    uhci_reset(s);
+
+    /* Use region 4 for consistency with real hardware.  BSD guests seem
+       to rely on this.  */
+    pci_register_io_region(&s->dev, 4, 0x20,
+                           PCI_ADDRESS_SPACE_IO, uhci_map);
+}
+
+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_conf[0x00] = 0x86;
+    pci_conf[0x01] = 0x80;
+    pci_conf[0x02] = 0x12;
+    pci_conf[0x03] = 0x71;
+    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_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);
 
     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_io_region(&s->dev, 4, 0x20,
                            PCI_ADDRESS_SPACE_IO, uhci_map);
 }